Android 的執行緒(AsyncTask、HandlerThread、IntentService詳解)和執行緒池
Android 的執行緒和執行緒池
在作業系統中,執行緒是作業系統排程的最小單元,同時執行緒又是一種受限的系統資源,即執行緒不可能無限制的產生,並且執行緒的建立和銷燬都有一定的開銷。
當系統中存在大量的執行緒時,系統會通過時間片輪轉的方式排程每個執行緒,因此執行緒不可能做到絕對的併發,除非執行緒數小於等於CUP的核心數,一般來說這是不可能的。如果在一個程序中頻繁的建立和銷燬執行緒,這顯然不是高效的做法。正確的做法是採用執行緒池,一個執行緒池中會快取一定數量的執行緒,通過執行緒池就可以避免因為頻繁建立和銷燬執行緒所帶來的系統開銷。Android中的執行緒池來源於Java,主要是通過Executor來派生特定型別的執行緒池,不同種類的執行緒池又具有各自的特性。
主執行緒和子執行緒
主執行緒是指程序所擁有的執行緒,在Java中預設情況下一個程序只有一個執行緒,這個執行緒就是主執行緒。
子執行緒也叫工作執行緒,除了主執行緒以為的執行緒都是子執行緒。
Android 中的執行緒形態
除了傳統的 Thread 以外,還包括 AsyncTask、HandlerThread 以及 IntentService,這三者底層實現也是執行緒。
AsyncTask
AsyncTask 是一種輕量級的非同步任務類,它可以線上程池中執行後臺任務,然後把執行的進度和最終的結果傳遞給主執行緒並在主執行緒中更新UI。
從實現上來說,AsyncTask封裝了 Thread 和 Handler,通過 AsyncTask 可以更加方便的執行後臺任務以及在主執行緒中訪問UI,但是不適合執行特別耗時的後臺任務,對於特別耗時的任務來說,還是使用執行緒池比較好。
AsyncTask 是一個抽象的泛型類,它提供了 Params、Progerss 和 Result 這三個泛型引數,其中 Params 表示引數的型別,Progress 表示後臺任務執行進度的型別,而 Result 則表示後臺任務的返回結果的型別,如果不需要傳遞具體的引數,那麼這三個泛型引數可以使用 Void 來代替:
public abstract class AsyncTask<Params, Progress, Result>
AsyncTask 提供了4個核心方法,它們的含義如下:
- onPreExecute()
這個方法會在後臺任務開始執行之間呼叫,用於進行一些介面上的初始化操作,比如顯示一個進度條對話方塊等。 - doInBackground(Params…)
這個方法中的所有程式碼都會在子執行緒中執行,我們應該在這裡去處理所有的耗時任務。任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個泛型引數指定的是Void,就可以不返回任務執行結果。注意,在這個方法中是不可以進行UI操作的,如果需要更新UI元素,比如說反饋當前任務的執行進度,可以呼叫publishProgress(Progress…)方法來完成 。 - onProgressUpdate(Progress…)
當在後臺任務中呼叫了publishProgress(Progress…)方法後,這個方法就很快會被呼叫,方法中攜帶的引數就是在後臺任務中傳遞過來的。在這個方法中可以對UI進行操作,利用引數中的數值就可以對介面元素進行相應的更新。 - onPostExecute(Result)
當後臺任務執行完畢並通過return語句進行返回時,這個方法就很快會被呼叫。返回的資料會作為引數傳遞到此方法中,可以利用返回的資料來進行一些UI操作,比如說提醒任務執行的結果,以及關閉掉進度條對話方塊等。
除了上述四個方法,AsyncTask 還提供了 onCancelled()方法,它同樣在主執行緒執行,當非同步任務被取消時,onCancelled() 方法會被呼叫,這個時候 onPostExecute則不會被呼叫。
AsyncTask 的工作原理
從它的 execute 方法開始分析,execute 方法又會呼叫 executeOnExecutor 方法:
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;
}
在上面的程式碼中,sDefaultExecutor 實際上是一個序列的執行緒池,一個程序中的所有的 AsyncTask 全部在這個序列的執行緒池中排隊執行。
在 executeOnExecutor 中,AsyncTask 的 onPreExecute() 方法最先被執行,然後執行緒池開始執行。
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
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);
}
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
首先系統會把 AsyncTask 的 Params 引數封裝為 FutureTask 物件,FutureTask 是一個併發類,在這裡它充當了 Runnable 的作用。接著這個FutureTask會交給 SerialExecutor 的 execute 方法去處理,SerialExecutor 的 execute 方法首先會把 FutureTask 物件插入到任務佇列 mTask中,如果這個時候沒有正在活動的 AsyncTask 任務,那麼就會呼叫SerialExecutor的scheduleNext方法來執行下一個AsyncTask任務。
同時當一個 AsyncTask 任務執行完後,AsyncTask 會繼續執行其他任務直到所有的任務都被執行為止,從這一點可以看出,在預設情況下, AsyncTask 是序列執行的。
AsyncTask 有兩個執行緒池(SerialExecutor 和 THREAD_POOL_EXECUTOR)和一個 Handler(IntentHandler),其中執行緒池 SerialExecutor 用於任務的排隊,而執行緒池 THREAD_POOL_EXECUTOR 用於真正的執行任務,InternalHandler 用於將執行環境從執行緒池切換到主執行緒。
在 AsyncTask 的構造方法中有如下一段程式碼,由於 FutureTask 的 run 方法會呼叫 mWork 的 call 方法,因此 mWork 的 call 方法最終會線上程池中執行。
public AsyncTask() {
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(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);
}
}
};
}
在 mWorker 的call方法中,首先將 mTaskInvoked 設定為true,表示當前任務已經呼叫過了,然後執行 AsyncTask 的 doBackground 方法,接著將其返回值傳遞給 postResult 方法。
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}
postResult 方法會通過 sHandler 傳送一個 MESSAGE_POST_RESULT 的訊息:
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;
}
}
}
可以發現,sHandler 是一個靜態物件,為了能夠將執行環境切換到主執行緒,這就要求 sHandler 這個物件必須在主執行緒中建立。由於靜態成員會在載入類的時候進行初始化,因此這就要求 AsyncTask 的類必須要在主執行緒中載入,否則同一個程序中的 AsyncTask 都將無法正常工作。
sHandler 收到 MESSAGE_POST_RESULT 這個訊息後會呼叫 AsyncTask 的 finish 方法:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
如果 AsyncTask 被取消的話,那麼就會呼叫onCancelled 方法,否則就會呼叫 onPostExecute 方法,doBackground 的返回結果會傳遞給 onPostExecute 方法,到這裡整個 AsyncTask 的工作工程分析完畢了。
HandlerThread
HandlerThread 繼承了Thread,它是一種可以使用 Handler 的 Thread,在 run 方法中通過 Looper.prepare() 來建立訊息佇列,並通過 Looper.loop() 來開啟訊息迴圈。
HandlerThread 的 run 方法:
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
在 HandlerThread 的內部建立了一個具體的任務,外界需要通過Handler的訊息方式來通知 HandlerThread 執行一個具體的任務。HandlerThread 的 run 方法是無限迴圈的,因此當明確不需要再使用 HandlerThread 時,可以通過它的 quit 或者 quitSafely 方法來終止執行緒的執行。
IntentService
IntentService 是一種特殊的 Service ,它繼承了 Service 並且它是一個抽象類,因此必須建立它的子類才能使用 IntentService。
IntentService 可用於執行後臺耗時的任務,當任務執行後它會自動停止,同時由於 IntentService 是服務的原因,這導致它的優先順序比單純的執行緒要高很多,比較適合一些高優先順序的後臺任務。
IntentService 封裝了 HandlerThread 和 Handler:
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 方法就會被呼叫,onCreate方法會建立一個 HandlerThread,然後使用它的 Looper 來構造一個 Handler 物件 mServiceHandler,這樣通過mServiceHandler 傳送的訊息最終都會在 HandlerThread 中執行,從這個角度來看,IntentService 也可用於執行後臺任務。
每次啟動 IntentService,它的 onStartCommand 方法就會呼叫一次,IntentService 在 onStartCommand 中處理每個後臺任務的 Intent。
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
IntentService 僅僅通過 mServiceHandler 傳送一個訊息,這個訊息會在 HandlerThread 中處理。 mServiceHandler 收到訊息後,會將 Intent 物件傳遞給 onHandleIntent 方法去處理。
注意:這個 Intent 物件的內容和外界的 startService(intent) 中的內容是完全一致的,通過這個 Intent 物件即可解析出外界啟動 IntentService 時所傳入的引數,這樣 onHandleIntent 方法就可以對不同的後臺任務做處理了。當 onHandleIntent 方法執行結束後,IntentService 會通過 stopSelf(int startId) 方法來嘗試停止服務。
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);
}
}
IntentService 的 onHandleIntent 方法是一個抽象方法,它的作用就是從 Intent 引數中區分具體的任務並執行這些任務。 如果目前只存在一個後臺任務,那麼 onHandleIntent 方法執行完這個任務後,stopSelf(int starId)就會直接停止任務,如果有多個任務,會執行完最後一個任務再停止服務,這些任務會按照外界發起的順序排隊執行。
Android中的執行緒池
執行緒池的優點:
- 重用執行緒池的執行緒,避免因為執行緒的建立和銷燬所帶來的效能開銷;
- 能有效的控制執行緒池的最大併發數,避免大量的執行緒之間因互相搶佔系統資源而導致的阻塞現象;
- 能夠對執行緒進行簡單的管理,並提供定時執行以及指定間隔迴圈執行等功能;
Android 中的執行緒池來源於 Java 中的 Executor,Executor 是一個介面,真正的執行緒池實現為 ThreadPOOLExecutor。
ThreadPoolExecutor
ThreadPoolExecutor 是執行緒池的真正實現,它的構造方法提供了一系列引數來配置執行緒池:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
corePoolSie
執行緒池的核心執行緒數,預設情況下,核心執行緒會線上程池中一直存活,即使它們處於閒置狀態。如果將 ThreadPoolExecutor 的 allowCoreThreadTimeOut 屬性設定為 true,那麼閒置的核心執行緒在等待新任務到來時會有超時策略,這個事件間隔由keepAliveTime 所指定,當等待事件超過 keepAliveTime 所指定的時間,核心執行緒會被終止。maximumPoolSize
執行緒池中所能容納的最大執行緒數,當活動執行緒數達到這個數值後,後續的任務會被阻塞。keepAliveTime
非核心執行緒閒置是的超時時長,超過這個時長,非核心執行緒就會被回收。如果將 ThreadPoolExecutor 的 allowCoreThreadTimeOut 屬性設定為 true,keepAliveTime 同樣會作用於核心執行緒。unit
用於指定 keepAliveTime 引數的時間單位,這是一個列舉,常用的有 TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分鐘)等。workQueue
執行緒池中的任務佇列,通過執行緒池的 execute 方法提交的 Runnable 物件會儲存在這個引數中。threadFactory
執行緒工廠,為執行緒池建立新執行緒的功能。ThreadFactory 是一個介面,它只有一個方法: Thread newThread(Runnable r).
ThreadPoolExecutor 執行任務時大致遵循如下規則:
- 如果執行緒池中的執行緒數量未達到核心執行緒的數量,那麼會直接啟動一個核心執行緒來執行任務。
- 如果執行緒池中的執行緒數量已經達到或者超過核心執行緒的數量,那麼任務會直接插入到任務佇列中排隊等待執行。
- 如果在步驟2中無法將任務插入到任務佇列裡,這往往是由於任務佇列已滿,這個時候如果執行緒數量未達到執行緒池規定的最大值,那麼會立刻啟動一個非核心執行緒來執行任務。
- 如果步驟3中執行緒數量已經達到執行緒池規定的最大值,那麼就拒絕執行此任務,ThreadPoolExecutor 會呼叫 RejectedExecutionHandler 的 rejectedExecution 方法來通知呼叫者。
執行緒池的分類
1. FixedThread
通過Executors 的 newFixedThreadPool 方法來建立,它是一種執行緒數量固定的執行緒池,當執行緒處於空閒狀態時,它們不會被回收,除非執行緒池被關閉了。
當所有的執行緒都處於活動狀態時,新任務都會處於等待狀態,直到有空閒執行緒。由於此執行緒池只有核心執行緒並且這些核心執行緒不會被回收,這意味著它能夠更加快速地響應外界的請求。
2. CacheThreadPool
通過Executors 的 newCacheThreadPool 方法來建立,它是一種執行緒數量不定的執行緒池,它只有非核心執行緒,並且最大執行緒數為 Integer.MAX_VALUE。當執行緒池中的執行緒都處於活動狀態時,執行緒池會建立新的執行緒來處理新任務,否則就利用空閒的執行緒來處理新任務。執行緒池中的空閒執行緒超時時長為60s,超過就會被回收,執行緒池的任務會立即執行。所以這類執行緒池比較適合執行大量的耗時較少的任務。當整個執行緒池都處於閒置狀態時,執行緒池中的執行緒都會超時而被停止,這個時候CacheThreadPool 之中實際上是沒有任何執行緒的,它幾乎是不佔用任何系統資源的。
3. ScheduleThreadPool
通過Executors 的 ScheduleThreadPool 方法來建立,它的核心執行緒數是固定的,而非核心執行緒數是沒有限制的,並且當非核心執行緒閒置時會被立即回收。ScheduleThreadPool 這類執行緒池主要用來執行定時任務和具有固定週期的重複任務。
4. SingleThreadExecutor
通過Executors 的 SingleThreadExecutor 方法來建立,這類執行緒池內部只有一個核心執行緒,它確保所有的任務都在同一個執行緒中按順序執行。SingleThreadExecutor 的意義在於統一所有的外界任務到一個執行緒中,這使得在這些任務之間不需要處理執行緒同步的問題。