Android多線程之AsyncTask源碼解析

AsyncTask 是一個較爲輕量級的異步任務類,在底層通過封裝 ThreadPool 和 Handler ,實現了線程的複用,後臺任務執行順序的控制、子線程和 UI 線程的切換,使得開發者可以以簡單的方法來執行一些耗時任務

此篇文章就基於 Android API 27 版本的源碼來對 AsyncTask 進行一次整體分析,以便對其底層工作流程有所瞭解

一般,AsyncTask 是以類似於以下的方式來調用的

        new AsyncTask<String, Integer, String>() {

            @Override
            protected String doInBackground(String... strings) {
                return null;
            }
            
        }.execute("leavesC");

所以這裏就從 execute() 方法入手

    //以默認的串行任務執行器 sDefaultExecutor 來執行後臺任務
    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

execute(Params)方法內部調用的是 executeOnExecutor(sDefaultExecutor, params)方法,當中 sDefaultExecutor用於定義任務隊列的執行方式,AsyncTask 默認使用的是串行任務執行器

    //以指定的任務執行器 Executor 來執行後臺任務
    @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) {
        //Task 只能被執行一次,如果 mStatus != Status.PENDING ,說明 Task 被重複執行,此時將拋出異常
        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;
        //在 doInBackground() 方法之前被調用,用於做一些界面層的準備工作
        onPreExecute();
        //執行耗時任務
        mWorker.mParams = params;
        exec.execute(mFuture);
        return this;
    }

mStatus是一個枚舉變量,用於定義當前 Task 的運行狀態,用於防止 Task 被重複執行

    //用於標記 Task 的當前狀態
    public enum Status {
        //Task 還未運行
        PENDING,

        //Task 正在運行
        RUNNING,

        //Task 已經結束
        FINISHED,
    }

之後就調用任務執行器,提交任務

        //執行耗時任務
        mWorker.mParams = params;
        exec.execute(mFuture);

executeOnExecutor(Executor, Params)方法可以從外部傳入自定義的任務執行器對象,例如可以傳入 AsyncTask.THREAD_POOL_EXECUTOR 使 AsyncTask 中的任務隊列以並行的方式來完成

這裏先來看下默認的串行任務執行器是如何執行的

每一個被提交的任務都會被加入任務隊列 mTasks當中,mActive表示當前在執行的任務,每當有新任務 Runnable 到來時,就會在 Runnable 的外層多包裹一層 Runnable ,然後將之插入到任務隊列中,當 execute(Runnable)方法第一次被執行時,mActive爲 null ,因此就會觸發 scheduleNext()方法獲取任務隊列的第一個任務並提交給線程池 THREAD_POOL_EXECUTOR 進行處理,當 r.run()方法返回時(即任務處理結束),在 finally中又會獲取下一個任務進行處理,從而實現了任務隊列的串行執行

    //串行任務執行器,即提交給線程池的任務是按照順序一個接一個被執行的
    private static class SerialExecutor implements Executor {

        //任務隊列
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();

        //當前在執行的任務
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            //向任務隊列尾端插入任務
            //在外部任務外部包裝多一層 Runnable
            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);
            }
        }
    }

再看下線程池 THREAD_POOL_EXECUTOR 是如何定義的

可以看到,具體的線程池實現類是 ThreadPoolExecutor,使用線程池從而避免了線程重複的創建與銷燬操作,有利於提高系統性能

    //CPU 核數量
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

    //線程池中的核心線程數
    //至少有2個,最多4個,線程數至少要比 CPU 核數量少1個,以避免 CPU 與後臺工作飽和
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));

    //線程池容納的最大線程數量
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;

    //線程在閒置時的存活時間(30秒),超出這個時間將被回收
    private static final int KEEP_ALIVE_SECONDS = 30;

    //線程隊列
    //當 LinkedBlockingDeque 已滿時,新增的任務會直接創建新線程來執行,當創建的線程數量超過最大線程數量 KEEP_ALIVE_SECONDS 時會拋出異常
    private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(128);

    //線程工廠,提供創建新線程的功能,通過線程工廠可以對線程的一些屬性進行定製
    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());
        }

    };

    //線程池對象
    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        //包括核心線程在內的所有線程在閒置時間超出 KEEP_ALIVE_SECONDS 後都將其回收
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

    //當前 Task 使用的任務執行器
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

看到線程池,這裏就又引出了另外一個問題,後臺任務是在子線程中調用的,那 AsyncTask 又是如何在 UI 線程中回調 onPreExecute()、onPostExecute(Result)、onProgressUpdate(Progress)這幾個方法的呢?

先看幾個相關方法的聲明

    //在子線程中被調用,用於執行後臺任務
    @WorkerThread
    protected abstract Result doInBackground(Params... params);

    //在 UI 線程中被調用,在 doInBackground() 方法之前調用,用於在後臺任務開始前做一些準備工作
    @MainThread
    protected void onPreExecute() {
    }

    //在 UI 線程中被調用,在 doInBackground() 方法之後調用,用於處理後臺任務的執行結果
    //參數 result 是 doInBackground() 方法的返回值
    @SuppressWarnings({"UnusedDeclaration"})
    @MainThread
    protected void onPostExecute(Result result) {
    }

    //在 UI 線程中被調用,當調用了 publishProgress() 方法後被觸發
    //用於更新任務進度值
    @SuppressWarnings({"UnusedDeclaration"})
    @MainThread
    protected void onProgressUpdate(Progress... values) {
    }

    //在 UI 線程中被調用
    //當調用了 cancel(boolean) 方法取消後臺任務後會被調用
    //在 doInBackground() 方法結束時也會被調用
    //方法內部默認調用了 onCancelled() 方法
    @SuppressWarnings({"UnusedParameters"})
    @MainThread
    protected void onCancelled(Result result) {
        onCancelled();
    }

    //在 UI 線程中被調用,被 onCancelled(Result) 方法調用
    @MainThread
    protected void onCancelled() {
    }

onPreExecute()executeOnExecutor(Executor, Params)中有被調用,因爲 executeOnExecutor()方法被要求在 UI 線程中調用,因此 onPreExecute()自然也會在 UI 線程中被執行

其它方法的調用則涉及到了 Handler、Looper 與 MessageQueue 的相關知識點,關於這些可以從這裏獲取詳細介紹: Java_Android_Learn ,這裏就簡單介紹下

看下 AsyncTask 類的三個構造函數。當中,除了無參構造函數,其他兩個構造函數都使用 @hide註解隱藏起來了,因此我們在一般情況下只能使用調用無參構造函數來初始化 AsyncTask

    //創建一個新的異步任務,必須在UI線程上調用此構造函數
    public AsyncTask() {
        this((Looper) null);
    }
    
    /**
     * 隱藏的構造函數
     * 創建一個新的異步任務,必須在UI線程上調用此構造函數
     *
     * @hide
     */
    public AsyncTask(@Nullable Handler handler) {
        this(handler != null ? handler.getLooper() : null);
    }
    
    /**
     * 隱藏的構造函數
     * 創建一個新的異步任務,必須在UI線程上調用此構造函數
     * @hide
     */
    public AsyncTask(@Nullable Looper callbackLooper) {
        //如果 callbackLooper 爲 null 或者是等於主線程 Looper ,則以主線程 Looper 對象爲參數構建一個與主線程關聯的 Handler 對象
        //否則就以傳入的 Looper 對象爲參數來構建與子線程關聯的 Handler
        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);
                    //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);
                }
            }
        };
    }

因此我們傳給構造函數 AsyncTask(Looper) 的參數爲 null ,因爲 mHandler 變量其實是賦值爲綁定了 UI 線程 LooperInternalHandler 變量

因爲 InternalHandler 綁定了 UI 線程的 Looper 對象,因此 handleMessage(Message)方法其實是在 UI 線程被執行,從而實現了子線程和 UI 線程之間的切換

    
    //按照正常情況來說,在初始化 AsyncTask 時我們使用的都是其無參構造函數
    //因此 InternalHandler 綁定的 Looper 對象即是與主線程關聯的 Looper 對象
    //所以 InternalHandler 可以用來在 UI 線程回調某些抽象方法,例如 onProgressUpdate() 方法
    private static InternalHandler sHandler;

    //等於 sHandler
    private final Handler mHandler;

    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:
                    //處理後臺任務的執行結果
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    //更新後臺任務的進度
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }    

    //獲取與主線程關聯的 Looper 對象,以此爲參數構建一個 Handler 對象
    //所以在 Task 的運行過程中,能夠通過此 Handler 在 UI 線程執行操作
    private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }

例如,在通過 publishProgress(Progress) 方法更新後臺任務的執行進度時,在內部就會將進度值包裝到 Message 中,然後傳遞給 Handler 進行處理

    //運行於工作線程,此方法用於更新任務的進度值
    //會觸發 onProgressUpdate() 被執行
    @WorkerThread
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            //將與進度值相關的參數 Progress 包裝到 AsyncTaskResult 對象當中,並傳遞給 Handler 進行處理 
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }

以上就是 AsyncTask 較爲關鍵的幾個點,看過後應該就能明白 AsyncTask 的整體工作流程了,如果需要 AsyncTask 更爲詳細的源碼註釋,可以看這裏:AsyncTask

更多的源碼解讀請看這裏:Java_Android_Learn

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