AsyncTask原始碼分析
簡介
有些時候,需要將任務放在子執行緒執行,執行完成後在主執行緒更新UI等,遇到這種情況,我們可以用執行緒間通訊來解決,比如Thread+hander的方式,但是這種需要我自己去完成通訊的邏輯,有讀者立馬會想起,使用Android系統的自帶的AsyncTask來實現,但是你知道不同Android版本AsyncTask有什麼不同,已經使用AsyncTask有什麼問題,需要注意什麼嗎?下面我將從原始碼的角度給大家講解,AsyncTask的實現和優缺點。
AsyncTask簡化模型
可以看成是:執行緒池+Handler,執行緒池執行耗時的後臺任務,Handler處理UI互動。
AsyncTask序列和並行
- android 1.5以前的時候execute是序列執行的
- android 1.6直到android 2.3.2被修改為並行執行,執行任務的執行緒池就是THREAD_POOL_EXECUTOR
- android 3.0以後,預設任務是序列執行的,如果想要並行執行任務可呼叫executeOnExecutor(Executor exec, Params.. params)
AsyncTask實現過程
- 建立AsyncTask任務物件
private AsyncTask task = newAsyncTask<Void, Integer, Boolean>() { //撤銷非同步任務 @Override protected void onCancelled() { super.onCancelled(); } //非同步執行耗時任務 @Override protected Boolean doInBackground(Void... params) { return true; } //處理任務執行完成後需要執行的操作 @Override protected void onPostExecute(Boolean aBoolean) { super.onPostExecute(aBoolean); } //非同步任務開始執行前需要執行的操作 @Override protected void onPreExecute() { super.onPreExecute(); } //主執行緒更新UI @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); } };
- AsyncTask類初始化
靜態資料初始化: 0001-執行緒池相關變數初始化: private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();//根據cpu的大小來配置核心的執行緒 private static final int CORE_POOL_SIZE = CPU_COUNT + 1;//核心執行緒數量 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;//執行緒池中允許的最大執行緒數目 private static final int KEEP_ALIVE = 1;//空閒執行緒的超時時間 private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable r) { return new Thread(r, "AsyncTask #" + mCount.getAndIncrement()); } }; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128); public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); 0002-非同步任務執行順序佇列變數初始化: public static final Executor SERIAL_EXECUTOR = new SerialExecutor();//這個內部類實現了非同步任務的序列執行。 AsyncTask建構函式: public AsyncTask() { mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //設定當前執行執行緒為後天執行緒 //noinspection unchecked return postResult(doInBackground(mParams));//將結果傳送出去 } }; mFuture = new FutureTask<Result>(mWorker) { //任務執行完畢後會呼叫done方法 @Override protected void done() { try { //get()表示獲取mWorker的call的返回值,即Result.然後看postResultIfNotInvoked方法 postResultIfNotInvoked(get()); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occured while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } }; }
AsyncTask的物件必須在主執行緒中例項化,原因下面講解。 執行AsyncTask非同步任務:task.execute();
public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); } ---sDefaultExecutor其實是一個SerialExecutor物件,實現了序列執行緒佇列。params其實最終會賦給doInBackground方法去處理。 executeOnExecutor(Executor exec,Params... params)方法: public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute();//用於在非同步任務執行前的初始化操作 mWorker.mParams = params; exec.execute(mFuture); //exec是一個SerialExecutor物件,實現了序列執行緒佇列 return this; }
這裡要說明一下,AsyncTask的非同步任務有三種狀態
- PENDING 待執行狀態。當AsyncTask被建立時,就進入了PENDING狀態。
- RUNNING 執行狀態。當呼叫executeOnExecutor,就進入了RUNNING狀態。
- FINISHED 結束狀態。當AsyncTask完成(使用者cancel()或任務執行完畢)時,就進入了FINISHED狀態。
由於要執行onPreExecute()方法,在這個方法裡面我們可能要去做有關UI操作的事情,所以這個操作必須在UI執行緒完成
- 實現序列AsyncTask非同步任務:exec.execute(mFuture);
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); } } }
每執行一次execute方法,就會向mTasks(雙端佇列)的隊尾插入一個Runnable物件。當第一次執行非同步任務的時候,mActive等於null,所以會從佇列裡面取出第一個utureTask物件,THREAD_POOL_EXECUTOR(建立的執行緒池物件)呼叫execute方法開始執行。當前任務執行完成後會執行到剛才向mTasks新增的Runable的run方面,從而執行傳遞過來的FutureTask物件的run方法,FutureTask實現了RunnableFuture介面,RunnableFuture繼承了Runable和Future介面。那麼FutureTask物件的run方法裡面都做了什麼操作呢?
-執行耗時的後臺任務:r.run()
檢視FutureTask原始碼裡面的run方法:如下 public void run() { if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); } if (ran) set(result); } } finally { // runner must be non-null until state is settled to // prevent concurrent calls to run() runner = null; // state must be re-read after nulling runner to prevent // leaked interrupts int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } } 檢測當前狀態是否是NEW,如果不是,說明任務已經完成或取消或中斷,所以直接返回,那麼什麼時候狀態被賦值為NEW的?請看呼叫AsyncTask建構函式裡對FutureTask物件 初始化,FutureTask建構函式如下: public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW;也是在非同步任務未執行前初始化的。 } result = c.call(),c就是等於構造FutureTask物件時傳遞過來的WorkerRunnable物件,該物件實現了Callable接口裡面的call方法, 所有會去執行WorkerRunnable物件裡的call()方法,該物件在AsyncTask建構函式裡面初始化的一個內部類。如下: public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked return postResult(doInBackground(mParams)); } 這裡會去執行doInBackground方法,由於現在不是在主執行緒裡面,所有可以在這裡執行耗時的後臺任務。那麼在我們的doInBackground裡面又需要做什麼操作呢?
- 執行我們的耗時後臺任務:doInBackground(Void... params)
@Override protected Boolean doInBackground(Void... params) { ... publishProgress(progress);//必須執行這個方法,為什麼請看publishProgress方法實現: ... return true; } protected final void publishProgress(Progress... values) { if (!isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget(); } }
- Handler訊息處理:
private static class InternalHandler extends Handler { public InternalHandler() { super(Looper.getMainLooper()); } @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; } } }
此時會去呼叫onProgressUpdate方法,改方法裡面我們進行UI更新操作。
- 解釋為什麼AsyncTask的物件必須在主執行緒中例項化
這個還得從上面InternalHandler類說起,在API 22以下的程式碼,會發現它沒有這個建構函式
public InternalHandler() { super(Looper.getMainLooper()); }
而是使用預設的;預設情況下,Handler會使用當前執行緒的Looper,如果你的AsyncTask是在子執行緒建立的,那麼很不幸,你的onProgressUpdate(Integer... values)
和onPostExecute並非在UI執行緒執行,而是被Handler post到建立子執行緒執行;如果你在這兩個執行緒更新了UI,那麼直接導致崩潰。
這也是大家口口相傳的AsyncTask必須在主執行緒建立的原因。
另外,AsyncTask裡面的這個Handler是一個靜態變數,也就是說它是在類載入的時候建立的;如果在你的APP程序裡面,以前從來沒有使用過AsyncTask,
然後在子執行緒使用AsyncTask的相關變數,那麼導致靜態Handler初始化,如果在API 16以下,那麼會出現上面同樣的問題;這也就是AsyncTask必須在主執行緒初始化 的原因。
事實上,在Android 4.1(API 16)以後,在APP主執行緒ActivityThread的main函式裡面,直接呼叫了AscynTask.init函式確保這個類是在主執行緒初始化的,這樣在使用非同步任務之前,
就能確保所用到的Handler用的是主執行緒的Looper;另外,init這個函式裡面獲取了InternalHandler的Looper,由於是在主執行緒執行的,
因此,AsyncTask的Handler用的也是主執行緒的Looper。這個問題從而得到徹底的解決
- postResult(doInBackground(mParams));
這時候,onProgressUpdate(Integer... values)在主執行緒更新UI,工作現在繼續執行。 private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; } 非同步任務執行完成,呼叫finish方法,finish方法如下:
- finish()
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
-
執行的大致流程
onPreExecute-> doInBackGround->onProgressUpdate(呼叫publishProgress的時候)->onPostExecute
-
取消非同步任務
AsyncTask.cancel(mayInterruptIfRunning); public final boolean cancel(boolean mayInterruptIfRunning) { mCancelled.set(true); return mFuture.cancel(mayInterruptIfRunning); } FutureTask.cancel(): public boolean cancel(boolean mayInterruptIfRunning) { //檢測當前狀態是否是NEW,如果不是,說明任務已經完成或取消或中斷,所以直接返回。 if (!(state == NEW && U.compareAndSwapInt(this, STATE, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) return false; try { /如果mayInterruptIfRunning為true的時候,執行緒就會呼叫interrupt()方法 if (mayInterruptIfRunning) { try { Thread t = runner; if (t != null) //呼叫interrupt方法,狀態設定為INTERRUPTING,然後試著中斷執行緒,完成後設定狀態為INTERRUPTED t.interrupt(); } finally { // final state //通知等待執行緒的結果(因為FutureTask.get()法獲得計算結果的唯一方法,如果計算沒有完成,此方法會堵塞直到計算完成) U.putOrderedInt(this, STATE, INTERRUPTED); } } } finally { finishCompletion(); } return true; }
總結
AsyncTask需要注意地方
1、AsyncTask的物件必須在主執行緒中例項化,execute方法也要在主執行緒呼叫(檢視3.1節-AsyncTask建構函式)
2、同一個AsyncTask任務只能被執行一次,即只能呼叫一次execute方法,多次呼叫時將會拋異常(檢視3.2裡面的第二小節)
3、cancel()方法無法直接中斷子執行緒,只是更改了中斷的標誌位。控制非同步任務執行結束後不會回撥onPostExecute()。正確的取消非同步任務要cancel()方法+doInbacground()做判斷跳出迴圈
4、AsyncTask在Activit通常作為匿名的內部類來使用,如果 AsyncTask 中的非同步任務在 Activity 退出時還沒執行完或者阻塞了,那麼這個保持的外部的 Activity 例項得不到釋放(內部類保持隱式外部類的例項的引用),最後導致會引起OOM,解決辦法是:在 AsyncTask 使用弱引用外部例項,或者保證在 Activity 退出時,所有的 AsyncTask 已執行完成或被取消
5、會產生阻塞問題,尤其是單任務順序執行的情況下,一個任務執行時間過長會阻塞其他任務的執行
6、不建議使用AsyncTask進行網路操作 AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs。 Android文件中有寫到AsyncTask應該處理幾秒鐘的操作(通常為輕量的本地IO操作),由於網路操作存在不確定性,可能達到幾秒以上,所以不建議使用。