Android多線程之AsyncTask源碼解析

本系列文章會陸續對 Android 的多線程機制進行總體介紹,幫助讀者瞭解 Android 環境下如何實現多線程編程,也算是對本身所學內容的一個總結概括java

項目主頁:github.com/leavesC/Jav…android

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

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

通常,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 被重複執行ide

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

        //Task 正在運行
        RUNNING,

        //Task 已經結束
        FINISHED,
    }
複製代碼

以後就調用任務執行器,提交任務函數

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

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

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

每個被提交的任務都會被加入任務隊列 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 的相關知識點,關於這些能夠從這裏獲取詳細介紹: JavaKotlinAndroidGuide ,這裏就簡單介紹下

看下 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();
        }
    }
複製代碼

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

相關文章
相關標籤/搜索