Android進階學習(六)Android的線程和線程池
文本是閱讀《Android開發藝術探索》的學習筆記記錄,詳細內容可以自行閱讀書本。
Android的線程和線程池
線程在Android中是一個很重要的概念,從用途來說,線程分爲主線程和子線程。主線程主要處理和界面相關的事情,而子線程則往往用於執行耗時操作。
除了Thread本身以外,在Android中可以扮演線程角色的還有很多,比如AsyncTask和IntentService,同時HandlerThread也是一種特殊的線程。
Android線程 | 底層實現 | 特性和使用場景 |
---|---|---|
AsyncTask | 封裝了線程池和Handler | 方便開發者在子線程更新UI |
IntentService | 底層使用線程 | 內部採用HandlerThread來執行任務,執行完成後IntentService自動退出。但是IntentService是一種服務,不容易被系統殺死。 |
HandlerThread | 底層使用線程 | 具有消息循環的線程,在它內部可以使用Handler |
線程是操作系統調度的最小單元,同時線程又是一種受限的系統資源。如果需要頻繁的創建和銷燬線程,正確做法就是使用線程池。Android中的線程池來源於Java,主要是通過Executor來派生特定類型的線程池。
1 主線程和子線程
Android中的線程除了傳統的Thread以外,還包含AsyncTask、HandlerThread以及IntentService。
2 Android中的線程形態
2.1 AsyncTask
AsyncTask是一種輕量級的異步任務類,它可以在線程池中執行後臺任務,然後把執行的進度和最終結果傳遞給主線程並在主線程中更新UI。從實現上來說,AsyncTask封裝了Thread和handler,通過AsyncTask可以更加方便執行後臺任務以及在主線程訪問UI,但AsyncTask並不適合進行特別耗時的後臺任務,對於特別耗時任務,建議使用線程池。
AsyncTask是一個抽象的泛型類,它提供了Params、Progress和Result三個泛型參數。
參數 | 含義 |
---|---|
Params | 表示參數類型 |
Progress | 表示後臺任務的執行進度的類型 |
Result | 表示後臺任務的返回結果的類型 |
public abstract class AsyncTask<Params, Progress, Result>
AsyncTask提供了4個核心方法,含義如下
方法名 | 含義 |
---|---|
onPreExecute() | 任務開始時執行,用於進行資源的準備和初始化。 |
doInBackground(Params… params) | 在子線程中運行,用於執行耗時異步任務,Params參數表示異步任務的輸入參數。此方法中可以通過調用publishProgress方法來更新任務進度,publishProgress會調用onProgressUpdate方法。另外此方法需要返回計算結果給onPostExecute。 |
onProgressUpdate(Progress… values) | 在主線程中執行,當後臺任務的執行進度發送改變時此方法被調用。 |
onPostExecute(Result result) | 在主線程中執行,在異步任務執行之後,此方法被調用。其中result參數是後臺任務的返回值,即doInBackground的返回值。 |
AsyncTask使用過程中,需要注意幾點:
1)AsyncTask對象必須在主線程中創建。
2)execute方法必須在UI線程調用。
3)不要在程序中直接調用onPreExecute、doInBackground、onProgressUpdate和onPostExecute方法。
4)一個AsyncTask對象只能執行一次,即調用一次execute方法,否則會報異常。
2.2 AsyncTask的工作原理
分析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方法中,會對狀態進行判斷,如果是正在運行或者已經完成則拋出異常。onPreExecute方法會最先執行,然後線程池開始執行。接下來分析線程池的執行過程:
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);
}
}
}
從SerialExecutor的實現分析AsyncTask的排隊執行過程。首先系統會把AsyncTask的Params參數封裝爲FutureTask對象,FutureTask是個併發類,在這裏它充當了Runnable的作用。
接着SerialExecutor的execute方法首先會把FutureTask對象插入任務隊列mTasks中,如果此時沒有正在活動的AsyncTask,那麼會調用
SerialExecutor的scheduleNext方法來執行下一個AsyncTask任務。同時當一個AsyncTask任務執行完成後,AsyncTask會繼續執行其他任務直到所有任務都被執行完成。可以看出,默認情況下,AsyncTask是串行執行的。
AsyncTask中有兩個線程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一個Handler(InternalHandler)。其中SerialExecutor用於任務的排隊,而線程池THREAD_POOL_EXECUTOR用於真正地執行任務。InternalHandler用於將執行環境從線程池切換到主線程。
用於FutureTask的run會調用mWorker的call方法,因此mWorker的call方法最終會在線程池中執行。在AsyncTask的構造方法中,聲明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;
}
};
從上述代碼可知,首先將mTaskInvoked設爲true,表示當前任務已經被調用過了。然後執行AsyncTask的doInBackground方法,接着將其返回值傳遞給postResult方法。postResult的實現如下:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
從上述代碼可知,postResult方法會通過Handler發送一個MESSAGE_POST_RESULT的消息,從代碼中可以看出這個getHandler方法返回的就是InternalHandler對象,這個InternalHandler定義如下:
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;
}
}
}
從上述代碼可知,InternalHandler是一個靜態Handler對象,爲了能夠切換到主線程,就需要InternalHandler對象在主線程中創建。用於靜態成員會在加載類的時候進行初始化,因此就要求AsyncTask必須在主線程創建。再來看看finish方法定義:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
從上述代碼可知,如果AsyncTask取消就調用onCancelled,否則就調用onPostExecute並把doInBackground的返回值傳遞過來。
2.3 HandlerThread
HandlerThread繼承了Thread,它是一種可以使用Handler和Thread。它在run方法中通過 Looper.prepare()來創建消息隊列,並通過Looper.loop()來開啓消息循環,這樣在實際使用中就允許在HandlerThread中創建Handler了。
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
從HandlerThread的實現來看,它和普通Thread有着顯著不同之處。普通Thread主要用於在run方法中執行一個耗時任務,而HandlerThread在內部創建了消息隊列,外界要通過Handler的消息方式來通知HandlerThread執行一個具體的任務。它在Android使用的具體場景是IntentService。
由於HandlerThread的run是無限循環,因此當明確不需要再使用HandlerThread時,可以通過它的quit或者quitSafely來終止線程的執行。
2.4 IntentService
IntentService是一個特殊的Service,它繼承Service並且是一個抽象類,因此必須創建它的子類纔可以使用。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,然後使用它的Looper來構造一個Handler對象mServiceHandler。這樣通過mServiceHandler發送的消息最終會在HandlerThread中執行。
每次啓動IntentService,它的onStartCommand方法就會調用一次,在onStartCommand方法中處理每個後臺任務的Intent。代碼如下:
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
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僅僅是通過mServiceHandler發送了一個消息,這個消息會在HandlerThread中處理。
然後消息會被mServiceHandler接收,會將Intent對象傳給onHandleIntent方法來處理。通過解析Intent對象就可以區分具體的後臺任務。當onHandleIntent方法執行完畢後,IntentService會通過stopSelf(int startId)方法來停止服務。不使用stopSelf()方法是因爲,立即停止可能會有未處理完成的其他消息。
qbIntentService的onHandleIntent是一個抽象方法,需要我們在子類中實現。如果目前只有一個存活的後臺任務,那麼onHandleIntent執行完畢後就會停止服務。
如果目前存在多個後臺任務,由於每次執行一個後臺任務就必須啓動一次IntentService,而IntentService內部通過消息方式向HandlerThread請求執行任務,Handler中的Looper是順序處理消息的。這意味着IntentService也是順序執行後臺任務的,後臺任務會按照外界發起的順序排隊執行。全部執行完畢後,服務才停止。
3 Android中的線程池
線程池的三個好處:
1)重用線程池中的線程,避免因爲線程的創建和銷燬所帶來的性能開銷
2)能有效控制線程池的最大併發數,避免大量的線程之間因互相搶佔系統資源而導致的阻塞現象。。
3)能夠對線程進行簡單的管理,並提供定時執行以及指定間隔循環執行等功能。
Android的線程池的概念來源於Java中的Executor。Executor是一個接口,真正線程池的實現爲ThreadPoolExecutor。ThreadPoolExecutor提供了一系列參數來配置線程池。從線程池的功能特性來說,Android的線程池主要分爲4類。
3.1 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);
}
參數名 | 含義 |
---|---|
corePoolSize | 線程池的核心線程數。默認情況下,核心線程會一直在線程池中一直存活,即使處於閒置狀態。除非將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置爲true,那麼閒置核心線程會在超時後終止。 |
maximumPoolSize | 線程池所能容納的最大線程數,當活動線程數達到這個數值後,後續新任務會被阻塞 |
keepAliveTime | 非核心線程閒置時的超時時長,超過這個時間,非核心線程會被回收。 |
unit | 用於指定keepAliveTime參數的時間單位,常用的有TimeUnit.MILLISECONDS、TimeUnit.SECONDS和TimeUnit.MINUTES |
workQueue | 線程池中的任務隊列,通過線程池的execute方法提交的Runnable對象會存儲在這個對象參數中 |
threadFactory | 線程工廠,爲線程池提供創建新線程的功能。threadFactory是一個接口,它只有一個方法:Thread newThread(Runnable r) |
RejectedExecutionHandler | 當線程池無法執行新的任務時,ThreadPoolExecutor會調用handler的rejectExecution方法 |
ThreadPoolExecutor執行任務大致遵循如下規則:
1)如果線程池中的線程數量未達到核心線程的數量,那麼會直接啓動一個核心線程來執行任務。
2)如果線程池中的線程數量已經達到或者超過核心線程的數量,那麼任務會被插入到任務隊列中排隊等待執行。
3)如果在步驟2中無法再插入任務,這個時候如果未達到線程池規定的最大值,那麼就會立刻啓動一個非核心線程來執行任務。
4)如果在步驟3也無法再插入任務,那麼就拒絕執行此任務,ThreadPoolExecutor會調用RejectedExecutionHandler的rejectedExecution方法來通知開發者。
3.2 線程池的分類
Android常見的4類具有不同功能特性的線程池,它們都是直接或者間接地通過配置ThreadPoolExecutor來實現自己的功能特性。
3.2.1 FixedThreadPool
通過Executors的newFixedThreadPool方法來創建。它是一種線程數量固定的線程池,當線程處於空閒狀態時,它們不會被回收,除非線程池被關閉。由於FixedThreadPool只有核心線程並且這些核心線程不會被回收,這意味着它能夠更加快速地響應外界的請求。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
3.2.2 CachedThreadPool
通過Executors的newCachedThreadPool方法來創建。它是一種線程數量不定的線程池。它只有非核心線程,並且其最大線程數量爲Integer.MAX_VALUE。空閒非核心線程的超時時間爲60s。這類CachedThreadPool比較適合執行大量的耗時較少的任務。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
3.2.3 ScheduledThreadPool
通過Executors的ScheduledThreadPoolExecutor方法來創建。它的核心線程數量是固定的,而非核心線程數是沒有限制的,並且非核心線程閒置時會被立即回收。這類ScheduledThreadPoolExecutor線程池主要用於執行定時任務和具有固定週期的重複任務。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
3.2.4 SingleThreadExecutor
通過Executors的newSingleThreadExecutor方法來創建。這類newSingleThreadExecutor線程池內部只有一個核心線程,它確保所有任務都在同一個線程中按順序執行,不需要處理線程同步問題。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
舉例了經典用法:
Runnable runnable = new Runnable() {
@Override
public void run() {
}
};
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
fixedThreadPool.execute(runnable);
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(runnable);
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
scheduledExecutorService.schedule(runnable, 2, TimeUnit.SECONDS);
scheduledExecutorService.scheduleAtFixedRate(runnable, 10, 2, TimeUnit.SECONDS);
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(runnable);