編輯:關於Android編程
前兩篇我們分析android的異步線程類HandlerThread與IntentService,它們都是android系統獨有的線程類,而android中還有另一個比較重要的異步線程類,它就是AsyncTask。本篇我們將從以下3點深入分析AsyncTask。
AsyncTask的常規使用分析以及案例實現 AsyncTask在不同android版本的下的差異 AsyncTask的工作原理流程??AsyncTask是一種輕量級的異步任務類,它可以在線程池中執行後台任務,然後會把執行的進度和最終結果傳遞給主線程並更新UI。AsyncTask本身是一個抽象類它提供了Params、Progress、Result 三個泛型參數,其類聲明如下:
public abstract class AsyncTask{
??由類聲明可以看出AsyncTask抽象類確實定義了三種泛型類型 Params,Progress和Result,它們分別含義如下:
Params :啟動任務執行的輸入參數,如HTTP請求的URL Progress : 後台任務執行的百分比 Result :後台執行任務最終返回的結果類型??如果AsyncTask不需要傳遞具體參數,那麼這三個泛型參數可以使用Void代替。好~,我們現在創建一個類繼承自AsyncTask如下:
package com.zejian.handlerlooper; import android.graphics.Bitmap; import android.os.AsyncTask; /** * Created by zejian * Time 16/9/4. * Description: */ public class DownLoadAsyncTask extends AsyncTask{ /** * onPreExecute是可以選擇性覆寫的方法 * 在主線程中執行,在異步任務執行之前,該方法將會被調用 * 一般用來在執行後台任務前對UI做一些標記和准備工作, * 如在界面上顯示一個進度條。 */ @Override protected void onPreExecute() { super.onPreExecute(); } /** * 抽象方法必須覆寫,執行異步任務的方法 * @param params * @return */ @Override protected Bitmap doInBackground(String... params) { return null; } /** * onProgressUpdate是可以選擇性覆寫的方法 * 在主線程中執行,當後台任務的執行進度發生改變時, * 當然我們必須在doInBackground方法中調用publishProgress() * 來設置進度變化的值 * @param values */ @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); } /** * onPostExecute是可以選擇性覆寫的方法 * 在主線程中執行,在異步任務執行完成後,此方法會被調用 * 一般用於更新UI或其他必須在主線程執行的操作,傳遞參數bitmap為 * doInBackground方法中的返回值 * @param bitmap */ @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); } /** * onCancelled是可以選擇性覆寫的方法 * 在主線程中,當異步任務被取消時,該方法將被調用, * 要注意的是這個時onPostExecute將不會被執行 */ @Override protected void onCancelled() { super.onCancelled(); } }
??如代碼所示,我們創建一個繼承自AsyncTask的異步線程類,在泛型參數方面,傳遞String類型(Url) , Integer類型(顯示進度),Bitmap類型作為返回值。接著重寫了抽象方法doInBackground(),以及覆寫了onPreExecute()、onProgressUpdate()、onPostExecute()、onCancelled()等方法,它們的主要含義如下:
(1)onPreExecute(), 該方法在主線程中執行,將在execute(Params… params)被調用後執行,一般用來做一些UI的准備工作,如在界面上顯示一個進度條。 (2)doInBackground(Params…params), 抽象方法,必須實現,該方法在線程池中執行,用於執行異步任務,將在onPreExecute方法執行後執行。其參數是一個可變類型,表示異步任務的輸入參數,在該方法中還可通過publishProgress(Progress… values)來更新實時的任務進度,而publishProgress方法則會調用onProgressUpdate方法。此外doInBackground方法會將計算的返回結果傳遞給onPostExecute方法。 (3)onProgressUpdate(Progress…),在主線程中執行,該方法在publishProgress(Progress… values)方法被調用後執行,一般用於更新UI進度,如更新進度條的當前進度。 (4)onPostExecute(Result), 在主線程中執行,在doInBackground 執行完成後,onPostExecute 方法將被UI線程調用,doInBackground 方法的返回值將作為此方法的參數傳遞到UI線程中,並執行一些UI相關的操作,如更新UI視圖。 (5)onCancelled(),在主線程中執行,當異步任務被取消時,該方法將被調用,要注意的是這個時onPostExecute將不會被執行。??我們這裡再強調一下它們的執行順序,onPreExecute方法先執行,接著是doInBackground方法,在doInBackground中如果調用了publishProgress方法,那麼onProgressUpdate方法將會被執行,最後doInBackground方法執行後完後,onPostExecute方法將被執行。說了這麼多,我們還沒說如何啟動AsyncTask呢,其實可以通過execute方法啟動異步線程,其方法聲明如下:
public final AsyncTaskexecute(Params... params)
??該方法是一個final方法,參數類型是可變類型,實際上這裡傳遞的參數和doInBackground(Params…params)方法中的參數是一樣的,該方法最終返回一個AsyncTask的實例對象,可以使用該對象進行其他操作,比如結束線程之類的。啟動范例如下:
new DownLoadAsyncTask().execute(url1,url2,url3);
??當然除了以上介紹的內容外,我們在使用AsyncTask時還必須遵守一些規則,以避免不必要的麻煩。
(1) AsyncTask的實例必須在主線程(UI線程)中創建 ,execute方法也必須在主線程中調用 (2) 不要在程序中直接的調用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)這幾個方法 (3) 不能在doInBackground(Params… params)中更新UI (5) 一個AsyncTask對象只能被執行一次,也就是execute方法只能調用一次,否則多次調用時將會拋出異常??到此,AsyncTask的常規方法說明和使用以及注意事項全部介紹完了,下面我們來看一個下載案例,該案例是去下載一張大圖,並實現下載實時進度。先來看看AsynTaskActivity.java的實現:
package com.zejian.handlerlooper; import android.content.Context; import android.os.AsyncTask; import android.os.Environment; import android.os.PowerManager; import android.widget.Toast; import com.zejian.handlerlooper.util.LogUtils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; /** * Created by zejian * Time 16/9/4. * Description: */ public class DownLoadAsyncTask extends AsyncTask{ private PowerManager.WakeLock mWakeLock; private int ValueProgress=100; private Context context; public DownLoadAsyncTask(Context context){ this.context=context; } /** * sync method which download file * @param params * @return */ @Override protected String doInBackground(String... params) { InputStream input = null; OutputStream output = null; HttpURLConnection connection = null; try { URL url = new URL(params[0]); connection = (HttpURLConnection) url.openConnection(); connection.connect(); // expect HTTP 200 OK, so we don't mistakenly save error report // instead of the file if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { return "Server returned HTTP " + connection.getResponseCode() + " " + connection.getResponseMessage(); } // this will be useful to display download percentage // might be -1: server did not report the length int fileLength = connection.getContentLength(); // download the file input = connection.getInputStream(); //create output output = new FileOutputStream(getSDCardDir()); byte data[] = new byte[4096]; long total = 0; int count; while ((count = input.read(data)) != -1) { // allow canceling with back button if (isCancelled()) { input.close(); return null; } total += count; // publishing the progress.... if (fileLength > 0) // only if total length is known publishProgress((int) (total * 100 / fileLength)); // Thread.sleep(100); output.write(data, 0, count); } } catch (Exception e) { return e.toString(); } finally { try { if (output != null) output.close(); if (input != null) input.close(); } catch (IOException ignored) { } if (connection != null) connection.disconnect(); } return null; } @Override protected void onPreExecute() { super.onPreExecute(); // take CPU lock to prevent CPU from going off if the user // presses the power button during download PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); mWakeLock.acquire(); //Display progressBar // progressBar.setVisibility(View.VISIBLE); } @Override protected void onPostExecute(String values) { super.onPostExecute(values); mWakeLock.release(); if (values != null) LogUtils.e("Download error: "+values); else { Toast.makeText(context, "File downloaded", Toast.LENGTH_SHORT).show(); } } /** * set progressBar * @param values */ @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); // progressBar.setmProgress(values[0]); //update progressBar if(updateUI!=null){ updateUI.UpdateProgressBar(values[0]); } } /** * get SD card path * @return */ public File getSDCardDir(){ if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){ // 創建一個文件夾對象,賦值為外部存儲器的目錄 String dirName = Environment.getExternalStorageDirectory()+"/MyDownload/"; File f = new File(dirName); if(!f.exists()){ f.mkdir(); } File downloadFile=new File(f,"new.jpg"); return downloadFile; } else{ LogUtils.e("NO SD Card!"); return null; } } public UpdateUI updateUI; public interface UpdateUI{ void UpdateProgressBar(Integer values); } public void setUpdateUIInterface(UpdateUI updateUI){ this.updateUI=updateUI; } }
??簡單說明一下代碼,在onPreExecute方法中,可以做了一些准備工作,如顯示進度圈,這裡為了演示方便,進度圈在常態下就是顯示的,同時,我們還鎖定了CPU,防止下載中斷,而在doInBackground方法中,通過HttpURLConnection對象去下載圖片,然後再通過int fileLength =connection.getContentLength();代碼獲取整個下載圖片的大小並使用publishProgress((int) (total * 100 / fileLength));更新進度,進而調用onProgressUpdate方法更新進度條。最後在onPostExecute方法中釋放CPU鎖,並通知是否下載成功。接著看看Activity的實現:
activity_download.xml
AsynTaskActivity.java
package com.zejian.handlerlooper; import android.Manifest; import android.app.Activity; import android.content.pm.PackageManager; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.view.View; import android.widget.Button; import com.zejian.handlerlooper.util.LoadProgressBarWithNum; import com.zejian.handlerlooper.util.LogUtils; /** * Created by zejian * Time 16/9/4. * Description:AsynTaskActivity */ public class AsynTaskActivity extends Activity implements DownLoadAsyncTask.UpdateUI { private static int WRITE_EXTERNAL_STORAGE_REQUEST_CODE=0x11; private static String DOWNLOAD_FILE_JPG_URL="http://img2.3lian.com/2014/f6/173/d/51.jpg"; private LoadProgressBarWithNum progressBar; private Button downloadBtn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_download); progressBar= (LoadProgressBarWithNum) findViewById(R.id.progressbar); downloadBtn= (Button) findViewById(R.id.downloadBtn); //create DownLoadAsyncTask final DownLoadAsyncTask downLoadAsyncTask= new DownLoadAsyncTask(AsynTaskActivity.this); //set Interface downLoadAsyncTask.setUpdateUIInterface(this); //start download downloadBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //execute downLoadAsyncTask.execute(DOWNLOAD_FILE_JPG_URL); } }); //android 6.0 權限申請 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { //android 6.0 API 必須申請WRITE_EXTERNAL_STORAGE權限 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_EXTERNAL_STORAGE_REQUEST_CODE); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); doNext(requestCode,grantResults); } private void doNext(int requestCode, int[] grantResults) { if (requestCode == WRITE_EXTERNAL_STORAGE_REQUEST_CODE) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Permission Granted LogUtils.e("Permission Granted"); } else { // Permission Denied LogUtils.e("Permission Denied"); } } } /** * update progressBar * @param values */ @Override public void UpdateProgressBar(Integer values) { progressBar.setmProgress(values);; } }
??在AsynTaskActivity中實現了更新UI的接口DownLoadAsyncTask.UpdateUI,用於更新主線程的progressBar的進度,由於使用的測試版本是android6.0,涉及到外部SD卡讀取權限的申請,所以在代碼中對SD卡權限進行了特殊處理(這點不深究,不明白可以google一下),LoadProgressBarWithNum是一個自定義的進度條控件。ok~,最後看看我們的運行結果:
??效果符合預期,通過這個案例,相信我們對AsyncTask的使用已相當清晰了。基本使用到此,然後再來聊聊AsyncTask在不同android版本中的差異。
??這裡我們主要區分一下android3.0前後版本的差異,在android 3.0之前,AsyncTask處理任務時默認采用的是線程池裡並行處理任務的方式,而在android 3.0之後 ,為了避免AsyncTask處理任務時所帶來的並發錯誤,AsyncTask則采用了單線程串行執行任務。但是這並不意味著android 3.0之後只能執行串行任務,我們仍然可以采用AsyncTask的executeOnExecutor方法來並行執行任務。接下來,編寫一個案例,分別在android 2.3.3 和 android 6.0上執行,然後打印輸出日志。代碼如下:
package com.zejian.handlerlooper; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.widget.Button; import com.zejian.handlerlooper.util.LogUtils; import java.text.SimpleDateFormat; import java.util.Date; /** * Created by zejian * Time 16/9/5. * Description: */ public class ActivityAsyncTaskDiff extends Activity { private Button btn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_async_diff); btn= (Button) findViewById(R.id.btn); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new AysnTaskDiff("AysnTaskDiff-1").execute(""); new AysnTaskDiff("AysnTaskDiff-2").execute(""); new AysnTaskDiff("AysnTaskDiff-3").execute(""); new AysnTaskDiff("AysnTaskDiff-4").execute(""); new AysnTaskDiff("AysnTaskDiff-5").execute(""); } }); } private static class AysnTaskDiff extends AsyncTask{ private String name; public AysnTaskDiff(String name){ super(); this.name=name; } @Override protected String doInBackground(String... params) { try { Thread.sleep(2000); }catch (Exception ex){ ex.printStackTrace(); } return name; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); LogUtils.e(s+" execute 執行完成時間:"+df.format(new Date())); } } }
??案例代碼比較簡單,不過多分析,我們直接看在android 2.3.3 和 android 6.0上執行的結果,其中android 2.3.3上執行Log打印如下:
在 android 6.0上執行Log打印如下:
?? 從打印log可以看出AsyncTask在android 2.3.3上確實是並行執行任務的,而在 android 6.0上則是串行執行任務。那麼了解這點有什麼用呢?其實以前我也只是知道這回事而已,不過最近在SDK開發中遇到了AsyncTask的開發問題,產生問題的場景是這樣的,我們團隊在SDK中使用了AsyncTask作為網絡請求類,因為現在大部分系統都是在Android 3.0以上的系統運行的,所以默認就是串行運行,一開始SDK在海外版往外提供也沒有出現什麼問題,直到後面我們提供國內一個publisher海外版本時,問題就出現了,該publisher接入我們的SDK後,他們的應用網絡加載速度變得十分慢,後來他們一直沒排查出啥問題,我們這邊也在懵逼中……直到我們雙方都找到一個點,那就是publisher的應用和我們的SDK使用的都是AsyncTask作為網絡請求,那麼問題就來,我們SDK是在在Application啟動時觸發網絡的,而他們的應用也是啟動Activity時去訪問網絡,所以SDK比應用先加載網絡數據,但是!!!AsyncTask默認是串行執行的,所以!!只有等我們的SDK網絡加載完成後,他們應用才開始加載網絡數據,這就造成應用的網絡加載延遲十分嚴重了。後面我們SDK在內部把AsyncTask改為並行任務後問題也就解決了(當然這也是SDK的一個BUG,考慮欠佳)。在Android 3.0之後我們可以通過下面代碼讓AsyncTask執行並行任務,其AsyncTask.THREAD_POOL_EXECUTOR為AsyncTask的內部線程池。
new AysnTaskDiff("AysnTaskDiff-5").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
?? 第一個參數傳遞是線程池,一般使用AsyncTask內部提供的線程池即可(也可以自己創建),第二個參數,就是最終會傳遞給doInBackground方法的可變參數,這裡不傳,所以直接給了空白符。執行效果就不再演示了,大家可以自行測試一下。
?? ok~,到此AsyncTask在不同android版本中的差異也分析完,感覺文章有點長了,那麼AsyncTask工作原理分析就放到下篇吧,晚安。。
一、前言前段時間聽朋友說在學習NDK開發,說現在的普通安卓程序猿馬上都要失業了,嚇得我趕緊去各大招聘平台搜了下,確實有不少招聘都寫著要求NDK開發經驗、JNI開發經驗、H
昨晚沒事手機下載了一些APP,發現現在仿win8的主界面越來越多,在大家見慣了類GridView或者類Tab後,給人一種耳目一新的感覺。今天在eoe上偶然發
1、Context說明 Context是一個用於訪問全局信息的接口,如應用程序的資源(如圖片,字符串等),一些常用的組件繼承自Context,如Activity和Serv
1.ContentProvider簡介 在Android中有些數據(如通訊錄、音頻、視頻文件等)是要供很多應用程序使用的,為了更好地對外提供