天天看點

Android應用程式線程消息循環模型分析(1)

         我們知道,Android應用程式是通過消息來驅動的,即在應用程式的主線程(UI線程)中有一個消息循環,負責處理消息隊列中的消息。我們也知道,Android應用程式是支援多線程的,即可以建立子線程來執行一些計算型的任務,那麼,這些子線程能不能像應用程式的主線程一樣具有消息循環呢?這些子線程又能不能往應用程式的主線程中發送消息呢?本文将分析Android應用程式線程消息處理模型,為讀者解答這兩個問題

        在開發Android應用程式中,有時候我們需要在應用程式中建立一些常駐的子線程來不定期地執行一些不需要與應用程式界面互動的計算型的任務。如果這些子線程具有消息循環,那麼它們就能夠常駐在應用程式中不定期的執行一些計算型任務了:當我們需要用這些子線程來執行任務時,就往這個子線程的消息隊列中發送一個消息,然後就可以在子線程的消息循環中執行我們的計算型任務了。我們在前面一篇文章Android系統預設Home應用程式(Launcher)的啟動過程源代碼分析中,介紹Launcher的啟動過程時,在Step 15(LauncherModel.startLoader)中,Launcher就是通過往一個子線程的消息隊列中發送一個消息(sWorker.post(mLoaderTask)),然後子線程就會在它的消息循環中處理這個消息的時候執行從PackageManagerService中擷取系統中已安裝應用程式的資訊清單的任務,即調用Step 16中的LoaderTask.run函數。

        在開發Android應用程式中,有時候我們又需要在應用程式中建立一些子線程來執行一些需要與應用程式界面進互動的計算型任務。典型的應用場景是當我們要從網上下載下傳檔案時,為了不使主線程被阻塞,我們通常建立一個子線程來負責下載下傳任務,同時,在下載下傳的過程,将下載下傳進度以百分比的形式在應用程式的界面上顯示出來,這樣就既不會阻塞主線程的運作,又能獲得良好的使用者體驗。但是,我們知道,Android應用程式的子線程是不可以操作主線程的UI的,那麼,這個負責下載下傳任務的子線程應該如何在應用程式界面上顯示下載下傳的進度呢?如果我們能夠在子線程中往主線程的消息隊列中發送消息,那麼問題就迎刃而解了,因為發往主線程消息隊列的消息最終是由主線程來處理的,在處理這個消息的時候,我們就可以在應用程式界面上顯示下載下傳進度了。

        上面提到的這兩種情況,Android系統都為我們提供了完善的解決方案,前者可以通過使用HandlerThread類來實作,而後者可以使用AsyncTask類來實作,本文就詳細這兩個類是如何實作的。不過,為了更好地了解HandlerThread類和AsyncTask類的實作,我們先來看看應用程式的主線程的消息循環模型是如何實作的。

        1. 應用程式主線程消息循環模型

        在前面一篇文章Android應用程式程序啟動過程的源代碼分析一文中,我們已經分析應用程式程序(主線程)的啟動過程了,這裡主要是針對它的消息循環模型作一個總結。當運作在Android應用程式架構層中的ActivityManagerService決定要為目前啟動的應用程式建立一個主線程的時候,它會在ActivityManagerService中的startProcessLocked成員函數調用Process類的靜态成員函數start為目前應用程式建立一個主線程:

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();   

        Looper類具體是如何通過loop函數進入消息循環以及處理消息隊列中的消息,可以參考前面一篇文章Android應用程式消息處理機制(Looper、Handler)分析,這裡就不再分析了,我們隻要知道ActivityThread類中的main函數執行了這一步之後,就為應用程式的主線程準備好消息循環就可以了。