如果在一個進程中,頻繁創建和銷毀線程,顯然不是高效的做法。正確的做法是采用線程池,一個線程池中會緩存一定數量的線程,通過線程池可以避免因為頻繁創建和銷毀線程所帶來的消耗。
1.AsyncTask簡介
Asynctask是一個抽象類,它是Android封裝的一個輕量級異步類(輕量級體現在使用方便,代碼簡潔),它可以在線程池中執行后臺任務,然后把執行的進度和最終的結果呈現給主線程,并且更新UI。
Asynctask內部封裝了兩個線程池(SerialExecutor
和THREAD_POOL_EXECUTOR
),和一個Handler(IntentHandler)
。
- SerialExecutor線程池用于任務的排隊,讓需要執行的多個耗時任務按順序排列。
- THREAD_POOL_EXECUTOR線程池才真正執行任務。
- IntentHandler用于從工作線程切換到主線程
1.1 AsyncTask的泛型參數
AsyncTask的類型聲明:
public abstract class AsyncTask<Params, Progress, Result> {}
AsyncTask是一個抽象的泛型類。
- Params: 開始異步任務執行時傳入的參數類型。
- Progress:異步任務執行過程中,返回下載進度值的類型。
-
Result: 異步任務執行完成,返回的結果類型。
如果AsyncTask確定不需要傳遞具體參數,那么這三個泛型參數可以用Void來代替。
1.2 AsyncTask的核心方法
- onPreExecute()(主線程中執行):這個方法會在后臺任務開始執行前調用,在主線程執行。用于進行一些界面上的初始化操作,比如顯示一個對話框列表什么的。
-
doInBackground(Params...)(子線程中執行): 這個方法在子線程中執行,負責處理耗時操作。
任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個參數指定的是Void,就可以不返回執行結果。如果要更新UI,可以通過調用publishProgress(Progess...)方法來完成比如反饋當前的進度。 - onProgressUpdate(Progress...)(主線程中執行):當在后臺進程中調用publishProgress(Progress...)方法后,這個方法就會被調用,方法中攜帶的參數就是后臺任務中傳遞過來的。在這個方法中可以對UI進行更新操作。
- onPostExecute(Result)(主線程中執行):當doInBackground(Params...)執行完畢并通過return語句進行返回時,這個方法就很快會被調用。返回的數據會作為參數傳遞到這個方法里面。利用參數的數據,就可以進行UI的修改。
上面幾個方法的調用順序:onPreExcute() --> doInBackground() --> publishProgress() --> onProgressUpdate() --> onPostExcute()
如果不需要執行更新UI操作,順序是:onPreExcute() --> doInBackground() --> onPostExcute()。
- onCancelled()(主線程中執行):該方法被調用,onPostExecute()方法將不會被執行,需要注意的是,AsncTask中的cancel()不是真正的取消任務,只是將任務標記為取消狀態,我們需要在doInBackground()內判斷終止任務。就好比想要終止一個線程,調用interrupt()方法,只是進行標記為中斷,需要在線程內部進行標記判斷,然后中斷線程。
1.3 AsyncTask的簡單使用
public class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
@Override
protected void onPreExecute() {
progressDialog.show();
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
while (true) {
int downloadPercent = doDownload();
publishProgress(downloadPercent);
if (downloadPercent >= 100) {
break;
}
}
} catch (Exception e) {
return false;
}
return true;
}
@Override
protected void onProgressUpdate(Integer... values) {
progressDialog.setMessage("當前下載進度: " + values[0] + "%");
}
@Override
protected void onPostExecute(Boolean result) {
progressDialog.dismiss();
if (result) {
Toast.makeText(context, "下載成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "下載失敗", Toast.LENGTH_SHORT).show();
}
}
}
這里模擬了一個下載任務,在doInBackground方法中,去執行具體的下載邏輯,在onProgressUpdate()方法中顯示下載的進度。在onPostExecute()方法中提示下載任務的結果。
調用方法:
new DownloadTask().execute();
1.4 使用AsyncTask的注意事項
- 異步任務的實例必須在UI線程中創建,即AsynacTask對象必須在UI線程中創建。
- execute(Params... params)必須在UI線程中調用。
- 不要手動調用onPreExecute(),doInBackground(),onProgressUpdate(),onPostExecute()這幾個方法。
- 不能在doInBackground()中更改UI組件的信息。
- 一個任務實例只能執行一次,如果執行第二次會拋出異常。
2.AsyncTask的源碼分析
AsyncTask的構造函數:
public AsyncTask() { this((Looper) null); }
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Result result = null;
try {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
postResultIfNotInvoked(get());
} catch (InterruptedException e) {
android.util.Log.w(LOG_TAG, e);
} catch (ExecutionException e) {
throw new RuntimeException("An error occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
}
在構造函數中,主要是初始化了3個成員變量, mHandler,mWorker,mFuture。并在初始化mFutrue的時候,mWorker作為參數傳入。 mWorker是一個Callable對象,mFuture是以一個FutureTask對象。FutureTask實現了Runnable接口。
mWorker中的call()方法執行了耗時操作,即result = doInBackground(mParams);,然后把得到的結果通過postResult(result);傳遞給內部的Handler跳轉到主線程中。這里實際是在實例化變量,并沒有開啟執行任務。
postResult()方法源碼:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
mFuture對象被加載線程池中,并加載的過程。
new DownloadTask().execute();
其中execute()就是執行了這一操作。
execute()源碼:
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
exec.execute(mFuture);
return this;
}
可以看出,execute()
方法,實際上是執行了executeOnExecutor()
方法,先執行了onPreExecute(),然后具體執行耗時任務是 exec.execute(mFuture);
。對象exec是sDefaultExecutor,往上追溯就是SerialExecutor的實例化對象。
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
SerialExecutor
是個內部靜態內部類,是所有實例化的AsyncTask
對象共有的SerialExecutor
,內部維護了一個隊列,通過鎖使得保證AsyncTask
中的任務是串行執行的,即多個任務需要一個個加到該隊列中。
這個方法中主要有兩個步驟。
- 向隊列中加入一個新的任務,即之前實例化的mFuture對象。
- 2.調用
scheduleNext()
,調用THREAD_POOL_EXECUTOR
執行隊列頭部任務。
由此可以知道SerialExecutor
僅僅是為了保持任務執行隊列是串行的實際執行交給了THREAD_POOL_EXECUTOR
。
THREAD_POOL_EXECUTOR源碼:
public static final Executor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), sThreadFactory);
threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
實際上就是個線程池,開啟了一定數量的核心線程和工作線程。然后調用線程池的execute()方法,執行具體的耗時任務,即開頭構造函數mWorker
中的call()方法。先執行doInBackground()方法,然后再執行postResult()方法。
在postResult()方法中,向在AsyncTask
構造函數中初始化的mHandler發送了一個消息。
在構造函數中通過調用getMainHandler(),實例化mHandler的源碼:
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return sHandler;
}
}
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
在InternalHandler
中,
- 如果收到的消息是
MESSAGE_POST_PROGRESS
, 回調onProgressUpdate()方法, 更新進度。 - 如果收到的消息是
MESSAGE_POST_RESULT
,即執行完了doInBackground()方法并傳遞結果,那就調用AsyncTask.finish()方法。
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
如果任務已經取消了,就回調onCancelled()方法,否則就回調onPostExecute()方法。
InternalHandler
是一個靜態類, 為了能夠將執行環境切換到主線程, 因此這個類必須在主線程中進行加載。 所以變相要求AsyncTask的類必須在主線程中進行加載。到此為止, 從任務執行的開始到結束都從源碼分析完了。
AsyncTask的串行和并行:
默認情況下因為有SerialExecutor
類來維持保證隊列的串行。如果想要使用并行執行任務,那么可以直接跳過SerialExecutor
類,使用executeOnExecutor()來執行任務。
3. AsyncTask使用不當的后果
-
1)生命周期
AsyncTask不與任何組件綁定生命周期,所以在Activity或Fragment中創建執行AsyncTask時候,最好在Activity/Fragment的onDestroy()方法中調用cancle(boolean)方法。 -
2)內存泄漏
如果AsyncTask被聲明為Activity的非靜態的內部類,那么AsyncTask會保留一個對創建了AsyncTask的Activity引用。如果Activity已經被銷毀,AsyncTask的后臺線程還在執行,它將繼續在內存中保存這個引用,導致Activity無法被回收,導致內存泄漏。 -
3)結構丟失
屏幕旋轉或者Activity在后臺被系統殺死等情況會導致Activity的重新創建,之前運行的AsyncTask(非內部靜態類)會持有之前的Activity的引用,這個引用已經無效,這時調用onPostExecute()再去更新界面將不會再生效。