Android開發:異步任務AsyncTask源碼解析

AsyncTask使用方式:

/**
 * @Author: david.lvfujiang
 * @Date: 2019/10/15
 * @Describe:
 */
public class DownloadTask extends AsyncTask<String, Integer, Boolean> {
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        Log.e("異步準備", "啓動");
    }
 
    @Override
    protected void onPostExecute(Boolean aBoolean) {
        super.onPostExecute(aBoolean);
        if (aBoolean == true){
            Log.e("異步結束", "結束");
        }
    }
 
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
        Log.e("更新ui", String.valueOf(values));
    }
 
    @Override
    protected Boolean doInBackground(String... strings) {
      Log.e("異步操作", "操作");
        for (int i = 0; i < 10; i++) {
           if (isCancelled()) {
                Log.e("cancell", isCancelled()+"");
                break;
            }
           else {
           		//通知ui更新
                publishProgress(new Integer(i));
                Log.e("cancell", isCancelled()+"");
            }
        }
        return true;
    }
}

DownloadTask task= new DownloadTask();
task.execute("name");

1.onPreExecute() :在這個方法是異步開始之前執行的,可以做一些初始化工作。它所在線程與異步任務的execute方法所在的線程一致,這裏若需要更新UI等操作,則execute方法不能再子線程中執行。
2.doInBackground(String name) :這個方法是運行在子線程,我們可以進行我們的耗時任務,我們可以每隔一秒在這個方法裏調用一次publishProgress(Integer i),之後系統將執行onProgressUpdate(Integer i)
3.onProgressUpdate(Integer... values):這個方法運行在主線程,每次調用publishProgress()後它也會被調用,所以我們可以在這裏面進行UI更新
4.onPostExecute(Boolean aBoolean)doInBackground()方法執行完後這個方法將會執行,我們可以在這做一些收尾工作
5.當如果我們異步任務想要停止的話可以調用cancell( Boolean b)方法,但是cancel()僅僅是給AsyncTask對象設置了一個標識位,發生的事情只有:AsyncTask對象的標識位變了,doInBackground()onProgressUpdate()還是會繼續執行直到doInBackground()結束,而onPostExecute()不會被回調。所以我們需要在doInBackground()的每次循環中調用isCancelled()去判斷是否繼續執行任務。

需要注意的是:

1.一個AsyncTask對象不能多次調用execute方法,否則會報異常

看完上面AsyncTask的用法我們會有幾個疑問:
1.爲什麼多次調用一個AsyncTask對象的execute方法會報異常?
2.AsyncTask的生命週期它的每一個方法是怎麼調用的?
3.爲什麼調用cancell()後onPostExecute()不會執行了?

我們從源碼來一步步分析:

我們使用AsyncTask的時候是創建對象然後調用execute方法,那我們先來看構造函數:

297    public AsyncTask() {
			//創建WorkerRunnable
298        mWorker = new WorkerRunnable<Params, Result>() {
299            public Result call() throws Exception {
300                mTaskInvoked.set(true);
301                Result result = null;
302                try {
303                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
304                    //doInBackground()在這被調用
305                    result = doInBackground(mParams);
306                    Binder.flushPendingCommands();
307                } catch (Throwable tr) {
308                    mCancelled.set(true);
309                    throw tr;
310                } finally {
311                    postResult(result);
312                }
313                return result;
314            }
315        };
316	
			//創建FutureTask對象
317        mFuture = new FutureTask<Result>(mWorker) {
318            @Override
319            protected void done() {
320                try {
321                    postResultIfNotInvoked(get());
322                } catch (InterruptedException e) {
323                    android.util.Log.w(LOG_TAG, e);
324                } catch (ExecutionException e) {
325                    throw new RuntimeException("An error occurred while executing doInBackground()",
326                            e.getCause());
327                } catch (CancellationException e) {
328                    postResultIfNotInvoked(null);
329                }
330            }
331        };
332    }
333

我們可以看到構造函數中初始化了倆個對象mWorker 、mFuture 。mWorker 是WorkerRunnable的實例,WorkerRunnable是AsyncTask的內部類實現了Callable接口,因此mWorker 其實就是一個線程,它啓動的時候會調用call()方法,call()方法又會調用doInBackground(),因此doInBackground()可以執行我們的耗時操作,至於mWorker 什麼時候被啓動我們後面講解:

692
693    private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
694        Params[] mParams;
695    }
696

mWorker 是FutureTask的實例,FutureTask可以像Runnable一樣運行,封裝異步任務,然後提交給Thread或線程池執行,然後獲取任務執行結果。不瞭解的可以看這篇博客:FutureTask用法及解析。在這裏FutureTask封裝了mWorker ,所以當我們的線程執行完成後會回調FutureTask的done()方法。構造函數到這解析完畢。

execute()方法源碼:

565    @MainThread
566    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
567        return executeOnExecutor(sDefaultExecutor, params);
568    }


603    @MainThread
604    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
605            Params... params) {
606        if (mStatus != Status.PENDING) {
				//拋出異常
607            switch (mStatus) {
608                case RUNNING:
609                    throw new IllegalStateException("Cannot execute task:"
610                            + " the task is already running.");
611                case FINISHED:
612                    throw new IllegalStateException("Cannot execute task:"
613                            + " the task has already been executed "
614                            + "(a task can be executed only once)");
615            }
616        }
617
618        mStatus = Status.RUNNING;
619
			//調用onPreExecute
620        onPreExecute();
621
622        mWorker.mParams = params;
			//將線程任務加入隊列
623        exec.execute(mFuture);
624
625        return this;
626    }

可以看到execute()方法有個註解 @MainThread,說明這個方法必須在主線程中調用。再來看executeOnExecutor()方法,從606行開始有個判斷,判斷mStatus 的值。mStatus 是用來標識AsyncTask是否已經在執行異步的一個狀態,默認是Status.PENDING,因此第一次調用execute()方法不會發生異常,最後在618行設置mStatus = Status.RUNNING;因此當我們第二次調用execute方法時將會拋出異常(上文第一個問題解決了)。然後在620行調用了 onPreExecute();,最後呢623行調用了exec.execute(mFuture)execAsyncTask的內部類SerialExecutor的實例。

235    private static class SerialExecutor implements Executor {
			//創建任務隊列
236        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
237        Runnable mActive;
238			//該方法使用同步鎖
239        public synchronized void execute(final Runnable r) {
				//創建一個線程加入任務隊列
240            mTasks.offer(new Runnable() {
				//這個線程的工作就是啓動傳進來的Runnable 對象
241                public void run() {
242                    try {
243                        r.run();
244                    } finally {
245                        scheduleNext();
246                    }
247                }
248            });
				//第一次執行這個方法會走這個判斷
249            if (mActive == null) {
250                scheduleNext();
251            }
252        }
253
254        protected synchronized void scheduleNext() {
				//獲取任務隊列的頭執行任務
255            if ((mActive = mTasks.poll()) != null) {
256                THREAD_POOL_EXECUTOR.execute(mActive);
257            }
258        }
259    }

		// Executor THREAD_POOL_EXECUTOR就是一個線程池
205    public static final Executor THREAD_POOL_EXECUTOR;
206
207    static {
208        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
209                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
210                sPoolWorkQueue, sThreadFactory);
211        threadPoolExecutor.allowCoreThreadTimeOut(true);
212        THREAD_POOL_EXECUTOR = threadPoolExecutor;
213    }

SerialExecutor 內部維護了一個任務隊列ArrayDequeArrayDeque也是集合中的一種,execute()方法則是創建一個線程加入到任務隊列,而線程則又封裝了我們傳入的Runnable 對象,即mFuture對象。第一次執行的時候mActive 爲null,因此250行執行了scheduleNext()方法,scheduleNext()方法則是從隊列中取出任務用線程池執行(THREAD_POOL_EXECUTOR是一個線程池),任務執行後則會執行241行到245行的代碼,243行執行了r.run(),即執行mFuture.run()方法,mFuture.run()內部又調用了mWorker .call()方法,call()方法又會調用doInBackground()。最後244行的finally 代碼塊中又調用了scheduleNext()方法,因此我們的異步任務就是這樣一個一個的被調用的。就是靠SerialExecutor 內部維護了一個任務隊列,我們把任務按順序的放到隊列中,然後不斷的從隊列取出任務執行直到隊列爲空。

現在我們知道真正執行異步任務的是靠THREAD_POOL_EXECUTOR這個線程池,並且這個線程池是靜態的,在多線程下創建不同的AsyncTask對象執行異步操作也都是需要串行執行。android開發:AsyncTask實現併發執行異步任務

在這裏插入圖片描述

任務執行異步任務的時候會回調Callable的call()方法,因此我們回到構造函數看:

```c
297    public AsyncTask() {
			//創建WorkerRunnable
298        mWorker = new WorkerRunnable<Params, Result>() {
299            public Result call() throws Exception {
300                mTaskInvoked.set(true);
301                Result result = null;
302                try {
303                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
304                    //doInBackground()在這被調用
305                    result = doInBackground(mParams);
306                    Binder.flushPendingCommands();
307                } catch (Throwable tr) {
308                    mCancelled.set(true);
309                    throw tr;
310                } finally {
311                    postResult(result);
312                }
313                return result;
314            }
315        };
316	
			//創建FutureTask對象
317        mFuture = new FutureTask<Result>(mWorker) {
318            @Override
319            protected void done() {
320                try {
321                    postResultIfNotInvoked(get());
322                } catch (InterruptedException e) {
323                    android.util.Log.w(LOG_TAG, e);
324                } catch (ExecutionException e) {
325                    throw new RuntimeException("An error occurred while executing doInBackground()",
326                            e.getCause());
327                } catch (CancellationException e) {
328                    postResultIfNotInvoked(null);
329                }
330            }
331        };
332    }
333

先是調用doInBackground()執行異步操作,311行調用了postResult(result)

341    private Result postResult(Result result) {
342        @SuppressWarnings("unchecked")
343        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
344                new AsyncTaskResult<Result>(this, result));
345        message.sendToTarget();
346        return result;
347    }

279		//獲取handler對象
280    private static Handler getHandler() {
281        synchronized (AsyncTask.class) {
282            if (sHandler == null) {
					//InternalHandler是一個內部類
283                sHandler = new InternalHandler();
284            }
285            return sHandler;
286        }
287    }
288

postResult(result)則是使用Handler發送MESSAGE_POST_RESULT消息。這個Handler又是從哪來的?

671
672    private static class InternalHandler extends Handler {
673        public InternalHandler() {
674            super(Looper.getMainLooper());
675        }
676
677        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
678        @Override
679        public void handleMessage(Message msg) {
680            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
681            switch (msg.what) {
682                case MESSAGE_POST_RESULT:
683                    // 結束異步
684                    result.mTask.finish(result.mData[0]);
685                    break;
					//更新UI
686                case MESSAGE_POST_PROGRESS:
687                    result.mTask.onProgressUpdate(result.mData);
688                    break;
689            }
690        }
691    }


663    private void finish(Result result) {
664        if (isCancelled()) {
665            onCancelled(result);
666        } else {
667            onPostExecute(result);
668        }
669        mStatus = Status.FINISHED;
670    }

InternalHandlerAsyncTask內部的一個靜態內部類。我們看到它的構造函數則是獲取主線程的Looper對象和handler進行綁定。所以我們使用這個handler發送消息時就可以在主線程中更新UI。可以看到handleMessage()調用了finish()onProgressUpdate()

因此doInBackground()執行完成後最後會調用到finish()onPostExecute()則會被執行。同時你可以看到如果我們調用了cancell(),,onPostExecute()則不會被執行,而是執行了 onCancelled(result)解決了上文我們的第三個問題

我們通知更新UI的時候是在doInBackground()調用publishProgress(),它就是使用handler發送一個MESSAGE_POST_PROGRESS消息,handler收到後則執行onProgressUpdate()

655    @WorkerThread
656    protected final void publishProgress(Progress... values) {
657        if (!isCancelled()) {
658            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
659                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
660        }
661    }

總結:

其實整個異步任務的大概流程就是這樣子的,其中涉及的知識點比較多,這裏總結一下:

1.AsyncTask內部維護了一個任務隊列,它會把我們的異步任務封裝成一個Callable實例,然後添加到任務隊列中,並且使用線程池不斷的從任務隊列中取出任務執行,而Callable實例最後會回調doInBackground()。

2.onPreExecute()方法主要用於在異步任務執行之前做一些操作,execute方法不能再子線程中執行。

3.通過剛剛的源碼分析可以知道異步任務一般是順序執行的,即一個任務執行完成之後纔會執行下一個任務。

doInBackground這個方法所在的線程爲任務所執行的線程,在這裏可以進行一些後臺操作。

4.AsyncTask有個handler內部類,構造方法綁定主線程的looper對象,異步任務執行完成之後會通過handler發送消息,最終回調我們的onPostExecute()方法,在doInBackground()方法中通知更新ui調用publishProgress()方法,publishProgress也是通過handler發送消息,最後回調onProgressUpdate()。

5.異步任務對象不能執行多次,即不能創建一個對象執行多次execute方法。(通過execute方法的源碼可以得知)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章