Android 原始碼分析(三)安卓中的執行緒

diamond_lin發表於2017-12-07

上一節我們一起探索了 Handler 的跨執行緒通訊機制,這裡我麼趁熱打鐵,一舉拿下 Android 中的執行緒。

今天我們主要探索 AsyncTask、HandlerThread、IntentService 三個類,這三個類的表現形式和傳統執行緒有一定的區別,但是本質還是基於執行緒做的處理。

本文將會涉及很多 hadnler 的知識點,建議學習本文的小夥伴先學習上一章 Android 原始碼分析(二)handler 機制.

執行緒的概念及基礎操作我在 Java 基礎中講了,如果不清楚的同學可以去翻一翻 Java 基礎執行緒篇,我們直接開始講重點了。

AsyncTask

這個類大家應該都知道吧,沒用過但是肯定聽說過。

來看看類註釋

AsyncTask enables proper and easy use of the UI thread. This class allows yo to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers

翻譯大概告訴我們這個類執行使用者在 UI 執行緒上執行非同步任務,而不需要操作執行緒。

跟我們想象中差不多,反正大家也都知道這個功能,我們先來看一下幾個核心方法,在去閱讀原始碼吧。

execute(Params... params)

AsyncTask的執行方法,呼叫這個方法,這個任務才會被丟進執行緒池執行,注意,這個方法只能呼叫一次。

protected void onPreExecute()

這個方法在excuse()方法裡面被呼叫,所以和 AsyncTask 的 excuse呼叫在一個執行緒,很多部落格都說只能在主執行緒呼叫,但是我親測在子執行緒也是可以呼叫的,因為原始碼裡面並沒有做執行緒檢測。補充一下,AsyncTask 類載入必須發生在主執行緒,因為幾個類靜態成員 handler是需要繫結主執行緒的 Looper,但是在 Android4.1及以上版本已經被系統完成了。

protected abstract Result doInBackground(Params... params);

這是唯一一個必須實現的抽象方法,這執行緒池中被呼叫。這個方法裡面可以呼叫publishProgress 方法來更新任務進度,publishProgress 會通過 handler呼叫onProgressUpdate。最後這個方法需要將結果返回,

onProgressUpdate(Progress... values)

在主執行緒中執行,上面說了,這個方法在 doInBackground 裡面通過 publishProgress 呼叫 handler,然後切回主執行緒回撥這個方法。

onPostExecute(Result result)

在主執行緒中執行,在非同步任務執行之後,此方法會被呼叫,其中 result 引數是doInBackground 的返回值。

so?接下來,就看原始碼吧,也沒什麼好看的,我講一遍核心原始碼,大家看了我的解析之後,再跟著我的理解,回頭讀一遍原始碼就行了。

AsyncTask 原始碼解析

其實原始碼真的沒什麼看點。。。。 那就,先從構造方法看起吧~

有三個構造方法,另外兩個最終都會呼叫這個構造方法

public AsyncTask(@Nullable Looper callbackLooper) {
    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);
            }
        }
    };
}
複製程式碼

mmp,有兩個構造方法被隱藏了,明明是公共構造方法。。。。。 好吧,那就當只有一個無參的公共構造方法吧,上面這個構造方法也是不能直接呼叫。 扯遠了,來分析這個構造方法吧。這個方法就初始化了三個變數mHandler、mWorker、mFuture。

這裡 mHandler 被繫結了主執行緒的 Looper(如果你要用反射質量的方法強行。。。可以繫結子執行緒 Looper),然後就是一個 WorkerRunnable 和 FutureTask 物件。這兩個類是執行緒相關的概念,不瞭解的可以去了解一下多執行緒篇,反正你記住,FutureTask的 run 方法裡面會呼叫WorkerRunnable的 call 方法,所以 mWorker 的 call 方法會線上程池中被呼叫,也就是非同步執行緒裡面被呼叫。

接下來,我們就從 execute方法被呼叫開始吧

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

這個方法比較簡單,直接呼叫了executeOnExecutor方法,但是這裡有個引數sDefaultExecutor 需要注意一下。先來看這個引數的定義:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new 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);
        }
    }
}
複製程式碼

從程式碼中我們可以看到sDefaultExecutor 是一個自定義的靜態執行緒池,注意,是靜態的哦,至於裡面的邏輯,我們稍後再講,先來看 executeOnExecutor 方法吧。

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;
}
複製程式碼

第一步:這個方法做了狀態監測,一旦執行了這個方法 mStatus 狀態就不再是 PENDING 了,所以當多次呼叫 execute 方法多次呼叫就會異常。 第二步:這個方法呼叫了onPreExecute 方法,注意,這裡沒有做任何執行緒切換,所以onPreExecute 方法回撥和 execute 方法是在同一個執行緒。 第三步:把 params 賦值給mWorker.mParams,這個沒什麼好說的,大家記得mFuture的 run 方法會呼叫mWorker的 call 方法即可。 第四步:把構造方法裡面的 mFuture 丟進了執行緒池裡面,這個執行緒池就是剛剛我們說的sDefaultExecutor,現在,我們來看sDefaultExecutor執行緒池的程式碼吧。

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(final Runnable r)方法裡面的引數 r 就是mFuture,然後存到佇列 mTask 裡面去了,這裡程式碼應該不太好理解,我直接根據結果分析原因了。就是為了保證mTasks佇列裡面的任務只能“序列”執行,也就是不允許併發。又因為這裡是靜態資源,獨立與某個 AsyncTask,而每個 AsyncTask 的 mFuture又被儲存在這裡面,等待執行。所以,AsyncTask任務實際上是序列執行的哦。

如果需要併發執行 AsyncTask,可以直接呼叫 executeOnExecutor 方法即可。 以上分析如果有不理解的童鞋需要對著原始碼理解一遍,原始碼不難讀。

好,我們繼續往下看,上面我們說到mFuture 被儲存在SerialExecutor 的佇列裡面,在 scheduleNext 方法裡面被消費,那麼我們來看看 THREAD_POOL_EXECUTOR 到底是什麼鬼~~

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);
    threadPoolExecutor.allowCoreThreadTimeOut(true);
    THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
複製程式碼

哦,THREAD_POOL_EXECUTOR 這貨也是一個執行緒池,也就是說這裡才是真正執行 AsyncTask 任務的執行緒池,但是被 sDefaultExecutor 執行緒池限制了一次只給它一個任務執行。

好,到這裡我們一個 AsyncTask 任務已經被丟進執行緒池消費了,接下來我們我們的 mFuture 被執行緒池執行會執行 run 方法,run 方法會呼叫mWorker的 call 方法。我們來看看 mWorker 的 call 方法吧。

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;
        }
    };
複製程式碼

注意,call 方法是線上程池裡面被呼叫的,此時已經是在非同步執行緒了哦。

首先是第一行一個原子 boolean,這只是一個狀態的變化。然後呼叫Process修改了執行緒的優先順序。 敲黑板!!!!!!注意這行程式碼result = doInBackground(mParams);,這裡就是doInBackground 的回撥,在子執行緒,也是我們唯一需要實現的抽象方法,這個方法裡面是做我們非同步任務的,然後剛剛我們說了,doInBackground 裡面怎麼做進度更新,這裡就不在贅述了。 然後就是 doInBackground 經過一番耗時操作,得到了結果是怎麼被處理的。我們來看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 發生資訊的方式不一樣,其實不用糾結,就等同於 getHandler.sendMessag(message)。getHandler()獲得的是誰,其實就是我們在構造方法裡面講過的那個 handler。然後我們來看看這個 handler 處理邏輯吧。

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:
                // There is only one result
                result.mTask.finish(result.mData[0]);
                break;
            case MESSAGE_POST_PROGRESS:
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}
複製程式碼

剛剛我們發生的 message.what是MESSAGE_POST_RESULT,所以根據 case,這裡面呼叫的是 AsyncTask 的finish方法。

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

這裡面的邏輯就很簡單了。

至此,一個 AsyncTask 任務執行結束,AsyncTask 原始碼分析結束。小夥伴們記得根據我的邏輯去理解一遍原始碼哦,自己理解了一遍就能鞏固了。

沒有總結了,所有的東西都在原始碼分析中提到了。

HandlerThread

這是一個很簡單的類,加上各種註釋也才160行程式碼。

HandlerThread繼承了 Thread,它是一種可以用 Handler 的 Thread,實現也很簡單,就是在 run 方法裡面開啟 Looper 迴圈,然後通過 Handler往迴圈裡面丟任務,這樣就能非同步執行了。

@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}
複製程式碼

吶,大家瞅一瞅這個 run 方法把,相信讀過我前面文章的小夥伴理解這個類應該不難吧。

需要注意的是,在不需要 HandlerThread 執行任務的時候,需要呼叫 quit 或者 quitSafely 方法來結束迴圈哦。

IntentService

剛剛我們講了 HandlerThread,HandlerThread 在 Android 中的一個具體使用場景就是 IntentService。

突然想起以前為了應付面試被的一個知識點。

Q:什麼是 IntentService?

A:就是一個幹完活可以自己死去的 Service。

其實並沒有用過 IntentService,只知道繼承自 Service,然後背了這個標準答案。確實這句話是對 IntentService 的一個高度概括,但是如果只背了這句話,未免有點囫圇吞棗,今天我們來全面瞭解IntentService。

IntentService是一個抽象類,加上50行的類註釋也不過才150行程式碼,同學們學習這個不要有心理壓力,這只是一個小知識點。

IntentService 可用於執行後臺耗時的任務,當任務執行後它會自動停止,同時由於 IntentService 是服務的原因,這導致它的優先順序別比單純的執行緒要高很多,不容易被殺死,所以 IntentService 比較適合執行一些高優先順序別的後臺任務。在實現上,IntentService 封裝了 HandlerThread 和 Handler。

我們先來看 oncreate 方法把

@Override
public void onCreate() {
    // TODO: It would be nice to have an option to hold a partial wakelock
    // during processing, and to have a static startService(Context, Intent)
    // method that would launch the service & hand off a wakelock.

    super.onCreate();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();

    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
}
複製程式碼

當 IntentService 第一次啟動時,它的 onCreate 方法會被呼叫,然後初始化HandlerThread和 Handler。

然後看 onStart 方法

@Override
public void onStart(@Nullable Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}
複製程式碼

直接把 intent 儲存到message 裡面去了,然後傳送到通過 handler 傳送到 HandlerThread 裡面去執行了。 執行結束會回撥 handler 的 handleMessage 方法,我們來看看這個方法。

private final class ServiceHandler extends Handler {
    public ServiceHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        onHandleIntent((Intent)msg.obj);
        stopSelf(msg.arg1);
    }
}
複製程式碼

直接呼叫抽象方法 onHandleIntent 來處理非同步方法,注意這裡onHandleIntent 是在子執行緒中被呼叫的哦。

然後會呼叫stopSelf(msg.arg1)方法來嘗試關閉服務,這是 Service 的方法,這個方法並不會立即關閉服務,而是會等待所有訊息都處理完畢之後才會終止服務,具體原因這裡就不做分析了,以後 Service 篇會講的。

好了,分析完了。

執行緒池

之前講 concurrent 包簡單提了一下執行緒池,這裡我們來補充一點執行緒池基礎吧。

使用執行緒池的優點

  • 重用執行緒池中的執行緒,避免建立執行緒帶來的開銷
  • 能有效的控制執行緒池的最大併發數,避免大量執行緒之間相互搶資源導致阻塞
  • 能簡單管理執行緒,並提供定時執行以及指定時間間隔執行等功能

ThreadPoolExecutor 的幾個核心引數

  • corePoolSize:執行緒的核心執行緒數
  • maximumPoolSize:執行緒池所能容納的最大執行緒數,當活動執行緒達到這個數值後,後續的新任務將會被阻塞
  • keepAliveTime:非核心執行緒的超時時長,超過就會被回收,當 allowCoreThreadTimeout 屬性設定為 true 時,keepAliveTime 同樣會作用與核心執行緒
  • unit:keepAliveTime的時間單位
  • workQueue:任務佇列
  • threadFactory:執行緒工廠,用來建立新執行緒

ThreadPoolExecutor 執行大致規則:

  • 如果執行緒池中的執行緒數量未達到核心執行緒數量,那麼會直接啟動一個核心執行緒來執行任務
  • 如果執行緒池中的執行緒數量已經達到核心執行緒數量或者超過,那麼任務會被插入到任務佇列中排隊等待執行
  • 如果上一步中無法將任務插入到任務佇列中,是因為任務佇列滿了,這時如果執行緒數量未達到執行緒池最大值,那麼會立即啟動一個非核心執行緒來執行任務
  • 如果上一步執行緒數量達到執行緒池規定的最大值,那麼就拒接執行此任務,會呼叫 RejeceedExecutionHandler 的 rejectedExecution 方法來通知呼叫者。

Executors

Executors能直接建立最常見的幾種具有不同功能的執行緒池。

  • Executors.newFixedThreadPool(int nThreads) 這是一種執行緒數量固定的執行緒池,當執行緒處於空閒時也不會被回收,除非執行緒池被關閉。

  • Executors.newCachedThreadPool(ThreadFactor factor) 這是一個0核心執行緒、非核心執行緒可以達到無限大且超時時長為60秒的執行緒池。說白了就是可以無限任務同時執行的執行緒,適合大量耗時段的任務需求。

  • Executors.newScheduledThreadPool(int corePoolSize) 核心執行緒固定,非核心執行緒無限多且非核心執行緒用完馬上被回收。

  • Executors.newSingleThreadExecutor() 只有一個核心執行緒,保證所有任務序列執行。好像 AsyncTask 裡面的 SerialExecutor 就可以用這個替換掉。

以上的種種,都可以通過配置 ThreadPoolExecutor實現。

Android 執行緒篇就到這裡吧。

參考資料

《Android 開發藝術探索》

相關文章