我們知道,Android應用程式是通過消息來驅動的,即在應用程式的主線程(UI線程)中有一個消息循環,負責處理消息隊列中的消息。我們也知道,Android應用程式是支援多線程的,即可以建立子線程來執行一些計算型的任務,那麼,這些子線程能不能像應用程式的主線程一樣具有消息循環呢?這些子線程又能不能往應用程式的主線程中發送消息呢?本文将分析Android應用程式線程消息處理模型,為讀者解答這兩個問題
在開發Android應用程式中,有時候我們又需要在應用程式中建立一些子線程來執行一些需要與應用程式界面進互動的計算型任務。典型的應用場景是當我們要從網上下載下傳檔案時,為了不使主線程被阻塞,我們通常建立一個子線程來負責下載下傳任務,同時,在下載下傳的過程,将下載下傳進度以百分比的形式在應用程式的界面上顯示出來,這樣就既不會阻塞主線程的運作,又能獲得良好的使用者體驗。但是,我們知道,Android應用程式的子線程是不可以操作主線程的UI的,那麼,這個負責下載下傳任務的子線程應該如何在應用程式界面上顯示下載下傳的進度呢?如果我們能夠在子線程中往主線程的消息隊列中發送消息,那麼問題就迎刃而解了,因為發往主線程消息隊列的消息最終是由主線程來處理的,在處理這個消息的時候,我們就可以在應用程式界面上顯示下載下傳進度了。
上面提到的這兩種情況,Android系統都為我們提供了完善的解決方案,前者可以通過使用HandlerThread類來實作,而後者可以使用AsyncTask類來實作,本文就詳細這兩個類是如何實作的。不過,為了更好地了解HandlerThread類和AsyncTask類的實作,我們先來看看應用程式的主線程的消息循環模型是如何實作的。
1. 應用程式主線程消息循環模型
public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
......
private final void startProcessLocked(ProcessRecord app,
String hostingType, String hostingNameStr) {
......
try {
int uid = app.info.uid;
int[] gids = null;
try {
gids = mContext.getPackageManager().getPackageGids(
app.info.packageName);
} catch (PackageManager.NameNotFoundException e) {
......
}
......
int debugFlags = 0;
int pid = Process.start("android.app.ActivityThread",
mSimpleProcessManagement ? app.processName : null, uid, uid,
gids, debugFlags, null);
} catch (RuntimeException e) {
}
}
}
這裡我們主要關注Process.start函數的第一個參數“android.app.ActivityThread”,它表示要在目前建立的線程中加載android.app.ActivityThread類,并且調用這個類的靜态成員函數main作為應用程式的入口點。ActivityThread類定義在frameworks/base/core/java/android/app/ActivityThread.java檔案中:
public final class ActivityThread {
......
public static final void main(String[] args) {
......
Looper.prepareMainLooper();
......
ActivityThread thread = new ActivityThread();
thread.attach(false);
......
Looper.loop();
thread.detach();
}
}
在這個main函數裡面,除了建立一個ActivityThread執行個體外,就是在進行消息循環了。
在進行消息循環之前,首先會通過Looper類的靜态成員函數prepareMainLooper為目前線程準備一個消息循環對象。Looper類定義在frameworks/base/core/java/android/os/Looper.java檔案中:
public class Looper {
......
// sThreadLocal.get() will return null unless you've called prepare().
private static final ThreadLocal sThreadLocal = new ThreadLocal();
private static Looper mMainLooper = null;
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
public static final void prepareMainLooper() {
prepare();
setMainLooper(myLooper());
private synchronized static void setMainLooper(Looper looper) {
mMainLooper = looper;
public synchronized static final Looper getMainLooper() {
return mMainLooper;
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
Looper類的靜态成員函數prepareMainLooper是專門應用程式的主線程調用的,應用程式的其它子線程都不應該調用這個函數來在本線程中建立消息循環對象,而應該調用prepare函數來在本線程中建立消息循環對象,下一節我們介紹一個線程類HandlerThread 時将會看到。
為什麼要為應用程式的主線程專門準備一個建立消息循環對象的函數呢?這是為了讓其它地方能夠友善地通過Looper類的getMainLooper函數來獲得應用程式主線程中的消息循環對象。獲得應用程式主線程中的消息循環對象又有什麼用呢?一般就是為了能夠向應用程式主線程發送消息了。
在prepareMainLooper函數中,首先會調用prepare函數在本線程中建立一個消息循環對象,然後将這個消息循環對象放線上程局部變量sThreadLocal中:
sThreadLocal.set(new Looper());
接着再将這個消息循環對象通過調用setMainLooper函數來儲存在Looper類的靜态成員變量mMainLooper中:
mMainLooper = looper;
這樣,其它地方才可以調用getMainLooper函數來獲得應用程式主線程中的消息循環對象。
消息循環對象建立好之後,回到ActivityThread類的main函數中,接下來,就是要進入消息循環了:
Looper.loop();
本文轉自 Luoshengyang 51CTO部落格,原文連結:http://blog.51cto.com/shyluo/966875,如需轉載請自行聯系原作者