該原創文章首發於微信公衆號“字節流動”
Android 多線程實現方式
通常來說,一個應用至少有一個進程,而一個進程至少有一個線程。
線程是 CPU 調度的基本單位,進程是系統資源分配的基本單位。
進程獨享內存資源,一個進程可以看作一個 JVM ,一個進程崩潰後,在保護模式下一般不會對其它進程產生影響。
同一個進程中的線程共享內存資源,一個線程死掉就導致整個進程死掉。
Android 提供了四種常用的多線程實現方式:
- AsyncTask
- 異步消息機制
- IntentService
- ThreadPoolExcutor
AsyncTask
我們的老朋友 AsyncTask 類,它是封裝好的線程池,操作 UI 線程極其方便。
瞅一眼,AsyncTask 的三個泛型參數:
public abstract class AsyncTask<Params, Progress, Result>
params
,傳入參數類型,即 doInBackground() 方法中的參數類型;Progress
,異步任務執行過程中返回的任務執行進度類型,即 publishProgress() 和onProgressUpdate() 方法中傳入的參數類型;Result
,異步任務執行完返回的結果類型,即 doInBackground() 方法中返回值的類型。
四個回調方法:
onPreExecute()
,在主線程執行,做一些準備工作。doInBackground()
,在線程池中執行,該方法是抽象方法,在此方法中可以調用publishProgress()
更新任務進度。onProgressUpdate()
,在主線程中執行,在 publishProgress() 調用之後被回調,展示任務進度。onPostExecute()
,在主線程中執行,異步任務結束後,回調此方法,處理返回結果。
注意:
- 當 AsyncTask 任務被取消時,回調 onCanceled(obj) ,此時 onPostExecute(),不會被調用,AsyncTask 中的 cancel() 方法並不是真正去取消任務,只是設置這個任務爲取消狀態,需要在 doInBackground() 中通過 isCancelled() 判斷終止任務。
- AsyncTask 必須在主線程中創建實例,execute() 方法也必須在主線程中調用。
- 每個 AsyncTask 實例只能執行一次 execute() ,多次執行會報錯,如需執行多次,則需創建多個實例。
- Android 3.0 之後, AsyncTask 對象默認執行多任務是串行執行,即 mAsyncTask.execute() ,併發執行的話需要使用 executeOnExecutor() 。
- AsyncTask 用的是線程池機制和異步消息機制(基於 ThreadPoolExecutor 和 Handler )。Android 2.3 以前,AsyncTask 線程池容量是 128 ,全局線程池只有 5 個工作線程,如果運用 AsyncTask 對象來執行多個併發異步任務,那麼同一時間最多只能有 5 個線程同時運行,其他線程將被阻塞。Android 3.0 之後 Google 又進行了調整,新增接口 executeOnExecutor() ,允許自定義線程池(那麼核心線程數以及線程容量也可自定義),並提供了 SERIAL_EXECUTOR 和 THREAD_POOL_EXECUTOR 預定義線程池。後來 Google 又做了一些調整(任何事物都不完美),將線程池的容量與 CPU 的核心數聯繫起來,如目前 SDK 25 版本中,預定義的核心線程數量最少有 2 個,最多 4 個,線程池容量範圍 5 ~ 9 。改動如下:
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
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;
異步消息機制
異步消息機制的三大主角: Handler ,Message 和 Looper 。
Looper 負責創建 MessageQueue 消息對列,然後進入一個無限 for 循環中,不斷地從消息隊列中取消息,如果消息隊列爲空,當前線程阻塞,Handler 負責向消息隊列中發送消息。
Looper
Looper 有兩個重要的方法: prepare() 和 loop()。
- prepare() , Looper 與當前線程綁定,一個線程只能有一個 Looper 實例和一個 MessageQueue 實例。
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(true)); 保證 Looper 對象在當前線程唯一
}
// Looper 的構造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
- loop ,進入一個無限 for 循環體中,不斷地從消息隊列中取消息,然後交給消息的 target 屬性的 dispatchMessage 方法去處理。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// 無限循環體,有沒有想過在 UI 線程裏,有這樣一個死循環,爲什麼界面沒卡死??
// 答案最後揭曉。
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
msg.recycle();
}
}
Handler
Handler 負責向消息隊列中發送消息。
在 Activity 中我們直接可以 new Handler ,那是因爲在 Activity 的啓動代碼中,已經在當前 UI 線程中調用了 Looper.prepare() 和 Looper.loop() 方法。
在子線程中 new Handler 必須要在當前線程(子線程)中創建好 Looper 對象和消息隊列,代碼如下
//在子線程中
Looper.prepare();
handler = new Handler() {
public void handleMessage(Message msg) {
//處理消息
};
};
Looper.loop();
之後,你拿着這個 Handler 對象就可以在其他線程中,往這個子線程的消息隊列中發消息了。
HandlerThread
HandlerThread 可以看作在子線程中創建一個異步消息處理機制的簡化版,HandlerThread 對象自動幫我們在工作線程裏創建 Looper 對象和消息隊列。
使用方法:
mHandlerThread = new HandlerThread("MyHandlerThread");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
//處理消息
}
};
之後你就可以使用 Handler 對象往工作線程中的消息隊列中發消息了。
看一下源碼片段:
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
}
注意:handler 在 UI 線程中初始化的,looper 在一個子線程中執行,我們必須等 mLooper 創建完成之後,才能調用 getLooper ,源碼中是通過 wait 和 notify 解決兩個線程的同步問題。
IntentService
IntentService 可以看成是 Service 和 HandlerThread 的合體。它繼承自 Service ,並可以處理異步請求,其內部有一個 WorkerThread 來處理異步任務,當任務執行完畢後,IntentService 自動停止。
如果多次啓動 IntentService 呢? 看到 HandlerThread ,你就應該想到多次啓動 IntentService ,就是將多個異步任務放到任務隊列裏面,然後在 onHandlerIntent 回調方法中串行執行,執行完畢後自動結束。
下面對源碼進行簡單的解析,IntentService 源碼:
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//onHandleIntent 方法在工作線程中執行,執行完調用 stopSelf() 結束服務。
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public IntentService(String name) {
super();
mName = name;
}
/**
* enabled == true 時,如果任務沒有執行完,當前進程就死掉了,那麼系統就會令當前進程重啓。
* 任務會被重新執行。
*/
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
@Override
public void onCreate() {
super.onCreate();
// 上面已經講過,HandlerThread 對象 start 之後,會在工作線程裏創建消息隊列 和 Looper 對象。
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
// 獲得 Looper 對象初始化 Handler 對象。
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
// IntentService 每次啓動都會往工作線程消息隊列中添加消息,不會創建新的線程。
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
// 官方建議 IntentService onStartCommand 方法不應該被重寫,注意該方法會調用 onStart 。
@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 onDestroy() {
//服務停止會清除消息隊列中的消息,除了當前執行的任務外,後續的任務不會被執行。
mServiceLooper.quit();
}
/**
* 不建議通過 bind 啓動 IntentService ,如果通過 bind 啓動 IntentService ,那麼 onHandlerIntent 方法不會被回調。Activity 與 IntentService 之間的通信一般採用廣播的方式。
*/
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
/**
* 子類必須要實現,執行具體的異步任務邏輯,由 IntentService 自動回調。
*/
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}
IntentService 源碼很容易理解,你也可以就自己的應用場景封裝自己的 IntentService 。
場景
- 正常情況下,啓動 IntentService ,任務完成,服務停止;
- 異步任務完成前,停止 IntentService ,服務停止,但任務還會執行完成,完成後,工作線程結束;
- 多次啓動 IntentService ,任務會被一次串行執行,執行結束後,服務停止;
- 多次啓動 IntentService ,在所有任務執行結束之前,停止 IntentService ,服務停止,除了當前執行的任務外,後續的任務不會被執行;
ThreadPoolExcutor
ThreadPool
用來管理一組工作線程,任務隊列( BlockingQueue )中持有的任務等待着被線程池中的空閒線程執行。
常用構造方法:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
);
- corePoolSize 核心線程池容量,即線程池中所維持線程的最低數量。corePoolSize 初始值爲 0 ,當有新任務加入到任務隊列中,新的線程將被創建,這個時候即使線程池中存在空閒線程,只要當前線程數小於 corePoolSize ,那麼新的線程依然被創建。
- maximumPoolSize 線程池中所維持線程的最大數量。
- keepAliveTime 空閒線程在沒有新任務到來時的存活時間。
- unit 參數 keepAliveTime 的時間單位。
- workQueue 任務隊列,必須是 BlockingQueue 。
簡單使用
創建 ThreadFactory ,當然也可以自定義。
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());
}
};
創建 ThreadPoolExecutor 。
// 根據 CPU 核心數確定線程池容量。
public static final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
mThreadPoolExecutor = new ThreadPoolExecutor(
NUMBER_OF_CORES * 2,
NUMBER_OF_CORES * 2 + 1,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
backgroundPriorityThreadFactory
);
執行
mThreadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
//do something
}
});
Future future = mThreadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
//do something
}
});
//任務可取消
future.cancel(true);
Future<Integer> futureInt = mThreadPoolExecutor.submit(new Callable<Integer>() {
@override
public Integer call() throws Exception {
return 0;
}
});
//獲取執行結果
futureInt.get();
FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>(){
@override
public Integer call() throws Exception {
return 0;
}
});
mThreadPoolExecutor.submit(task);
task.get();
聯繫與交流
微信公衆號
個人微信