從原始碼角度談談AsyncTask的使用及其原理

晨雨細曲發表於2019-02-27

從事Android開發的都知道,我們在進行耗時操作的時候是不能在主執行緒進行的,不然會報ANR異常,因此我們必須開啟一個子執行緒,線上程中處理耗時操作。但是在子執行緒中我們拿到了耗時操作返回的資料之後需要在UI上展示,但是在子執行緒又不能對UI進行更新,於是乎在Android內部就有了訊息通訊機制Handler以及AsyncTask。今天我們來講講AsyncTask的使用以及配合他的原始碼來講講他的內部原理。

介紹

AsyncTask是一個抽象類,內部其實他定義了兩個執行緒池(ThreadPoolExecutor、SerialExecutor)以及一個Handler(InternalHandler)。而我們在使用AsyncTask的時候,一般會往這個類裡面傳遞三個引數,ParamsProgressResult

Params:這個引數代表需要傳進來進行載入或者進行耗時操作的引數。一般來說我們可以傳入一個網路請求的地址。

Progress:這個引數代表我們需要在進行耗時操作的時候更進進度條的引數返回值。

Result:這個引數代表我們在進行完成耗時操作之後拿到的結果資料。

而在這個類內部擁有四個方法。

onPreExecute():我們在進行初始化資料的時候呼叫這個方法,這個方法是在主執行緒執行。

doInBackground():我們在進行耗時操作的時候呼叫這個方法,所有的耗時操作都在這個裡面進行操作。並且將耗時操作的結果返回回去。

onProgressUpdata():對控制元件的進度進行操作。

onPostExecute():這個方法裡面會拿到doInBackground()方法中返回的引數結果,我們在這個方法裡面可以對UI進行操作,從而達到更新UI的結果。

整體程式碼如下所示:

class MyAsyncTask extends AsyncTask<String,Integer,String>{
 
        @Override
        protected void onPreExecute() {
            //資料初始化操作
            super.onPreExecute();
        }
 
        @Override
        protected String doInBackground(String... strings) {
            //耗時操作,並將結果返回
            return null;
        }
 
        @Override
        protected void onProgressUpdate(Integer... values) {
            //對進度條進行更新操作
            super.onProgressUpdate(values);
        }
 
        @Override
        protected void onPostExecute(String s) {
            //UI更新操作
            super.onPostExecute(s);
        }
    }
複製程式碼

之後我們在主執行緒中使用execute()方法來開啟整個任務,執行任務。

AsyncTask的使用其實非常簡單,很多地方其本身就已經幫助我們封裝好了,這使得我們使用起來簡單方便。但是作為一個有追求的工程師,單單會使用是遠遠不夠的,我們還必須瞭解其內部的原理,從而達到知其然也知其所以然的目的。下面我們來結合原始碼來談談內部原理。

原始碼解析

我們在之前說過,其實AsyncTask的內部主要是對三個東西進行了封裝處理,兩個執行緒池(ThreadPoolExecutor、SerialExecutor)以及一個Handler(InternalHandler)。那內部是怎麼對這些東西進行耗時操作的吶?我們結合我們的時候,一步一步來進行分析。

首先,我們在使用AsyncTask的時候,一般都會在主執行緒中new出這個物件來,那麼我們先來看看他的構造方法裡面做了什麼處理。

 /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    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);
                }
            }
        };
    }
複製程式碼

我們可以看到這裡面程式碼很長,但是主要就是做了兩步操作。第一,例項化了一個WorkerRunnable方法,第二個例項化了FutureTask方法。WorkerRunnable方法其實是一個Runnable方法,在第12行我們看到了我們之前的耗時操作doInBackground()方法,可見我們在進行耗時操作的時候,其實是在這個方法中執行的。執行完成之後將結果通過postResult()方法返回。而FureTask方法其實是實現了Runnable介面,我們將WorkerRunnable這個方法傳遞進去,具體的使用我們在後面會進行講解。

在執行AsyncTask的時候,我們會呼叫execute()。我們看下這個方法內部進行的操作。

 @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }
複製程式碼

可以看到這個方法內部其實是呼叫了executeOnExecutor()這個方法,我們在來看看這個方法內部做了什麼操作。

 @MainThread
    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);
 
        return this;
    }
複製程式碼

我們看到這個方法內部其實做了這麼幾步操作。

首先通過一個switch來進行了兩次判斷,第一次判斷是否任務正在執行,RUNNING。如果正在執行則丟擲一個異常。這就代表一個任務只能呼叫一次execute,呼叫兩次則會報錯。第二次判斷這個任務是否已經結束了,FINISHED,如果結束了還執行也會丟擲異常,這個任務已經執行完成,一個task只能執行一次操作。

之後我們用呼叫onPreExecute()方法來進行初始化資料操作。

然後我們看到在最後我們執行了一個方法exec.execute(mFutrue)將我們之前的mFuture任務傳遞進行。

那麼exec是什麼?其實exec是sDefaultExecutor,再追溯上去發現這個sDefaultExecutor其實是SerialExecutor。那我們在跳進這個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);
            }
        }
    }
複製程式碼

可以看到,裡面邏輯並不複雜。首先是例項化了一個任務佇列ArrayDeque,之後用一個synchronized進行一個同步鎖操作。這樣保證的目的是一次只能往任務佇列中新增一個任務,只有等任務佇列頭部執行完成之後,才能呼叫scheduleNext去執行下一個任務。這就導致一個什麼結果吶?這就說明,其實我們的AsyncTask在執行耗時操作任務的時候,並不是並行操作,而是使用了SerialExecutor來行了維護了一個任務佇列,通過同步鎖的形式一個一個往裡面新增執行的任務,最後拿出來一個一個的操作。其實其內部是個序列操作。

之後我們在來看下ThreadPoolExecutor。

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
複製程式碼

我們可以看到,其實這裡面維護了一個執行緒池的操作,開啟了一定數量的執行緒,然後呼叫execute()方法來執行這個執行緒,即為我們開頭說的那個workerRunnable執行緒中的任務操作。最後將結果交給postResult()方法去處理。

我們再來看看這個postResult()方法內部是怎麼進行操作的。

private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }
複製程式碼

我們可以從程式碼中看到,其實內部有一個Message的訊息機制,他通過sendToTarget()方法想Handler傳送了一條訊息。那麼我們再跳轉到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;
            }
        }
    }
複製程式碼

從原始碼中我們可以看到,這個handleMessage裡面做了兩步操作,第一是判斷這個任務是否後已經結束了MESSAGE_POST_RESULT,如果是返回了這個則代表任務已經結束了,呼叫finish()方法來結束掉。第二個是判斷MESSAGE_POST_PROGRESS,代表更新進度條的操作,等於說我們在使用更新進度條的時候一般會去呼叫onPorgressUpdate()方法就是在這裡面進行操作的。

最後我們在來看下finish()方法中做了什麼操作。

private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }
複製程式碼

我們可以很清楚的看到,這裡面其實也做了兩步操作,第一是判斷這個非同步任務是否取消cancel了,如果cancle了就直接呼叫onCancelled()方法取消任務;如果不是cancle了那麼就直接呼叫onPostExecute()方法將result結果返回給主執行緒去使用。

到此,AsyncTask的內部原理就全部分析完成了。

我們來總結一下:

AsyncTask內部其實是維護了兩個執行緒池(ThreadPoolExecutor、SerialExecutor)和一個訊息機制Handler(InternalHandler)。我們在使用AsyncTask的時候,在構造方法中會先例項化一個Runnable介面物件WorkerRunnable和一個任務物件FureTask。WorkerRunnable是一個Runnable,一般我們的doInbackground等耗時操作都在這個裡面進行,最後將這個物件新增進入FureTask內部。

而在AsyncTask內部有兩個執行緒池,SerialExecutor的作用其實並不是執行執行緒操作。而是維護了一個任務佇列,通過一個同步鎖的形式不斷往任務佇列中新增非同步任務,並且在執行完佇列頭才能再去執行下一個任務,這就導致了我們在執行多個非同步任務的時候並非我們所想的是並行,而是序列。當然如果你想進行並行操作直接呼叫executeOnExecutor()方法即可。

而另一個執行緒池ThreadPoolExecutor則是真正的執行非同步操作的執行緒池物件。他開啟執行緒,執行FutureTask中的任務,在執行完成之後通過一個內部的Handler,即InternalHandler將結果通知給主執行緒,即onPostExecutor()方法,從而執行更新UI的操作。從而完成以上所以內部的請求。

從原始碼角度談談AsyncTask的使用及其原理

相關文章