零零碎碎的東西老是記不長久,僅僅學習別人的文章也只是他人咀嚼後留下的殘渣。無心中發現了這個每日一道面試題 ,想了想若是隻是簡單地去思考,那麼不只會收效甚微,甚至難一點的題目本身可能都懶得去想,堅持不下來。因此不如把每一次的思考、理解以及別人的看法記錄下來。不只加深本身的理解,更要激勵本身堅持下去。java
前幾天的那個面試弄得我一臉懵逼,如今有點空閒時間,仔細的探討一下當時的問題。印象最深的就是關於 HandlerThread 與 AsyncTask 的問題。首先列一下探討過程當中想到的問題以及面試時問的問題。android
先簡單講講這兩個的源碼吧,都挺簡單的,並無那麼複雜。如下源碼來自於 Android-28git
HandlerThread 其實是 Thread+Looper+Handler 的一個簡單封裝。github
HandlerThread 類繼承 Thread 類,因此須要 start() 方法來開啓線程。面試
HandlerThread handlerThread = new HandlerThread("workThread");
handlerThread.start();
Handler threadHandler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//根據不一樣的 message 類型處理不一樣的耗時任務
}
};
複製代碼
這是簡單的使用方法。建立 Handler 時須要傳入 HandlerThread 中的 Looper 對象,這樣 handlerMessage 方法就會在子線程中處理耗時任務。須要耗時任務能夠經過 threadHandler.sendMessage() 發送消息,而後下 handlerMessage 方法中進行處理。網絡
源碼中最重要的就是一個重寫的 run 方法。併發
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();//喚醒線程能夠獲取 Looper 對象了
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
複製代碼
也很簡單,在咱們執行 handlerThread.start() 開啓一個線程後,就會執行此方法。經過 Looper.prepare() 在此線程中建立一個 Looper 對象,而後通知其餘線程能夠獲取 Looper 對象,設置線程優先級。onLooperPrepared() 是一個空的方法,咱們能夠重寫此方法進行一些 Looper 開啓 loop 循環以前的準備。less
一切都準備好以後,就是經過 Handler 發送消息,而後在 handlerMessage() 中進行耗時操做。簡單說明一下,Handler 類中的 handlerMessage() 方法在哪一個線程中執行,是由 Handler 中的 Looper 對象所在的線程決定的,這是由於在 loop 循環中經過 msg.target.dispatchMessage()--->handleMessage() 間接地調用了 handlerMessage 方法,而 Looper.loop 是在子線程中執行的。具體可看 android 的消息機制詳解---每日一道面試題(第 9 期)---談談 Handler 機制和原理 異步
AsyncTask 則是線程池與 Handler 的封裝ide
一些基本使用就不在詳細說明了,主要來看看源碼。首先是構造方法,有三個。無參、Handler、Looper,前兩個都會調用第三個構造方法。
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);
}
}
};
}
複製代碼
有點長,但其實就三步操做。
一般使用時經過 execute 方法開啓任務,看看源碼中幹了什麼。
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
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;
}
複製代碼
execute 中執行的是 executeOnExecutor 方法,並傳入 sDefaultExecutor 與耗時任務須要的參數。首先是檢查狀態,mStatus 是一個枚舉變量,有 PENDING、RUNNING、FINSHED 三種狀態,這三種狀態都是惟一的,按 PENDING---RUNNING---FINISHED 順序,初始化對象時是 PENDING,在 executeOnExecutor 中變爲 RUNNING,在 finish 方法中更新爲 FINSHED 狀態。所以能夠看出 executor 方法只能在一個對象中執行一次,屢次執行就會拋出異常。而後更新狀態,調用 onPreExecute 方法,咱們能夠種重寫此方法作些進行耗時操做前的準備。傳入參數,而後就是 exec.execute 提交任務,也就是構造函數中包裝好的 FutureTask 對象。
這個 exec 是成員變量 sDefaultExecutor,是 AsyncTask 內部定義的靜態類,實現了 Executor 接口。
private static class SerialExecutor implements Executor {
//雙端隊列,按照先進先出的原則儲存 FutureTask 對象
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
//對傳入的 mFuture 又進行了一次封裝,以便於串行處理任務
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) {
//由此可看出 SerialExecutor 只負責任務的串行處理,真正的耗時任務操做是交給 THREAD_POOL_EXECUTOR 線程池進行調度
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
複製代碼
這是一個靜態類,也就是說全部的耗時任務都要通過此類進行串行處理。SerialExecutor 就是爲了使耗時任務可以串行的被處理才存在的,真正處理耗時任務的則是 THREAD_POOL_EXECUTOR 線程池。
//得到沒有睡眠的 CPU 數量
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
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;
private static final int KEEP_ALIVE_SECONDS = 30;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);//cas 操做的 int 變量
public Thread newThread(Runnable r) {
//記錄建立線程的數量
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
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,這部分操做是在靜態代碼塊中進行的,也就是說只會在被類加載的時候執行一次。全部的耗時任務都是在這僅有的一個線程池中執行任務。簡單說下這個線程池中的參數。
剩下的就是線程池中調度 mFuture 執行耗時任務,執行其中的 mFuture 中 Callable 接口的 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;
}
};
複製代碼
能夠看出,在進行耗時操做後,不管是處理完,還有發生異常,都要 postResult() 方法進行收尾。
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
複製代碼
用 Handler 包裝了一個信息,發送了出去。標記爲 MESSAGE_POST_RESULT,意思就是耗時任務的結果。這個 Handler,就是構造函數中初始化的那個 Handler 對象,只不過在經過 getMainHandler 中是用自定義的靜態內部 Handler 類進行了包裝。
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler(Looper.getMainLooper());
}
return sHandler;
}
}
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
//AsyncTask 的靜態內部類,方便傳遞結果數據與對應的 AsyncTask 對象
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_POST_RESULT,就調用 finish 方法;若是是 MESSAGE_POST_PROGRESS,就是 onProgressUpdate(result.mData) 方法,也就是咱們能夠用來重寫更新進度的操做。MESSAGE_POST_PROGRESS 類消息只有在你調用 publishProgress 方法時纔會被調用。
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
複製代碼
finish 方法中會根據 mCancel 的狀態決定調用 onCancelled(result) 仍是 onPostExecute(result),也就是說這兩個只會調用其中一個方法,這兩個方法也是咱們在使用 AsyncTask 須要重寫的方法。mCanael 咱們能夠經過調用 cancel() 方法改變狀態。
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
//在 mFuture 中中斷線程
return mFuture.cancel(mayInterruptIfRunning);
}
複製代碼
整個內部大體的流程就差不過了。將耗時任務封裝進 FutureTask,SerialExecutor 對 FutureTask 在進行包裝使耗時任務能夠串行執行,最後由 THREAD_POOL_EXECUTOR 線程池進行真正的耗時任務調度處理。
HandlerThread 是在子線程的 loop 循環中進行的耗時操做,只有當前的耗時操做完成,才能獲取下一個消息處理,因此是串行的。至於變爲並行的,不能夠。簡單驗證下
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.US);
Handler threadHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
HandlerThread handlerThread = new HandlerThread("workThread");
handlerThread.start();
threadHandler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("HandlerThread", "message" + msg.what + ":" + df.format(new Date()));
}
};
}
public void onLoginClick(View v){
int i = 0;
threadHandler.sendEmptyMessage(++i);
threadHandler.sendEmptyMessage(++i);
threadHandler.sendEmptyMessage(++i);
threadHandler.sendEmptyMessage(++i);
threadHandler.sendEmptyMessage(++i);
}
複製代碼
自定義一個簡單的 AsyncTask
static class MyAsyncTask extends AsyncTask<Void, Void, String>{
String name = "AsyncTask";
private MyAsyncTask(String name){
super();
this.name = name;
}
@Override
protected String doInBackground(Void... voids) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return name;
}
@Override
protected void onPostExecute(String string) {
super.onPostExecute(string);
Log.e("AsyncTask", string + ":" + df.format(new Date()));
}
}
複製代碼
當咱們進行一連串的耗時任務時,就會串行處理
new MyAsyncTask("AsyncTask#1").execute();
new MyAsyncTask("AsyncTask#2").execute();
new MyAsyncTask("AsyncTask#3").execute();
new MyAsyncTask("AsyncTask#4").execute();
new MyAsyncTask("AsyncTask#5").execute();
new MyAsyncTask("AsyncTask#6").execute();
new MyAsyncTask("AsyncTask#7").execute();
new MyAsyncTask("AsyncTask#8").execute();
複製代碼
從結果中能夠看出嚴格的按照先入先出串行處理任務。那麼能不能變爲併發的呢?在源碼分析中咱們知道串行是受 SerialExecutor 對象進行控制的,而此對象是在 execute 中經過 return executeOnExecutor(sDefaultExecutor, params) 傳入進去的。而恰好咱們能夠調用 executeOnExecutor 方法。因此咱們能夠自定義線程池甚至傳入 AsyncTask 的 THREAD_POOL_EXECUTOR 線程池跳過 SerialExecutor 的串行控制,直接用線程池進行併發處理任務。
new MyAsyncTask("AsyncTask#1").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new MyAsyncTask("AsyncTask#2").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new MyAsyncTask("AsyncTask#3").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new MyAsyncTask("AsyncTask#4").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new MyAsyncTask("AsyncTask#5").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new MyAsyncTask("AsyncTask#6").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new MyAsyncTask("AsyncTask#7").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new MyAsyncTask("AsyncTask#8").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
複製代碼
從結果中能夠看出,是四個併發進行的任務處理。爲何是 4 個呢?這個是線程池的調度問題。測試用的手機正在工做 CPU 數目是 8,因此核心線程池數量是 Math.max(2, Math.min(CPU_COUNT - 1, 4)) = 4,最大的線程數量是 CPU_COUNT * 2 + 1 = 17。線程池首先會用核心線程處理任務,核心線程滿了後,後面的認爲就會被放入阻塞隊列,當隊列也滿後,再來任務的話,就會建立非核心線程,取隊首任務進行處理,後來的任務放入隊尾。若是非核心線程也滿了,在來任務就會拋出異常。有個圖可能看着會更明白些。
因此說任務少是就只是核心線程在併發處理任務。
那麼爲何要默認串行處理任務,那樣豈不是丟失了線程池的最大優點?
這是由於默認線程池其實所能容下的線程並很少,就拿 8 核的例子來看,最大線程數爲 17,阻塞隊列容量爲 128,加起來所能容下的最大線程數爲 17+128=145,在高併發的狀況下很容易就會滿,而且 THREAD_POOL_EXECUTOR 對象在整個應用程序中是惟一的。因此默認是串行處理,若是真的有高併發處理的狀況,能夠根據需求自定義線程池進行併發處理。
對於這個面試時拋出的問題,我簡單說下個人理解。由於我實際開發經驗少的可憐,因此說的可能有錯誤或者很片面,包括上面的幾個問題的回答。如今網上的資料真的不敢隨便相信,有的還自相矛盾,我突然明白麪試時面試官問我日常都看誰的文章,知道哪些在 Android 方面比較專業的人士的用意了。
大量的耗時操做,若是任務之間沒有什麼關聯的話,在我看來若是是串行處理的話都不怎麼好,由於會阻塞後面的任務,而任務之間並不須要有個先後執行的順序。因此在 AsyncTask 中並行處理比較好。而若是任務之間有關聯,則需串行執行,此時就要看這些耗時任務的執行邏輯是否一致,若是不一致的話那就要自定義多個 AsyncTask,也非常麻煩。在我看來 AsyncTask 更多的是強調與 UI 線程的交互吧。
其實對於 Android 中的耗時任務處理,HandlerThread、IntentService、AsyncTask、ThreadPoolExecutor 這幾個具體應用場景,有哪些差異,還真的說不出來個因此然來,還需努力。(總之就是菜(滑稽))