[深入理解Android卷二 全文-第六章]深入理解ActivityManagerService

阿拉神農發表於2015-08-03

由於《深入理解Android 卷一》和《深入理解Android卷二》不再出版,而知識的傳播不應該因為紙質媒介的問題而中斷,所以我將在CSDN部落格中全文轉發這兩本書的全部內容


第6章 深入理解ActivityManagerService

本章主要內容:

·  詳細分析ActivityManagerService

本章所涉及的原始碼檔名及位置:

·  SystemServer.java

frameworks/base/services/java/com/android/server/SystemServer.java

·  ActivityManagerService.java

frameworks/base/services/java/com/android/server/am/ActivityManagerService.java

·  ContextImpl.java

frameworks/base/core/java/android/app/ContextImpl.java

·  ActivityThread.java

frameworks/base/core/java/android/app/ActivityThread.java

·  ActivityStack.java

frameworks/base/services/java/com/android/server/am/ActivityStack.java

·  Am.java

frameworks/base/cmds/am/src/com/android/commands/am/Am.java

·  ProcessRecord.java

frameworks/base/services/java/com/android/server/am/ProcessRecord.java

·  ProcessList.java

frameworks/base/services/java/com/android/server/am/ProcessList.java

·  RuntimeInit.java

frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

6.1  概述

相信絕大部分讀者對本書提到的ActivityManagerService(以後簡稱AMS)都有所耳聞。AMS是Android中最核心的服務,主要負責系統中四大元件的啟動、切換、排程及應用程式的管理和排程等工作,其職責與作業系統中的程式管理和排程模組相類似,因此它在Android中非常重要。

AMS是本書碰到的第一塊難啃的骨頭[①],涉及的知識點較多。為了幫助讀者更好地理解AMS,本章將帶領讀者按五條不同的線來分析它。

·  第一條線:同其他服務一樣,將分析SystemServer中AMS的呼叫軌跡。

·  第二條線:以am命令啟動一個Activity為例,分析應用程式的建立、Activity的啟動,以及它們和AMS之間的互動等知識。

·  第三條線和第四條線:分別以Broadcast和Service為例,分析AMS中Broadcast和Service的相關處理流程。

·  第五條線:以一個Crash的應用程式為出發點,分析AMS如何打理該應用程式的身後事。

除了這五條線外,還將統一分析在這五條線中頻繁出現的與AMS中應用程式的排程、記憶體管理等相關的知識。

提示ContentProvider將放到下一章分析,不過本章將涉及和ContentProvider有關的知識點。

先來看AMS的家族圖譜,如圖6-1所示。


圖6-1  AMS家族圖譜

由圖6-1可知:

·  AMS由ActivityManagerNative(以後簡稱AMN)類派生,並實現Watchdog.Monitor和BatteryStatsImpl.BatteryCallback介面。而AMN由Binder派生,實現了IActivityManager介面。

·  客戶端使用ActivityManager類。由於AMS是系統核心服務,很多API不能開放供客戶端使用,所以設計者沒有讓ActivityManager直接加入AMS家族。在ActivityManager類內部通過呼叫AMN的getDefault函式得到一個ActivityManagerProxy物件,通過它可與AMS通訊。

AMS的簡單介紹就到此為止,下面分析AMS。相信不少讀者已經磨拳擦掌,躍躍欲試了。

提示讀者們最好在桌上放一杯清茶,以保持AMS分析旅途中頭腦清醒。

6.2  初識ActivityManagerService

AMS由SystemServer的ServerThread執行緒建立,提取它的呼叫軌跡,程式碼如下:

[-->SystemServer.java::ServerThread的run函式]

//①呼叫main函式,得到一個Context物件

context =ActivityManagerService.main(factoryTest);

 

//②setSystemProcess:這樣SystemServer程式可加到AMS中,並被它管理

ActivityManagerService.setSystemProcess();

 

//③installSystemProviders:將SettingsProvider放到SystemServer程式中來執行

ActivityManagerService.installSystemProviders();

 

//④在內部儲存WindowManagerService(以後簡稱WMS)

ActivityManagerService.self().setWindowManager(wm);

 

//⑤和WMS互動,彈出“啟動進度“對話方塊

ActivityManagerNative.getDefault().showBootMessage(

             context.getResources().getText(

               //該字串中文為:“正在啟動應用程式”

               com.android.internal.R.string.android_upgrading_starting_apps),

              false);

 

//⑥AMS是系統的核心,只有它準備好後,才能呼叫其他服務的systemReady

//注意,有少量服務在AMS systemReady之前就緒,它們不影響此處的分析

ActivityManagerService.self().systemReady(newRunnable() {

    publicvoid run() {

   startSystemUi(contextF);//啟動systemUi。如此,狀態列就準備好了

    if(batteryF != null) batteryF.systemReady();

    if(networkManagementF != null) networkManagementF.systemReady();

    ......

    Watchdog.getInstance().start();//啟動Watchdog

    ......//呼叫其他服務的systemReady函式

}

在以上程式碼中,一共列出了6個重要呼叫及這些呼叫的簡單說明,本節將分析除與WindowManagerService(以後簡稱WMS)互動的4、5外的其餘四項呼叫。

先來分析1處呼叫。

6.2.1  ActivityManagerService main分析

AMS的main函式將返回一個Context型別的物件,該物件在SystemServer中被其他服務大量使用。Context,顧名思義,代表了一種上下文環境(筆者覺得其意義和JNIEnv類似),有了這個環境,我們就可以做很多事情(例如獲取該環境中的資源、Java類資訊等)。那麼AMS的main將返回一個怎樣的上下文環境呢?來看以下程式碼:

[-->ActivityManagerService.java::main]

 publicstatic final Context main(int factoryTest) {

    AThreadthr = new AThread();//①建立一個AThread執行緒物件

    thr.start();

    ......//等待thr建立成功

    ActivityManagerServicem = thr.mService;

    mSelf =m;

    //②呼叫ActivityThread的systemMain函式

    ActivityThreadat = ActivityThread.systemMain();

    mSystemThread= at;

 

    //③得到一個Context物件,注意呼叫的函式名為getSystemContext,何為System Context

    Contextcontext = at.getSystemContext();

    context.setTheme(android.R.style.Theme_Holo);

    m.mContext= context;

    m.mFactoryTest= factoryTest;

 

    //ActivtyStack是AMS中用來管理Activity的啟動和排程的核心類,以後再分析它

    m.mMainStack = new ActivityStack(m, context,true);

    //呼叫BSS的publish函式,我們在第5章的BSS知識中介紹過了

    m.mBatteryStatsService.publish(context);

    //另外一個service:UsageStatsService。後續再分析該服務

    m.mUsageStatsService.publish(context);

    synchronized (thr) {

           thr.mReady = true;

           thr.notifyAll();//通知thr執行緒,本執行緒工作完成

     }

 

    //④呼叫AMS的startRunning函式

    m.startRunning(null, null, null, null);

       

   returncontext;

}

在main函式中,我們又列出了4個關鍵函式,分別是:

·  建立AThread執行緒。雖然AMS的main函式由ServerThread執行緒呼叫,但是AMS自己的工作並沒有放在ServerThread中去做,而是新建立了一個執行緒,即AThread執行緒。

·  ActivityThread.systemMain函式。初始化ActivityThread物件。

·  ActivityThread.getSystemContext函式。用於獲取一個Context物件,從函式名上看,該Context代表了System的上下文環境。

·  AMS的startRunning函式。

注意,main函式中有一處等待(wait)及一處通知(notifyAll),原因是:

·  main函式首先需要等待AThread所線上程啟動並完成一部分工作。

·  AThread完成那一部分工作後,將等待main函式完成後續的工作。

這種雙執行緒互相等待的情況,在Android程式碼中比較少見,讀者只需瞭解它們的目的即可。下邊來分析以上程式碼中的第一個關鍵點。

1.  AThread分析

(1) AThread分析

AThread的程式碼如下:

[-->ActivityManagerService.java::AThread]

static class AThread extends Thread {//AThread從Thread類派生

   ActivityManagerServicemService;

   booleanmReady = false;

   publicAThread() {

     super("ActivityManager");//執行緒名就叫“ActivityManager”

   }

   publicvoid run() {

     Looper.prepare();//看來,AThread執行緒將支援訊息迴圈及處理功能

     android.os.Process.setThreadPriority(//設定執行緒優先順序

                   android.os.Process.THREAD_PRIORITY_FOREGROUND);

     android.os.Process.setCanSelfBackground(false);

      //建立AMS物件

     ActivityManagerService m = new ActivityManagerService();

     synchronized (this) {

           mService= m;//賦值AThread內部成員變數mService,指向AMS

          notifyAll();  //通知main函式所線上程

      }

     synchronized (this) {

        while (!mReady) {

           try{

                 wait();//等待main函式所線上程的notifyAll

               }......

           }

       }......

    Looper.loop();//進入訊息迴圈

 }

 }

從本質上說,AThread是一個支援訊息迴圈及處理的執行緒,其主要工作就是建立AMS物件,然後通知AMS的main函式。這樣看來,main函式等待的就是這個AMS物件。

(2) AMS的建構函式分析

AMS的建構函式的程式碼如下:

[-->ActivityManagerService.java::ActivityManagerService構造]

private ActivityManagerService() {

    FiledataDir = Environment.getDataDirectory();//指向/data/目錄

    FilesystemDir = new File(dataDir, "system");//指向/data/system/目錄

   systemDir.mkdirs();//建立/data/system/目錄

 

    //建立BatteryStatsService(以後簡稱BSS)和UsageStatsService(以後簡稱USS)

   //我們在分析PowerManageService時已經見過BSS了

   mBatteryStatsService = new BatteryStatsService(new File(

               systemDir, "batterystats.bin").toString());

   mBatteryStatsService.getActiveStatistics().readLocked();

    mBatteryStatsService.getActiveStatistics().writeAsyncLocked();

   mOnBattery = DEBUG_POWER ? true

               : mBatteryStatsService.getActiveStatistics().getIsOnBattery();

   mBatteryStatsService.getActiveStatistics().setCallback(this);

 

    //建立USS

    mUsageStatsService= new UsageStatsService(new File(

               systemDir, "usagestats").toString());

    //獲取OpenGl版本

   GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",

           ConfigurationInfo.GL_ES_VERSION_UNDEFINED);

     //mConfiguration型別為Configuration,用於描述資原始檔的配置屬性,例如

     //字型、語言等。後文再討論這方面的內容

    mConfiguration.setToDefaults();

    mConfiguration.locale = Locale.getDefault();

     //mProcessStats為ProcessStats型別,用於統計CPU、記憶體等資訊。其內部工作原理就是

    //讀取並解析/proc/stat檔案的內容。該檔案由核心生成,用於記錄kernel及system

    //一些執行時的統計資訊。讀者可在Linux系統上通過man proc命令查詢詳細資訊

    mProcessStats.init();

 

     //解析/data/system/packages-compat.xml檔案,該檔案用於儲存那些需要考慮螢幕尺寸

    //的APK的一些資訊。讀者可參考AndroidManifest.xml中compatible-screens相關說明。

    //當APK所執行的裝置不滿足要求時,AMS會根據設定的引數以採用螢幕相容的方式去執行它

    mCompatModePackages = new CompatModePackages(this, systemDir);

 

     Watchdog.getInstance().addMonitor(this);

     //建立一個新執行緒,用於定時更新系統資訊(和mProcessStats互動)

    mProcessStatsThread = new Thread("ProcessStats") {...//先略去該段程式碼}

    mProcessStatsThread.start();

 }

AMS的建構函式比想象得要簡單些,下面回顧一下它的工作:

·  建立BSS、USS、mProcessStats (ProcessStats型別)、mProcessStatsThread執行緒,這些都與系統執行狀況統計相關。

·  建立/data/system目錄,為mCompatModePackages(CompatModePackages型別)和mConfiguration(Configuration型別)等成員變數賦值。

AMS main函式的第一個關鍵點就分析到此,再來分析它的第二個關鍵點。

2.  ActivityThread.systemMain函式分析

ActivityThread是Android Framework中一個非常重要的類,它代表一個應用程式的主執行緒(對於應用程式來說,ActivityThread的main函式確實是由該程式的主執行緒執行),其職責就是排程及執行在該執行緒中執行的四大元件。

注意應用程式指那些執行APK的程式,它們由Zyote 派生(fork)而來,上面執行了dalvik虛擬機器。與應用程式相對的就是系統程式(包括Zygote和SystemServer)。

另外,讀者須將“應用程式和系統程式”與“應用APK和系統APK”的概念區分開來。APK的判別依賴其檔案所在位置(如果apk檔案在/data/app目錄下,則為應用APK)。

該函式程式碼如下:

[-->ActivityThread.java::systemMain]

public static final ActivityThread systemMain() {

   HardwareRenderer.disable(true);//禁止硬體渲染加速

   //建立一個ActivityThread物件,其建構函式非常簡單

  ActivityThread thread = new ActivityThread();

  thread.attach(true);//呼叫它的attach函式,注意傳遞的引數為true

   returnthread;

 }

在分析ActivityThread的attach函式之前,先提一個問題供讀者思考:前面所說的ActivityThread代表應用程式(其上執行了APK)的主執行緒,而SystemServer並非一個應用程式,那麼為什麼此處也需要ActivityThread呢?

·  還記得在PackageManagerService分析中提到的framework-res.apk嗎?這個APK除了包含資原始檔外,還包含一些Activity(如關機對話方塊),這些Activity實際上執行在SystemServer程式中[②]。從這個角度看,SystemServer是一個特殊的應用程式。

·  另外,通過ActivityThread可以把Android系統提供的元件之間的互動機制和互動介面(如利用Context提供的API)也擴充到SystemServer中使用。

提示解答這個問題,對於理解SystemServer中各服務的互動方式是尤其重要的。

下面來看ActivityThread的attach函式。

(1) attach函式分析

[-->ActivityThread.java::attach]

private void attach(boolean system) {

    sThreadLocal.set(this);

    mSystemThread= system;//判斷是否為系統程式

    if(!system) {

        ......//應用程式的處理流程

     } else {//系統程式的處理流程,該情況只在SystemServer中處理

       //設定DDMS時看到的systemserver程式名為system_process

       android.ddm.DdmHandleAppName.setAppName("system_process");

       try {

            //ActivityThread的幾員大將出場,見後文的分析

            mInstrumentation = new Instrumentation();

            ContextImpl context = new ContextImpl();

            //初始化context,注意第一個引數值為getSystemContext

            context.init(getSystemContext().mPackageInfo, null, this);

            Application app = //利用Instrumentation建立一個Application物件

                    Instrumentation.newApplication(Application.class,context);

             //一個程式支援多個Application,mAllApplications用於儲存該程式中

            //的Application物件

            mAllApplications.add(app);

             mInitialApplication = app;//設定mInitialApplication

            app.onCreate();//呼叫Application的onCreate函式

           }......//try/catch結束

      }//if(!system)判斷結束

 

     //註冊Configuration變化的回撥通知

     ViewRootImpl.addConfigCallback(newComponentCallbacks2() {

          publicvoid onConfigurationChanged(Configuration newConfig) {

            ......//當系統配置發生變化(如語言切換等)時,需要呼叫該回撥

          }

           public void onLowMemory() {}

           public void onTrimMemory(int level) {}

        });

 }

attach函式中出現了幾個重要成員,其型別分別是Instrumentation類、Application類及Context類,它們的作用如下(為了保證準確,這裡先引用Android的官方說明)。

·  Instrumentation:Base class for implementingapplication instrumentation code. When running with instrumentation turned on,this class will be instantiated for you before any of the application code,allowing you to monitor all of the interaction the system has with the application.An Instrumentation implementation is described to the system through anAndroidManifest.xml's <instrumentation> tag.大意是:Instrumentaion是一個工具類。當它被啟用時,系統先建立它,再通過它來建立其他元件。另外,系統和元件之間的互動也將通過Instrumentation來傳遞,這樣,Instrumentation就能監測系統和這些元件的互動情況了。在實際使用中,我們可以建立Instrumentation的派生類來進行相應的處理。讀者可查詢Android中Junit的使用來了解Intrstrumentation的作用。本書不討論Intrstrumentation方面的內容。

·  Application:Base class for those who need tomaintain global application state. You can provide your own implementation byspecifying its name in your AndroidManifest.xml's <application> tag,which will cause that class to be instantiated for you when the process foryour application/package is created.大意是:Application類儲存了一個全域性的application狀態。Application由AndroidManifest.xml中的<application>標籤宣告。在實際使用時需定義Application的派生類。

·  Context:Interface to global informationabout an application environment. This is an abstract class whoseimplementation is provided by the Android system. It allows access toapplication-specific resources and classes, as well as up-calls forapplication-level operations such as launching activities, broadcasting andreceiving intents, etc.大意是:Context是一個介面,通過它可以獲取並操作Application對應的資源、類,甚至包含於Application中的四大元件。

提示此處的Application是Android中的一個概念,可理解為一種容器,它內部包含四大元件。另外,一個程式可以執行多個Application。

Context是一個抽象類,而由AMS建立的將是它的子類ContextImpl。如前所述,Context提供了Application的上下文資訊,這些資訊是如何傳遞給Context的呢?此問題包括兩個方面:

·  Context本身是什麼?

·  Context背後所包含的上下文資訊又是什麼?

下面來關注上邊程式碼中呼叫的getSystemContext函式。

(2) getSystemContext函式分析

[-->ActivityThread.java::getSystemContext]

public ContextImpl getSystemContext() {

  synchronized(this) {

   if(mSystemContext == null) {//單例模式

       ContextImplcontext =  ContextImpl.createSystemContext(this);

       //LoadedApk是2.3引入的一個新類,代表一個載入到系統中的APK

       LoadedApkinfo = new LoadedApk(this, "android", context, null,

                       CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO);

       //初始化該ContextImpl物件

      context.init(info, null, this);

      //初始化資源資訊

      context.getResources().updateConfiguration(

                        getConfiguration(),getDisplayMetricsLocked(

                       CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, false));

       mSystemContext = context;//儲存這個特殊的ContextImpl物件

      }

   }

    returnmSystemContext;

}

以上程式碼無非是先建立一個ContextImpl,然後再將其初始化(呼叫init函式)。為什麼函式名是getSystemContext呢?因為在初始化ContextImp時使用了一個LoadedApk物件。如註釋中所說,LoadedApk是Android 2.3引入的一個類,該類用於儲存一些和APK相關的資訊(如資原始檔位置、JNI庫位置等)。在getSystemContext函式中初始化ContextImpl的這個LoadedApk所代表的package名為“android”,其實就是framework-res.apk,由於該APK僅供SystemServer使用,所以此處叫getSystemContext。

上面這些類的關係比較複雜,可通過圖6-2展示它們之間的關係。


圖6-2  ContextImpl和它的“兄弟”們

由圖6-2可知:

·  先來看派生關係, ApplicationContentResolver從ConentResolver派生,它主要用於和ContentProvider打交道。ContextImpl和ContextWrapper均從Context繼承,而Application則從ContextWrapper派生。

·  從社會關係角度看,ContextImpl交際面最廣。它通過mResources指向Resources,mPackageInfo指向LoadedApk,mMainThread指向ActivityThread,mContentResolver指向ApplicationContentResolver。

·  ActivityThread代表主執行緒,它通過mInstrumentation指向Instrumentation。另外,它還儲存多個Application物件。

注意在函式中有些成員變數的型別為基類型別,而在圖6-2中直接指向了實際型別。

(3) systemMain函式總結

呼叫systemMain函式結束後,我們得到了什麼?

·  得到一個ActivityThread物件,它代表應用程式的主執行緒。

·  得到一個Context物件,它背後所指向的Application環境與framework-res.apk有關。

費了如此大的功夫,systemMain函式的目的到底是什麼?一針見血地說:

systemMain函式將為SystemServer程式搭建一個和應用程式一樣的Android執行環境。這句話涉及兩個概念。

·  程式:來源於作業系統,是在OS中看到的執行體。我們編寫的程式碼一定要執行在一個程式中。

·  Android執行環境:Android努力構築了一個自己的執行環境。在這個環境中,程式的概念被模糊化了。元件的執行及它們之間的互動均在該環境中實現。

Android執行環境是構建在程式之上的。有Android開發經驗的讀者可能會發現,在應用程式中,一般只和Android執行環境互動。基於同樣的道理,SystemServer希望它內部的那些Service也通過Android執行環境互動,因此也需為它建立一個執行環境。由於SystemServer的特殊性,此處呼叫了systemMain函式,而普通的應用程式將在主執行緒中呼叫ActivityThread的main函式來建立Android執行環境。

另外,ActivityThread雖然本意是代表程式的主執行緒,但是作為一個Java類,它的例項到底由什麼執行緒建立,恐怕不是ActivityThread自己能做主的,所以在SystemServer中可以發現,ActivityThread物件由其他執行緒建立,而在應用程式中,ActivityThread將由主執行緒來建立。

3. ActivityThread.getSystemContext函式分析

該函式在上一節已經見過了。呼叫該函式後,將得到一個代表系統程式的Context物件。到底什麼是Context?先來看如圖6-3所示的Context家族圖譜。

注意該族譜成員並不完全。另外,Activity、Service和Application所實現的介面也未畫出。


圖6-3  Context家族圖譜

由圖6-3可知:

·  ContextWrapper比較有意思,其在SDK中的說明為“Proxying implementation ofContext that simply delegates all of its calls to another Context. Can besubclassed to modify behavior without changing the original Context.”大概意思是:ContextWrapper是一個代理類,被代理的物件是另外一個Context。在圖6-3中,被代理的類其實是ContextImpl,由ContextWrapper通過mBase成員變數指定。讀者可檢視ContextWrapper.java,其內部函式功能的實現最終都由mBase完成。這樣設計的目的是想把ContextImpl隱藏起來。

·  Application從ContextWrapper派生,並實現了ComponentCallbacks2介面。Application中有一個LoadedApk型別的成員變數mLoadedApk。LoadedApk代表一個APK檔案。由於一個AndroidManifest.xml檔案只能宣告一個Application標籤,所以一個Application必然會和一個LoadedApk繫結。

·  Service從ContextWrapper派生,其中Service內部成員變數mApplication指向Application(在AndroidManifest.xml中,Service只能作為Application的子標籤,所以在程式碼中Service必然會和一個Application繫結)。

·  ContextThemeWrapper過載了和Theme(主題)相關的兩個函式。這些和介面有關,所以Activity作為Android系統中的UI容器,必然也會從ContextThemeWrapper派生。與Service一樣,Activity內部也通過mApplication成員變數指向Application。

對Context的分析先到這裡,再來分析第三個關鍵函式startRunning。

4.  AMS的startRunning函式分析

[-->ActivityManagerService.java::startRunning]

//注意呼叫該函式時所傳遞的4個引數全為null

public final void startRunning(String pkg, Stringcls, String action,

                                    String data) {

  synchronized(this) {

     if (mStartRunning) return;  //如果已經呼叫過該函式,則直接返回

 

    mStartRunning = true;

     //mTopComponent最終賦值為null

    mTopComponent = pkg != null && cls != null

                   ? new ComponentName(pkg, cls) : null;

    mTopAction = action != null ? action : Intent.ACTION_MAIN;

    mTopData = data; //mTopData最終為null

     if(!mSystemReady) return; //此時mSystemReady為false,所以直接返回

    }

   systemReady(null);//這個函式很重要,可惜不在本次startRunning中呼叫

}

startRunning函式很簡單,此處不贅述。

至此,ASM 的main函式所涉及的4個知識點已全部分析完。下面回顧一下AMS 的main函式的工作。

5.  ActivityManagerService的main函式總結

AMS的main函式的目的有兩個:

·  首先也是最容易想到的目的是建立AMS物件。

·  另外一個目的比較隱晦,但是非常重要,那就是建立一個供SystemServer程式使用的Android執行環境。

根據目前所分析的程式碼,Android執行環境將包括兩個成員:ActivityThread和ContextImpl(一般用它的基類Context)。

圖6-4展示了在這兩個類中定義的一些成員變數,通過它們可看出ActivityThread及ContextImpl的作用。


圖6-4  ActivityThread和ContextImpl的部分成員變數

由圖6-4可知:

·  ActivityThread中有一個mLooper成員,它代表一個訊息迴圈。這恐怕是ActivityThread被稱做“Thread”的一個直接證據。另外,mServices用於儲存Service,Activities用於儲存ActivityClientRecord,mAllApplications用於儲存Application。關於這些變數的具體作用,以後遇到時再說。

·  對於ContextImpl,其成員變數表明它和資源、APK檔案有關。

AMS的main函式先分析到此,至於其建立的Android執行環境將在下節分析中派上用場。

接下來分析AMS的第三個呼叫函式setSystemProcess。

6.2.2  AMS的setSystemProcess分析

AMS的setSystemProcess的程式碼如下:

[-->ActivityManagerService.java::setSystemProcess]

public static void setSystemProcess() {

  try {

      ActivityManagerService m = mSelf;

       //向ServiceManager註冊幾個服務

       ServiceManager.addService("activity",m);

       //用於列印記憶體資訊

      ServiceManager.addService("meminfo", new MemBinder(m));

 

       /*

        Android4.0新增的,用於列印應用程式使用硬體顯示加速方面的資訊(Applications

         Graphics Acceleration Info)。讀者通過adb shell dumpsys gfxinfo看看具體的

         輸出

       */

      ServiceManager.addService("gfxinfo", new GraphicsBinder(m));

 

        if(MONITOR_CPU_USAGE)//該值預設為true,新增cpuinfo服務

            ServiceManager.addService("cpuinfo", new CpuBinder(m));

 

        //向SM註冊許可權管理服務PermissionController

       ServiceManager.addService("permission", newPermissionController(m));

 

      /*    重要說明:

        向PackageManagerService查詢package名為"android"的ApplicationInfo。

        注意這句呼叫:雖然PKMS和AMS同屬一個程式,但是二者互動仍然藉助Context。

        其實,此處完全可以直接呼叫PKMS的函式。為什麼要費如此周折呢

      */

      ApplicationInfo info = //使用AMS的mContext物件

               mSelf.mContext.getPackageManager().getApplicationInfo(

                        "android",STOCK_PM_FLAGS);

 

          //①呼叫ActivityThread的installSystemApplicationInfo函式

            mSystemThread.installSystemApplicationInfo(info);

           synchronized (mSelf) {

               //②此處涉及AMS對程式的管理,見下文分析

               ProcessRecord app = mSelf.newProcessRecordLocked(

                       mSystemThread.getApplicationThread(), info,

                        info.processName);//注意,最後一個引數為字串,值為“system”

               app.persistent = true;

               app.pid = MY_PID;

               app.maxAdj = ProcessList.SYSTEM_ADJ;

               //③儲存該ProcessRecord物件

               mSelf.mProcessNames.put(app.processName, app.info.uid, app);

               synchronized (mSelf.mPidsSelfLocked) {

                   mSelf.mPidsSelfLocked.put(app.pid, app);

               }

               //根據系統當前狀態,調整程式的排程優先順序和OOM_Adj,後續將詳細分析該函式

                mSelf.updateLruProcessLocked(app, true,true);

           }

        } ......//拋異常

    }

在以上程式碼中列出了一個重要說明和兩個關鍵點。

·  重要說明:AMS向PKMS查詢名為“android”的ApplicationInfo。此處AMS和PKMS的互動是通過Context來完成的,檢視這一系列函式呼叫的程式碼,最終發現AMS將通過Binder傳送請求給PKMS來完成查詢功能。AMS和PKMS同屬一個程式,它們完全可以不通過Context來互動。此處為何要如此大費周章呢?原因很簡單,Android希望SystemServer中的服務也通過Android執行環境來互動。這更多是從設計上來考慮的,比如元件之間互動介面的統一及未來系統的可擴充套件性。

·  關鍵點一:ActivityThread的installSystemApplicationInfo函式。

·  關鍵點二:ProcessRecord類,它和AMS對程式的管理有關。

通過重要說明,相信讀者能真正理解AMS的 main函式中第二個隱含目的的作用,故此處不再展開敘述。

現在來看第一個關鍵點,即ActivityThread的installSystemApplicationInfo函式。

1.  ActivityThread的installSystemApplicationInfo函式分析

installSystemApplicationInfo函式的引數為一個ApplicationInfo物件,該物件由AMS通過Context查詢PKMS中一個名為“android”的package得來(根據前面介紹的知識,目前只有framework-res.apk宣告其package名為“android”)。

再來看installSystemApplicationInfo的程式碼,如下所示:

[-->ActivityThread.java::installSystemApplicationInfo]

public voidinstallSystemApplicationInfo(ApplicationInfo info) {

 synchronized (this) {

   //返回的ContextImpl物件即之前在AMS的main函式一節中建立的那個物件

   ContextImpl context = getSystemContext();

    //又呼叫init初始化該Context,是不是重複呼叫init了?

   context.init(new LoadedApk(this, "android", context, info,

              CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO), null, this);

     //建立一個Profiler物件,用於效能統計

     mProfiler = new Profiler();

     }

 }

在以上程式碼中看到呼叫context.init的地方,讀者可能會有疑惑,getSystemContext函式將返回mSystemContext,而此mSystemContext在AMS的main函式中已經初始化過了,此處為何再次初始化呢?

仔細檢視看程式碼便會發現:

·  第一次執行init時,在LoadedApk建構函式中第四個表示ApplicationInfo的引數為null。

·  第二次執行init時,LoadedApk建構函式的第四個引數不為空,即該引數將真正指向一個實際的ApplicationInfo,該ApplicationInfo來源於framework-res.apk。

基於上面的資訊,某些讀者可能馬上能想到:Context第一次執行init的目的僅僅是為了建立一個Android執行環境,而該Context並沒有和實際的ApplicationInfo繫結。而第二次執行init前,先利用Context和PKMS互動得到一個實際的ApplicationInfo,然後再通過init將此Context和ApplicationInfo繫結。

是否覺得前面的疑惑已豁然而解?且慢,此處又丟擲了一個更難的問題:

第一次執行init後得到的Context雖然沒有繫結ApplicationInfo,不是也能使用嗎?此處為何非要和一個ApplicationInfo繫結?

答案很簡單,因為framework-res.apk(包括後面將介紹的SettingsProvider.apk)執行在SystemServer中。和其他所有apk一樣,它的執行需要一個正確初始化的Android執行環境。

長噓一口氣,這個大難題終於弄明白了!在此即基礎上,AMS下一步的工作就就順理成章了。

由於framework-res.apk是一個APK檔案,和其他APK檔案一樣,它應該執行在一個程式中。而AMS是專門用於程式管理和排程的,所以執行APK的程式應該在AMS中有對應的管理結構。因此AMS下一步工作就是將這個執行環境和一個程式管理結構對應起來並交由AMS統一管理。

AMS中的程式管理結構是ProcessRecord。

2.  關於ProcessRecord和IApplicationThread的介紹

分析ProcessRecord之前,先來思考一個問題:

AMS如何與應用程式互動?例如AMS啟動一個位於其他程式的Activity,由於該Activity執行在另外一程式中,因此AMS勢必要和該程式進行跨程式通訊。

答案自然是通過Binder進行通訊。為此,Android提供了一個IApplicationThread介面,該介面定義了AMS和應用程式之間的互動函式,如圖6-5所示為該介面的家族圖譜。


圖6-5  ApplicationThread類

由圖6-5可知:

·  ApplicationThreadNative實現了IApplicationThread介面。從該介面定義的函式可知,AMS通過它可以和應用程式進行互動,例如,AMS啟動一個Activity的時候會呼叫該介面的scheduleLaunchActivity函式。

·  ActivityThread通過成員變數mAppThread指向它的內部類ApplicationThread,而ApplicationThread從ApplicationThreadNative派生。

基於以上知識,讀者能快速得出下面問題的答案嗎?

IApplicationThread的Binder服務端在應用程式中還是在AMS中?

提示如果讀者知道Binder系統支援客戶端監聽服務端的死亡訊息,那麼這個問題的答案就簡單了:服務端自然在應用程式中,因為AMS需要監聽應用程式的死亡通知。

有了IApplicationThread介面,AMS就可以和應用程式互動了。例如,對於下面一個簡單的函式:

[-->ActivityThread.java::scheduleStopActivity]

public final void scheduleStopActivity(IBindertoken, boolean showWindow,

                                     intconfigChanges) {

  queueOrSendMessage(//該函式內部將給一個Handler傳送對應的訊息

               showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,

               token, 0, configChanges);

 }

當AMS想要停止(stop)一個Activity時,會呼叫對應程式IApplicationThread Binder客戶端的scheduleStopActivity函式。該函式服務端實現的就是向ActivityThread所線上程傳送一個訊息。在應用程式中,ActivityThread執行在主執行緒中,所以這個訊息最終在主執行緒被處理。

提示Activity的onStop函式也將在主執行緒中被呼叫。

IApplicationThread僅僅是AMS和另外一個程式互動的介面,除此之外,AMS還需要更多的有關該程式的資訊。在AMS中,程式的資訊都儲存在ProcessRecord資料結構中。那麼,ProcessRecord是什麼呢?先來看setSystemProcess的第二個關鍵點,即newProcessRecordLocked函式,其程式碼如下:

[-->ActivityManagerService.java::newProcessRecordLocked]

final ProcessRecordnewProcessRecordLocked(IApplicationThread thread,

                ApplicationInfo info, String customProcess) {

   Stringproc = customProcess != null ? customProcess : info.processName;

  BatteryStatsImpl.Uid.Proc ps = null;

  BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();

  synchronized (stats) {

        //BSImpl將為該程式建立一個耗電量統計項

        ps =stats.getProcessStatsLocked(info.uid, proc);

   }

   //建立一個ProcessRecord物件,用於和其他程式通訊的thread作為第一個引數

   returnnew ProcessRecord(ps, thread, info, proc);

 }

ProcessRecord的成員變數較多,先來看看再其建構函式中都初始化了哪些成員變數。

[-->ProcessRecord.java::ProcessRecord]

ProcessRecord(BatteryStatsImpl.Uid.Proc_batteryStats,

        IApplicationThread_thread,ApplicationInfo _info, String _processName) {

    batteryStats = _batteryStats; //用於電量統計

     info =_info; //儲存ApplicationInfo

    processName = _processName; //儲存程式名

      //一個程式能執行多個Package,pkgList用於儲存package名

    pkgList.add(_info.packageName);

     thread= _thread;//儲存IApplicationThread,通過它可以和應用程式互動

 

     //下面這些xxxAdj成員變數和程式排程優先順序及OOM_adj有關。以後再分析它的作用

     maxAdj= ProcessList.EMPTY_APP_ADJ;

    hiddenAdj = ProcessList.HIDDEN_APP_MIN_ADJ;

    curRawAdj = setRawAdj = -100;

     curAdj= setAdj = -100;

   //用於控制該程式是否常駐記憶體(即使被殺掉,系統也會重啟它),只有重要的程式才會有此待遇

    persistent = false;

     removed= false;

}

ProcessRecord除儲存和應用程式通訊的IApplicationThread物件外,還儲存了程式名、不同狀態對應的Oom_adj值及一個ApplicationInfo。一個程式雖然可執行多個Application,但是ProcessRecord一般儲存該程式中先執行的那個Application的ApplicationInfo。

至此,已經建立了一個ProcessRecord物件,和其他應用程式不同的是,該物件對應的程式為SystemServer。為了彰顯其特殊性,AMS為其中的一些成員變數設定了特定的值:

   app.persistent = true;//設定該值為true

   app.pid =MY_PID;//設定pid為SystemServer的程式號

   app.maxAdj= ProcessList.SYSTEM_ADJ;//設定最大OOM_Adj,系統程式預設值為-16

   //另外,app的processName被設定成“system”

這時,一個針對SystemServer的ProcessRecord物件就建立完成了。此後AMS將把它併入自己的勢力範圍內。

AMS中有兩個成員變數用於儲存ProcessRecord,一個是mProcessNames,另一個是mPidsSelfLocked,如圖6-6所示為這兩個成員變數的資料結構示意圖。


圖6-6  mPidsSelfLocked和mProcessNames資料結構示意圖

3.  AMS的setSystemProcess總結

現在來總結回顧setSystemProcess的工作:

·  註冊AMS、meminfo、gfxinfo等服務到ServiceManager中。

·  根據PKMS返回的ApplicationInfo初始化Android執行環境,並建立一個代表SystemServer程式的ProcessRecord,從此,SystemServer程式也併入AMS的管理範圍內。

 

6.2.3  AMS的installSystemProviders函式分析

還記得Settings資料庫嗎?SystemServer中很多Service都需要向它查詢配置資訊。為此,Android提供了一個SettingsProvider來幫助開發者。該Provider在SettingsProvider.apk中,installSystemProviders就會載入該APK並把SettingsProvider放到SystemServer程式中來執行。

此時的SystemServer已經載入了framework-res.apk,現在又要載入另外一個APK檔案,這就是多個APK執行在同一程式的典型案例。另外,通過installSystemProviders函式還能見識ContentProvider的安裝過程,下面就來分析它。

提示讀者在定製自己的Android系統時,萬不可去掉/system/app/SettingsProvider.apk,否則系統將無法正常啟動。

[-->ActivityManagerService.java::installSystemProviders]

public static final void installSystemProviders(){

 List<ProviderInfo> providers;

 synchronized (mSelf) {

    /*

    從mProcessNames找到程式名為“system”且uid為SYSTEM_UID的ProcessRecord,

    返回值就是前面在installSystemApplication中建立的那個ProcessRecord,它代表

    SystemServer程式

    */

   ProcessRecord app = mSelf.mProcessNames.get("system",Process.SYSTEM_UID);

 

    //①關鍵呼叫,見下文分析

    providers= mSelf.generateApplicationProvidersLocked(app);

    if(providers != null) {

       ......//將非系統APK(即未設ApplicationInfo.FLAG_SYSTEM標誌)提供的Provider

      //從providers列表中去掉

        }

     if(providers != null) {//②為SystemServer程式安裝Provider

         mSystemThread.installSystemProviders(providers);

     }

    //監視Settings資料庫中Secure表的變化,目前只關注long_press_timeout配置的變化

    mSelf.mCoreSettingsObserver = new CoreSettingsObserver(mSelf);

 

     //UsageStatsService的工作,以後再討論

    mSelf.mUsageStatsService.monitorPackages();

 }

在程式碼中列出了兩個關鍵呼叫,分別是:

·  呼叫generateApplicationProvidersLocked函式,該函式返回一個ProviderInfo List。

·  呼叫ActivityThread的installSystemProviders函式。ActivityThread可以看做是程式的Android執行環境,那麼installSystemProviders表示為該程式安裝ContentProvider。

注意此處不再區分系統程式還是應用程式。由於只和ActivityThread互動,因此它執行在什麼程式無關緊要。

下面來看第一個關鍵點generateApplicationProvidersLocked函式。

1.  AMS的 generateApplicationProvidersLocked函式分析

[-->ActivityManagerService.java::generateApplicationProvidersLocked]

private final List<ProviderInfo> generateApplicationProvidersLocked(

                              ProcessRecordapp) {

  List<ProviderInfo> providers = null;

  try {

         //①向PKMS查詢滿足要求的ProviderInfo,最重要的查詢條件包括:程式名和程式uid

         providers = AppGlobals.getPackageManager().

            queryContentProviders(app.processName, app.info.uid,

             STOCK_PM_FLAGS | PackageManager.GET_URI_PERMISSION_PATTERNS);

        } ......

   if(providers != null) {

      finalint N = providers.size();

      for(int i=0; i<N; i++) {

         //②AMS對ContentProvider的管理,見下文解釋

        ProviderInfo cpi =   (ProviderInfo)providers.get(i);

        ComponentName comp = new ComponentName(cpi.packageName, cpi.name);

        ContentProviderRecord cpr = mProvidersByClass.get(comp);

         if(cpr == null) {

             cpr = new ContentProviderRecord(cpi, app.info, comp);

              //ContentProvider在AMS中用ContentProviderRecord來表示

              mProvidersByClass.put(comp, cpr);//儲存到AMS的mProvidersByClass中

          }

          //將資訊也儲存到ProcessRecord中

         app.pubProviders.put(cpi.name, cpr);

          //儲存PackageName到ProcessRecord中

         app.addPackage(cpi.applicationInfo.packageName);

         //對該APK進行dex優化

         ensurePackageDexOpt(cpi.applicationInfo.packageName);

         }

     }

    returnproviders;

 }

由以上程式碼可知:generateApplicationProvidersLocked先從PKMS那裡查詢滿足條件的ProviderInfo資訊,而後將它們分別儲存到AMS和ProcessRecord中對應的資料結構中。

先看查詢函式queryContentProviders。

(1) PMS中 queryContentProviders函式分析

[-->PackageManagerService.java::queryContentProviders]

public List<ProviderInfo>queryContentProviders(String processName,

    int uid,int flags) {

  ArrayList<ProviderInfo> finalList = null;

  synchronized (mPackages) {

     //還記得mProvidersByComponent的作用嗎?它以ComponentName為key,儲存了

     //PKMS掃描APK得到的PackageParser.Provider資訊。讀者可參考圖4-8

     finalIterator<PackageParser.Provider> i =

                      mProvidersByComponent.values().iterator();

       while(i.hasNext()) {

          final PackageParser.Provider p = i.next();

           //下面的if語句將從這些Provider中搜尋本例設定的processName為“system”,

          //uid為SYSTEM_UID,flags為FLAG_SYSTEM的Provider

          if (p.info.authority != null

               && (processName == null

                   ||(p.info.processName.equals(processName)

                        &&p.info.applicationInfo.uid == uid))

               && mSettings.isEnabledLPr(p.info, flags)

                && (!mSafeMode || ( p.info.applicationInfo.flags

                        &ApplicationInfo.FLAG_SYSTEM) != 0)) {

                    if (finalList == null) {

                        finalList = newArrayList<ProviderInfo>(3);

                   }

                 //由PackageParser.Provider得到ProviderInfo,並新增到finalList中

                //關於Provider類及ProviderInfo類,可參考圖4-5

                  finalList.add(PackageParser.generateProviderInfo(p,flags));

               }

           }

        }

 

   if(finalList != null)

      //最終結果按provider的initOrder排序,該值用於表示初始化ContentProvider的順序

      Collections.sort(finalList, mProviderInitOrderSorter);

   returnfinalList;//返回最終結果

 }

queryContentProviders函式很簡單,就是從PKMS那裡查詢滿足條件的Provider,然後生成AMS使用的ProviderInfo資訊。為何偏偏能找到SettingsProvider呢?來看它的AndroidManifest.xml檔案,如圖6-7所示。


圖6-7  SettingsProvider的AndroidManifest.xml檔案示意

由圖6-7可知,SettingsProvider設定了其uid為“android.uid.system”,同時在application中設定了process名為“system”。而在framework-res.apk中也做了相同的設定。所以,現在可以確認SettingsProvider將和framework-res.apk執行在同一個程式,即SystemServer中。

提示從執行效率角度來說,這樣做也是合情合理的。因為SystemServer的很多Service都依賴Settings資料庫,把它們放在同一個程式中,可以降低由於程式間通訊帶來的效率損失。

(2)  關於ContentProvider的介紹

前面介紹的從PKMS那裡查詢到的ProviderInfo還屬於公有財產,現在我們要將它與AMS及ProcessRecord聯絡起來。

·  AMS儲存ProviderInfo的原因是它要管理ContentProvider。

·  ProcessRecord儲存ProviderInfo的原因是ContentProvider最終要落實到一個程式中。其實也是為了方便AMS管理,例如該程式一旦退出,AMS需要把其中的ContentProvider資訊從系統中去除。

AMS及ProcessRecord均使用了一個新的資料結構ContentProviderRecord來管理ContentProvider資訊。圖6-8展示了ContentProviderRecord相應的資料結構。


圖6-8  ContentProvicerRecord及相應的“管理團隊”

由圖6-8可知:

·  ContentProviderRecord從ContentProviderHolder派生,內部儲存了ProviderInfo、該Provider所駐留的程式ProcessRecord,以及使用該ContentProvider的客戶端程式ProcessRecord(即clients成員變數)。

·  AMS的mProviderByClass成員變數及ProcessRecord的pubProviders成員變數均以ComponentName為Key來儲存對應的ContentProviderRecord物件。

至此,Provider資訊已經儲存到AMS及ProcessRecord中了。那麼,下一步的工作是什麼呢?

2.  ActivityThread 的installSystemProviders函式分析

在AMS和ProcessRecord中都儲存了Provider資訊,但這些僅僅都是一些資訊,並不是ContentProvider,因此下面要建立一個ContentProvider例項(即SettingsProvider物件)。該工作由ActivityThread的installSystemProviders來完成,程式碼如下:

[-->ActivityThread.java::installSystemProviders]

public final void installSystemProviders(List<ProviderInfo>providers) {

  if(providers != null)

    //呼叫installContentProviders,第一個引數真實型別是Application

   installContentProviders(mInitialApplication, providers);

}

installContentProviders這個函式是所有ContentProvider產生的必經之路,其程式碼如下:

[-->ActivityThread.java::installContentProviders]

private void installContentProviders(

           Context context, List<ProviderInfo> providers) {

 

   finalArrayList<IActivityManager.ContentProviderHolder> results =

                   new ArrayList<IActivityManager.ContentProviderHolder>();

 

  Iterator<ProviderInfo> i = providers.iterator();

   while(i.hasNext()) {

    ProviderInfo cpi = i.next();

     //①呼叫installProvider函式,得到一個IContentProvider物件

    IContentProvider cp = installProvider(context, null, cpi, false);

     if (cp!= null) {

       IActivityManager.ContentProviderHolder cph =

                    newIActivityManager.ContentProviderHolder(cpi);

       cph.provider = cp;

       //將返回的cp儲存到results陣列中

       results.add(cph);

        synchronized(mProviderMap){

         //mProviderRefCountMap,;型別為HashMap<IBinder,ProviderRefCount>,

         //主要通過ProviderRefCount對ContentProvider進行引用計數控制,一旦引用計數

         //降為零,表示系統中沒有地方使用該ContentProvider,要考慮從系統中登出它

         mProviderRefCountMap.put(cp.asBinder(), new ProviderRefCount(10000));

         }

     }

   }

    try {

       //②呼叫AMS的publishContentProviders註冊這些ContentProvider,第一個引數

       //為ApplicationThread

       ActivityManagerNative.getDefault().publishContentProviders(

                      getApplicationThread(), results);

        } ......

 

    }

installContentProviders實際上是標準的ContentProvider安裝時呼叫的程式。安裝ContentProvider包括兩方面的工作:

·  先在ActivityThread中通過installProvider得到一個ContentProvider例項。

·  向AMS釋出這個ContentProvider例項。如此這般,一個APK中宣告的ContentProvider才能登上歷史舞臺,發揮其該有的作用。

提示 上述工作其實和Binder Service類似,一個Binder Service也需要先建立,然後註冊到ServiceManager中。

馬上來看ActivityThread的installProvider函式。

(1) ActivityThread的installProvider函式分析

[-->ActivityThread.java::installProvider]

private IContentProvider installProvider(Contextcontext,

           IContentProvider provider, ProviderInfoinfo, boolean noisy) {

//注意本例所傳的引數:context為mInitialApplication,provider為null,info不為null,

// noisy為false

 

  ContentProvider localProvider = null;

   if(provider == null) {

      Context c = null;

      ApplicationInfo ai = info.applicationInfo;

       /*

        下面這個if判斷的作用就是為該ContentProvider找到對應的Application。

        在AndroidManifest.xml中,ContentProvider是Application的子標籤,所以

       ContentProvider和Application有一種對應關係。在本例中,傳入的context(

        其實是mInitialApplication)代表的是framework-res.apk,而Provider代表的

        是SettingsProvider。而SettingsProvider.apk所對應的Application還未建立,

        所以下面的判斷語句最終會進入最後的else分支

      */

       if(context.getPackageName().equals(ai.packageName)) {

          c= context;

       }else if (mInitialApplication != null &&

                 mInitialApplication.getPackageName().equals(ai.packageName)){

          c = mInitialApplication;

       } else {

         try{

             //ai.packageName應該是SettingsProvider.apk的Package,

             //名為“com.android.providers.settings”

             //下面將建立一個Context,指向該APK

              c = context.createPackageContext(ai.packageName,

                           Context.CONTEXT_INCLUDE_CODE);

           }

       }//if(context.getPackageName().equals(ai.packageName))判斷結束

 

       if (c == null)  return null;

       try {

         /*

         為什麼一定要找到對應的Context呢?除了ContentProvider和Application的

         對應關係外,還有一個決定性原因:即只有對應的Context才能載入對應APK的Java位元組碼,

         從而可通過反射機制生成ContentProvider例項

        */

         finaljava.lang.ClassLoader cl = c.getClassLoader();

         //通過Java反射機制得到真正的ContentProvider,

         //此處將得到一個SettingsProvider物件

         localProvider=(ContentProvider)cl.loadClass(info.name).newInstance();

         //從ContentProvider中取出其mTransport成員(見下文分析)

         provider =localProvider.getIContentProvider();

         if (provider == null) return null;

         //初始化該ContentProvider,內部會呼叫其onCreate函式

        localProvider.attachInfo(c, info);

      }......

   }//if(provider == null)判斷結束

  synchronized (mProviderMap) {

      /*

        ContentProvider必須指明一個和多個authority,在第4章曾經提到過,

         在URL中host:port的組合表示一個authority。這個單詞不太好理解,可簡單

         認為它用於指定ContentProvider的位置(類似網站的域名)

     */

      String names[] =PATTERN_SEMICOLON.split(info.authority);

      for (int i=0; i<names.length; i++) {

             ProviderClientRecord pr = newProviderClientRecord(names[i],

                                           provider, localProvider);

            try {

                //下面這句對linkToDeath的呼叫頗讓人費解,見下文分析

                  provider.asBinder().linkToDeath(pr, 0);

                  mProviderMap.put(names[i], pr);

           }......

      }//for迴圈結束

     if(localProvider != null) {

         // mLocalProviders用於儲存由本程式建立的ContentProvider資訊

          mLocalProviders.put(provider.asBinder(),

                   new ProviderClientRecord(null, provider, localProvider));

              }

     }//synchronized 結束

  return provider;

}

以上程式碼不算複雜,但是涉及一些資料結構和一句令人費解的對inkToDeath函式的呼叫。先來說說那句令人費解的呼叫。

在本例中,provider變數並非通過函式引數傳入,而是在本程式內部建立的。provider在本例中是Bn端(後面分析ContentProvider的getIContentProvider時即可知道),Bn端程式為Bn端設定死亡通知本身就比較奇怪。如果Bn端程式死亡,它設定的死亡通知也無法傳送給自己。幸好原始碼中有句註釋:“Cache the pointer for the remote provider”。意思是如果provider引數是通過installProvider傳遞過來的(即該Provider代表遠端程式的ContentProvider,此時它應為Bp端),那麼這種處理是合適的。不管怎樣,這僅僅是為了儲存pointer,所以也無關巨集旨。

至於程式碼中涉及的資料結構,我們整理為如圖6-9所示。


圖6-9  ActivityThread中ContentProvider涉及的資料結構

由圖6-9可知:

·  ContentProvider類本身只是一個容器,而跨程式呼叫的支援是通過內部類Transport實現的。Transport從ContentProviderNative派生,而ContentProvider的成員變數mTransport指向該Transport物件。ContentProvider的getIContentProvider函式即返回mTransport成員變數。

·  ContentProviderNative從Binder派生,並實現了IContentProvider介面。其內部類ContentProviderProxy是供客戶端使用的。

·  ProviderClientRecord是ActivityThread提供的用於儲存ContentProvider資訊的一個資料結構。它的mLocalProvider用於儲存ContentProvider物件,mProvider用於儲存IContentProvider物件。另外一個成員mName用於儲存該ContentProvider的一個authority。注意,ContentProvider可以定義多個authority,就好像一個網站有多個域名一樣。

至此,本例中的SettingProvider已經建立完畢,接下來的工作就是把它推向歷史舞臺——即釋出該Provider。

(2) ASM的 publishContentProviders分析

publicContentProviders函式用於向AMS註冊ContentProviders,其程式碼如下:

[-->ActivityManagerService.java::publishContentProviders]

 publicfinal void publishContentProviders(IApplicationThread caller,

                           List<ContentProviderHolder>providers) {

  ......

  synchronized(this){

    //找到呼叫者所在的ProcessRecord物件

   finalProcessRecord r = getRecordForAppLocked(caller);

   ......

   finallong origId = Binder.clearCallingIdentity();

   final intN = providers.size();

   for (inti=0; i<N; i++) {

      ContentProviderHolder src = providers.get(i);

       ......

       //①注意:先從該ProcessRecord中找對應的ContentProviderRecord

     ContentProviderRecord dst = r.pubProviders.get(src.info.name);

      if(dst != null) {

          ComponentName comp = newComponentName(dst.info.packageName,

                                                         dst.info.name);

          //以ComponentName為key,儲存到mProvidersByClass中

         mProvidersByClass.put(comp, dst);

         String names[] = dst.info.authority.split(";");

         for (int j = 0; j < names.length; j++)

               mProvidersByName.put(names[j], dst);//以authority為key,儲存

              //mLaunchingProviders用於儲存處於啟動狀態的Provider

               int NL = mLaunchingProviders.size();

               int j;

               for (j=0; j<NL; j++) {

                 if (mLaunchingProviders.get(j) ==dst) {

                      mLaunchingProviders.remove(j);

                      j--;

                      NL--;

                  }//

              }//for (j=0; j<NL; j++)結束

              synchronized (dst) {

                  dst.provider = src.provider;

                  dst.proc = r;

                  dst.notifyAll();

              }//synchronized結束

             updateOomAdjLocked(r);//每釋出一個Provider,需要調整對應程式的oom_adj

            }//for(int j = 0; j < names.length; j++)結束

        }//for(int i=0; i<N; i++)結束

        Binder.restoreCallingIdentity(origId);

    }// synchronized(this)結束

 }

這裡應解釋一下publishContentProviders的工作流程:

·  先根據呼叫者的pid找到對應的ProcessRecord物件。

·  該ProcessRecord的pubProviders中儲存了ContentProviderRecord資訊。該資訊由前面介紹的AMS的generateApplicationProvidersLocked函式根據Package本身的資訊生成。此處將判斷要釋出的ContentProvider是否由該Package宣告。

·  如果判斷返回成功,則將該ContentProvider及其對應的authority加到mProvidersByName中。注意,AMS中還有一個mProvidersByClass變數,該變數以ContentProvider的ComponentName為key,即系統提供多種方式找到某一個ContentProvider,一種是通過 authority,另一種方式就是指明ComponentName。

·  mLaunchingProviders和最後的notifyAll函式用於通知那些等待ContentProvider所在程式啟動的客戶端程式。例如,程式A要查詢一個資料庫,需要通過程式B中的某個ContentProvider 來實施。如果B還未啟動,那麼AMS就需要先啟動B。在這段時間內,A需要等待B啟動並註冊對應的ContentProvider。B一旦完成註冊,就需要告知A退出等待以繼續後續的查詢工作。

現在,一個SettingsProvider就算正式在系統中掛牌並註冊了,此後,和Settings資料庫相關的操作均由它來打理。

3.  ASM的installSystemProviders總結

AMS的installSystemProviders函式其實就是用於啟動SettingsProvider,其中比較複雜的是ContentProvider相關的資料結構,讀者可參考圖6-9。

6.2.4  ASM的systemReady分析

作為核心服務,AMS的systemReady會做什麼呢?由於該函式內容較多,我們將它分為三段。首先看第一段的工作。

1.  systemReady第一階段的工作

[-->ActivityManagerService.java::systemReady]

public void systemReady(final RunnablegoingCallback) {

     synchronized(this){

       ......

       if(!mDidUpdate) {//判斷是否為升級

           if(mWaitingUpdate)   return; //升級未完成,直接返回

           //準備PRE_BOOT_COMPLETED廣播

            Intent intent = newIntent(Intent.ACTION_PRE_BOOT_COMPLETED);

           List<ResolveInfo> ris = null;

           //向PKMS查詢該廣播的接收者

            ris= AppGlobals.getPackageManager().queryIntentReceivers(

                                intent, null,0);

            ......//從返回的結果中刪除那些非系統APK的廣播接收者

            intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE);

            //讀取/data/system/called_pre_boots.dat檔案,這裡儲存了上次啟動時候已經

             //接收並處理PRE_BOOT_COMPLETED廣播的元件。鑑於該廣播的特殊性,系統希望

           //該廣播僅被這些接收者處理一次

             ArrayList<ComponentName>lastDoneReceivers =

                                          readLastDonePreBootReceivers();

              final ArrayList<ComponentName> doneReceivers=

                                         newArrayList<ComponentName>();

             ......//從PKMS返回的接收者中刪除那些已經處理過該廣播的物件

 

            for (int i=0; i<ris.size(); i++) {

                ActivityInfo ai = ris.get(i).activityInfo;

                 ComponentName comp = newComponentName(ai.packageName, ai.name);

                doneReceivers.add(comp);

                intent.setComponent(comp);

                IIntentReceiver finisher = null;

                if (i == ris.size()-1) {

                    //為最後一個廣播接收者註冊一個回撥通知,當該接收者處理完廣播後,將呼叫該

                   //回撥

                   finisher = new IIntentReceiver.Stub() {

                       public voidperformReceive(Intent intent, int resultCode,

                                        Stringdata, Bundle extras, boolean ordered,

                                        booleansticky) {

                                 mHandler.post(newRunnable() {

                                   public void run(){

                                   synchronized(ActivityManagerService.this) {

                                       mDidUpdate = true;

                                    }

                                 //儲存那些處理過該廣播的接收者資訊

                                 writeLastDonePreBootReceivers(doneReceivers);

                                  showBootMessage(mContext.getText(

                                        R.string.android_upgrading_complete),

                                        false);

                                   systemReady(goingCallback);

                                }//run結束

                            });// new Runnable結束

                        }//performReceive結束

                  };//finisher建立結束

              }// if (i == ris.size()-1)判斷結束

           //傳送廣播給指定的接收者

             broadcastIntentLocked(null, null, intent,null, finisher,

              0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID);

            if (finisher != null)   mWaitingUpdate = true;

         }

       if(mWaitingUpdate)    return;

       mDidUpdate= true;

  }

           

   mSystemReady = true;

    if(!mStartRunning)  return;

   }//synchronized(this)結束

由以上程式碼可知,systemReady第一階段的工作並不輕鬆,其主要職責是傳送並處理與PRE_BOOT_COMPLETED廣播相關的事情。目前程式碼中還沒有接收該廣播的地方,不過從程式碼中的註釋中可猜測到,該廣播接收者的工作似乎和系統升級有關。

建議如有哪位讀者瞭解與此相關的知識,不妨和大家分享。

下面來介紹systemReady第二階段的工作。

2.  systemReady第二階段的工作

[-->ActivityManagerService.java::systemReady]

   ArrayList<ProcessRecord>procsToKill = null;

  synchronized(mPidsSelfLocked) {

     for(int i=mPidsSelfLocked.size()-1; i>=0; i--) {

         ProcessRecord proc = mPidsSelfLocked.valueAt(i);

          //從mPidsSelfLocked中找到那些先於AMS啟動的程式,哪些程式有如此能耐,

         //在AMS還未啟動完畢就啟動完了呢?對,那些宣告瞭persistent為true的程式有可能

          if(!isAllowedWhileBooting(proc.info)){

            if (procsToKill == null)

                 procsToKill = new ArrayList<ProcessRecord>();

             procsToKill.add(proc);

           }

       }//for結束

   }// synchronized結束

       

   synchronized(this){

       if(procsToKill != null) {

          for (int i=procsToKill.size()-1; i>=0; i--) {

             ProcessRecord proc = procsToKill.get(i);

             //把這些程式關閉,removeProcessLocked函式比較複雜,以後再分析

              removeProcessLocked(proc, true, false);

           }

        }

      //至此,系統已經準備完畢

      mProcessesReady = true;

   }

  synchronized(this) {

     if(mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) {

     }//和工廠測試有關,不對此進行討論

   }

   //查詢Settings資料,獲取一些配置引數

   retrieveSettings();

systemReady第二階段的工作包括:

·  殺死那些竟然在AMS還未啟動完畢就先啟動的應用程式。注意,這些應用程式一定是APK所在的Java程式,因為只有應用程式才會向AMS註冊,而一般Native(例如mediaserver)程式是不會向AMS註冊的。

·  從Settings資料庫中獲取配置資訊,目前只取4個配置引數,分別是:"debug_app"(設定需要debug的app的名稱)、"wait_for_debugger"(如果為1,則等待偵錯程式,否則正常啟動debug_app)、"always_finish_activities"(當一個activity不再有地方使用時,是否立即對它執行destroy)、"font_scale"(用於控制字型放大倍數,這是Android 4.0新增的功能)。以上配置項由Settings資料庫的System表提供。

 

3.  systemReady第三階段的工作

[-->ActivityManagerService.java::systemReady]

//呼叫systemReady傳入的引數,它是一個Runnable物件,下節將分析此函式

if (goingCallback != null) goingCallback.run();

       

 synchronized (this) {

     if(mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {

        try{

            //從PKMS中查詢那些persistent為1的ApplicationInfo

            List apps = AppGlobals.getPackageManager().

                           getPersistentApplications(STOCK_PM_FLAGS);

            if (apps != null) {

                int N = apps.size();

                 int i;

                 for (i=0; i<N; i++) {

                   ApplicationInfo info = (ApplicationInfo)apps.get(i);

                   //由於framework-res.apk已經由系統啟動,所以這裡需要把它去除

                   //framework-res.apk的packageName為"android"

                   if (info != null && !info.packageName.equals("android"))

                        addAppLocked(info);//啟動該Application所在的程式

                      }

                   }

               }......

      }

 

   mBooting= true; //設定mBooting變數為true,其作用後面會介紹

   try {

        if(AppGlobals.getPackageManager().hasSystemUidErrors()) {

            ......//處理那些Uid有錯誤的Application

      }......

       //啟動全系統第一個Activity,即Home

      mMainStack.resumeTopActivityLocked(null);

    }// synchronized結束

}

systemReady第三階段的工作有3項:

·  呼叫systemReady設定的回撥物件goingCallback的run函式。

·  啟動那些宣告瞭persistent的APK。

·  啟動桌面。

先看回撥物件goingCallback的run函式的工作。

(1) goingCallback的run函式分析

[-->SystemServer.java::ServerThread.run]

ActivityManagerService.self().systemReady(newRunnable() {

    publicvoid run() {

   startSystemUi(contextF);//啟動SystemUi

   //呼叫其他服務的systemReady函式

    if(batteryF != null) batteryF.systemReady();

    if(networkManagementF != null) networkManagementF.systemReady();

    ......

    Watchdog.getInstance().start();//啟動Watchdog

    ......//呼叫其他服務的systemReady函式

 }

run函式比較簡單,執行的工作如下:

·  執行startSystemUi,在該函式內部啟動SystemUIService,該Service和狀態列有關。

·  呼叫一些服務的systemReady函式。

·  啟動Watchdog。

startSystemUi的程式碼如下:

[-->SystemServer.java::startSystemUi]

static final void startSystemUi(Context context) {

     Intentintent = new Intent();

     intent.setComponent(new ComponentName("com.android.systemui",

                 "com.android.systemui.SystemUIService"));

       context.startService(intent);

 }

SystemUIService由SystemUi.apk提供,它實現了系統的狀態列。

注意在精簡ROM時,也不能刪除SystemUi.apk。

(2)  啟動Home介面

如前所述,resumeTopActivityLocked將啟動Home介面,此函式非常重要也比較複雜,故以後再詳細分析。我們提取了resumeTopActivityLocked啟動Home介面時的相關程式碼,如下所示:

[-->ActivityStack.java::resumeTopActivityLocked]

final booleanresumeTopActivityLocked(ActivityRecord prev) {

   //找到下一個要啟動的Activity

  ActivityRecord next = topRunningActivityLocked(null);

   finalboolean userLeaving = mUserLeaving;

  mUserLeaving = false;

   if (next== null) {

       //如果下一個要啟動的ActivityRecord為空,則啟動Home

       if(mMainStack) {//全系統就一個ActivityStack,所以mMainStack永遠為true

          //mService指向AMS

          return mService.startHomeActivityLocked();//mService指向AMS

        }

    }

   ......//以後再詳細分析

}

下面來看AMS的startHomeActivityLocked函式,程式碼如下:

[-->ActivityManagerService.java::startHomeActivityLocked]

boolean startHomeActivityLocked() {

   Intentintent = new Intent( mTopAction,

               mTopData != null ? Uri.parse(mTopData) :null);

   intent.setComponent(mTopComponent);

   if(mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL)

       intent.addCategory(Intent.CATEGORY_HOME);//新增Category為HOME類別

    

    //向PKMS查詢滿足條件的ActivityInfo

   ActivityInfo aInfo =

           intent.resolveActivityInfo(mContext.getPackageManager(),

                                  STOCK_PM_FLAGS);

    if(aInfo != null) {

       intent.setComponent(new ComponentName(

                          aInfo.applicationInfo.packageName,aInfo.name));

       ProcessRecordapp = getProcessRecordLocked(aInfo.processName,

                                             aInfo.applicationInfo.uid);

       //在正常情況下,app應該為null,因為剛開機,Home程式肯定還沒啟動

       if(app == null || app.instrumentationClass == null) {

            intent.setFlags(intent.getFlags()|

                                 Intent.FLAG_ACTIVITY_NEW_TASK);

            //啟動Home

            mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo,

                                  null, null, 0, 0, 0, false, false,null);

          }

      }//if(aInfo != null)判斷結束

  return true;

}

至此,AMS攜諸位Service都啟動完畢,Home也靚麗登場,整個系統就準備完畢,只等待使用者的檢驗了。不過在分析邏輯上還有一點沒涉及,那會是什麼呢?

(3) 傳送ACTION_BOOT_COMPLETED廣播

由前面的程式碼可知,AMS傳送了ACTION_PRE_BOOT_COMPLETED廣播,可系統中沒有地方處理它。在前面的章節中,還碰到一個ACTION_BOOT_COMPLETED廣播,該廣播廣受歡迎,卻不知道它是在哪裡傳送的。

當Home Activity啟動後,ActivityStack的activityIdleInternal函式將被呼叫,其中有一句程式碼頗值得注意:

[-->ActivityStack.java::activityIdleInternal]

final ActivityRecord activityIdleInternal(IBindertoken, boolean fromTimeout,

           Configuration config) {

   booleanbooting = false;

   ......

   if(mMainStack) {

      booting = mService.mBooting; //在systemReady的第三階段工作中設定該值為true

      mService.mBooting = false;

   }

   ......

   if(booting)   mService.finishBooting();//呼叫AMS的finishBooting函式

}

[-->ActivityManagerService.java::finishBooting]

final void finishBooting() {

   IntentFilter pkgFilter = new IntentFilter();

   pkgFilter.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART);

   pkgFilter.addDataScheme("package");

   mContext.registerReceiver(new BroadcastReceiver() {

      public void onReceive(Context context, Intent intent) {

       ......//處理Package重啟的廣播

      }

    }, pkgFilter);

       

  synchronized (this) {

      finalint NP = mProcessesOnHold.size();

      if (NP> 0) {

          ArrayList<ProcessRecord> procs =

                   new ArrayList<ProcessRecord>(mProcessesOnHold);

           for(int ip=0; ip<NP; ip++)//啟動那些等待啟動的程式

             startProcessLocked(procs.get(ip), "on-hold", null);

           }

       if(mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {

          //每15鍾檢查系統各應用程式使用電量的情況,如果某程式使用WakeLock時間

          //過長,AMS將關閉該程式

          Message nmsg =

                      mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);

          mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY);

           //設定系統屬性sys.boot_completed的值為1

           SystemProperties.set("sys.boot_completed","1");

          //傳送ACTION_BOOT_COMPLETED廣播

          broadcastIntentLocked(null, null,

                        newIntent(Intent.ACTION_BOOT_COMPLETED, null),

                        null, null, 0, null,null,

                       android.Manifest.permission.RECEIVE_BOOT_COMPLETED,

                        false, false, MY_PID,Process.SYSTEM_UID);

           }

        }

 }

原來,在Home啟動成功後,AMS才傳送ACTION_BOOT_COMPLETED廣播。

4.  ASM的 systemReady總結

systemReady函式完成了系統就緒的必要工作,然後它將啟動Home Activity。至此,Android系統就全部啟動了。

6.2.5  初識ActivityManagerService總結

本節所分析的4個關鍵函式均較複雜,與之相關的知識點總結如下:

·  AMS的main函式:建立AMS例項,其中最重要的工作是建立Android執行環境,得到一個ActivityThread和一個Context物件。

·  AMS的setSystemProcess函式:該函式註冊AMS和meminfo等服務到ServiceManager中。另外,它為SystemServer建立了一個ProcessRecord物件。由於AMS是Java世界的程式管理及排程中心,要做到對Java程式一視同仁,儘管SystemServer貴為系統程式,此時也不得不將其併入AMS的管理範圍內。

·  AMS的installSystemProviders:為SystemServer載入SettingsProvider。

·  AMS的systemReady:做系統啟動完畢前最後一些掃尾工作。該函式呼叫完畢後,HomeActivity將呈現在使用者面前。

對AMS 呼叫軌跡分析是我們破解AMS的第一條線,希望讀者反覆閱讀,以真正理解其中涉及的知識點,尤其是和Android執行環境及Context相關的知識。

6.3  startActivity分析

本節將重點分析Activity的啟動過程,它是五條線中最難分析的一條,只要用心相信讀者能啃動這塊“硬骨頭”。

6.3.1  從am說起

am和pm(見4.4.2節)一樣,也是一個指令碼,它用來和AMS互動,如啟動Activity、啟動Service、傳送廣播等。其核心檔案在Am.java中,程式碼如下:

[-->Am.java::main]

public static void main(String[] args) {

   try {

        (newAm()).run(args);//構造一個Am物件,並呼叫run函式

   }......

 }

am的用法很多,讀者可通過adb shell登入手機,然後執行am,這樣就能獲取它的用法。

利用am啟動一個activity的方法如下:

am start [-D] [-W] [-P <FILE>][--start-profiler <FILE>] [-S] <INTENT>

其中:

·  方括號中的引數為可選項,尖括號中的引數為必選項。

·  <INTENT>引數有很多,主要是用於設定Intent的各項引數。

假設已知某個Activity的ComponentName(package名和Activity的Class名),啟動這個Activity的相應命令如下:

am start -W -n com.dfp.test/.TestActivity

其中,-W選項表示am將會等目標Activity啟動後才返回,-n表示後面的引數用於設定Intent的Component。就本例而言,com.dfp.test為Package名,.TestActivity為該Package下對應的Activity類名,所以將要啟動的Activity的全路徑名為com.dfp.test.TestActivity。

現在就以上面的命令為例來分析Am的run函式,程式碼如下:

[-->Am.java::run]

 privatevoid run(String[] args) throws Exception {

  mAm =ActivityManagerNative.getDefault();

  mArgs =args;

  String op= args[0];

  mNextArg =1;

  if (op.equals("start"))  runStart();//用於啟動Activity

  else if ......//處理其他引數

}

runStart函式用於處理Activity啟動請求,其程式碼如下:

[-->Am.java::runStart]

 privatevoid runStart() throws Exception {

    Intentintent = makeIntent();

 

    StringmimeType = intent.getType();

    //獲取mimeType,

    if(mimeType == null && intent.getData() != null

         && "content".equals(intent.getData().getScheme())) {

        mimeType = mAm.getProviderMimeType(intent.getData());

    }

 

    if(mStopOption) {

     ......//處理-S選項,即先停止對應的Activity,再啟動它

    }

 

    //FLAG_ACTIVITY_NEW_TASK這個標籤很重要

   intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

 

   ParcelFileDescriptor fd = null;

 

    if(mProfileFile != null) {

         try{......//處理-P選項,用於效能統計

            fd = ParcelFileDescriptor.open(......)

          }......

    }

 

   IActivityManager.WaitResult result = null;

    int res;

    if(mWaitOption) {//mWaitOption控制是否等待啟動結果,如果有-W選項,則該值為true

               //呼叫AMS的startActivityAndWait函式

        result = mAm.startActivityAndWait(null,intent, mimeType,

                        null, 0, null, null, 0,false, mDebugOption,

                        mProfileFile, fd,mProfileAutoStop);

         res= result.result;

     } ......

     ......//列印結果

 }

am最終將呼叫AMS的startActivityAndWait函式來處理這次啟動請求。下面將深入到AMS內部去繼續這次旅程。

提示為什麼選擇從am來分析Activity的啟動呢?如果選擇從一個Activity來分析如何啟動另一個Activity,則將給人一種雞生蛋、蛋孵雞的感覺,故此處選擇從am入手。除此之外,從am來分析Activity的啟動也是Activity啟動分析中相對簡單的一條路線。

6.3.2  AMS的startActivityAndWait函式分析

startActivityAndWait函式有很多引數,先來認識一下它們。

[-->ActiivtyManagerService.java::startActivityAndWait原型]

 publicfinal WaitResult startActivityAndWait(

   /*

    在絕大多數情況下,一個Acitivity的啟動是由一個應用程式發起的,IApplicationThread是

    應用程式和AMS互動的通道,也可算是呼叫程式的標示,在本例中,AM並非一個應用程式,所以

    傳遞的caller為null

   */

  IApplicationThread caller,

   //Intent及resolvedType,在本例中,resolvedType為null

   Intentintent, String resolvedType,

   //下面兩個引數和授權有關,讀者可參考第3章對CopyboardService分析中介紹的授權知識

   Uri[] grantedUriPermissions,//在本例中為null

   intgrantedMode,//在本例中為0

   IBinder resultTo,//在本例中為null,用於接收startActivityForResult的結果

   StringresultWho,//在本例中為null

   //在本例中為0,該值的具體意義由呼叫者解釋。如果該值大於等於0,則AMS內部儲存該值,

   //並通過onActivityResult返回給呼叫者

   int requestCode,

   boolean onlyIfNeeded,//本例為false

   boolean debug,//是否除錯目標程式

   //下面3個引數和效能統計有關

   StringprofileFile,

   ParcelFileDescriptor profileFd, booleanautoStopProfiler)

關於以上程式碼中一些引數的具體作用,以後碰到時會再作分析。建議讀者先閱讀SDK文件中關於Activity類定義的幾個函式,如startActivity、startActivityForResult及onActivityResult等。

startActivityAndWait的程式碼如下:

[-->ActivityManagerService.java::startActivityAndWait]

 publicfinal WaitResult startActivityAndWait(IApplicationThread caller,

      Intent intent, String resolvedType, Uri[]grantedUriPermissions,

      int grantedMode, IBinder resultTo,StringresultWho, int requestCode,

      booleanonlyIfNeeded, boolean debug,String profileFile,

      ParcelFileDescriptor profileFd, booleanautoStopProfiler) {

     //建立WaitResult物件用於儲存處理結果

    WaitResult res = new WaitResult();

    //mMainStack為ActivityStack型別,呼叫它的startActivityMayWait函式

   mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,

               grantedUriPermissions, grantedMode, resultTo, resultWho,

               requestCode, onlyIfNeeded, debug, profileFile, profileFd,

              autoStopProfiler, res, null);//最後一個引數為Configuration,

    //在本例中為null

    returnres;

 }

mMainStack為AMS的成員變數,型別為ActivityStack,該類是Activity排程的核心角色。正式分析它之前,有必要先介紹一下相關的基礎知識。

1.  Task、Back Stack、ActivityStack及Launch mode

(1) 關於Task及Back Stack的介紹

先來看圖6-10。


圖6-10  使用者想幹什麼

圖6-10列出了使用者在Android系統上想幹的三件事情,分別用A、B、C表示,將每一件事情稱為一個Task。一個Task還可細分為多個子步驟,即Activity。

提示為什麼叫Activity?讀者可參考Merrian-Webster詞典對Activity的解釋[③]:“an organizational unit forperforming a specific function”,也就是說,它是一個有組織的單元,用於完成某項指定功能。

由圖6-10可知,A、B兩個Task使用了不同的Activity來完成相應的任務。注意,A、B這兩個Task的Activity之間沒有複用。

再來看C這個Task,它可細分為4個Activity,其中有兩個Activity分別使用了A Task的A1、B Task的B2。C Task為什麼不新建自己的Activity,反而要用其他Task的呢?這是因為使用者想做的事情(即Task)可以完全不同,但是當細分Task為Activity時,就可能出現Activity功能類似的情況。既然A1、B2已能滿足要求,為何還要重複“發明輪子”呢?另外,通過重用Activity,也可為使用者提供一致的介面和體驗。

瞭解Android設計理念後,我們來看看Android是如何組織Task及它所包含的Activity的。此處有一個簡單的例子,如圖6-11所示。


圖6-11  Task及Back Stack示例

由圖6-11可知:

·  本例中的Task包含4個Activity。使用者可單擊按鈕跳轉到下一個Activity。同時,通過返回鍵可回到上一個Activity。

·  虛線下方是這些Activity的組織方式。Android採用了Stack的方法管理這3個Activity。例如在A1啟動A2後,A2入棧成為棧頂成員,A1成為棧底成員,而介面顯示的是棧頂成員的內容。當按返回鍵時,A3出棧,這時候A2成為棧頂,介面顯示也相應變成了A2。

以上是一個Task的情況。那麼,多個Task又會是何種情況呢?如圖6-12所示。


圖6-12  多個Task的情況

由圖6-12可知:對多Task的情況來說,系統只支援一個處於前臺的Task,即使用者當前看到的Activity所屬的Task,其餘的Task均處於後臺,這些後臺Task內部的Activity保持順序不變。使用者可以一次將整個Task挪到後臺或者置為前臺。

提示用過Android手機的讀者應該知道,長按Home鍵,系統會彈出近期Task列表,使使用者能快速在多個Task間切換。

以上內容從抽象角度介紹了什麼是Task,以及Android如何分解Task和管理Activity,那麼在實際程式碼中,是如何考慮並設計的呢?

(2) 關於ActivityStack的介紹

通過上述分析,我們對Android的設計有了一定了解,那麼如何用程式碼來實現這一設計呢?此處有兩點需要考慮:

·  Task內部Activity的組織方式。由圖6-11可知,Android通過先入後出的方式來組織Activity。資料結構中的Stack即以這種方式工作。

·  多個Task的組織及管理方式。

Android設計了一個ActivityStack類來負責上述工作,它的組成如圖6-13所示。


圖6-13  ActivityStack及相關成員

由圖6-13可知:

·  Activity由ActivityRecord表示,Task由TaskRecord表示。ActivityRecord的task成員指向該Activity所在的Task。state變數用於表示該Activity所處的狀態(包括INITIALIZING、RESUMED、PAUSED等狀態)。

·  ActivityStack用mHistory這個ArrayList儲存ActivityRecord。令人大跌眼鏡的是,該mHistory儲存了系統中所有Task的ActivityRecord,而不是針對某個Task進行儲存。

·  ActivityStack的mMainStack成員比較有意思,它代表此ActivityStack是否為主ActivityStack。有主必然有從,但是目前系統中只有一個ActivityStack,並且它的mMainStack為true。從ActivityStack的命名可推測,Android在開發之初也想用ActivityStack來管理單個Task中的ActivityRecord(在ActivityStack.java的註釋中說過,該類為“State and management of asingle stack of activities”),但不知何故,在現在的程式碼實現將所有Task的ActivityRecord都放到mHistory中了,並且依然保留mMainStack。

·  ActivityStack中沒有成員用於儲存TaskRecord。

由上述內容可知,ActivityStack採用陣列的方式儲存所有Task的ActivityRecord,並且沒有成員儲存TaskRecord。這種實現方式有優點亦有缺點:

·  優點是少了TaskRecord一級的管理,直接以ActivityRecord為管理單元。這種做法能降低管理方面的開銷。

·  缺點是弱化了Task的概念,結構不夠清晰。

下面來看ActivityStack中幾個常用的搜尋ActivityRecord的函式,程式碼如下:

[-->ActivityStack.java::topRunningActivityLocked]

/* topRunningActivityLocked:

找到棧中第一個與notTop不同的,並且不處於finishing狀態的ActivityRecord。當notTop為

null時,該函式即返回棧中第一個需要顯示的ActivityRecord。提醒讀者,棧的出入口只能是棧頂。

雖然mHistory是一個陣列,但是查詢均從陣列末端開始,所以其行為也粗略符合Stack的定義

*/

final ActivityRecordtopRunningActivityLocked(ActivityRecord notTop) {

    int i =mHistory.size()-1;

    while (i>= 0) {

        ActivityRecord r = mHistory.get(i);

         if (!r.finishing && r != notTop)  return r;

         i--;

     }

     returnnull;

  }

類似的函式還有:

[-->ActivityStack.java::topRunningNonDelayedActivityLocked]

/* topRunningNonDelayedActivityLocked

與topRunningActivityLocked類似,但ActivityRecord要求增加一項,即delayeResume為

false

*/

final ActivityRecordtopRunningNonDelayedActivityLocked(ActivityRecord notTop) {

     int i =mHistory.size()-1;

     while(i >= 0) {

        ActivityRecord r = mHistory.get(i);

        //delayedResume變數控制是否暫緩resume Activity

         if (!r.finishing && !r.delayedResume&& r != notTop)   return r;

        i--;

     }

    returnnull;

  }

ActivityStack還提供findActivityLocked函式以根據Intent及ActivityInfo來查詢匹配的ActivityRecord,同樣,查詢也是從mHistory尾端開始,相關程式碼如下:

[-->ActivityStack.java::findActivityLocked]

private ActivityRecord findActivityLocked(Intentintent, ActivityInfo info) {

  ComponentName cls = intent.getComponent();

   if(info.targetActivity != null)

        cls= new ComponentName(info.packageName, info.targetActivity);

   final intN = mHistory.size();

   for (inti=(N-1); i>=0; i--) {

      ActivityRecord r = mHistory.get(i);

     if (!r.finishing)

         if (r.intent.getComponent().equals(cls))return r;

   }

  return null;

 }

另一個findTaskLocked函式的返回值是ActivityRecord,其程式碼如下:

[ActivityStack.java::findTaskLocked]

private ActivityRecord findTaskLocked(Intentintent, ActivityInfo info) {

  ComponentName cls = intent.getComponent();

   if(info.targetActivity != null)

        cls= new ComponentName(info.packageName, info.targetActivity);

 

  TaskRecord cp = null;

 

   final intN = mHistory.size();

   for (inti=(N-1); i>=0; i--) {

       ActivityRecord r = mHistory.get(i);

       //r.task!=cp,表示不搜尋屬於同一個Task的ActivityRecord

        if(!r.finishing && r.task != cp

                   && r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE) {

              cp = r.task;

              //如果Task的affinity相同,則返回這條ActivityRecord

              if(r.task.affinity != null) {

               if(r.task.affinity.equals(info.taskAffinity))  return r;

               }else if (r.task.intent != null

                        &&r.task.intent.getComponent().equals(cls)) {

                 //如果Task Intent的ComponentName相同

                     return r;

                }else if (r.task.affinityIntent != null

                        &&r.task.affinityIntent.getComponent().equals(cls)) {

                   //Task affinityIntent考慮

                       return r;

               }//if (r.task.affinity != null)判斷結束

         }//if(!r.finishing && r.task != cp......)判斷結束

     }//for迴圈結束

    returnnull;

 }

其實,findTaskLocked是根據mHistory中ActivityRecord所屬的Task的情況來進行相應的查詢工作。

以上這4個函式均是ActivityStack中常用的函式,如果不需要逐項(case by case)地研究AMS,那麼讀者僅需瞭解這幾個函式的作用即可。

(3) 關於Launch Mode的介紹

Launch Mode用於描述Activity的啟動模式,目前一共有4種模式,分別是standard、singleTop、singleTask和singleInstance。初看它們,較難理解,實際上不過是Android玩的一個“小把戲“而已。啟動模式就是用於控制Activity和Task關係的。

·  standard:一個Task中可以有多個相同型別的Activity。注意,此處是相同型別的Activity,而不是同一個Activity物件。例如在Task中有A、B、C、D4個Activity,如果再啟動A類Activity, Task就會變成A、B、C、D、A。最後一個A和第一個A是同一型別,卻並非同一物件。另外,多個Task中也可以有同型別的Activity。

·  singleTop:當某Task中有A、B、C、D4個Activity時,如果D想再啟動一個D型別的Activity,那麼Task將是什麼樣子呢?在singleTop模式下,Task中仍然是A、B、C、D,只不過D的onNewIntent函式將被呼叫以處理這個新Intent,而在standard模式下,則Task將變成A、B、C、D、D,最後的D為新建立的D型別Activity物件。在singleTop這種模式下,只有目標Acitivity當前正好在棧頂時才有效,例如只有處於棧頂的D啟動時才有用,如果D啟動不處於棧頂的A、B、C等,則無效。

·  singleTask:在這種啟動模式下,該Activity只存在一個例項,並且將和一個Task繫結。當需要此Activity時,系統會以onNewIntent方式啟動它,而不會新建Task和Activity。注意,該Activity雖只有一個例項,但是在Task中除了它之外,還可以有其他的Activity。

·  singleInstance:它是singleTask的加強版,即一個Task只能有這麼一個設定了singleInstance的Activity,不能再有別的Activity。而在singleTask模式中,Task還可以有其他的Activity。

注意,Android建議一般的應用開發者不要輕易使用最後兩種啟動模式。因為這些模式雖然名意上為Launch Mode,但是它們也會影響Activity出棧的順序,導致使用者按返回鍵返回時導致不一致的使用者體驗。

除了啟動模式外,Android還有其他一些標誌用於控制Activity及Task之間的關係。這裡只列舉一二,詳細資訊請參閱SDK文件中Intent的相關說明。

·  FLAG_ACTIVITY_NEW_TASK:將目標Activity放到一個新的Task中。

·  FLAG_ACTIVITY_CLEAR_TASK:當啟動一個Activity時,先把和目標Activity有關聯的Task“幹掉“,然後啟動一個新的Task,並把目標Activity放到新的Task中。該標誌必須和FLAG_ACTIVITY_NEW_TASK標誌一起使用。

·  FLAG_ACTIVITY_CLEAR_TOP:當啟動一個不處於棧頂的Activity時候,先把排在它前面的Activity“幹掉”。例如Task有A、B、C、D4個Activity,要要啟動B,直接把C、D“幹掉”,而不是新建一個B。

提示這些啟動模式和標誌,在筆者看來很像洗撲克牌時的手法,因此可以稱之為小把戲。雖是小把戲,但是相關程式碼的邏輯及分支卻異常繁雜,我們應從更高的角度來看待它們。

介紹完上面的知識後,下面來分析ActivityStack的startActivityMayWait函式。

2.  ActivityStack的startActivityMayWait函式分析

startActivityMayWait函式的目標是啟動com.dfp.test.TestActivity,假設系統之前沒有啟動過該Activity,本例最終的結果將是:

·  由於在am中設定了FLAG_ACTIVITY_NEW_TASK標誌,因此除了會建立一個新的ActivityRecord外,還會新建立一個TaskRecord。

·  還需要啟動一個新的應用程式以載入並執行com.dfp.test.TestActivity的一個例項。

·  如果TestActivity不是Home,還需要停止當前正在顯示的Activity。

好了,將這個函式分三部分進行介紹,先來分析第一部分。

(1) startActivityMayWait分析之一

[-->ActivityStack.java::startActivityMayWait]

final int startActivityMayWait(IApplicationThreadcaller, int callingUid,

        Intentintent, String resolvedType, Uri[] grantedUriPermissions,

        intgrantedMode, IBinder resultTo,

        StringresultWho, int requestCode, boolean onlyIfNeeded,

        booleandebug, String profileFile, ParcelFileDescriptor profileFd,

        booleanautoStopProfiler, WaitResult outResult, Configuration config) {

    ......

    //在本例中,已經指明瞭Component,這樣可省去為Intent匹配搜尋之苦

    booleancomponentSpecified = intent.getComponent() != null;

 

    //建立一個新的Intent,防止客戶傳入的Intent被修改

    intent =new Intent(intent);

 

    //查詢滿足條件的ActivityInfo,在resolveActivity內部和PKMS互動,讀者不妨自己

   //嘗試分析該函式

    ActivityInfoaInfo = resolveActivity(intent, resolvedType, debug,

                     profileFile, profileFd,autoStopProfiler);

 

    synchronized(mService) {

      int callingPid;

      if (callingUid >= 0) {

          callingPid= -1;

      } else if (caller == null) {//本例中,caller為null

           callingPid= Binder.getCallingPid();//取出呼叫程式的Pid

           //取出呼叫程式的Uid。在本例中,呼叫程式是am,它由shell啟動

           callingUid= Binder.getCallingUid();

       } else {

           callingPid= callingUid = -1;

       }// if (callingUid >= 0)判斷結束

 

     //在本例中config為null

     mConfigWillChange= config != null

                  && mService.mConfiguration.diff(config) != 0;

    finallong origId = Binder.clearCallingIdentity();

    if (mMainStack && aInfo != null&&  (aInfo.applicationInfo.flags&

                  ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0){

        /*

        AndroidManifest.xml中的Application標籤可以宣告一個cantSaveState

        屬性,設定了該屬性的Application將不享受系統提供的狀態儲存/恢復功能。

        當一個Application退到後臺時,系統會為它儲存狀態,當排程其到前臺執行時,        將恢復它之前的狀態,以保證使用者體驗的連續性。宣告瞭該屬性的Application被稱為

        “heavy weight process”。可惜系統目前不支援該屬性,因為PackageParser

        將不解析該屬性。詳情請見PackageParser.java parseApplication函式

       */

    }

   ......//待續

startActivityMayWait第一階段的工作內容相對較簡單:

·  首先需要通過PKMS查詢匹配該Intent的ActivityInfo。

·  處理FLAG_CANT_SAVE_STATE的情況,但系統目前不支援此情況。

·  另外,獲取呼叫者的pid和uid。由於本例的caller為null,故所得到的pid和uid均為am所在程式的uid和pid。

下面介紹startActivityMayWait第二階段的工作。

(2) startActivityMayWait分析之二

[-->ActivityStack.java::startActivityMayWait]

    //呼叫此函式啟動Activity,將返回值儲存到res

   int res = startActivityLocked(caller, intent,resolvedType,

            grantedUriPermissions, grantedMode, aInfo,

            resultTo, resultWho, requestCode, callingPid, callingUid,

            onlyIfNeeded, componentSpecified, null);

     //如果configuration發生變化,則呼叫AMS的updateConfigurationLocked

    //進行處理。關於這部分內容,讀者學完本章後可自行分析

    if(mConfigWillChange && mMainStack) {

            mService.enforceCallingPermission(

                  android.Manifest.permission.CHANGE_CONFIGURATION,

                  "updateConfiguration()");

            mConfigWillChange= false;

            mService.updateConfigurationLocked(config,null, false);

   }

此處,啟動Activity的核心函式是startActivityLocked,該函式異常複雜,將用一節專門分析。下面先繼續分析startActivityMayWait第三階段的工作。

(3) startActivityMayWait分析之三

[-->ActivityStack.java::startActivityMayWait]

    if(outResult != null) {

      outResult.result = res;//設定啟動結果

       if(res == IActivityManager.START_SUCCESS) {

          //將該結果加到mWaitingActivityLaunched中儲存

           mWaitingActivityLaunched.add(outResult);

           do {

                try {

                         mService.wait();//等待啟動結果

                       }      

                } while (!outResult.timeout && outResult.who == null);

          }else if (res == IActivityManager.START_TASK_TO_FRONT) {

              ......//處理START_TASK_TO_FRONT結果,讀者可自行分析

          }

       }//if(outResult!= null)結束

           return res;

     }

 }

第三階段的工作就是根據返回值做一些處理,那麼res返回成功(即res== IActivityManager.START_SUCCESS的時候)後為何還需要等待呢?

這是因為目標Activity要執行在一個新的應用程式中,就必須等待那個應用程式正常啟動並處理相關請求。注意,只有am設定了-W選項,才會進入wait這一狀態。

6.3.3  startActivityLocked分析

startActivityLocked是startActivityMayWait第二階段的工作重點,該函式有點長,請讀者耐心看程式碼。

[-->ActivityStack.java::startActivityLocked]

final int startActivityLocked(IApplicationThreadcaller,

           Intent intent, String resolvedType,

           Uri[] grantedUriPermissions,

           int grantedMode, ActivityInfo aInfo, IBinder resultTo,

           String resultWho, int requestCode,

           int callingPid, int callingUid, boolean onlyIfNeeded,

             boolean componentSpecified, ActivityRecord[]outActivity) {

 

   int err = START_SUCCESS;

  ProcessRecord callerApp = null;

  //如果caller不為空,則需要從AMS中找到它的ProcessRecord。本例的caller為null

   if(caller != null) {

      callerApp = mService.getRecordForAppLocked(caller);

       //其實就是想得到呼叫程式的pid和uid

       if(callerApp != null) {

           callingPid = callerApp.pid;//一定要保證呼叫程式的pid和uid正確

           callingUid = callerApp.info.uid;

          }else {//如呼叫程式沒有在AMS中註冊,則認為其是非法的

               err = START_PERMISSION_DENIED;

         }

     }// if (caller != null)判斷結束

 

   //下面兩個變數意義很重要。sourceRecord用於描述啟動目標Activity的那個Activity,

 //resultRecord用於描述接收啟動結果的Activity,即該Activity的onActivityResult

  //將被呼叫以通知啟動結果,讀者可先閱讀SDK中startActivityForResult函式的說明

   ActivityRecordsourceRecord = null;

  ActivityRecord resultRecord = null;

   if(resultTo != null) {

      //本例resultTo為null,

   }

   //獲取Intent設定的啟動標誌,它們是和Launch Mode相類似的“小把戲”,

  //所以,讀者務必理解“關於Launch Mode的介紹”一節的知識點

   intlaunchFlags = intent.getFlags();

   if((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0

       && sourceRecord != null) {

       ......

       /*

         前面介紹的Launch Mode和Activity的啟動有關,實際上還有一部分標誌用於控制

         Activity啟動結果的通知。有關FLAG_ACTIVITY_FORWARD_RESULT的作用,讀者可

         參考SDK中的說明。使用這個標籤有個前提,即A必須先存在,正如if中sourceRecord

         不為null的判斷所示。另外,讀者自己可嘗試編寫例子,以測試FLAG_ACTIVITY_FORWARD_

         RESULT標誌的作用

      */

    }

   //檢查err值及Intent的情況

    if (err== START_SUCCESS && intent.getComponent() == null)

           err = START_INTENT_NOT_RESOLVED;

    ......

    //如果err不為0,則呼叫sendActivityResultLocked返回錯誤

    if (err!= START_SUCCESS) {

        if(resultRecord != null) {// resultRecord接收啟動結果

          sendActivityResultLocked(-1,esultRecord, resultWho, requestCode,

            Activity.RESULT_CANCELED, null);

        }

       .......

        returnerr;

    }

    //檢查許可權

    finalint perm = mService.checkComponentPermission(aInfo.permission,

            callingPid,callingUid,aInfo.applicationInfo.uid, aInfo.exported);

    ......//許可權檢查失敗的處理,不必理會

   if (mMainStack) {

       //可為AMS設定一個IActivityController型別的監聽者,AMS有任何動靜都會回撥該

       //監聽者。不過誰又有如此本領去監聽AMS呢?在進行Monkey測試的時候,Monkey會

       //設定該回撥物件。這樣,Monkey就能根據AMS放映的情況進行相應處理了

        if(mService.mController != null) {

             boolean abort = false;

            try {

                   Intent watchIntent = intent.cloneFilter();

                   //交給回撥物件處理,由它判斷是否能繼續後面的行程

                   abort = !mService.mController.activityStarting(watchIntent,

                           aInfo.applicationInfo.packageName);

               }......

            //回撥物件決定不啟動該Activity。在進行Monkey測試時,可設定黑名單,位於

            //黑名單中的Activity將不能啟動

             if (abort) {

                .......//通知resultRecord

                return START_SUCCESS;

             }

           }

      }// if(mMainStack)判斷結束

 

  //建立一個ActivityRecord物件

   ActivityRecordr = new ActivityRecord(mService, this, callerApp, callingUid,

               intent, resolvedType, aInfo, mService.mConfiguration,

               resultRecord, resultWho, requestCode, componentSpecified);

   if(outActivity != null)

        outActivity[0] = r;//儲存到輸入引數outActivity陣列中

 

   if(mMainStack) {

       //mResumedActivity代表當前介面顯示的Activity

       if(mResumedActivity == null

         || mResumedActivity.info.applicationInfo.uid!= callingUid) {

           //檢查呼叫程式是否有許可權切換Application,相關知識見下文的解釋

           if(!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,

               "Activity start")) {

              PendingActivityLaunch pal = new PendingActivityLaunch();

             //如果呼叫程式沒有許可權切換Activity,則只能把這次Activity啟動請求儲存起來,

             //後續有機會時再啟動它

              pal.r = r;

              pal.sourceRecord = sourceRecord;

              ......

              //所有Pending的請求均儲存到AMS mPendingActivityLaunches變數中

              mService.mPendingActivityLaunches.add(pal);

              mDismissKeyguardOnNextActivity = false;

              return START_SWITCHES_CANCELED;

           }

      }//if(mResumedActivity == null...)判斷結束

 

     if (mService.mDidAppSwitch) {//用於控制app switch,見下文解釋

      mService.mAppSwitchesAllowedTime = 0;

     } else{

        mService.mDidAppSwitch = true;

     }

 

    //啟動處於Pending狀態的Activity

    mService.doPendingActivityLaunchesLocked(false);

   }// if(mMainStack)判斷結束

 

   //呼叫startActivityUncheckedLocked函式

   err =startActivityUncheckedLocked(r, sourceRecord,

            grantedUriPermissions, grantedMode, onlyIfNeeded, true);

   ......

   return err;

}

startActivityLocked函式的主要工作包括:

·  處理sourceRecord及resultRecord。其中,sourceRecord表示發起本次請求的Activity,resultRecord表示接收處理結果的Activity(啟動一個Activity肯定需要它完成某項事情,當目標Activity將事情成後,就需要告知請求者該事情的處理結果)。在一般情況下,sourceRecord和resultRecord應指向同一個Activity。

·  處理app Switch。如果AMS當前禁止app switch,則只能把本次啟動請求儲存起來,以待允許app switch時再處理。從程式碼中可知,AMS在處理本次請求前,會先呼叫doPendingActivityLaunchesLocked函式,在該函式內部將啟動之前因系統禁止app switch而儲存的Pending請求。

·  呼叫startActivityUncheckedLocked處理本次Activity啟動請求。

先來看app Switch,它雖然是一個小變數,但是意義重大。

1.  關於resume/stopAppSwitches的介紹

AMS提供了兩個函式,用於暫時(注意,是暫時)禁止App切換。為什麼會有這種需求呢?因為當某些重要(例如設定賬號等)Activity處於前臺(即使用者當前所見的Activity)時,不希望系統因使用者操作之外的原因而切換Activity(例如恰好此時收到來電訊號而彈出來電介面)。

先來看stopAppSwitches,程式碼如下:

[-->ActivityManagerService.java::stopAppSwitches]

public void stopAppSwitches() {

    ......//檢查呼叫程式是否有STOP_APP_SWITCHES許可權

  synchronized(this) {

      //設定一個超時時間,過了該時間,AMS可以重新切換App(switch app)了

     mAppSwitchesAllowedTime = SystemClock.uptimeMillis()

                   + APP_SWITCH_DELAY_TIME;

     mDidAppSwitch = false;//設定mDidAppSwitch為false

     mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG);

     Message msg =//防止應用程式呼叫了stop卻沒呼叫resume,5秒後處理該訊息

           mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG);

     mHandler.sendMessageDelayed(msg, APP_SWITCH_DELAY_TIME);

   }

}

在以上程式碼中有兩點需要注意:

·  此處控制機制名叫app switch,而不是activity switch。為什麼呢?因為如果從受保護的activity中啟動另一個activity,那麼這個新activity的目的應該是針對同一任務,這次啟動就不應該受app switch的制約,反而應該對其大開綠燈。目前,在執行Settings中設定裝置策略(DevicePolicy)時就會stopAppSwitch。

·  執行stopAppSwitch後,應用程式應該調resumeAppSwitches以允許app switch,但是為了防止應用程式有意或無意忘記resume app switch,系統設定了一個超時(5秒)訊息,過了此超時時間,系統將處理相應的訊息,其內部會resume app switch。

再來看resumeAppSwitches函式,程式碼如下:

[-->ActivityManagerService::resumeAppSwitches]

 public voidresumeAppSwitches() {

    ......//檢查呼叫程式是否有STOP_APP_SWITCHES許可權

    synchronized(this) {

       mAppSwitchesAllowedTime = 0;

     }

    //注意,系統並不在此函式內啟動那些被阻止的Activity

}

在resumeAppSwitches中只設定mAppSwitchesAllowedTime的值為0,它並不處理在stop和resume這段時間內積攢起的Pending請求,那麼這些請求是在何時被處理的呢?

·  從前面程式碼可知,如果在執行resume app switch後,又有新的請求需要處理,則先處理那些pending的請求(呼叫doPendingActivityLaunchesLocked)。

·  在resumeAppSwitches中並未撤銷stopAppSwitches函式中設定的超時訊息,所以在處理那條超時訊息的過程中,也會處理pending的請求。

在本例中,由於不考慮app switch的情況,那麼接下來的工作就是呼叫startActivityUncheckedLocked函式來處理本次activity的啟動請求。此時,我們已經建立了一個ActivityRecord用於儲存目標Activity的相關資訊。

2.  startActivityUncheckedLocked函式分析

startActivityUncheckedLocked函式很長,但是目的比較簡單,即為新建立的ActivityRecord找到一個合適的Task。雖然本例最終的結果是建立一個新的Task,但是該函式的處理邏輯卻比較複雜。先看第一段分析。

(1) startActivityUncheckedLocked分析之一

[-->ActivityStack.java::startActivityUncheckedLocked]

final intstartActivityUncheckedLocked(ActivityRecord r,

   ActivityRecord sourceRecord, Uri[] grantedUriPermissions,

    intgrantedMode, boolean onlyIfNeeded, boolean doResume) {

   //在本例中,sourceRecord為null,onlyIfNeeded為false,doResume為true

   finalIntent intent = r.intent;

   final intcallingUid = r.launchedFromUid;

       

   intlaunchFlags = intent.getFlags();

   //判斷是否需要呼叫因本次Activity啟動而被系統移到後臺的當前Activity的

  //onUserLeaveHint函式。可閱讀SDK文件中關於Activity onUserLeaveHint函式的說明

  mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) ==0;

 

   //設定ActivityRecord的delayedResume為true,本例中的doResume為true

   if (!doResume)   r.delayedResume = true;

 

    //在本例中,notTop為null

    ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)

               != 0 ? r : null;

    if(onlyIfNeeded) {....//在本例中,該變數為false,故略去相關程式碼

    }

 

   //根據sourceRecord的情況進行對應處理,能理解下面這段if/else的判斷語句嗎

   if(sourceRecord == null) {

      //如果請求的發起者為空,則當然需要新建一個Task

      if((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0)

         launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;

   }else if (sourceRecord.launchMode ==ActivityInfo.LAUNCH_SINGLE_INSTANCE){

      //如果sourceRecord單獨佔一個Instance,則新的Activity必然處於另一個Task中

     launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;

   } else if(r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE

               || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {

      //如果啟動模式設定了singleTask或singleInstance,則也要建立新Task

      launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;

   }//if(sourceRecord== null)判斷結束

 

   //如果新Activity和接收結果的Activity不在一個Task中,則不能啟動新的Activity

   if(r.resultTo!= null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {

      sendActivityResultLocked(-1,r.resultTo, r.resultWho, r.requestCode,

                                     Activity.RESULT_CANCELED, null);

      r.resultTo = null;

   }

startActivityUncheckedLocked第一階段的工作還算簡單,主要確定是否需要為新的Activity建立一個Task,即是否設定FLAG_ACTIVITY_NEW_TASK標誌。

接下來看下一階段的工作。

(2) startActivityUncheckedLocked分析之二

[-->ActivityStack.java::startActivityUncheckedLocked]

   booleanaddingToTask = false;

  TaskRecord reuseTask = null;

   if(((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&

         (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK)== 0)

               || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK

               || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {

          if(r.resultTo == null) {

              //搜尋mHistory,得到一個ActivityRecord

                ActivityRecord taskTop = r.launchMode !=

                                   ActivityInfo.LAUNCH_SINGLE_INSTANCE

                        ?findTaskLocked(intent, r.info)

                        : findActivityLocked(intent,r.info);

               if (taskTop != null ){

                   ......//一堆複雜的邏輯處理,無非就是找到一個合適的Task,然後對應做一些

                  //處理。此處不討論這段程式碼,讀者可根據工作中的具體情況進行研究

               }

         }//if(r.resultTo == null)判斷結束

 }

在本例中,目標Activity首次登場,所以前面的邏輯處理都沒有起作用,建議讀者根據具體情況分析該段程式碼。

下面來看startActivityUncheckLocked第三階段的工作。

(3) startActivityUncheckLocked分析之三

[-->ActivityStack.java::startActivityUncheckLocked]

   if(r.packageName != null) {

        //判斷目標Activity是否已經在棧頂,如果是,需要判斷是建立一個新的Activity

        //還是呼叫onNewIntent(singleTop模式的處理)

       ActivityRecord top = topRunningNonDelayedActivityLocked(notTop);

        if (top != null && r.resultTo == null){

            ......//不討論此段程式碼

        }//if(top != null...)結束

    } else {

         ......//通知錯誤

         returnSTART_CLASS_NOT_FOUND;

   }

  //在本例中,肯定需要建立一個Task

   booleannewTask = false;

   booleankeepCurTransition = false;

   if(r.resultTo == null && !addingToTask

               && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {

       if(reuseTask == null) {

          mService.mCurTask++;//AMS中儲存了當前Task的數量

          if (mService.mCurTask <= 0) mService.mCurTask = 1;

          //為該AactivityRecord設定一個新的TaskRecord

          r.setTask(new TaskRecord(mService.mCurTask, r.info, intent),

                                          null,true);

       }else   r.setTask(reuseTask, reuseTask,true);

 

      newTask = true;

      //下面這個函式為Android 4.0新增的,用於處理FLAG_ACTIVITY_TASK_ON_HOME的情況,

      //請閱讀SDK文件對Intent的相關說明

      moveHomeToFrontFromLaunchLocked(launchFlags);

   }elseif......//其他處理情況

 

  //授權控制。在SDK中啟動Activity的函式沒有授權設定方面的引數。在實際工作中,筆者曾碰

  //到過一個有趣的情況:在開發的一款定製系統中,用瀏覽器下載了受DRM保護的圖片,

  //此時要啟動Gallery3D來檢視該圖片,但是由於為DRM目錄設定了讀寫許可權,而Gallery3D

  //並未宣告相關許可權,結果丟擲異常,導致不能瀏覽該圖片。由於startActivity等函式不能設定

  //授權,最終只能修改Gallery3D併為其新增use-permissions項了

  if(grantedUriPermissions != null && callingUid > 0) {

         for(int i=0; i<grantedUriPermissions.length; i++) {

            mService.grantUriPermissionLocked(callingUid, r.packageName,

              grantedUriPermissions[i],grantedMode,

             r.getUriPermissionsLocked());

   }

  mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,

               intent, r.getUriPermissionsLocked());

   //呼叫startActivityLocked,此時ActivityRecord和TaskRecord均建立完畢

  startActivityLocked(r, newTask, doResume, keepCurTransition);

   return START_SUCCESS;

}//startActivityUncheckLocked函式結束

startActivityUncheckLocked的第三階段工作也比較複雜,不過針對本例,它將建立一個新的TaskRecord,並呼叫startActivityLocked函式進行處理。

下面我們轉戰startActivityLocked函式。

(4) startActivityLocked函式分析

[-->ActivityStack.java::startActivityLocked]

private final voidstartActivityLocked(ActivityRecord r, boolean newTask,

           boolean doResume, boolean keepCurTransition) {

   final intNH = mHistory.size();

   intaddPos = -1;

   if(!newTask){//如果不是新Task,則從mHistory中找到對應的ActivityRecord的位置

   ......

   }

   if(addPos < 0)  addPos = NH;

   //否則加到mHistory陣列的最後

   mHistory.add(addPos,r);

   //設定ActivityRecord的inHistory變數為true,表示已經加到mHistory陣列中了

  r.putInHistory();

  r.frontOfTask = newTask;

   if (NH> 0) {

    //判斷是否顯示Activity切換動畫之類的事情,需要與WindowManagerService互動

  }

   //最終呼叫resumeTopActivityLocked

   if (doResume) resumeTopActivityLocked(null);//重點分析這個函式

 }

在以上列出的startActivityLocked函式中,略去了一部分邏輯處理,這部分內容和Activity之間的切換動畫有關(通過這些動畫,使切換過程看起來更加平滑和美觀,需和WMS互動)。

提示筆者認為,此處將Activity切換和動畫處理這兩個邏輯揉到一起並不合適,但是似乎也沒有更合適的地方來進行該工作了。讀者不妨自行研讀一下該段程式碼以加深體會。

(5) startActivityUncheckedLocked總結

說實話,startActivityUncheckedLocked函式的複雜度超乎筆者的想象,光這些函式名就夠讓人頭疼的。但是針對本例而言,相關邏輯的難度還算適中,畢竟這是Activity啟動流程中最簡單的情況。可用一句話總結本例中startActivityUncheckedLocked函式的功能:建立ActivityRecord和TaskRecord並將ActivityRecord新增到mHistory末尾,然後呼叫resumeTopActivityLocked啟動它。

下面用一節來分析resumeTopActivityLocked函式。

3.  resumeTopActivityLocked函式分析

[-->ActivityStack.java::resumeTopActivityLocked]

 finalboolean resumeTopActivityLocked(ActivityRecord prev) {

   //從mHistory中找到第一個需要啟動的ActivityRecord

  ActivityRecord next = topRunningActivityLocked(null);

   finalboolean userLeaving = mUserLeaving;

  mUserLeaving = false;

   if (next== null) {

      //如果mHistory中沒有要啟動的Activity,則啟動Home

      if(mMainStack)    returnmService.startHomeActivityLocked();

   }

   //在本例中,next將是目標Activity

   next.delayedResume= false;

   ......//和WMS互動,略去

   //將該ActivityRecord從下面幾個佇列中移除

   mStoppingActivities.remove(next);

  mGoingToSleepActivities.remove(next);

  next.sleeping = false;

  mWaitingVisibleActivities.remove(next);

   //如果當前正在中斷一個Activity,需先等待那個Activity pause完畢,然後系統會重新

   //呼叫resumeTopActivityLocked函式以找到下一個要啟動的Activity

   if(mPausingActivity != null)  return false;

   /************************請讀者注意***************************/

   //①mResumedActivity指向上一次啟動的Activity,也就是當前介面顯示的這個Activity

  //在本例中,當前Activity就是Home介面

   if(mResumedActivity != null) {

      //先中斷 Home。這種情況放到最後進行分析

      startPausingLocked(userLeaving,false);

      return true;

   }

   //②如果mResumedActivity為空,則一定是系統第一個啟動的Activity,讀者應能猜測到它就

   //是Home

   ......//如果prev不為空,則需要通知WMS進行與Activity切換相關的工作

   try {

           //通知PKMS修改該Package stop狀態,詳細資訊參考第4章“readLPw的‘佐料’”

           //一節的說明

           AppGlobals.getPackageManager().setPackageStoppedState(

                   next.packageName, false);

    }......

   if(prev!= null){

   ......//還是和WMS有關,通知它停止繪畫

   }

  if(next.app != null && next.app.thread != null) {

   //如果該ActivityRecord已有對應的程式存在,則只需要重啟Activity。就本例而言,

   //此程式還不存在,所以要先建立一個應用程式

  }  else {

           //第一次啟動

           if (!next.hasBeenLaunched) {

               next.hasBeenLaunched = true;

           } else {

               ......//通知WMS顯示啟動介面

           }

       //呼叫另外一個startSpecificActivityLocked函式

      startSpecificActivityLocked(next, true, true);

    }

    returntrue;

}

resumeTopActivityLocked函式中有兩個非常重要的關鍵點:

·  如果mResumedActivity不為空,則需要先暫停(pause)這個Activity。由程式碼中的註釋可知,mResumedActivity代表上一次啟動的(即當前正顯示的)Activity。現在要啟動一個新的Activity,須先停止當前Activity,這部分工作由startPausingLocked函式完成。

·  那麼,mResumedActivity什麼時候為空呢?當然是在啟動全系統第一個Activity時,即啟動Home介面的時候。除此之外,該值都不會為空。

先分析第二個關鍵點,即mResumedActivity為null的情況選擇分析此種情況的原因是:如果先分析startPausingLocked,則後續分析會牽扯三個程式,即當前Activity所在程式、AMS所在程式及目標程式,分析的難度相當大。

好了,繼續我們的分析。resumeTopActivityLocked最後將呼叫另外一個startSpecificActivityLocked,該函式將真正建立一個應用程式。

(1) startSpecificActivityLocked分析

[-->ActivityStack.java::startSpecificActivityLocked]

private final voidstartSpecificActivityLocked(ActivityRecord r,

           boolean andResume, boolean checkConfig) {

 

   //從AMS中查詢是否已經存在滿足要求的程式(根據processName和uid來查詢)

  //在本例中,查詢結果應該為null

  ProcessRecord app = mService.getProcessRecordLocked(r.processName,

               r.info.applicationInfo.uid);

   //設定啟動時間等資訊

   if(r.launchTime == 0) {

        r.launchTime = SystemClock.uptimeMillis();

       if(mInitialStartTime == 0)  mInitialStartTime = r.launchTime;

   } else if(mInitialStartTime == 0) {

           mInitialStartTime = SystemClock.uptimeMillis();

   }

   //如果該程式存在並已經向AMS註冊(例如之前在該程式中啟動了其他Activity)

   if (app!= null && app.thread != null) {

       try {

           app.addPackage(r.info.packageName);

           //通知該程式中的啟動目標Activity

           realStartActivityLocked(r, app, andResume, checkConfig);

           return;

         }......

    }

    //如果該程式不存在,則需要呼叫AMS的startProcessLocked建立一個應用程式

    mService.startProcessLocked(r.processName, r.info.applicationInfo,

              true, 0,"activity",r.intent.getComponent(), false);

}

來看AMS的startProcessLocked函式,它將建立一個新的應用程式。

(2) startProcessLocked分析

[-->ActivityManagerService.java::startProcessLocked]

final ProcessRecord startProcessLocked(StringprocessName,

           ApplicationInfo info, boolean knownToBeDead, int intentFlags,

           String hostingType, ComponentName hostingName,

            boolean allowWhileBooting) {

   //根據processName和uid尋找是否已經存在ProcessRecord

   ProcessRecordapp = getProcessRecordLocked(processName, info.uid);

   if (app!= null && app.pid > 0) {

       ......//處理相關情況

   }

 

    StringhostingNameStr = hostingName != null

               ? hostingName.flattenToShortString() : null;

       

   //①處理FLAG_FROM_BACKGROUND標誌,見下文解釋

   if((intentFlags&Intent.FLAG_FROM_BACKGROUND) != 0) {

       if(mBadProcesses.get(info.processName, info.uid) != null)

            return null;

     } else {

        mProcessCrashTimes.remove(info.processName,info.uid);

        if(mBadProcesses.get(info.processName, info.uid) != null) {

            mBadProcesses.remove(info.processName, info.uid);

            if (app != null)    app.bad =false;

         }

   }

       

    if (app== null) {

        //建立一個ProcessRecord,並儲存到mProcessNames中。注意,此時還沒有建立實際程式

        app= newProcessRecordLocked(null, info, processName);

       mProcessNames.put(processName, info.uid, app);

    }else   app.addPackage(info.packageName);

       ......

    //②呼叫另外一個startProcessLocked函式

   startProcessLocked(app, hostingType, hostingNameStr);

     return(app.pid != 0) ? app : null;

 }

在以上程式碼中列出兩個關鍵點,其中第一點和FLAG_FROM_BACKGROUND有關,相關知識點如下:

·  FLAG_FROM_BACKGROUND標識發起這次啟動的Task屬於後臺任務。很顯然,手機中沒有介面供使用者操作位於後臺Task中的Activity。如果沒有設定該標誌,那麼這次啟動請求就是由前臺Task因某種原因而觸發的(例如使用者單擊某個按鈕)。

·  如果一個應用程式在1分鐘內連續崩潰超過2次,則AMS會將其ProcessRecord加入所謂的mBadProcesses中。一個應用崩潰後,系統會彈出一個警告框以提醒使用者。但是,如果一個後臺Task啟動了一個“BadProcess”,然後該Process崩潰,結果彈出一個警告框,那麼使用者就會覺得很奇怪:“為什麼突然彈出一個框?”因此,此處將禁止後臺Task啟動“Bad Process”。如果使用者主動選擇啟動(例如單擊一個按鈕),則不能禁止該操作,並且要把應用程式從mBadProcesses中移除,以給它們“重新做人”的機會。當然,要是該應用每次啟動時都會崩潰,而且使用者不停地去啟動,那該使用者可能是位測試工作者。

提示這其實是一種安全機制,防止不健全的程式不斷啟動可能會崩潰的元件,但是這種機制並不限制使用者的行為。

下面來看第二個關鍵點,即另一個startProcessLocked函式,其程式碼如下:

[-->ActivityManagerService.java::startProcessLocked]

private final voidstartProcessLocked(ProcessRecord app,

                            String hostingType, StringhostingNameStr) {

   if(app.pid > 0 && app.pid != MY_PID) {

       synchronized (mPidsSelfLocked) {

        mPidsSelfLocked.remove(app.pid);

        mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);

       }

      app.pid = 0;

  }

   //mProcessesOnHold用於儲存那些在系統還沒有準備好就提前請求啟動的ProcessRecord

   mProcessesOnHold.remove(app);

   updateCpuStats();

 

  System.arraycopy(mProcDeaths, 0, mProcDeaths, 1, mProcDeaths.length-1);

   mProcDeaths[0] = 0;

  

    try {

         intuid = app.info.uid;

        int[] gids = null;

           try {//從PKMS中查詢該程式所屬的gid

               gids = mContext.getPackageManager().getPackageGids(

                        app.info.packageName);

           }......

       ......//工廠測試

 

      intdebugFlags = 0;

      if((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {

                debugFlags |=Zygote.DEBUG_ENABLE_DEBUGGER;

              debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;

         }......//設定其他一些debugFlags

 

     //傳送訊息給Zygote,它將派生一個子程式,該子程式執行ActivityThread的main函式

    //注意,我們傳遞給Zygote的引數並沒有包含任何與Activity相關的資訊。現在僅僅啟動

    //一個應用程式

    Process.ProcessStartResult startResult =

                   Process.start("android.app.ActivityThread",

                   app.processName, uid, uid, gids, debugFlags,

                   app.info.targetSdkVersion, null);

     //電量統計項

    BatteryStatsImpl bs = app.batteryStats.getBatteryStats();

    synchronized (bs) {

          if(bs.isOnBattery()) app.batteryStats.incStartsLocked();

     }

      //如果該程式為persisitent,則需要通知Watchdog,實際上processStarted內部只

      //關心剛才建立的程式是不是com.android.phone

     if(app.persistent) {

         Watchdog.getInstance().processStarted(app.processName,

                                    startResult.pid);

     }

 

    app.pid= startResult.pid;

   app.usingWrapper = startResult.usingWrapper;

   app.removed = false;

   synchronized (mPidsSelfLocked) {

          //以pid為key,將代表該程式的ProcessRecord物件加入到mPidsSelfLocked中保管

         this.mPidsSelfLocked.put(startResult.pid, app);

          //傳送一個超時訊息,如果這個新建立的應用程式10秒內沒有和AMS互動,則可斷定

         //該應用程式啟動失敗

         Message msg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG);

         msg.obj = app;

          //正常的超時時間為10秒。不過如果該應用程式通過valgrind載入,則延長到300秒

        //valgrind是Linux平臺上一款檢查記憶體洩露的程式,被載入的應用將在它的環境中工作,

         //這項工作需耗費較長時間。讀者可查詢valgrind的用法

          mHandler.sendMessageDelayed(msg,startResult.usingWrapper

                        ?PROC_START_TIMEOUT_WITH_WRAPPER : PROC_START_TIMEOUT);

    }......

 }

startProcessLocked通過傳送訊息給Zygote以派生一個應用程式[④],讀者仔細研究所發訊息的內容,大概會發現此處並未設定和Activity相關的資訊,也就是說,該程式啟動後,將完全不知道自己要幹什麼,怎麼辦?下面就此進行分析。

4.  startActivity分析之半程總結

很抱歉,我們現在還處於startActivity分析之旅的中間點,即使越過了很多險灘惡途,一路走來還是發覺有點艱難。此處用圖6-14來記錄半程中的各個關鍵點。


圖6-14  startActivity半程總結

圖6-14列出了針對本例的呼叫順序,其中對每個函式的大體功能也做了簡單描述。

注意圖6-14中的呼叫順序及功能說明只是針對本例而言的。讀者以後可結合具體情況再深入研究其中的內容。

5.  應用程式的建立及初始化

如前所述,應用程式的入口是ActivityThread的main函式,它是在主執行緒中執行的,其程式碼如下:

[-->ActivityThread.java::main]

public static void main(String[] args) {

   SamplingProfilerIntegration.start();

   //和除錯及strictMode有關

  CloseGuard.setEnabled(false);

   //設定程式名為"<pre-initialized>"

  Process.setArgV0("<pre-initialized>");

   //準備主執行緒訊息迴圈

  Looper.prepareMainLooper();

   if(sMainThreadHandler == null)

      sMainThreadHandler = new Handler();

   //建立一個ActivityThread物件

  ActivityThread thread = new ActivityThread();

   //①呼叫attach函式,注意其引數值為false

  thread.attach(false);

  Looper.loop(); //進入主執行緒訊息迴圈

   throw newRuntimeException("Main thread loop unexpectedly exited");

}

在main函式內部將建立一個訊息迴圈Loop,接著呼叫ActivityThread的attach函式,最終將主執行緒加入訊息迴圈。

我們在分析AMS的setSystemProcess時曾分析過ActivityThread的attach函式,那時傳入的引數值為true。現在來看設定其為false的情況:

[-->ActivityThread.java::attach]

private void attach(boolean system) {

  sThreadLocal.set(this);

  mSystemThread = system;

   if(!system) {

      ViewRootImpl.addFirstDrawHandler(new Runnable() {

          public void run() {

             ensureJitEnabled();

           }

         });

       //設定在DDMS中看到的本程式的名字為"<pre-initialized>"

      android.ddm.DdmHandleAppName.setAppName("<pre-initialized>");

       //設定RuntimeInit的mApplicationObject引數,後續會介紹RuntimeInit類

      RuntimeInit.setApplicationObject(mAppThread.asBinder());

       //獲取和AMS互動的Binder客戶端

      IActivityManager mgr = ActivityManagerNative.getDefault();

       try {

            //①呼叫AMS的attachApplication,mAppThread為ApplicationThread型別,

            //它是應用程式和AMS互動的介面

             mgr.attachApplication(mAppThread);

         }......

   } else......// system process的處理

 

   ViewRootImpl.addConfigCallback(newComponentCallbacks2()

   {.......//新增回撥函式});

}

我們知道,AMS建立一個應用程式後,會設定一個超時時間(一般是10秒)。如果超過這個時間,應用程式還沒有和AMS互動,則斷定該程式建立失敗。所以,應用程式啟動後,需要儘快和AMS互動,即呼叫AMS的attachApplication函式。在該函式內部將呼叫attachApplicationLocked,所以此處直接分析attachApplicationLocked,先看其第一階段的工作。

(1) attachApplicationLocked分析之一

[-->ActivityManagerService.java::attachApplicationLocked]

private final booleanattachApplicationLocked(IApplicationThread thread,

           int pid) {//此pid代表呼叫程式的pid

   ProcessRecord app;

    if (pid != MY_PID && pid >= 0) {

        synchronized (mPidsSelfLocked) {

           app = mPidsSelfLocked.get(pid);//根據pid查詢對應的ProcessRecord物件

        }

    }else    app = null;

 

    /*

    如果該應用程式由AMS啟動,則它一定在AMS中有對應的ProcessRecord,讀者可回顧前面建立

    應用程式的程式碼:AMS先建立了一個ProcessRecord物件,然後才發命令給Zygote。

    如果此處app為null,表示AMS沒有該程式的記錄,故需要“殺死”它

   */

    if (app== null) {

       if(pid > 0 && pid != MY_PID) //如果pid大於零,且不是SystemServer程式,則

           //Quietly(即不列印任何輸出)”殺死”process

           Process.killProcessQuiet(pid);

       else{

           //呼叫ApplicationThread的scheduleExit函式。應用程式完成處理工作後

           //將退出執行

           //為何不像上面一樣直接殺死它呢?可查閱linux pid相關的知識並自行解答

          thread.scheduleExit();

        }

      returnfalse;

   }

   /*

     判斷app的thread是否為空,如果不為空,則表示該ProcessRecord物件還未和一個

     應用程式繫結。注意,app是根據pid查詢到的,如果舊程式沒有被殺死,系統則不會重用

     該pid。為什麼此處會出現ProcessRecord thread不為空的情況呢?見下面程式碼的註釋說明

   */

   if(app.thread != null)  handleAppDiedLocked(app, true, true);

   StringprocessName = app.processName;

   try {

          /*

          建立一個應用程式訃告接收物件。當應用程式退出時,該物件的binderDied將被調

          用。這樣,AMS就能做相應處理。binderDied函式將在另外一個執行緒中執行,其內部也會

          呼叫handleAppDiedLocked。假如使用者在binderDied被呼叫之前又啟動一個程式,

          那麼就會出現以上程式碼中app.thread不為null的情況。這是多執行緒環境中常出現的

          情況,不熟悉多執行緒程式設計的讀者要仔細體會。

          */

         AppDeathRecipient adr = new AppDeathRecipient(pp, pid, thread);

         thread.asBinder().linkToDeath(adr, 0);

         app.deathRecipient = adr;

    }......

   //設定該程式的排程優先順序和oom_adj等成員

   app.thread= thread;

  app.curAdj = app.setAdj = -100;

  app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;

  app.setSchedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;

  app.forcingToForeground = null;

  app.foregroundServices = false;

  app.hasShownUi = false;

  app.debugging = false;

   //啟動成功,從訊息佇列中撤銷PROC_START_TIMEOUT_MSG訊息

  mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);

attachApplicationLocked第一階段的工作比較簡單:

·  設定代表該應用程式的ProcessRecrod物件的一些成員變數,例如用於和應用程式互動的thread物件、程式排程優先順序及oom_adj的值等。

·  從訊息佇列中撤銷PROC_START_TIMEOUT_MSG。

至此,該程式啟動成功,但是這一階段的工作僅針對程式本身(如設定排程優先順序,oom_adj等),還沒有涉及和Activity啟動相關的內容,這部分工作將在第二階段完成。

(2) attachApplicationLocked分析之二

[-->ActivityManagerService.java::attachApplicationLocked]

   ......

   //SystemServer早就啟動完畢,所以normalMode為true

   booleannormalMode = mProcessesReady || isAllowedWhileBooting(app.info);

 

   /*

    我們在6.2.3的標題1中分析過generateApplicationProvidersLocked函式,

    在該函式內部將查詢(根據程式名,uid確定)PKMS以獲取需執行在該程式中的ContentProvider

   */

   Listproviders = normalMode ? generateApplicationProvidersLocked(app) : null;

   try {

         int testMode = IApplicationThread.DEBUG_OFF;

          if(mDebugApp != null && mDebugApp.equals(processName)) {

               ......//處理debug選項

          }

          ......//處理Profile

 

          boolean isRestrictedBackupMode = false;

          ......//

          //dex化對應的apk包

          ensurePackageDexOpt(app.instrumentationInfo!= null ?

                app.instrumentationInfo.packageName : app.info.packageName);

         //如果設定了Instrumentation類,該類所在的Package也需要dex化

         if(app.instrumentationClass != null)

              ensurePackageDexOpt(app.instrumentationClass.getPackageName());

         

          ApplicationInfo appInfo =app.instrumentationInfo != null

                             ? app.instrumentationInfo :app.info;

         //查詢該Application使用的CompatibiliyInfo

         app.compat =compatibilityInfoForPackageLocked(appInfo);

         if (profileFd != null) //用於記錄效能檔案

               profileFd = profileFd.dup();

 

           //①通過ApplicationThread和應用程式互動,呼叫其bindApplication函式

            thread.bindApplication(processName,appInfo, providers,

                   app.instrumentationClass, profileFile, profileFd,

                    profileAutoStop,app.instrumentationArguments,

                    app.instrumentationWatcher,testMode,

                   isRestrictedBackupMode || !normalMode, app.persistent,

                   mConfiguration, app.compat, getCommonServicesLocked(),

                   mCoreSettingsObserver.getCoreSettingsLocked());

 

           //updateLruProcessLocked函式以後再作分析

           updateLruProcessLocked(app,false, true);

           //記錄兩個時間

           app.lastRequestedGc= app.lastLowMemory = SystemClock.uptimeMillis();

   }......//try結束

 

..//從mProcessesOnHold和mPersistentStartingProcesses中刪除相關資訊

   mPersistentStartingProcesses.remove(app);

  mProcessesOnHold.remove(app);

由以上程式碼可知,第二階段的工作主要是為呼叫ApplicationThread的bindApplication做準備,將在後面的章節中分析該函式的具體內容。此處先來看它的原型。

/*

   正如我們在前面分析時提到的,剛建立的這個程式並不知道自己的歷史使命是什麼,甚至連自己的

   程式名都不知道,只能設為"<pre-initialized>"。其實,Android應用程式的歷史使命是

   AMS在其啟動後才賦予它的,這一點和我們理解的一般意義上的程式不太一樣。根據之前的介紹,   Android的元件應該執行在Android執行環境中。從OS角度來說,該執行環境需要和一個程式繫結。

   所以,建立應用程式這一步只是建立了一個能執行Android執行環境的容器,而我們的工作實際上

   還遠未結束。

   bindApplication的功能就是建立並初始化位於該程式中的Android執行環境

*/

public final void bindApplication(

       StringprocessName,//程式名,一般是package名

      ApplicationInfo appInfo,//該程式對應的ApplicationInfo

       List<ProviderInfo> providers,//在該APackage中宣告的Provider資訊

       ComponentName instrumentationName,//和instrumentation有關

       //下面3個引數和效能統計有關

       StringprofileFile,

      ParcelFileDescriptor profileFd, boolean autoStopProfiler,

      //這兩個和Instrumentation有關,在本例中,這幾個引數暫時都沒有作用

      Bundle instrumentationArgs,

       IInstrumentationWatcherinstrumentationWatcher,

       intdebugMode,//除錯模式

       boolean isRestrictedBackupMode,

       boolean persistent,//該程式是否是persist

       Configuration config,//當前的配置資訊,如螢幕大小和語言等

       CompatibilityInfocompatInfo,//相容資訊

       //AMS將常用的Service資訊傳遞給應用程式,目前傳遞的Service資訊只有PKMS、

       //WMS及AlarmManagerService。讀者可參看AMS getCommonServicesLocked函式

       Map<String,IBinder> services,

       BundlecoreSettings)//核心配置引數,目前僅有“long_press”值

對bindApplication的原型分析就到此為止,再來看attachApplicationLocked最後一階段的工作。

(3) attachApplicationLocked分析之三

[-->ActivityManagerService.java::attachApplicationLocked]

   booleanbadApp = false;

   booleandidSomething = false;

   /*

   至此,應用程式已經準備好了Android執行環境,下面這句呼叫程式碼將返回ActivityStack中

   第一個需要執行的ActivityRecord。由於多執行緒的原因,難道能保證得到的hr就是我們的目標

   Activity嗎?

   */

  ActivityRecord hr = mMainStack.topRunningActivityLocked(null);

  if (hr !=null && normalMode) {

       //需要根據processName和uid等確定該Activity是否執行與目標程式有關

       if(hr.app == null && app.info.uid == hr.info.applicationInfo.uid

            && processName.equals(hr.processName)) {

            try {

               //呼叫AS的realStartActivityLocked啟動該Activity,最後兩個引數為true

                if (mMainStack.realStartActivityLocked(hr, app, true, true)) {

                      didSomething = true;

                 }

            } catch (Exception e) {

                  badApp = true; //設定badApp為true

            }

      } else{

         //如果hr和目標程式無關,則呼叫ensureActivitiesVisibleLocked函式處理它

        mMainStack.ensureActivitiesVisibleLocked(hr, null, processName, 0);

       }

 }// if (hr!= null && normalMode)判斷結束

   //mPendingServices儲存那些因目標程式還未啟動而處於等待狀態的ServiceRecord

   if(!badApp && mPendingServices.size() > 0) {

       ServiceRecord sr = null;

        try{

           for (int i=0; i<mPendingServices.size(); i++) {

                sr = mPendingServices.get(i);

                //和Activity不一樣的是,如果Service不屬於目標程式,則暫不處理

                if (app.info.uid != sr.appInfo.uid

                     ||!processName.equals(sr.processName)) continue;//繼續迴圈

 

                   //該Service將執行在目標程式中,所以從mPendingService中移除它

                   mPendingServices.remove(i);

                   i--;

                   //處理此service的啟動,以後再作分析

                   realStartServiceLocked(sr, app);

                   didSomething = true;//設定該值為true

               }

           }

      }......

   ......//啟動等待的BroadcastReceiver

   ......//啟動等待的BackupAgent,相關程式碼類似Service的啟動

  if(badApp) {

   //如果以上幾個元件啟動有錯誤,則設定badApp為true。此處將呼叫handleAppDiedLocked

   //進行處理。該函式我們以後再作分析

   handleAppDiedLocked(app, false, true);

    returnfalse;

  }

   /*

   調整程式的oom_adj值。didSomething表示在以上流程中是否啟動了Acivity或其他元件。

   如果啟動了任一元件,則didSomething為true。讀者以後會知道,這裡的啟動只是向

   應用程式發出對應的指令,客戶端程式是否成功處理還是未知數。基於這種考慮,所以此處不宜

   馬上調節程式的oom_adj。

   讀者可簡單地把oom_adj看做一種優先順序。如果一個應用程式沒有執行任何元件,那麼當記憶體

   出現不足時,該程式是最先被系統殺死的。反之,如果一個程式執行的元件越多,那麼它就越不易被

   系統殺死以回收記憶體。updateOomAdjLocked就是根據該程式中元件的情況對應調節程式的

   oom_adj值的。

  */

   if(!didSomething)  updateOomAdjLocked();

   returntrue;

 }

attachApplicationLocked第三階段的工作就是通知應用程式啟動Activity和Service等元件,其中用於啟動Activity的函式是ActivityStack realStartActivityLocked。

此處先來分析應用程式的bindApplication,該函式將為應用程式繫結一個Application。

提示還記得AMS中System Context執行的兩次init嗎?第二次init的功能就是將Context和對應的Application繫結在一起。

(4) ApplicationThread的bindApplication分析

bindApplication在ApplicationThread中的實現,其程式碼如下:

[-->ActivityThread.java::bindApplication]

public final void bindApplication(......) {

 

   if(services != null)//儲存AMS傳遞過來的系統Service資訊

       ServiceManager.initServiceCache(services);

    //向主執行緒訊息佇列新增SET_CORE_SETTINGS訊息

   setCoreSettings(coreSettings);

     //建立一個AppBindData物件,其實就是用來儲存一些引數

    AppBindData data = new AppBindData();

    data.processName = processName;

    data.appInfo = appInfo;

    data.providers = providers;

    data.instrumentationName = instrumentationName;

    ......//將AMS傳過來的引數儲存到AppBindData中

     //向主執行緒傳送H.BIND_APPLICATION訊息

    queueOrSendMessage(H.BIND_APPLICATION, data);

 }

由以上程式碼可知,ApplicationThread接收到來自AMS的指令後,均會將指令中的引數封裝到一個資料結構中,然後通過傳送訊息的方式轉交給主執行緒去處理。BIND_APPLICATION最終將由handleBindApplication函式處理。該函式並不複雜,但是其中有些點是值得關注的,這些點主要是初始化應用程式的一些引數。handleBindApplication函式的程式碼如下:

[-->ActivityThread.java::handleBindApplication]

private void handleBindApplication(AppBindDatadata) {

   mBoundApplication = data;

   mConfiguration = new Configuration(data.config);

   mCompatConfiguration = new Configuration(data.config);

    //初始化效能統計物件

   mProfiler = new Profiler();

   mProfiler.profileFile = data.initProfileFile;

   mProfiler.profileFd = data.initProfileFd;

   mProfiler.autoStopProfiler = data.initAutoStopProfiler;

 

    //設定程式名。從此,之前那個默默無名的程式終於有了自己的名字

   Process.setArgV0(data.processName);

   android.ddm.DdmHandleAppName.setAppName(data.processName);

 

    if(data.persistent) {

       //對於persistent的程式,在低記憶體裝置上,不允許其使用硬體加速顯示

       Display display =

                 WindowManagerImpl.getDefault().getDefaultDisplay();

       //當記憶體大於512MB,或者螢幕尺寸大於1024*600,可以使用硬體加速

       if(!ActivityManager.isHighEndGfx(display))

           HardwareRenderer.disable(false);

     }

    //啟動效能統計

     if(mProfiler.profileFd != null)   mProfiler.startProfiling();

   //如果目標SDK版本小於12,則設定AsyncTask使用pool executor,否則使用

   //serializedexecutor。這些executor涉及Java Concurrent類,對此不熟悉的讀者

   //請自行學習和研究。

   if(data.appInfo.targetSdkVersion <= 12)

           AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

 

    //設定timezone

   TimeZone.setDefault(null);

    //設定語言

    Locale.setDefault(data.config.locale);

     //設定資源及相容模式

    applyConfigurationToResourcesLocked(data.config, data.compatInfo);

    applyCompatConfiguration();

 

      //根據傳遞過來的ApplicationInfo建立一個對應的LoadApk物件

     data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);

     //對於系統APK,如果當前系統為userdebug/eng版,則需要記錄log資訊到dropbox的日誌記錄

     if((data.appInfo.flags &

            (ApplicationInfo.FLAG_SYSTEM |

             ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0) {

           StrictMode.conditionallyEnableDebugLogging();

    }

    /*

    如目標SDK版本大於9,則不允許在主執行緒使用網路操作(如Socketconnect等),否則丟擲

    NetworkOnMainThreadException,這麼做的目的是防止應用程式在主執行緒中因網路操作執行

    時間過長而造成使用者體驗下降。說實話,沒有必要進行這種限制,在主執行緒中是否網路操作

    是應用的事情。再說,Socket也可作為程式間通訊的手段,在這種情況下,網路操作耗時很短。

    作為系統,不應該設定這種限制。另外,Goolge可以提供一些開發指南或規範來指導開發者,

    而不應如此蠻橫地強加限制。

    */

    if (data.appInfo.targetSdkVersion> 9)

        StrictMode.enableDeathOnNetwork();

 

    //如果沒有設定螢幕密度,則為Bitmap設定預設的螢幕密度

   if((data.appInfo.flags

               &ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) == 0)

    Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);

   if(data.debugMode != IApplicationThread.DEBUG_OFF){

    ......//除錯模式相關處理

   }

   IBinder b= ServiceManager.getService(Context.CONNECTIVITY_SERVICE);

  IConnectivityManager service =

                        IConnectivityManager.Stub.asInterface(b);

  try {

       //設定Http代理資訊

       ProxyPropertiesproxyProperties = service.getProxy();

      Proxy.setHttpProxySystemProperty(proxyProperties);

   } catch(RemoteException e) {}

 

   if(data.instrumentationName != null){

        //在正常情況下,此條件不滿足

   } else {

      //建立Instrumentation物件,在正常情況都再這個條件下執行

     mInstrumentation = new Instrumentation();

   }

   //如果Package中宣告瞭FLAG_LARGE_HEAP,則可跳過虛擬機器的記憶體限制,放心使用記憶體

   if((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0)

        dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();

 

   //建立一個Application,data.info為LoadedApk型別,在其內部會通過Java反射機制

   //建立一個在該APK AndroidManifest.xml中宣告的Application物件

   Applicationapp = data.info.makeApplication(

                                     data.restrictedBackupMode, null);

 //mInitialApplication儲存該程式中第一個建立的Application

  mInitialApplication = app;

 

   //安裝本Package中攜帶的ContentProvider

   if(!data.restrictedBackupMode){

      List<ProviderInfo> providers = data.providers;

        if(providers != null) {

               //installContentProviders我們已經分析過了

               installContentProviders(app, providers);

               mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);

           }

  }

   //呼叫Application的onCreate函式,做一些初始工作

  mInstrumentation.callApplicationOnCreate(app);

}

由以上程式碼可知,bindApplication函式將設定一些初始化引數,其中最重要的有:

·  建立一個Application物件,該物件是本程式中執行的第一個Application。

·  如果該Application有ContentProvider,則應安裝它們。

提示從以上程式碼可知,ContentProvider的建立就在bindApplication函式中,其時機早於其他元件的建立。

(5) 應用程式的建立及初始化總結

本節從應用程式的入口函式main開始,分析了應用程式和AMS之間的兩次重要互動,它們分別是:

·  在應用程式啟動後,需要儘快呼叫AMS的attachApplication函式,該函式是這個剛呱呱墜地的應用程式第一次和AMS互動。此時的它還默默“無名”,連一個確定的程式名都沒有。不過沒關係,attachApplication函式將根據建立該應用程式之前所儲存的ProcessRecord為其準備一切“手續”。

·  attachApplication準備好一切後,將呼叫應用程式的bindApplication函式,在該函式內部將發訊息給主執行緒,最終該訊息由handleBindApplication處理。handleBindApplication將為該程式設定程式名,初始化一些策略和引數資訊等。另外,它還建立一個Application物件。同時,如果該Application宣告瞭ContentProvider,還需要為該程式安裝ContentProvider。

提示這個流程有點類似生孩子,一般生之前需要到醫院去登記,生完後又需去註冊戶口,如此這般,這個孩子才會在社會有合法的身份。

6.  ActivityStack realStartActivityLocked分析

如前所述,AMS呼叫完bindApplication後,將通過realStartActivityLocked啟動Activity。在此之前,要建立完應用程式並初始化Android執行環境(除此之外,連ContentProvider都安裝好了)。

[-->ActivityStack.java::realStartActivityLocked]

//注意,在本例中該函式的最後兩個引數的值都為true

final booleanrealStartActivityLocked(ActivityRecord r, ProcessRecord app,

       boolean andResume, boolean checkConfig)   throws RemoteException {

 

   r.startFreezingScreenLocked(app, 0);

    mService.mWindowManager.setAppVisibility(r,true);

 

    if(checkConfig) {

       ......//處理Config發生變化的情況

      mService.updateConfigurationLocked(config, r, false);

    }

 

    r.app =app;

   app.waitingToKill = null;

    //將ActivityRecord加到ProcessRecord的activities中儲存

    int idx= app.activities.indexOf(r);

    if (idx< 0)   app.activities.add(r);

    //更新程式的排程優先順序等,以後再分析該函式

    mService.updateLruProcessLocked(app, true, true);

 

     try {

        List<ResultInfo> results = null;

        List<Intent> newIntents = null;

         if(andResume) {

               results = r.results;

               newIntents = r.newIntents;

           }

          if(r.isHomeActivity)  mService.mHomeProcess = app;

        //看看是否有dex對應Package的需要

         mService.ensurePackageDexOpt(

                   r.intent.getComponent().getPackageName());

        r.sleeping = false;

        r.forceNewConfig = false;

       ......

          //①通知應用程式啟動Activity

           app.thread. scheduleLaunchActivity (new Intent(r.intent), r,

                   System.identityHashCode(r), r.info, mService.mConfiguration,

                   r.compat, r.icicle, results, newIntents, !andResume,

                   mService.isNextTransitionForward(), profileFile, profileFd,

                    profileAutoStop);

           

           if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) {

               ......//處理heavy-weight的情況

               }

           }

     } ......//try結束

 

    r.launchFailed = false;

     ......

     if(andResume) {

         r.state = ActivityState.RESUMED;

         r.stopped = false;

        mResumedActivity = r;//設定mResumedActivity為目標Activity

        r.task.touchActiveTime();

         //新增該任務到近期任務列表中

         if(mMainStack)  mService.addRecentTaskLocked(r.task);

         //②關鍵函式,見下文分析

        completeResumeLocked(r);

         //如果在這些過程中,使用者按了Power鍵,怎麼辦?

        checkReadyForSleepLocked();

        r.icicle = null;

        r.haveState = false;

    }......

    //啟動系統設定嚮導Activity,當系統更新或初次使用時需要進行配置

    if(mMainStack)  mService.startSetupActivityLocked();

    returntrue;

 }

在以上程式碼中有兩個關鍵函式,分別是:scheduleLaunchActivity和completeResumeLocked。其中,scheduleLaunchActivity用於和應用程式互動,通知它啟動目標Activity。而completeResumeLocked將繼續AMS的處理流程。先來看第一個關鍵函式。

(1) scheduleLaunchActivity函式分析

[-->ActivityThread.java::scheduleLaunchActivity]

public final void scheduleLaunchActivity(Intentintent, IBinder token, int ident,

     ActivityInfo info, Configuration curConfig,CompatibilityInfo compatInfo,

     Bundlestate, List<ResultInfo> pendingResults,

    List<Intent> pendingNewIntents, boolean notResumed, booleanisForward,

     StringprofileName, ParcelFileDescriptor profileFd,

     booleanautoStopProfiler) {

  ActivityClientRecord r = new ActivityClientRecord();

   ......//儲存AMS傳送過來的引數資訊

   //向主執行緒傳送訊息,該訊息的處理在handleLaunchActivity中進行

  queueOrSendMessage(H.LAUNCH_ACTIVITY, r);

 }

[-->ActivityThread.java::handleMessage]

public void handleMessage(Message msg) {

  switch(msg.what) {

       case LAUNCH_ACTIVITY: {

        ActivityClientRecord r = (ActivityClientRecord)msg.obj;

        //根據ApplicationInfo得到對應的PackageInfo

        r.packageInfo = getPackageInfoNoCheck(

                           r.activityInfo.applicationInfo, r.compatInfo);

         //呼叫handleLaunchActivity處理

        handleLaunchActivity(r, null);

       }break;

......

}

[-->ActivityThread.java::handleLaunchActivity]

private voidhandleLaunchActivity(ActivityClientRecord r,

                             Intent customIntent){

   unscheduleGcIdler();

 

   if (r.profileFd != null) {......//略去}

 

  handleConfigurationChanged(null, null);

   /*

   ①建立Activity,通過Java反射機制建立目標Activity,將在內部完成Activity生命週期

   的前兩步,即呼叫其onCreate和onStart函式。至此,我們的目標com.dfp.test.TestActivity

   建立完畢

   */

   Activitya = performLaunchActivity(r, customIntent);

   if (a !=null) {

     r.createdConfig = new Configuration(mConfiguration);

      BundleoldState = r.state;

      //②呼叫handleResumeActivity,其內部有個關鍵點,見下文分析

     handleResumeActivity(r.token, false, r.isForward);

      if(!r.activity.mFinished && r.startsNotResumed) {

         .......//

.       r.paused = true;

       }else {

            //如果啟動錯誤,通知AMS

           ActivityManagerNative.getDefault()

                    .finishActivity(r.token,Activity.RESULT_CANCELED, null);

      }

  }

handleLaunchActivity的工作包括:

·  首先呼叫performLaunchActivity,該在函式內部通過Java反射機制建立目標Activity,然後呼叫它的onCreate及onStart函式。

·  呼叫handleResumeActivity,會在其內部呼叫目標Activity的onResume函式。除此之外,handleResumeActivity還完成了一件很重要的事情,見下面的程式碼:

[-->ActivityThread.java::handleResumeActivity]

final void handleResumeActivity(IBinder token,boolean clearHide,

                                     booleanisForward) {

 unscheduleGcIdler();

  //內部呼叫目標Activity的onResume函式

 ActivityClientRecord r = performResumeActivity(token, clearHide);

 

  if (r !=null) {

   finalActivity a = r.activity;

 

   final int forwardBit = isForward ?

          WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;

   ......

   if(!r.onlyLocalRequest) {

       //將上面完成onResume的Activity儲存到mNewActivities中

      r.nextIdle = mNewActivities;

      mNewActivities = r;

      //①向訊息佇列中新增一個Idler物件

     Looper.myQueue().addIdleHandler(new Idler());

   }

   r.onlyLocalRequest = false;

   ......

 }

根據第2章對MessageQueue的分析,當訊息佇列中沒有其他要處理的訊息時,將處理以上程式碼中通過addIdleHandler新增的Idler物件,也就是說,Idler物件的優先順序最低,這是不是說它的工作不重要呢?非也。至少在handleResumeActivity函式中新增的這個Idler並不不簡單,其程式碼如下:

[-->ActivityThread.java::Idler]

private class Idler implements MessageQueue.IdleHandler{

    publicfinal boolean queueIdle() {

   ActivityClientRecord a = mNewActivities;

    booleanstopProfiling = false;

    ......

   if (a !=null) {

    mNewActivities = null;

    IActivityManager am = ActivityManagerNative.getDefault();

    ActivityClientRecord prev;

    do {

       if(a.activity != null && !a.activity.mFinished) {

          //呼叫AMS的activityIdle

          am.activityIdle(a.token, a.createdConfig, stopProfiling);

          a.createdConfig = null;

      }

     prev =a;

     a =a.nextIdle;

    prev.nextIdle = null;

    } while(a != null); //do迴圈結束

  }//if(a!=null)判斷結束

   ......

   ensureJitEnabled();

    returnfalse;

   }// queueIdle函式結束

 }

由以上程式碼可知,Idler將為那些已經完成onResume的Activity呼叫AMS的activityIdle函式。該函式是Activity成功建立並啟動的流程中與AMS互動的最後一步。雖然對應用程式來說,Idler處理的優先順序最低,但AMS似乎不這麼認為,因為它還設定了超時等待,以處理應用程式沒有及時呼叫activityIdle的情況。這個超時等待即由realStartActivityLocked中最後一個關鍵點completeResumeLocked函式設定。

(2) completeResumeLocked函式分析

[-->ActivityStack.java::completeResumeLocked]

private final voidcompleteResumeLocked(ActivityRecord next) {

   next.idle = false;

   next.results = null;

   next.newIntents = null;

   //傳送一個超時處理訊息,預設為10秒。IDLE_TIMEOUT_MSG就是針對acitivityIdle函式的

    Messagemsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG);

    msg.obj= next;

   mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT);

     //通知AMS

     if(mMainStack)   mService.reportResumedActivityLocked(next);

     ......//略去其他邏輯的程式碼

}

由以上程式碼可知,AMS給了應用程式10秒的時間,希望它在10秒內呼叫activityIdle函式。這個時間不算長,和前面AMS等待應用程式啟動的超時時間一樣。所以,筆者有些困惑,為什麼要把這麼重要的操作放到idler中去做。

下面來看activityIdle函式,在其內部將呼叫ActivityStack activityIdleInternal。

(3) activityIdleInternal函式分析

[-->ActivityStack.java::activityIdleInternal]

final ActivityRecord activityIdleInternal(IBindertoken, boolean fromTimeout,

                                      Configuration config) {

   /*

   如果應用程式在超時時間內呼叫了activityIdleInternal函式,則fromTimeout為false

   否則,一旦超時,在IDLE_TIMEOUT_MSG的訊息處理中也會呼叫該函式,並設定fromTimeout

   為true

   */

   ActivityRecord res = null;

 

  ArrayList<ActivityRecord> stops = null;

  ArrayList<ActivityRecord> finishes = null;

  ArrayList<ActivityRecord> thumbnails = null;

   int NS =0;

   int NF =0;

   int NT =0;

  IApplicationThread sendThumbnail = null;

   booleanbooting = false;

   booleanenableScreen = false;

 

  synchronized (mService) {

     //從訊息佇列中撤銷IDLE_TIMEOUT_MSG

     if(token != null)   mHandler.removeMessages(IDLE_TIMEOUT_MSG, token);

      int index= indexOfTokenLocked(token);

      if(index >= 0) {

        ActivityRecord r = mHistory.get(index);

         res =r;

        //注意,只有fromTimeout為true,才會走執行下面的條件語句

        if(fromTimeout) reportActivityLaunchedLocked(fromTimeout, r, -1, -1);

        if(config != null)  r.configuration =config;

        /*

        mLaunchingActivity是一個WakeLock,它能防止在操作Activity過程中掉電,同時

        這個WakeLock又不能長時間使用,否則有可能耗費過多電量。所以,系統設定了一個超時

        處理訊息LAUNCH_TIMEOUT_MSG,超時時間為10秒。一旦目標Activity啟動成功,

        就需要需要釋放 WakeLock

        */

        if(mResumedActivity == r && mLaunchingActivity.isHeld()) {

            mHandler.removeMessages(LAUNCH_TIMEOUT_MSG);

            mLaunchingActivity.release();

         }

       r.idle = true;

       mService.scheduleAppGcsLocked();

       ......

       ensureActivitiesVisibleLocked(null, 0);

     if(mMainStack) {

         if(!mService.mBooted) {

            mService.mBooted = true;

             enableScreen = true;

          }

       }//if (mMainStack)判斷結束

   } else if(fromTimeout) {//注意,只有fromTimeout為true,才會走下面的case

       reportActivityLaunchedLocked(fromTimeout, null, -1, -1);

  }

  /*

   ①processStoppingActivitiesLocked函式返回那些因本次Activity啟動而

   被暫停(paused)的Activity

  */

  stops =processStoppingActivitiesLocked(true);

 ......

   for (i=0;i<NS; i++) {

      ActivityRecord r = (ActivityRecord)stops.get(i);

       synchronized (mService) {

      //如果這些Acitivity 處於finishing狀態,則通知它們執行Destroy操作,最終它們

     //的onDestroy函式會被呼叫

       if(r.finishing) finishCurrentActivityLocked(r, FINISH_IMMEDIATELY);

       else  //否則將通知它們執行stop操作,最終Activity的onStop被呼叫

         stopActivityLocked(r);

       }//synchronized結束

  }//for迴圈結束

 

   ......//處理等待結束的Activities

 

   //傳送ACTION_BOOT_COMPLETED廣播

   if(booting)  mService.finishBooting();

   ......

   returnres;

 }

在activityIdleInternal中有一個非常重要的關鍵點,即處理那些因為本次Activity啟動而被暫停的Activity。有兩種情況需考慮:

·  如果被暫停的Activity處於finishing狀態(例如Activity在其onStop中呼叫了finish函式),則呼叫finishCurrentActivityLocked。

·  否則,要呼叫stopActivityLocked處理暫停的Activity。

此處涉及除AMS和目標程式外的第三個程式,即被切換到後臺的那個程式。不過至此,我們的目標Activity終於正式登上了歷史舞臺。

提示本例的分析結束了嗎?沒有。因為am設定了-W選項,所以其實我們還在startActivityAndWait函式中等待結果。ActivityStack中有兩個函式能夠觸發AMS notifyAll,一個是reportActivityLaunchedLocked,另一個是reportActivityVisibleLocked。前面介紹的activityInternal函式只在fromTimeout為true時才會呼叫reportActivityLaunchedLocked,而本例中fromTimeout為false,如何是好?該問題的解答非常複雜,姑且先一語帶過:當Activity顯示出來時,其在AMS中對應ActivityRecord物件的windowVisible函式將被呼叫,其內部會觸發reportActivityLaunchedLocked函式,這樣我們的startActivityAndWait才能被喚醒。

7.  startActivity分析之後半程總結

總結startActivity後半部分的流程,主要涉及目標程式和AMS的互動,如圖6-15所示。


圖6-15  startActivity後半程總結

圖6-15中涉及16個重要函式呼叫,而且這僅是startActivity後半部分的呼叫流程,可見整個流程有多麼複雜!

8. startPausingLocked函式分析

現在我們分析圖6-14中的startPausingLocked分支。根據前面的介紹,當啟動一個新Activity時,系統將先行處理當前的Activity,即呼叫startPausingLocked函式來暫停當前Activity。

(1) startPausingLocked分析

[-->ActivityStack.java::startPausingLocked]

private final void startPausingLocked(booleanuserLeaving, boolean uiSleeping) {

   //mResumedActivity儲存當前正顯示的Activity,

  ActivityRecord prev = mResumedActivity;

  mResumedActivity = null;

 

   //設定mPausingActivity為當前Activity

  mPausingActivity = prev;

  mLastPausedActivity = prev;

 

  prev.state = ActivityState.PAUSING;//設定狀態為PAUSING

  prev.task.touchActiveTime();

   ......

   if(prev.app != null && prev.app.thread != null) {

     try {

        //①呼叫當前Activity所在程式的schedulePauseActivity函式

        prev.app.thread.schedulePauseActivity(prev,prev.finishing,

                                userLeaving,prev.configChangeFlags);

       if(mMainStack) mService.updateUsageStats(prev, false);

     } ......//catch分支

    }......//else分支

 

   if(!mService.mSleeping && !mService.mShuttingDown) {

        //獲取WakeLock,以防止在Activity切換過程中掉電

       mLaunchingActivity.acquire();

        if(!mHandler.hasMessages(LAUNCH_TIMEOUT_MSG)) {

            Message msg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG);

            mHandler.sendMessageDelayed(msg, LAUNCH_TIMEOUT);

        }

   }

 

    if(mPausingActivity != null) {

      //暫停輸入事件派發

      if(!uiSleeping) prev.pauseKeyDispatchingLocked();

     //設定PAUSE超時,時間為500毫秒,這個時間相對較短

     Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG);

     msg.obj = prev;

     mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT);

    }......//else分支

 }

startPausingLocked將呼叫應用程式的schedulePauseActivity函式,並設定500毫秒的超時時間,所以應用程式需儘快完成相關處理。和scheduleLaunchActivity一樣,schedulePauseActivity將向ActivityThread主執行緒傳送PAUSE_ACTIVITY訊息,最終該訊息由handlePauseActivity來處理。

(2) handlePauseActivity分析

[-->ActivityThread.java::handlePauseActivity]

private void handlePauseActivity(IBinder token,boolean finished,

                               boolean userLeaving, int configChanges){

  //當Activity處於finishing狀態時,finished引數為true,不過在本例中該值為false

  ActivityClientRecord r = mActivities.get(token);

   if (r !=null) {

      //呼叫Activity的onUserLeaving函式,

      if(userLeaving) performUserLeavingActivity(r);

      r.activity.mConfigChangeFlags |=configChanges;

      //呼叫Activity的onPause函式

     performPauseActivity(token, finished, r.isPreHoneycomb());

      ......

      try {

             //呼叫AMS的activityPaused函式

             ActivityManagerNative.getDefault().activityPaused(token);

         }......

    }

 }

[-->ActivityManagerService.java::activityPaused]

public final void activityPaused(IBinder token) {

   ......

   mMainStack.activityPaused(token, false);

}

[-->ActivityStack.java::activityPaused]

final void activityPaused(IBinder token, booleantimeout) {

 ActivityRecord r = null;

 

  synchronized (mService) {

   int index= indexOfTokenLocked(token);

   if (index>= 0) {

        r =mHistory.get(index);

         //從訊息佇列中撤銷PAUSE_TIMEOUT_MSG訊息

        mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);

         if(mPausingActivity == r) {

            r.state = ActivityState.PAUSED;//設定ActivityRecord的狀態

            completePauseLocked();//完成本次Pause操作

        }......

   }

 }

(3) completePauseLocked分析

[-->ActivityStack.java::completePauseLocked]

private final void completePauseLocked() {

  ActivityRecord prev = mPausingActivity;

   if (prev!= null) {

     if(prev.finishing) {

          prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE);

      } elseif (prev.app != null) {

        if(prev.configDestroy) {

             destroyActivityLocked(prev, true, false);

         } else {

          //①將剛才被暫停的Activity儲存到mStoppingActivities中

          mStoppingActivities.add(prev);

          if(mStoppingActivities.size() > 3) {

            //如果被暫停的Activity超過3個,則傳送IDLE_NOW_MSG訊息,該訊息最終

           //由我們前面介紹的activeIdleInternal處理

             scheduleIdleLocked();

            }

         }

      //設定mPausingActivity為null,這是圖6-14②、③分支的分割點

       mPausingActivity = null;

    }

     //②resumeTopActivityLocked將啟動目標Activity

    if(!mService.isSleeping()) resumeTopActivityLocked(prev);

 

   ......

}

就本例而言,以上程式碼還算簡單,最後還是通過resumeTopActivityLocked來啟動目標Activity。當然,由於之前已經設定了mPausingActivity為null,所以最終會走到圖6-14中③的分支。

(4) stopActivityLocked分析

根據前面的介紹,此次目標Activity將走完onCreate、onStart和onResume流程,但是被暫停的Activity才剛走完onPause流程,那麼它的onStop什麼時候呼叫呢?

答案就在activityIdelInternal中,它將為mStoppingActivities中的成員呼叫stopActivityLocked函式。

[-->ActivityStack.java::stopActivityLocked]

 privatefinal void stopActivityLocked(ActivityRecord r) {

   if((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0

           || (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) {

       if(!r.finishing) {

           requestFinishActivityLocked(r, Activity.RESULT_CANCELED, null,

                       "no-history");

         }

     } elseif (r.app != null && r.app.thread != null) {

         try {

         r.stopped = false;

          //設定STOPPING狀態,並呼叫對應的scheduleStopActivity函式

          r.state = ActivityState.STOPPING;

          r.app.thread.scheduleStopActivity(r, r.visible,

                     r.configChangeFlags);

    }......

  }

對應程式的scheduleStopActivity函式將根據visible的情況,向主執行緒訊息迴圈傳送H. STOP_ACTIVITY_HIDE或H. STOP_ACTIVITY_SHOW訊息。不論哪種情況,最終都由handleStopActivity來處理。

[-->ActivityThread.java::handleStopActivity]

private void handleStopActivity(IBinder token,boolean show, int configChanges) {

 ActivityClientRecord r = mActivities.get(token);

 r.activity.mConfigChangeFlags |= configChanges;

 

  StopInfoinfo = new StopInfo();

  //呼叫Activity的onStop函式

 performStopActivityInner(r, info, show, true);

 ......

  try { //呼叫AMS的activityStopped函式

        ActivityManagerNative.getDefault().activityStopped(

               r.token, r.state, info.thumbnail, info.description);

   }

}

AMS沒有為stop設定超時訊息處理。嚴格來說,還是有超時限制的,只是這個超時處理與activityIdleInternal結合起來了。

(5) startPausingLocked總結

總結startPausingLocked流程,如圖6-16所示。


圖6-16  startPausingActivity流程總結

圖6-16比較簡單,讀者最好結合程式碼再把流程走一遍,以加深理解。

9.  startActivity總結

Activity的啟動就介紹到這裡。這一路分析下來,相信讀者也和筆者一樣覺得此行絕不輕鬆。先回顧一下此次旅程:

·   行程的起點是am。am是Android中很重要的程式,讀者務必要掌握它的用法。我們利用am start命令,發起本次目標Activity的啟動請求。

·  接下來進入ActivityManagerService和ActivityStack這兩個核心類。對於啟動Activity來說,這段行程又可分細分為兩個階段:第一階段的主要工作就是根據啟動模式和啟動標誌找到或建立ActivityRecord及對應的TaskRecord;第二階段工作就是處理Activity啟動或切換相關的工作。

·  首先討論了AMS直接建立目標程式並執行Activity的流程,其中涉及目標程式的建立,在目標程式中Android執行環境的初始化,目標Activity的建立以及觸發onCreate、onStart及onResume等其生命週期中重要函式呼叫等相關知識點。

·  接著又討論了AMS先pause當前Activity,然後再建立目標程式並執行Activity的流程。其中牽扯到兩個應用程式和AMS的互動,其難度之大可見一斑。

讀者在閱讀本節時,務必要區分此旅程中兩個階段工作的重點:其一是找到合適的ActivityRecord和TaskRecord;其二是排程相關程式進行Activity切換。在SDK文件中,介紹最為詳細的是第一階段中系統的處理策略,例如啟動模式、啟動標誌的作用等。第二階段工作其實是與Android元件排程相關的工作。SDK文件只是針對單個Activity進行生命週期方面的介紹。

坦誠地說,這次旅程略過不少邏輯情況。原因有二,一方面受限於精力和篇幅,另方面是作為排程核心類,和AMS相關的程式碼及處理邏輯非常複雜,而且其間還夾雜了與WMS的互動邏輯,使複雜度更甚。再者,筆者個人感覺這部分程式碼絕談不上高效、嚴謹和美觀,甚至有些醜陋(在分析它們的過程中,遠沒有研究Audio、Surface時那種暢快淋漓的感覺)。

此處列出幾個供讀者深入研究的點:

·  各種啟動模式、啟動標誌的處理流程。

·  Configuration發生變化時Activity的處理,以及在Activity中對狀態儲存及恢復的處理流程。

·  Activity生命週期各個階段的轉換及相關處理。Android 2.3以後新增的與Fragment的生命週期相關的轉換及處理。

建議在研究程式碼前,先仔細閱讀SDK文件相關內容,以獲取必要的感性認識,否則直接看程式碼很容易迷失方向。

6.4  Broadcast和BroadcastReceiver分析

Broadcast,漢語意思為“廣播”。它是Android平臺中的一種通知機制。從廣義來說,它是一種程式間通訊的手段。有廣播,就對應有廣播接收者。Android中四大元件之一的BroadcastReceiver即代表廣播接收者。目前,系統提供兩種方式來宣告一個廣播接收者。

·  在AndroidManifest.xml中宣告<receiver>標籤。在應用程式執行時,系統會利用Java反射機制構造一個廣播接收者例項。本書將這種廣播接收者稱為靜態註冊者或靜態接收者。

·  在應用程式執行過程中,可呼叫Context提供的registerReceiver函式註冊一個廣播接收者例項。本書將這種廣播接收者稱為動態註冊者或動態接收者。與之相對應,當應用程式不再需要監聽廣播時(例如當應用程式退到後臺時),則要呼叫unregisterReceiver函式撤銷之前註冊的BroadcastReceiver例項。

當系統將廣播派發給對應的廣播接收者時,廣播接收者的onReceive函式會被呼叫。在此函式中,可對該廣播進行相應處理。

另外,Android定義了三種不同型別的廣播傳送方式,它們分別是:

·  普通廣播傳送方式,由sendBroadcast及相關函式傳送。以工作中的場景為例,當程式設計師們正埋頭工作之時,如果有人大喊一聲“吃午飯去”,前刻還在專心編碼的人即作鳥獸散。這種方式即為普通廣播傳送方式,所有對“吃午飯”感興趣的接收者都會響應。

·  序列廣播傳送方式,即ordered廣播,由sendOrdedBroadcast及相關函式傳送。在該型別方式下,按接收者的優先順序將廣播一個個地派發給接收者。只有等這一個接收者處理完畢,系統才將該廣播派發給下一個接收者。其中,任意一個接收者都可以中止後續的派發流程。還是以工作中的場景為例:經常有專案經理(PM)深夜組織一幫人跟蹤bug的狀態。PM看見一個bug,問某程式設計師,“這個bug你能改嗎?”如果得到的答案是“暫時不會”或“暫時沒時間”,他會將目光轉向下一個神情輕鬆者,直到找到一個擔當者為止。這種方式即為ordered廣播傳送方式,很明顯,它的特點是“一個一個來”。

·  Sticky廣播傳送方式,由sendStickyBroadcast及相關函式傳送。Sticky的意思是“粘”,其背後有一個很重要的考慮。我們舉個例子:假設某廣播傳送者每5秒傳送一次攜帶自己狀態資訊的廣播,此時某個應用程式註冊了一個動態接收者來監聽該廣播,那麼該接收者的OnReceive函式何時被呼叫呢?在正常情況下需要等這一輪的5秒週期結束後才呼叫(因為傳送者在本週期結束後會主動再發一個廣播)。而在Sticky模式下,系統將馬上派發該廣播給剛註冊的接收者。注意,這個廣播是系統傳送的,其中儲存的是上一次廣播傳送者的狀態資訊。也就是說,在Sticky模式下,廣播接收者能立即得到廣播傳送者的資訊,而不用等到這一輪週期結束。其實就是系統會儲存Sticky的廣播[⑤],當有新廣播接收者來註冊時,系統就把Sticky廣播發給它。

以上我們對廣播及廣播接收者做了一些簡單介紹,讀者也可參考SDK文件中的相關說明來加強理解。

下面將以動態廣播接收者為例,分析Android對廣播的處理流程。

6.4.1  registerReceiver流程分析

1.  ContextImpl registerReceiver分析

registerReceiver函式用於註冊一個動態廣播接收者,該函式在Context.java中宣告。根據本章前面對Context家族的介紹(參考圖6-3),其功能最終將通過ContextImpl類的registerReceiver函式來完成,可直接去看ContextImpl是如何實現此函式的。在SDK中一共定義了兩個同名的registerReceiver函式,其程式碼如下:

[-->ContextImpl.java::registerReceiver]

/*

  在SDK中輸出該函式,這也是最常用的函式。當廣播到來時,BroadcastReceiver物件的onReceive

  函式將在主執行緒中被呼叫

*/

public Intent registerReceiver(BroadcastReceiverreceiver, IntentFilter filter) {

    returnregisterReceiver(receiver, filter, null, null);

 }

/*

   功能和前面類似,但增加了兩個引數,分別是broadcastPermission和scheduler,作用有

   兩個:

   其一:對廣播者的許可權增加了控制,只有擁有相應許可權的廣播者發出的廣播才能被此接收者接收

   其二:BroadcastReceiver物件的onReceiver函式可排程到scheduler所在的執行緒中執行

*/

 publicIntent registerReceiver(BroadcastReceiver receiver,

       IntentFilterfilter, String broadcastPermission, Handler scheduler) {

/*

 注意,下面所呼叫函式的最後一個引數為getOuterContext的返回值。前面曾說過,ContextImpl為Context家族中真正幹活的物件,而它對外的代理人可以是Application和Activity等,

getOuterContext就返回這個對外代理人。一般在Activity中呼叫registerReceiver函式,故此處getOuterContext返回的對外代理人的型別就是Activity。

*/

     returnregisterReceiverInternal(receiver, filter, broadcastPermission,

               scheduler, getOuterContext());

}

殊途同歸,最終的功能由registerReceiverInternal來完成,其程式碼如下:

[-->ContextImpl.java::registerReceiverInternal]

 privateIntent registerReceiverInternal(BroadcastReceiver receiver,

      IntentFilter filter, String broadcastPermission, Handler scheduler,

      Context context) {

   IIntentReceiver rd = null;

   if(receiver != null) {

        //①準備一個IIntentReceiver物件

        if(mPackageInfo != null && context != null) {

          //如果沒有設定scheduler,則預設使用主執行緒的Handler

          if (scheduler == null)   scheduler= mMainThread.getHandler();

          //通過getReceiverDispatcher函式得到一個IIntentReceiver型別的物件

          rd = mPackageInfo.getReceiverDispatcher(

             receiver, context, scheduler, mMainThread.getInstrumentation(),

             true);

        } else {

          if (scheduler == null)  scheduler= mMainThread.getHandler();

          //直接建立LoadedApk.ReceiverDispatcher物件

          rd = new LoadedApk.ReceiverDispatcher(receiver, context, scheduler,

                   null, true).getIIntentReceiver();

         }//if (mPackageInfo != null && context != null)結束

     }// if(receiver != null)結束

  try {

        //②呼叫AMS的registerReceiver函式

       return ActivityManagerNative.getDefault().registerReceiver(

                     mMainThread.getApplicationThread(),mBasePackageName,

                     rd, filter,broadcastPermission);

        } ......

 }

以上程式碼列出了兩個關鍵點:其一是準備一個IIntentReceiver物件;其二是呼叫AMS的registerReceiver函式。

先來看IIntentReceiver,它是一個Interface,圖6-17列出了和它相關的成員圖譜。


圖6-17  IIntentReceiver相關成員示意圖

由圖6-17可知:

·  BroadcastReceiver內部有一個PendingResult類。該類是Android 2.3以後新增的,用於非同步處理廣播訊息。例如,當BroadcastReceiver收到一個廣播時,其onReceive函式將被呼叫。一般都是在該函式中直接處理該廣播。不過,當該廣播處理比較耗時,還可採用非同步的方式進行處理,即先呼叫BroadcastReceiver的goAsync函式得到一個PendingResult物件,然後將該物件放到工作執行緒中去處理,這樣onReceive就可以立即返回而不至於耽誤太長時間(這一點對於onReceive函式被主執行緒呼叫的情況尤為有用)。在工作執行緒處理完這條廣播後,需呼叫PendingResult的finish函式來完成整個廣播的處理流程。

·  廣播由AMS發出,而接收及處理工作卻在另外一個程式中進行,整個過程一定涉及程式間通訊。在圖6-17中,雖然在BroadcastReceiver中定義了一個廣播接收者,但是它與Binder沒有有任何關係,故其並不直接參與程式間通訊。與之相反,IIntentReceiver介面則和Binder有密切關係,故可推測廣播的接收是由IIntentReceiver介面來完成的。確實,在整個流程中,首先接收到來自AMS的廣播的將是該介面的Bn端,即LoadedApk.ReceiverDispather的內部類InnerReceiver。

接收廣播的處理將放到本節最後再來分析,下面先來看AMS 的registerReceiver函式。

2.  AMS的registerReceiver分析

AMS的registerReceiver函式比較簡單,但是由於其中將出現一些新的變數型別和成員,因此接下來按分兩部分進行分析。

(1) registerReceiver分析之一

registerReceiver的返回值是一個Intent,它指向一個匹配過濾條件(由filter引數指明)的Sticky Intent。即使有多個符合條件的Intent,也只返回一個。

[-->ActivityManagerService.java::registerReceiver]

public Intent registerReceiver(IApplicationThreadcaller, String callerPackage,

           IIntentReceiver receiver, IntentFilter filter, String permission) {

  synchronized(this) {

      ProcessRecord callerApp = null;

      if(caller != null) {

         callerApp = getRecordForAppLocked(caller);

         ....... //如果callerApp為null,則丟擲異常,即系統不允許未登記照冊的程式註冊

         //動態廣播接收者

 

          //檢查呼叫程式是否有callerPackage的資訊,如果沒有,也拋異常

          if(callerApp.info.uid != Process.SYSTEM_UID &&

             !callerApp.pkgList.contains(callerPackage)){

                

                throw new SecurityException(......);

             }

       }......//if(caller != null)判斷結束

 

     List allSticky = null;

      //下面這段程式碼的功能是從系統中所有Sticky Intent中查詢匹配IntentFilter的Intent,

     //匹配的Intent儲存在allSticky中

     Iterator actions = filter.actionsIterator();

      if(actions != null) {

        while (actions.hasNext()) {

              String action = (String)actions.next();

              allSticky = getStickiesLocked(action, filter, allSticky);

           }

       } ......

     //如果存在sticky的Intent,則選取第一個Intent作為本函式的返回值

     Intentsticky = allSticky != null ? (Intent)allSticky.get(0) : null;

     //如果沒有設定接收者,則直接返回sticky 的intent

     if(receiver == null)  return sticky;

 

    //新的資料型別ReceiverList及mRegisteredReceivers成員變數,見下文的解釋

  //receiver.asBinder將返回IIntentReceiver的Bp端

    ReceiverList rl

               = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());

 

     //如果是首次呼叫,則此處rl的值將為null

     if (rl== null) {

       rl =new ReceiverList(this, callerApp, Binder.getCallingPid(),

                     Binder.getCallingUid(),receiver);

       if (rl.app != null) {

           rl.app.receivers.add(rl);

       }else {

         try {

                 //監聽廣播接收者所在程式的死亡訊息

                 receiver.asBinder().linkToDeath(rl, 0);

             }......

          rl.linkedToDeath = true;

        }// if(rl.app != null)判斷結束

 

           //將rl儲存到mRegisterReceivers中

          mRegisteredReceivers.put(receiver.asBinder(), rl);

     }

    //新建一個BroadcastFilter物件

    BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,

                                                        permission);

    rl.add(bf);//將其儲存到rl中

     // mReceiverResolver成員變數,見下文解釋

    mReceiverResolver.addFilter(bf);

以上程式碼的流程倒是很簡單,不過其中出現的幾個成員變數和資料型別卻嚴重阻礙了我們的思維活動。先解決它們,BroadcastFilter及相關成員變數如圖6-18所示。


圖6-18  BroadcastFilter及相關成員變數

結合程式碼,對圖6-18中各資料型別和成員變數的作用及關係的解釋如下:

·  在AMS中,BroadcastReceiver的過濾條件由BroadcastFilter表示,該類從IntentFilter派生。由於一個BroadcastReceiver可設定多個過濾條件(即多次為同一個BroadcastReceiver物件呼叫registerReceiver函式以設定不同的過濾條件),故AMS使用ReceiverList(從ArrayList<BroadcastFilter>派生)這種資料型別來表達這種一對多的關係。

·  ReceiverList除了能儲存多個BroadcastFilter外,還應該有成員指向某一個具體BroadcastReceiver,否則如何知道到底是哪個BroadcastReceiver設定的過濾條件呢?前面說過,BroadcastReceiver接收廣播是通過IIntentReceiver介面進行的,故ReceiverList中有receiver成員變數指向IIntentReceiver。

·  AMS提供mRegisterReceivers用於儲存IIntentReceiver和對應ReceiverList的關係。此外,AMS還提供mReceiverResolver變數用於儲存所有動態註冊的BroadcastReceiver所設定的過濾條件。

清楚這些成員變數和資料型別之間的關係後,接著來分析registerReceiver第二階段的工作。

(2) registerReceiver分析之二

[-->ActivityManagerService.java::registerReceiver]

    //如果allSticky不為空,則表示有Sticky的Intent,需要立即排程廣播傳送

     if(allSticky != null) {

        ArrayList receivers = new ArrayList();

         receivers.add(bf);

         intN = allSticky.size();

         for(int i=0; i<N; i++) {

            Intent intent = (Intent)allSticky.get(i);

            //為每一個需要傳送的廣播建立一個BroadcastRecord(暫稱之為廣播記錄)物件

            BroadcastRecord r = new BroadcastRecord(intent, null,

                            null, -1, -1, null,receivers, null, 0, null, null,

                            false, true, true);

            //如果mParallelBroadcasts當前沒有成員,則需要觸發AMS傳送廣播

            if (mParallelBroadcasts.size() == 0)

                 scheduleBroadcastsLocked();//向AMS傳送BROADCAST_INTENT_MSG訊息

             //所有非ordered廣播記錄都儲存在mParallelBroadcasts中

             mParallelBroadcasts.add(r);

          }//for迴圈結束

    }//if (allSticky != null)判斷結束

    returnsticky;

   }//synchronized結束

}

這一階段的工作用一句話就能說清楚:為每一個滿足IntentFilter的Sticky的intent建立一個BroadcastRecord物件,並將其儲存到mParllelBroadcasts陣列中,最後,根據情況排程AMS傳送廣播。

從上邊的描述中可以看出,一旦存在滿足條件的Sticky的Intent,系統需要儘快排程廣播傳送。說到這裡,想和讀者分享一種在實際工作中碰到的情況。

我們註冊了一個BroadcastReceiver,用於接收USB的連線狀態。在註冊完後,它的onReceiver函式很快就會被呼叫。當時筆者的一些同事認為是註冊操作觸發USB模組又傳送了一次廣播,卻又感到有些困惑,USB模組應該根據USB的狀態變化去觸發廣播傳送,而不應理會廣播接收者的註冊操作,這到底是怎麼一回事呢?

相信讀者現在應該輕鬆解決這種困惑了吧?對於Sticky的廣播,一旦有接收者註冊,系統會馬上將該廣播傳遞給它們。

和ProcessRecord及ActivityRecord類似,AMS定義了一個BroadcastRecord資料結構,用於儲存和廣播相關的資訊,同時還有兩個成員變數,它們作用和關係如圖6-19所示。


圖6-19  BroadcastReceiver及相關變數

圖6-19比較簡單,讀者可自行研究。

在程式碼中,registerReceiver將呼叫scheduleBroadcastsLocked函式,通知AMS立即著手開展廣播傳送工作,在其內部就傳送BROADCAST_INTENT_MSG訊息給AMS。相關的處理工作將放到本節最後再來討論。

 

6.4.2  sendBroadcast流程分析

在SDK中同樣定義了好幾個函式用於傳送廣播。不過,根據之前的經驗,最終和AMS互動的函式可能通過一個介面就能完成。來看最簡單的廣播傳送函式sendBroadcast,其程式碼如下:

[-->ContextImpl.java::sendBroadcast]

public void sendBroadcast(Intent intent) {

   StringresolvedType = intent.resolveTypeIfNeeded(getContentResolver());

   try {

       intent.setAllowFds(false);

         //呼叫AMS的brodcastIntent,在SDK中定義的廣播傳送函式最終都會呼叫它

        ActivityManagerNative.getDefault().broadcastIntent(

               mMainThread.getApplicationThread(), intent, resolvedType, null,

               Activity.RESULT_OK, null, null, null, false, false);

        }......

 }

AMS的broadcastIntent函式的主要工作將交由AMS的broadcastIntentLocked來完成,故此處直接分析broadcastIntentLocked。

1.  broadcastIntentLocked分析

我們分階段來分析broadcastIntentLocked的工作,先來看第一階段工作。

(1) broadcastIntentLocked分析之一

[-->ActivityManagerService.java::broadcastIntentLocked]

private final int broadcastIntentLocked(ProcessRecordcallerApp,

      StringcallerPackage, Intent intent, String resolvedType,

     IIntentReceiver resultTo, int resultCode, String resultData,

      Bundlemap, String requiredPermission,

     boolean ordered, boolean sticky, int callingPid, int callingUid) {

 

   intent =new Intent(intent);

   //為Intent增加FLAG_EXCLUDE_STOPPED_PACKAGES標誌,表示該廣播不會傳遞給被STOPPED

   //的Package

  intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);

   //處理一些特殊的廣播,包括UID_REMOVED,PACKAGE_REMOVED和PACKAGE_ADDED等

   finalboolean uidRemoved = Intent.ACTION_UID_REMOVED.equals(

                                                        intent.getAction());

   ......//處理特殊的廣播,主要和PACKAGE相關,例如接收到PACKAGE_REMOVED廣播後,AMS

   //需將該Package的元件從相關成員中刪除,相關程式碼可自行閱讀

 

   //處理TIME_ZONE變化廣播

   if(intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction()))

        mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);

   //處理CLEAR_DNS_CACHE廣播

   if(intent.ACTION_CLEAR_DNS_CACHE.equals(intent.getAction()))

        mHandler.sendEmptyMessage(CLEAR_DNS_CACHE);

   //處理PROXY_CHANGE廣播

   if(Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) {

        ProxyProperties proxy = intent.getParcelableExtra("proxy");

         mHandler.sendMessage(mHandler.obtainMessage(

                                  UPDATE_HTTP_PROXY,proxy));

   }

從以上程式碼可知,broadcastIntentLocked第一階段的工作主要是處理一些特殊的廣播訊息。

下面來看broadcastIntentLocked第二階段的工作。

(2) broadcastIntentLocked分析之二

[-->ActivityManagerService.java::broadcastIntentLocked]

   //處理髮送sticky廣播的情況

  if(sticky) {

   ......//檢查傳送程式是否有BROADCAST_STICKY許可權

 

   if(requiredPermission != null) {

       //在傳送Sticky廣播的時候,不能攜帶許可權資訊

        returnBROADCAST_STICKY_CANT_HAVE_PERMISSION;

     }

   //在傳送Stikcy廣播的時候,也不能指定特定的接收物件

   if(intent.getComponent() != null) ......//拋異常

 

   //將這個Sticky的Intent儲存到mStickyBroadcasts中

  ArrayList<Intent> list =mStickyBroadcasts.get(intent.getAction());

   if (list== null) {

        list= new ArrayList<Intent>();

        mStickyBroadcasts.put(intent.getAction(), list);

    }

    ......//如果list中已經有該intent,則直接用新的intent替換舊的intent

    //否則將該intent加入到list中儲存

    if (i >= N) list.add(new Intent(intent));

  }

 

    //定義兩個變數,其中receivers用於儲存匹配該Intent的所有廣播接收者,包括靜態或動態

    //註冊的廣播接收者,registeredReceivers用於儲存符合該Intent的所有動態註冊者

   Listreceivers = null;

   List<BroadcastFilter>registeredReceivers = null;

 

   try {

       if(intent.getComponent() != null) {

        ......//如果指定了接收者,則從PKMS中查詢它的資訊

       }else {

          //FLAG_RECEIVER_REGISTERED_ONLY標籤表明該廣播只能發給動態註冊者

          if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)== 0) {

            //如果沒有設定前面的標籤,則需要查詢PKMS,獲得那些在AndroidManifest.xml

           //中宣告的廣播接收者,即靜態廣播接收者的資訊,並儲存到receivers中

             receivers =

                   AppGlobals.getPackageManager().queryIntentReceivers(

                          intent, resolvedType,STOCK_PM_FLAGS);

            }

          //再從AMS的mReceiverResolver中查詢符合條件的動態廣播接收者

            registeredReceivers =mReceiverResolver.queryIntent(intent,

                                                resolvedType, false);

         }

        }......

 

  /*

   判斷該廣播是否設定了REPLACE_PENDING標籤。如果設定了該標籤,並且之前的那個Intent還

   沒有被處理,則可以用新的Intent替換舊的Intent。這麼做的目的是為了減少不必要的廣播傳送,

   但筆者感覺,這個標籤的作用並不靠譜,因為只有舊的Intent沒被處理的時候,它才能被替換。

   因為舊Intent被處理的時間不能確定,所以不能保證廣播傳送者的一番好意能夠實現。因此,

   在傳送廣播時,千萬不要以為設定了該標誌就一定能節約不必要的廣播傳送。

  */

   final boolean replacePending =

        (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;

 

  //先處理動態註冊的接收者

   int NR =registeredReceivers != null ? registeredReceivers.size() : 0;

   //如果此次廣播為非序列化傳送,並且符合條件的動態註冊接收者個數不為零

   if(!ordered && NR > 0) {

      //建立一個BroadcastRecord物件即可,注意,一個BroadcastRecord物件可包括所有的

     //接收者(可參考圖6-19)

     BroadcastRecord r = new BroadcastRecord(intent, callerApp,

                   callerPackage, callingPid, callingUid, requiredPermission,

                   registeredReceivers, resultTo, resultCode, resultData, map,

                   ordered, sticky, false);

 

       boolean replaced = false;

       if (replacePending) {

       ......//從mParalledBroadcasts中查詢是否有舊的Intent,如果有就替代它,並設定

      //replaced為true

     }

       if(!replaced) {//如果沒有被替換,則儲存到mParallelBroadcasts中

          mParallelBroadcasts.add(r);

          scheduleBroadcastsLocked();//排程一次廣播傳送

       }

       //至此,動態註冊的廣播接收者已處理完畢,設定registeredReceivers為null

      registeredReceivers = null;//

       NR =0;

  }

broadcastIntentLocked第二階段的工作有兩項:

·  查詢滿足條件的動態廣播接收者及靜態廣播接收者。

·  當本次廣播不為ordered時,需要儘快傳送該廣播。另外,非ordered的廣播都被AMS儲存在mParallelBroadcasts中。

(3) broadcastIntentLocked分析之三

下面來看broadcastIntentLocked最後一階段的工作,其程式碼如下:

[-->ActivityManagerService.java::broadcastIntentLocked]

   int ir = 0;

   if(receivers != null) {

      StringskipPackages[] = null;

     ......//處理PACKAGE_ADDED的Intent,系統不希望有些應用程式一安裝就啟動。

      //這類程式的工作原理是什麼呢?即在該程式內部監聽PACKAGE_ADDED廣播。如果系統

     //沒有這一招防備,則PKMS安裝完程式後所傳送的PAKCAGE_ADDED訊息將觸發該應用的啟動

 

    ......//處理ACTION_EXTERNAL_APPLICATIONS_AVAILABLE廣播

 

   ......//將動態註冊的接收者registeredReceivers的元素合併到receivers中去

   //處理完畢後,所有的接收者(無論動態還是靜態註冊的)都儲存到receivers變數中了

   if((receivers != null && receivers.size() > 0) || resultTo != null) {

         //建立一個BroadcastRecord物件,注意它的receivers中包括所有的接收者

        BroadcastRecord r = new BroadcastRecord(intent, callerApp,

              callerPackage, callingPid, callingUid, requiredPermission,

              receivers, resultTo, resultCode, resultData, map, ordered,

              sticky, false);

       booleanreplaced = false;

       if (replacePending) {

        ......//替換mOrderedBroadcasts中舊的Intent

        }//

     if(!replaced) {//如果沒有替換,則新增該條廣播記錄到mOrderedBroadcasts中

        mOrderedBroadcasts.add(r);

        scheduleBroadcastsLocked();//排程AMS進行廣播傳送工作

     }

  }

  returnBROADCAST_SUCCESS;

}

由以上程式碼可知,AMS將動態註冊者和靜態註冊者都合併到receivers中去了。注意,如果本次廣播不是ordered,那麼表明動態註冊者就已經在第二階段工作中被處理了。因此在合併時,將不會有動態註冊者被加到receivers中。最終所建立的廣播記錄儲存在mOrderedBroadcasts中,也就是說,不管是否序列化傳送,靜態接收者對應的廣播記錄都將儲存在mOrderedBroadcasts中。為什麼不將它們儲存在mParallelBroadcasts中呢?

結合程式碼會發現,儲存在mParallelBroadcasts中的BroadcastRecord所包含的都是動態註冊的廣播接收者資訊,這是因為動態接收者所在的程式是已經存在的(如果該程式不存在,如何去註冊動態接收者呢?),而靜態廣播接收者就不能保證它已經和一個程式繫結在一起了(靜態廣播接收者此時可能還僅僅是在AndroidManifest.xml中宣告的一個資訊,相應的程式並未建立和啟動)。根據前一節所分析的Activity啟動流程,AMS還需要進行建立應用程式,初始化Android執行環境等一系列複雜的操作。讀到後面內容時會發現,AMS將在一個迴圈中逐條處理mParallelBroadcasts中的成員(即派發給接收者)。如果將靜態廣播接收者也儲存到mParallelBroadcasts中,會有什麼後果呢?假設這些靜態接收者所對應的程式全部未建立和啟動,那麼AMS將在那個迴圈中建立多個程式!結果讓人震驚,系統壓力一下就上去了。所以,對於靜態註冊者,它們對應的廣播記錄都被放到mOrderedBroadcasts中儲存。AMS在處理這類廣播資訊時,一個程式一個程式地處理,只有處理完一個接收者,才繼續下一個接收者。這種做法的好處是,避免了驚群效應的出現,壞處則是延時較長。

下面進入AMS的BROADCAST_INTENT_MSG訊息處理函式,看看情況是否如上所說。

6.4.3  BROADCAST_INTENT_MSG訊息處理函式

BROADCAST_INTENT_MSG訊息將觸發processNextBroadcast函式,下面分階段來分析它。

1.  processNextBroadcast分析之一

[-->ActivityManagerService.java::processNextBroadcast]

private final void processNextBroadcast(booleanfromMsg) {

   //如果是BROADCAST_INTENT_MSG訊息觸發該函式,則fromMsg為true

  synchronized(this) {

    BroadcastRecord r;

   updateCpuStats();//更新CPU使用情況

    if(fromMsg)  mBroadcastsScheduled = false;

 

    //先處理mParallelBroadcasts中的成員。如前所述,AMS在一個迴圈中處理它們

    while(mParallelBroadcasts.size() > 0) {

         r =mParallelBroadcasts.remove(0);

         r.dispatchTime =SystemClock.uptimeMillis();

         r.dispatchClockTime =System.currentTimeMillis();

        final int N = r.receivers.size();

         for(int i=0; i<N; i++) {

           Object target = r.receivers.get(i);

            //①mParallelBroadcasts中的成員全為BroadcastFilter型別,所以下面的函式

           //將target直接轉換成BroadcastFilter型別。注意,最後一個引數為false

           deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target,

                                                         false);

        }

        //將這條處理過的記錄儲存到mHistoryBroadcast中,供除錯使用

        addBroadcastToHistoryLocked(r);

  }

deliverToRegisteredReceiverLocked函式的功能就是派發廣播給接收者,其程式碼如下:

[-->ActivityManagerService.java::deliverToRegisteredReceiverLocked]

private final voiddeliverToRegisteredReceiverLocked(BroadcastRecord r,

                         BroadcastFilter filter, booleanordered) {

   booleanskip = false;

   //檢查傳送程式是否有filter要求的許可權

   if(filter.requiredPermission != null) {

       intperm = checkComponentPermission(filter.requiredPermission,

                   r.callingPid, r.callingUid, -1, true);

       if(perm != PackageManager.PERMISSION_GRANTED) skip = true;

  }

   //檢查接收者是否有傳送者要求的許可權

   if(r.requiredPermission != null) {

       intperm = checkComponentPermission(r.requiredPermission,

               filter.receiverList.pid, filter.receiverList.uid, -1, true);

       if(perm != PackageManager.PERMISSION_GRANTED) skip = true;

    }

 

   if(!skip) {

     if(ordered) {

     ......//設定一些狀態,成員變數等資訊,不涉及廣播傳送

      }

     try {

         //傳送廣播

        performReceiveLocked(filter.receiverList.app,   

              filter.receiverList.receiver,new Intent(r.intent), r.resultCode,

              r.resultData, r.resultExtras, r.ordered, r.initialSticky);

 

         if(ordered) r.state = BroadcastRecord.CALL_DONE_RECEIVE;

       }......

     }

   }

 }

來看performReceiveLocked函式,其程式碼如下:

[-->ActivityManagerService.java::performReceiveLocked]

static void performReceiveLocked(ProcessRecordapp, IIntentReceiver receiver,

      Intentintent, int resultCode, String data, Bundle extras,

     boolean ordered, boolean sticky) throws RemoteException {

      if(app != null && app.thread != null) {

         //如果app及app.thread不為null,則排程scheduleRegisteredReceiver,

        //注意這個函式名為scheduleRegisteredReceiver,它只針對動態註冊的廣播接收者

        app.thread.scheduleRegisteredReceiver(receiver,intent, resultCode,

                   data, extras, ordered, sticky);

      } else{

       //否則呼叫IIntentReceiver的performReceive函式

      receiver.performReceive(intent, resultCode, data, extras,

                                     ordered, sticky);

   }

 }

對於動態註冊者而言,在大部分情況下會執行if分支,所以應用程式ApplicationThread的scheduleRegisteredReceiver函式將被呼叫。稍後再分析應用程式的廣播處理流程。

2.  processNextBroadcast分析之二

至此,processNextBroadcast已經在一個while迴圈中處理完mParallelBroadcasts的所有成員了,實際上,這種處理方式也會造成驚群效應,但影響相對較少。這是因為對於動態註冊者來說,它們所在的應用程式已經建立並初始化成功。此處的廣播傳送只是呼叫應用程式的一個函式而已。相比於建立程式,再初始化Android執行環境所需的工作量,呼叫scheduleRegisteredReceiver的工作就比較輕鬆了。

來看processNextBroadcast第二階段的工作,程式碼如下:

[-->ActivityManagerService.java::processNextBroadcast]

   /*

    現在要處理mOrderedBroadcasts中的成員。如前所述,它要處理一個接一個的接受者,如果

    接收者所在程式還未啟動,則需要等待。mPendingBroadcast變數用於標識因為應用程式還未

    啟動而處於等待狀態的BroadcastRecord。

   */

  if(mPendingBroadcast != null) {

     boolean isDead;

     synchronized (mPidsSelfLocked) {

        isDead= (mPidsSelfLocked.get(mPendingBroadcast.curApp.pid) == null);

      }

      /*重要說明

       判斷要等待的程式是否為dead程式,如果沒有dead程式,則繼續等待。仔細思考,此處直接

       返回會有什麼問題。

       問題不小!假設有兩個ordered廣播A和B,有兩個接收者,AR和BR,並且BR所

       在程式已經啟動並完成初始化Android執行環境。如果processNextBroadcast先處理A,

       再處理B,那麼此處B的處理將因為mPendingBroadcast不為空而被延後。雖然B和A

       之間沒有任何關係(例如完全是兩個不同的廣播訊息),

       但是事實上,大多數開發人員理解的order是針對單個廣播的,例如A有5個接收者,那麼對這

       5個接收者的廣播的處理是序列的。通過此處的程式碼發現,系統竟然序列處理A和B廣播,即

       B廣播要待到A的5個接收者都處理完了才能處理。

      */

      if(!isDead)   return;

      else {

       mPendingBroadcast.state = BroadcastRecord.IDLE;

       mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;

       mPendingBroadcast = null;

     }

   }

 

  boolean looped = false;

  do {

        // mOrderedBroadcasts處理完畢

        if (mOrderedBroadcasts.size() == 0) {

            scheduleAppGcsLocked();

            if (looped)  updateOomAdjLocked();

             return;

        }

        r =mOrderedBroadcasts.get(0);

       boolean forceReceive = false;

        //下面這段程式碼用於判斷此條廣播是否處理時間過長

        //先得到該條廣播的所有接收者

        intnumReceivers = (r.receivers != null) ? r.receivers.size() : 0;

        if(mProcessesReady && r.dispatchTime > 0) {

            long now = SystemClock.uptimeMillis();

           //如果總耗時超過2倍的接收者個數*每個接收者最長處理時間(10秒),則

           //強制結束這條廣播的處理

            if ((numReceivers > 0) &&

                   (now > r.dispatchTime +

                              (2*BROADCAST_TIMEOUT*numReceivers))){

               broadcastTimeoutLocked(false);//讀者閱讀完本節後可自行研究該函式

              forceReceive = true;

              r.state = BroadcastRecord.IDLE;

            }

      }//if(mProcessesReady...)判斷結束

 

     if (r.state != BroadcastRecord.IDLE)   return;

     //如果下面這個if條件滿足,則表示該條廣播要麼已經全部被處理,要麼被中途取消

     if(r.receivers == null || r.nextReceiver >= numReceivers

            || r.resultAbort || forceReceive) {

         if(r.resultTo != null) {

          try {

               //將該廣播的處理結果傳給設定了resultTo的接收者

               performReceiveLocked(r.callerApp, r.resultTo,

                  new Intent(r.intent), r.resultCode,

                 r.resultData, r.resultExtras, false, false);

               r.resultTo = null;

            }......

       }

      cancelBroadcastTimeoutLocked();

       addBroadcastToHistoryLocked(r);

      mOrderedBroadcasts.remove(0);

       r = null;

      looped = true;

       continue;

     }

  } while (r== null);

processNextBroadcast第二階段的工作比較簡單:

·  首先根據是否處於pending狀態(mPendingBroadcast不為null)進行相關操作。讀者要認真體會程式碼中的重要說明。

·  處理超時的廣播記錄。這個超時時間是2*BROADCAST_TIMEOUT*numReceivers。BROADCAST_TIMEOUT預設為10秒。由於涉及建立程式,初始化Android執行環境等重體力活,故此處超時時間還乘以一個固定倍數2。

3.  processNextBroadcast分析之三

來看processNextBroadcast第三階段的工作,程式碼如下:

[-->ActivityManagerService.java::processNextBroadcast]

  int recIdx= r.nextReceiver++;

 r.receiverTime = SystemClock.uptimeMillis();

  if (recIdx== 0) {

     r.dispatchTime = r.receiverTime;//記錄本廣播第一次處理開始的時間

      r.dispatchClockTime= System.currentTimeMillis();

  }

  //設定廣播處理超時時間,BROADCAST_TIMEOUT為10秒

  if (!mPendingBroadcastTimeoutMessage) {

        longtimeoutTime = r.receiverTime + BROADCAST_TIMEOUT;

       setBroadcastTimeoutLocked(timeoutTime);

  }

  //取該條廣播下一個接收者

  ObjectnextReceiver = r.receivers.get(recIdx);

  if(nextReceiver instanceof BroadcastFilter) {

     //如果是動態接收者,則直接呼叫deliverToRegisteredReceiverLocked處理

    BroadcastFilter filter = (BroadcastFilter)nextReceiver;

    deliverToRegisteredReceiverLocked(r, filter, r.ordered);

     if(r.receiver == null || !r.ordered) {

        r.state = BroadcastRecord.IDLE;

        scheduleBroadcastsLocked();

       }

     return;//已經通知一個接收者去處理該廣播,需要等它的處理結果,所以此處直接返回

  }

 //如果是靜態接收者,則它的真實型別是ResolveInfo

 ResolveInfo info =  (ResolveInfo)nextReceiver;

  booleanskip = false;

  //檢查許可權

  int perm =checkComponentPermission(info.activityInfo.permission,

        r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,

        info.activityInfo.exported);

  if (perm!= PackageManager.PERMISSION_GRANTED) skip = true;

  if(info.activityInfo.applicationInfo.uid != Process.SYSTEM_UID &&

        r.requiredPermission != null) {

        ......

       }

   //設定skip為true

   if(r.curApp != null && r.curApp.crashing) skip = true;

 

   if (skip){

     r.receiver = null;

     r.curFilter = null;

     r.state = BroadcastRecord.IDLE;

     scheduleBroadcastsLocked();//再排程一次廣播處理

     return;

   }

 

    r.state= BroadcastRecord.APP_RECEIVE;

    StringtargetProcess = info.activityInfo.processName;

   r.curComponent = new ComponentName(

             info.activityInfo.applicationInfo.packageName,

             info.activityInfo.name);

  r.curReceiver = info.activityInfo;

   try {

          //設定Package stopped的狀態為false

          AppGlobals.getPackageManager().setPackageStoppedState(

                       r.curComponent.getPackageName(),false);

         } ......

 

  ProcessRecord app = getProcessRecordLocked(targetProcess,

                         info.activityInfo.applicationInfo.uid);

   //如果該接收者對應的程式已經存在

   if (app!= null && app.thread != null) {

    try {

           app.addPackage(info.activityInfo.packageName);

           //該函式內部呼叫應用程式的scheduleReceiver函式,讀者可自行分析

           processCurBroadcastLocked(r, app);

           return;//已經觸發該接收者處理本廣播,需要等待處理結果

        }......

     }

   //最糟的情況就是該程式還沒有啟動

   if((r.curApp=startProcessLocked(targetProcess,

          info.activityInfo.applicationInfo, true,......)!= 0)) == null) {

          ......//程式啟動失敗的處理

         return;

   }

  mPendingBroadcast = r; //設定mPendingBroadcast

  mPendingBroadcastRecvIndex = recIdx;

 }

對processNextBroadcast第三階段的工作總結如下:

·  如果廣播接收者為動態註冊物件,則直接呼叫deliverToRegisteredReceiverLocked處理它。

·  如果廣播接收者為靜態註冊物件,並且該物件對應的程式已經存在,則呼叫processCurBroadcastLocked處理它。

·  如果廣播接收者為靜態註冊物件,並且該物件對應的程式還不存在,則需要建立該程式。這是最糟糕的情況。

此處,不再討論新程式建立及Android執行環境初始化相關的邏輯,讀者可返回閱讀“attachApplicationLocked分析之三”,其中有處理mPendingBroadcast的內容。

6.4.4   應用程式處理廣播分析

下面來分析當應用程式收到廣播後的處理流程,以動態接收者為例。

1.  ApplicationThreadscheduleRegisteredReceiver函式分析

如前所述,AMS將通過scheduleRegisteredReceiver函式將廣播交給應用程式,該函式程式碼如下:

[-->ActivityThread.java::scheduleRegisteredReceiver]

public voidscheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,

       intresultCode, String dataStr, Bundle extras, boolean ordered,

      boolean sticky) throws RemoteException {

  //又把receiver物件傳了回來。還記得註冊時傳遞的一個IIntentReceiver型別

  //的物件嗎?

   receiver.performReceive(intent,resultCode, dataStr, extras, ordered,

                               sticky);

 }

就本例而言,receiver物件的真實型別為LoadedApk.ReceiverDispatcher,來看它的performReceive函式,程式碼如下:

[-->LoadedApk.java::performReceive]

public void performReceive(Intent intent, intresultCode,

   Stringdata, Bundle extras, boolean ordered, boolean sticky) {

   //Args是一個runnable物件

   Args args= new Args(intent, resultCode, data, extras, ordered, sticky);

      // mActivityThread是一個Handler,還記得SDK提供的兩個同名的registerReceiver

     //函式嗎?如果沒有傳遞Handler,則使用主執行緒的Handler。

  if(!mActivityThread.post(args)) {

      if(mRegistered && ordered) {

        IActivityManager mgr = ActivityManagerNative.getDefault();

        args.sendFinished(mgr);

      }

   }

}

scheduleRegisteredReceiver最終向主執行緒的Handler投遞了一個Args物件,這個物件的run函式將在主執行緒中被呼叫。

2.  Args.run分析

[-->LoadedApk.java::Args.run]

   publicvoid run() {

     finalBroadcastReceiver receiver = mReceiver;

     final boolean ordered = mOrdered;

     finalIActivityManager mgr = ActivityManagerNative.getDefault();

     finalIntent intent = mCurIntent;

    mCurIntent = null;

     ......

    try {

          //獲取ClassLoader物件,千萬注意,此處並沒有通過反射機制建立一個廣播接收者,

          //對於動態接收者來說,在註冊前就已經建立完畢

           ClassLoadercl = mReceiver.getClass().getClassLoader();

          intent.setExtrasClassLoader(cl);

          setExtrasClassLoader(cl);

          receiver.setPendingResult(this);//設定pendingResult

          //呼叫該動態接收者的onReceive函式

          receiver.onReceive(mContext, intent);

    }......

     //呼叫finish完成工作

     if(receiver.getPendingResult() != null) finish();

    }

Finish的程式碼很簡單,此處不在贅述,在其內部會通過sendFinished函式呼叫AMS的finishReceiver函式,以通知AMS。

3.  AMS的finishReceiver函式分析

不論ordered還是非orded廣播,AMS的finishReceiver函式都會被呼叫,它的程式碼如下:

[-->ActivityManagerService.java::finishReceiver]

public void finishReceiver(IBinder who, intresultCode, String resultData,

           Bundle resultExtras, boolean resultAbort) {

      ......

    booleandoNext;

    final long origId =Binder.clearCallingIdentity();

    synchronized(this){

        //判斷是否還需要繼續排程後續的廣播傳送

       doNext = finishReceiverLocked(

             who, resultCode, resultData, resultExtras, resultAbort, true);

        }

        if(doNext) { //發起下一次廣播傳送

           processNextBroadcast(false);

        }

       trimApplications();

   Binder.restoreCallingIdentity(origId);

}

由以上程式碼可知,finishReceiver將根據情況排程下一次廣播傳送。

6.4.5  廣播處理總結

廣播處理的流程及相關知識點還算比較簡單,可以用圖6-20來表示本例的流程。


圖6-20  Broadcast處理流程

在圖6-20中,將呼叫函式所屬的實際物件型別標註了出來,其中第11步的MyBroadcastReceiver為本例中所註冊的廣播接收者。

注意任何廣播對於靜態註冊者來說,都是ordered,而且該order是全域性性的,並非只針對該廣播的接收者,故從廣播發出到靜態註冊者的onReceive函式被呼叫中間經歷的這段時間相對較長。

 

6.5  startService之按圖索驥

Service是Android的四大元件之一。和Activity,BroadcastReceiver相比,Service定位於業務層邏輯處理,而Activity定位於前端UI層邏輯處理,BroadcastReceiver定位於通知邏輯的處理。

做為業務服務提供者,Service自有一套規則,先來看有關Service的介紹。

6.5.1  Service知識介紹

四大元件之一的Service,其定義非常符合C/S架構中Service的概念,即為Client服務,處理Client的請求。在Android中,目前接觸最多的是Binder中的C/S架構。在這種架構中,Client通過呼叫預先定義好的業務函式向對應的Service傳送請求。作為四大元件之一Service,其響應Client的請求方式有兩種:

·  Client通過呼叫startService向Service端傳送一個Intent,該Intent攜帶請求資訊。而Service的onStartCommand會接受該Intent,並處理之。該方式是Android平臺特有的,藉助Intent來傳遞請求。

·  Client呼叫bindService函式和一個指定的Service建立Binder關係,即繫結成功後,Client端將得到處理業務邏輯的Binder Bp端。此後Client直接呼叫Bp端提供的業務函式向Service端發出請求。注意,在這種方式中,Service的onBind函式被呼叫,如果該Service支援Binder,需返回一個IBinder物件給客戶端。

以上介紹的是Service響應客戶端請求的兩種方式,務必將兩者分清楚。此外,這兩種方式還影響Service物件的生命週期,簡單總結如下:

·  對於以startService方式啟動的Service物件,其生命週期一直延續到stopSelf或stopService被呼叫為止。

·  對於以bindService方式啟動的Service物件,其生命週期延續到最後一個客戶端呼叫完unbindService為止。

注意生命週期控制一般都涉及引用計數的使用。如果某Service物件同時支援這兩種請求方式,那麼當總引用計數減為零時,其生命就走向終點。

和Service相關的知識還有,當系統記憶體不足時,系統如何處理Service。如果Service和UI某個部分繫結(例如類似通知欄中Music播放的資訊),那麼此Service優先順序較高(可通過呼叫startForeground把自己變成一個前臺Service),系統不會輕易殺死這些Service來回收記憶體。

以上這些內容都較簡單,閱讀SDK文件中Service的相關說明即可瞭解,具體路徑為SDK路徑/docs/guide/topics/fundamentals/services.html。

 

6.5.2  startService流程圖

本章不過多介紹和Service相關的知識,原因有二:

·  Service的處理流程和本章重點介紹的Activity的處理流程差不多,並且Service的處理邏輯更簡單。能閱讀到此處的讀者,想必對拿下Service信心滿滿。

·  “授人以魚,不如授人以漁”。希望讀者在經歷過如此大量而又複雜的程式碼分析考驗後,能學會和掌握分析方法,因此本節將以startService為分析物件,把相關的流程圖描繪出來,旨在幫讀者根據該流程圖自行研讀與Service相關的處理邏輯。

startService呼叫軌跡如圖6-21和圖6-22所示。


圖6-21  startService流程圖之一

圖6-21列出了和startService相關的呼叫流程。在這個流程中,可假設Service所對應的程式已經存在。

單獨提取圖6-21中Service所在程式對H.CREATE_SERVICE等訊息的處理流程如圖6-22所示。


圖6-22  startService中相關Message的處理流程

注意圖6-21和圖6-22中也包含了bindService的處理流程。在實際分析時,讀者可分開研究bindService和startService的處理流程。

6.6  AMS中的程式管理

前面曾反覆提到,Android平臺中很少能接觸到程式的概念,取而代之的是有明確定義的四大元件。但是作為執行在Linux使用者空間內的一個系統或框架,Android不僅不能脫離程式,反而要大力利用Linux OS提供的程式管理機制和手段,更好地為自己服務。作為Android平臺中元件執行管理的核心服務,ActivityManagerService當仁不讓地接手了這方面的工作。目前,AMS對程式的管理僅涉及兩個方面:

·  調節程式的排程優先順序和排程策略。

·  調節程式的OOM值。

先來看在Linux OS中這兩方面的程式管理和控制手段。

6.6.1  Linux程式管理介紹[⑥]

1.  Linux程式排程優先順序和排程策略

排程優先順序和排程策略是作業系統中一個很重要的概念。簡而言之,它是系統中CPU資源的管理和控制手段。如何理解?此處進行簡單介紹。讀者可自行閱讀作業系統方面的書籍以加深理解。

·  相對於在OS上執行的應用程式個數來說,CPU的資源非常有限。

·  排程優先順序是OS分配CPU資源給應用程式時(即排程應用程式執行)需要參考的一個指標。一般而言,優先順序高的程式將更有機會得到CPU資源。

排程策略用於描述OS排程模組分配CPU給應用程式所遵循的規則,即當將CPU控制權交給排程模組時,系統如何選擇下一個要執行的程式。此處不能僅考慮各程式的排程優先順序,因為存在多個程式具有相同排程優先順序的情況。在這種情況下,一個需要考慮的重要因素是,每個程式所分配的時間片及它們的使用情況.

提示可簡單認為,排程優先順序及排程策略二者一起影響了系統分配CPU資源給應用程式。注意,此處的措詞為“影響”,而非“控制”。因為對於使用者空間可以呼叫的API來說,如果二者能控制CPU資源分配,那麼該系統的安全性會大打折扣。

Linux提供了兩個API用於設定排程優先順序及排程策略。先來看設定排程優先順序的函式setpriority,其原型如下:

int setpriority(int which, int who, int prio);

其中:

·  which和who引數聯合使用。當which為PRIO_PROGRESS時,who代表一個程式;當which為PRIO_PGROUP時,who代表一個程式組;當which為PRIO_USER時,who代表一個uid。

·  第三個引數prio用於設定應用程式的nicer值,可取範圍從-20到19。Linux kernel用nicer值來描述程式的排程優先順序,該值越大,表明該程式越友(nice),其被排程執行的機率越低。

下面來看設定排程策略的函式sched_setscheduler,其原型如下:

int sched_setscheduler(pid_t pid, int policy,conststruct sched_param *param);

其中:

·  第一個引數為程式id。

·  第二個引數為排程策略。目前Android支援三種排程策略:SCHED_OTHER,標準round-robin分時共享策略(也就是預設的策略);SCHED_BATCH,針對具有batch風格(批處理)程式的排程策略;SCHED_IDLE,針對優先順序非常低的適合在後臺執行的程式。除此之外,Linux還支援實時(Real-time)排程策略,包括SCHED_FIFO,先入先出排程策略;SCHED_RR,:round-robin排程策略,也就是迴圈排程。

·  param引數中最重要的是該結構體中的sched_priority變數。針對Android中的三種非實時排程策略,該值必須為NULL。

以上介紹了排程優先順序和排程策略的概念。建議讀者做個小實驗來測試調動優先順序及調動策略的作用,步驟如下:

·  掛載SD卡到PC機並往向其中複製一些媒體檔案,然後重新掛載SD卡到手機。該操作就能觸發MediaScanner掃描新增的這些媒體檔案。

·  利用top命令檢視CPU使用率,會發現程式com.process.media(即MediaScanner所在的程式)佔用CPU較高(可達70%以上),原因是該程式會掃描媒體檔案,該工作將利用較多的CPU資源。

·  此時,如果啟動另一個程式,然後做一些操作,會發現MediaScanner所在程式的CPU利用率會降下來(例如下降到30%~40%),這表明系統將CPU資源更多地分給了使用者正在操作的這個程式。

出現這種現象的原因是,MediaScannerSerivce的掃描執行緒將排程優先順序設定為11,而預設的排程優先順序為0。 相比而言,MediaScannerService優先順序真的很高。

2.  關於Linux程式oom_adj的介紹

從Linux kernel 2.6.11開始,核心提供了程式的OOM控制機制,目的是當系統出現記憶體不足(out of memory,簡稱OOM)的情況時,Kernel可根據程式的oom_adj來選擇並殺死一些程式,以回收記憶體。簡而言之,oom_adj可標示Linux程式記憶體資源的優先順序,其可取範圍從-16到15,另外有一個特殊值-17用於禁止系統在OOM情況下殺死該程式。和nicer值一樣,oom_adj的值越高,那麼在OOM情況下,該程式越有可能被殺掉。每個程式的oom_adj初值為0。

Linux沒有提供單獨的API用於設定程式的oom_adj。目前的做法就是向/proc/程式id/oom_adj檔案中寫入對應的oom_adj值,通過這種方式就能調節程式的oom_adj了。

另外,有必要簡單介紹一下Android為Linux Kernel新增的lowmemorykiller(以後簡稱LMK)模組的工作方式:LMK的職責是根據當前記憶體大小去殺死對應oom_adj及以上的程式以回收記憶體。這裡有兩個關鍵引數:為LMK設定不同的記憶體閾值及oom_adj,它們分別由/sys/module/lowmemorykiller/parameters/minfree和/sys/module/lowmemorykiller/parameters/adj控制。

注意這兩個引數的典型設定為:

minfree,2048,3072,4096,6144,7168,8192 用於描述不同級別的記憶體閾值,單位為KB。

adj,0,1,2,4,7,15 用於描述對應記憶體閾值的oom_adj值。

表示當剩餘記憶體為2048KB時,LMK將殺死oom_adj大於等於0的程式。

網路上有一些關於Android手機記憶體優化的方法,其中一種就利用了LMK的工作原理。

提示lowmemorykiller的程式碼在kernel/drivers/staging/android/lowmemorykiller.c中,感興趣的讀者可嘗試自行閱讀。

6.6.2  關於Android中的程式管理的介紹

前面介紹了Linux OS中程式管理(包括排程和OOM控制)方面的API,但AMS是如何利用它們的呢?這就涉及AMS中的程式管理規則了。這裡簡單介紹相關規則。

Android將應用程式分為五大類,分別為Forground類、Visible類、Service類、Background類及Empty類。這五大類的劃分各有規則。

1.  程式分類

(1) Forground類

該類中的程式重要性最高,屬於該類的程式包括下面幾種情況:

·  含一個前端Activity(即onResume函式被呼叫過了,或者說當前正在顯示的那個Activity)。

·  含一個Service,並且該Service和一個前端Activity繫結(例如Music應用包括一個前端介面和一個播放Service,當我們一邊聽歌一邊操作Music介面時,該Service即和一個前端Activity繫結)。

·  含一個呼叫了startForground的Service,或者該程式的Service正在呼叫其生命週期的函式(onCreate、onStart或onDestroy)。

·  最後一種情況是,該程式中有BroadcastReceiver例項正在執行onReceive函式。

(2) Visible類

屬於Visible類的程式中沒有處於前端的元件,但是使用者仍然能看到它們,例如位於一個對話方塊後的Activity介面。目前該類程式包括兩種:

·  該程式包含一個僅onPause被呼叫的Activity(即它還在前臺,只不過部分介面被遮住)。

·  或者包含一個Service,並且該Service和一個Visible(或Forground)的Activity繫結(從字面意義上看,這種情況不太好和Forground程式中第二種情況區分)。

(3) Service類、Background類及Empty類

這三類程式都沒有可見的部分,具體情況如下。

·  Service程式:該類程式包含一個Service。此Service通過startService啟動,並且不屬於前面兩類程式。這種程式一般在後臺默默地幹活,例如前面介紹的MediaScannerService。

·  Background程式:該類程式包含當前不可見的Activity(即它們的onStop被呼叫過)。系統儲存這些程式到一個LRU(最近最少使用)列表。當系統需要回收記憶體時,該列表中那些最近最少使用的程式將被殺死。

·  Empty程式:這類程式中不包含任何元件。為什麼會出現這種不包括任何元件的程式呢?其實很簡單,假設該程式僅建立了一個Activity,它完成工作後主動呼叫finish函式銷燬(destroy)自己,之後該程式就會成為Empty程式。系統保留Empty程式的原因是當又重新需要它們時(例如使用者在別的程式中通過startActivity啟動了它們),可以省去fork程式、建立Android執行環境等一系列漫長而艱苦的工作。

通過以上介紹可發現,當某個程式和前端顯示有關係時,其重要性相對要高,這或許是體現Google重視使用者體驗的一個很直接的證據吧。

建議讀者可閱讀SDK/docs/guide/topics/fundamentals/processes-and-threads.html以獲取更為詳細的資訊。

2.  Process類API介紹

我們先來介紹Android平臺中程式排程和OOM控制的API,它們統一被封裝在Process.java中,其相關程式碼如下:

[-->Process.java]

//設定執行緒的排程優先順序,Linux kernel並不區分執行緒和程式,二者對應同一個資料結構Task

public static final native void setThreadPriority(inttid, int priority)

           throws IllegalArgumentException, SecurityException;

 

/*

   設定執行緒的Group,實際上就是設定執行緒的排程策略,目前Android定義了三種Group。

   由於缺乏相關資料,加之筆者感覺對應的註釋也只可意會不可言傳,故此處直接照搬了英文

   註釋,敬請讀者諒解。

   THREAD_GROUP_DEFAULT:Default thread group - gets a 'normal'share of the CPU

   THREAD_GROUP_BG_NONINTERACTIVE:Background non-interactive thread group.

   Allthreads in this group are scheduled with a reduced share of the CPU

   THREAD_GROUP_FG_BOOST:Foreground 'boost' thread group - Allthreads in

   this group are scheduled with an increasedshare of the CPU.

   目前程式碼中還沒有地方使用THREAD_GROUP_FG_BOOST這種Group

*/

public static final native void setThreadGroup(inttid, int group)

           throws IllegalArgumentException, SecurityException;

//設定程式的排程策略,包括該程式的所有執行緒

public static final native voidsetProcessGroup(int pid, int group)

           throws IllegalArgumentException, SecurityException;

//設定執行緒的排程優先順序

public static final native voidsetThreadPriority(int priority)

           throws IllegalArgumentException, SecurityException;

//調整程式的oom_adj值

public static final native boolean setOomAdj(intpid, int amt);

Process類還為不同排程優先順序定義一些非常直觀的名字以避免在程式碼中直接使用整型,例如為最低的排程優先順序19定義了整型變數THREAD_PRIORITY_LOWEST。除此之外,Process還提供了fork子程式等相關的函式。

注意Process.java中的大多數函式是由JNI層實現的,其中Android在排程策略設定這一功能上還有一些特殊的地方,感興趣的讀者不妨閱讀system/core/libcutils/sched_policy.c檔案。

3.  關於ProcessList類和ProcessRecord類的介紹

(1) ProcessList類的介紹

ProcessList類有兩個主要功能:

·  定義一些成員變數,這些成員變數描述了不同狀態下程式的oom_adj值。

·  在Android 4.0之後,LMK的配置引數由ProcessList綜合考慮手機總記憶體大小和螢幕尺寸後再行設定(在Android 2.3中,LMK的配置引數在init.rc中由init程式設定,並且沒有考慮螢幕尺寸的影響)。讀者可自行閱讀其updateOomLevels函式,此處不再贅述。

本節主要關注ProcessList對oom_adj的定義。雖然前面介紹時將Android程式分為五大類,但是在實際程式碼中的劃分更為細緻,考慮得更為周全。

[-->ProcessList.java]

class ProcessList {

    //當一個程式連續發生Crash的間隔小於60秒時,系統認為它是為Bad程式

    staticfinal int MIN_CRASH_INTERVAL = 60*1000;

   //下面定義各種狀態下程式的oom_adj

 

   //不可見程式的oom_adj,最大15,最小為7。殺死這些程式一般不會帶來使用者能夠察覺的影響

    staticfinal int HIDDEN_APP_MAX_ADJ = 15;

    staticint HIDDEN_APP_MIN_ADJ = 7;

 

   /*

   位於B List中Service所在程式的oom_adj。什麼樣的Service會在BList中呢?其中的

   解釋只能用原文來表達” these are the old anddecrepit services that aren't as

   shiny andinteresting as the ones in the A list“

   */

    staticfinal int SERVICE_B_ADJ = 8;

 

   /*

   前一個程式的oom_adj,例如從A程式的Activity跳轉到位於B程式的Activity,

   B程式是當前程式,而A程式就是previous程式了。因為從當前程式A退回B程式是一個很簡單

   卻很頻繁的操作(例如按back鍵退回上一個Activity),所以previous程式的oom_adj

   需要單獨控制。這裡不能簡單按照五大類來劃分previous程式,還需要綜合考慮

   */

   staticfinal int PREVIOUS_APP_ADJ = 7;

 

   //Home程式的oom_adj為6,使用者經常和Home程式互動,故它的oom_adj也需要單獨控制

    staticfinal int HOME_APP_ADJ = 6;

 

   //Service類中程式的oom_adj為5

    staticfinal int SERVICE_ADJ = 5;

 

   //正在執行backup操作的程式,其oom_adj為4

    staticfinal int BACKUP_APP_ADJ = 4;

 

   //heavyweight的概念前面曽提到過,該類程式的oom_adj為3

    staticfinal int HEAVY_WEIGHT_APP_ADJ = 3;

 

   //Perceptible程式指那些當前並不在前端顯示而使用者能感覺到它在執行的程式,例如正在

   //後臺播放音樂的Music

    staticfinal int PERCEPTIBLE_APP_ADJ = 2;

 

   //Visible類程式,oom_adj為1

    staticfinal int VISIBLE_APP_ADJ = 1;

 

   //Forground類程式,oom_adj為0

    staticfinal int FOREGROUND_APP_ADJ = 0;

 

   //persistent類程式(即退出後,系統需要重新啟動的重要程式),其oom_adj為-12

    staticfinal int PERSISTENT_PROC_ADJ = -12;

 

    //核心服務所在程式,oom_adj為-12

    staticfinal int CORE_SERVER_ADJ = -12;

 

    //系統程式,其oom_adj為-16

    staticfinal int SYSTEM_ADJ = -16;

 

    //記憶體頁大小定義為4KB

    staticfinal int PAGE_SIZE = 4*1024;

 

    //系統中能容納的不可見程式數最少為2,最多為15。在Android 4.0系統中,可通過設定程式來

    //調整

    staticfinal int MIN_HIDDEN_APPS = 2;

    staticfinal int MAX_HIDDEN_APPS = 15;

 

  //下面兩個引數用於調整程式的LRU權重。注意,它們的註釋和具體程式碼中的用法不能統一起來

  staticfinal long CONTENT_APP_IDLE_OFFSET = 15*1000;

  staticfinal long EMPTY_APP_IDLE_OFFSET = 120*1000;

 

  //LMK設定了6個oom_adj閾值

  privatefinal int[] mOomAdj = new int[] {

           FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,

           BACKUP_APP_ADJ, HIDDEN_APP_MIN_ADJ, EMPTY_APP_ADJ

  };

   //用於記憶體較低機器(例如小於512MB)中LMK的記憶體閾值配置

    privatefinal long[] mOomMinFreeLow = new long[] {

           8192, 12288, 16384,

           24576, 28672, 32768

    };

   //用於較高記憶體機器(例如大於1GB)的LMK記憶體閾值配置

   privatefinal long[] mOomMinFreeHigh = new long[] {

           32768, 40960, 49152,

           57344, 65536, 81920

    };

從以上程式碼中定義的各種ADJ值可知,AMS中的程式管理規則遠比想象得要複雜(讀者以後見識到具體的程式碼,更會有這樣的體會)。

說明在ProcessList中定義的大部分變數在Android 2.3程式碼中定義於ActivityManagerService.java中,但這段程式碼的開發者僅把程式碼複製了過來,其中的註釋並未隨著系統升級而更新。

(2) ProcessRecord中相關成員變數的介紹

 ProcessRecord定義了較多成員變數用於程式管理。筆者不打算深究其中的細節。這裡僅把其中的主要變數及一些註釋列舉出來。下文會分析到它們的作用。

[-->ProcessRecord.java]

//用於LRU列表控制

long lastActivityTime;   // For managing the LRU list

long lruWeight;      // Weight for ordering in LRU list

//和oom_adj有關

int maxAdj;           // Maximum OOM adjustment for thisprocess

int hiddenAdj;       // If hidden, this is the adjustment touse

int curRawAdj;       // Current OOM unlimited adjustment forthis process

int setRawAdj;       // Last set OOM unlimited adjustment forthis process

int curAdj;           // Current OOM adjustment for thisprocess

int setAdj;           // Last set OOM adjustment for thisprocess

//和排程優先順序有關

int curSchedGroup;   // Currently desired scheduling class

int setSchedGroup;   // Last set to background scheduling class

//回收記憶體級別,見後文解釋

int trimMemoryLevel; // Last selected memorytrimming level

//判斷該程式的狀態,主要和其中執行的Activity,Service有關

boolean keeping;     // Actively running code sodon't kill due to that?

boolean setIsForeground;    // Running foreground UI when last set?

boolean foregroundServices; // Running anyservices that are foreground?

boolean foregroundActivities; // Running anyactivities that are foreground?

boolean systemNoUi;   // This is a system process, but notcurrently showing UI.

boolean hasShownUi;  // Has UI been shown in this process since itwas started?

boolean pendingUiClean;     // Want to clean up resources from showingUI?

boolean hasAboveClient;     // Bound using BIND_ABOVE_CLIENT, so wantto be lower

//是否處於系統BadProcess列表

boolean bad;                // True if disabled in the badprocess list

//描述該程式因為是否有太多後臺元件而被殺死

boolean killedBackground;   // True when proc has been killed due to toomany bg

String waitingToKill;       // Process is waiting to be killed whenin the bg; reason

//序號,每次調節程式優先順序或者LRU列表位置時,這些序號都會遞增

int adjSeq;                 // Sequence id for identifyingoom_adj assignment cycles

int lruSeq;                 // Sequence id for identifyingLRU update cycles

上面註釋中提到了LRU(最近最少使用)一詞,它和AMS另外一個用於管理應用程式ProcessRecord的資料結構有關。

提示程式管理和排程一向比較複雜,從ProcessRecord定義的這些變數中可見一斑。需要提醒讀者的是,對這部分功能的相關說明非常少,程式碼讀起來會感覺比較晦澀。

6.6.3  AMS程式管理函式分析

在AMS中,和程式管理有關的函式只要有兩個,分別是updateLruProcessLocked和updateOomAdjLocked。這兩個函式的呼叫點有多處,本節以attachApplication為切入點,嘗試對它們進行分析。

注意AMS一共定義了3個updateOomAdjLocked函式,此處將其歸為一類。

先回顧一下attachApplication函式被呼叫的情況:AMS新建立一個應用程式,該程式啟動後最重要的就是呼叫AMS的attachApplication。

提示不熟悉的讀者可閱讀6.3.3節的第5小節。

其相關程式碼如下:

[-->ActivityManagerService.java::attachApplicationLocked]

//attachApplication主要工作由attachApplicationLocked完成,故直接分析它

private final booleanattachApplicationLocked(IApplicationThread thread,

                                                      int pid) {

 ProcessRecord app;

  //根據之前的介紹的內容,AMS在建立應用程式前已經將對應的ProcessRecord儲存到

  //mPidsSelfLocked中了

  ...... //其他一些處理

 

  //初始化ProcessRecord中的一些成員變數

  app.thread= thread;

  app.curAdj= app.setAdj = -100;

 app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;

  app.setSchedGroup= Process.THREAD_GROUP_BG_NONINTERACTIVE;

 app.forcingToForeground = null;

 app.foregroundServices = false;

 app.hasShownUi = false;

  ......

  //呼叫應用程式的bindApplication,以初始化其內部的Android執行環境

  thread.bindApplication(......);

  //①呼叫updateLruProcessLocked函式

 updateLruProcessLocked(app, false, true);

 app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();

  ......//啟動Activity等操作

 

  //②didSomething為false,則呼叫updateOomAdjLocked函式

  if(!didSomething) {

      updateOomAdjLocked();

 }

在以上這段程式碼中有兩個重要函式呼叫,分別是updateLruProcessLocked和updateOomAdjLocked。

1.  updateLruProcessLocked函式分析

根據前文所述,我們知道了系統中所有應用程式(同時包括SystemServer)的ProcessRecord資訊都儲存在mPidsSelfLocked成員中。除此之外,AMS還有一個成員變數mLruProcesses也用於儲存ProcessRecord。mLruProcesses的型別雖然是ArrayList,但其內部成員卻是按照ProcessRecord的lruWeight大小排序的。在執行過程中,AMS會根據lruWeight的變化調整mLruProcesses成員的位置。

就本例而言,剛連線(attach)上的這個應用程式的ProcessRecord需要通過updateLruProcessLocked函式加入mLruProcesses陣列中。來看它的程式碼,如下所示:

[-->ActivityManagerService.java::updateLruProcessLocked]

final void updateLruProcessLocked(ProcessRecordapp,

           boolean oomAdj, boolean updateActivityTime) {

  mLruSeq++;//每一次調整LRU列表,系統都會分配一個唯一的編號

  updateLruProcessInternalLocked(app, oomAdj, updateActivityTime, 0);

 }

[-->ActivityManagerService.java::updateLruProcessInternalLocked]

private final voidupdateLruProcessInternalLocked(ProcessRecord app,

           boolean oomAdj, boolean updateActivityTime, int bestPos) {

 

  //獲取app在mLruProcesses中的索引位置,對於本例而言,返回值lrui為-1

  int lrui =mLruProcesses.indexOf(app);

  //如果之前有記錄,則先從陣列中刪掉,因為此處需要重新調整位置

  if (lrui>= 0) mLruProcesses.remove(lrui);

 

  //獲取mLruProcesses中陣列索引的最大值,從0開始

  int i =mLruProcesses.size()-1;

  intskipTop = 0;

       

  app.lruSeq= mLruSeq;  //將系統全域性的lru調整編號賦給ProcessRecord的lruSeq

       

  //更新lastActivityTime值,其實就是獲取一個時間

  if(updateActivityTime) {

        app.lastActivityTime =SystemClock.uptimeMillis();

  }

 

  if(app.activities.size() > 0) {

      //如果該app含Activity,則lruWeight為當前時間

      app.lruWeight = app.lastActivityTime;

   } else if(app.pubProviders.size() > 0) {

       /*

        如果有釋出的ContentProvider,則lruWeight要減去一個OFFSET。

        對此的理解需結合CONTENT_APP_IDLE_OFFSET的定義。讀者暫時把它

        看做一個常數

        */

       app.lruWeight = app.lastActivityTime -

                            ProcessList.CONTENT_APP_IDLE_OFFSET;

        //設定skipTop。這個變數實際上沒有用,放在此處讓人很頭疼

        skipTop = ProcessList.MIN_HIDDEN_APPS;

   } else {

         app.lruWeight = app.lastActivityTime -

                              ProcessList.EMPTY_APP_IDLE_OFFSET;

         skipTop = ProcessList.MIN_HIDDEN_APPS;

   }

 

  //從陣列最後一個元素開始迴圈

  while (i>= 0) {

     ProcessRecord p = mLruProcesses.get(i);

 

     //下面這個if語句沒有任何意義,因為skipTop除了做自減操作外,不影響其他任何內容

      if(skipTop > 0 && p.setAdj >= ProcessList.HIDDEN_APP_MIN_ADJ) {

          skipTop--;

       }

       //將app調整到合適的位置

       if(p.lruWeight <= app.lruWeight || i < bestPos) {

           mLruProcesses.add(i+1, app);

           break;

       }

       i--;

  }

  //如果沒有找到合適的位置,則把app加到佇列頭

  if (i <0)   mLruProcesses.add(0, app);

 

   //如果該將app 繫結到其他service,則要對應調整Service所在程式的LRU

   if (app.connections.size() > 0) {

       for(ConnectionRecord cr : app.connections) {

          if(cr.binding != null && cr.binding.service != null

                        && cr.binding.service.app!= null

                        &&cr.binding.service.app.lruSeq != mLruSeq) {

                        updateLruProcessInternalLocked(cr.binding.service.app,

                               oomAdj,updateActivityTime,i+1);

            }

        }

    }

  //conProviders也是一種Provider,相關資訊下一章再介紹

    if(app.conProviders.size() > 0) {

         for(ContentProviderRecord cpr : app.conProviders.keySet()) {

            ......//對ContentProvider所在程式做類似的調整

 

           }

        }

  //在本例中,oomAdj為false,故updateOomAdjLocked不會被呼叫

  if (oomAdj) updateOomAdjLocked(); //以後分析

}

從以上程式碼可知,updateLruProcessLocked的主要工作是根據app的lruWeight值調整它在陣列中的位置。lruWeight值越大,其在陣列中的位置就越靠後。如果該app和某些Service(僅考慮通過bindService建立關係的那些Service)或ContentProvider有互動關係,那麼這些Service或ContentProvider所在的程式也需要調節lruWeight值。

下面介紹第二個重要函式updateOomAdjLocked。

提示在以上程式碼中, skipTop變數完全沒有實際作用,卻給為閱讀程式碼帶來了很大干擾。

 

2.  updateOomAdjLocked函式分析

(1) updateOomAdjLocked分析之一

分段來看updateOomAdjLocked函式。

[-->ActivityManagerService.java::updateOomAdjLocked()]

final void updateOomAdjLocked() {

  //在一般情況下,resumedAppLocked返回 mResumedActivity,即當前正處於前臺的Activity

  finalActivityRecord TOP_ACT = resumedAppLocked();

  //得到前臺Activity所屬程式的ProcessRecord資訊

  finalProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;

 

  mAdjSeq++;//oom_adj在進行調節時也會有唯一的序號

 

  mNewNumServiceProcs= 0;

 

  /*

    下面這幾句程式碼的作用如下:

    1 根據hidden adj劃分級別,一共有9個級別(即numSlots值)

    2 根據mLruProcesses的成員個數計算平均落在各個級別的程式數(即factor值)。但是這裡

      的魔數(magic number)4卻令人頭疼不已。如有清楚該內容的讀者,不妨讓分享一下

      研究結果

  */

  intnumSlots = ProcessList.HIDDEN_APP_MAX_ADJ -

                                          ProcessList.HIDDEN_APP_MIN_ADJ + 1;

  int factor= (mLruProcesses.size()-4)/numSlots;

  if (factor< 1) factor = 1;

 

 

  int step =0;

  intnumHidden = 0;

       

  int i =mLruProcesses.size();

  intcurHiddenAdj = ProcessList.HIDDEN_APP_MIN_ADJ;

 //從mLruProcesses陣列末端開始迴圈

  while (i> 0) {

     i--;

     ProcessRecordapp = mLruProcesses.get(i);

 

     //①呼叫另外一個updateOomAdjLocked函式

     updateOomAdjLocked(app,curHiddenAdj, TOP_APP, true);

 

     // updateOomAdjLocked函式會更新app的curAdj

    if(curHiddenAdj < ProcessList.HIDDEN_APP_MAX_ADJ

                              &&app.curAdj == curHiddenAdj) {

       /*

       這段程式碼的目的其實很簡單。即當某個adj級別的ProcessRecord處理個數超過均值後,

       就跳到下一級別進行處理。注意,這段程式碼的結果會影響updateOomAdjLocked的第二個引數

      */

           step++;

           if(step >= factor) {

               step = 0;

               curHiddenAdj++;

           }

       }// if(curHiddenAdj < ProcessList.HIDDEN_APP_MAX_ADJ...)判斷結束

 

     //app.killedBackground初值為false

     if(!app.killedBackground) {

          if (app.curAdj >= ProcessList.HIDDEN_APP_MIN_ADJ) {

                numHidden++;

              //mProcessLimit初始值為ProcessList.MAX(值為15),

             //可通過setProcessLimit函式對其進行修改

                if (numHidden > mProcessLimit) {

                      app.killedBackground =true;

                      //如果後臺程式個數超過限制,則會殺死對應的後臺程式

                     Process.killProcessQuiet(app.pid);

                }

            }

      }//if(!app.killedBackground)判斷結束

  }//while迴圈結束

updateOomAdjLocked第一階段的工作看起來很簡單,但是其中也包含一些較難理解的內容。

·  處理hidden adj,劃分9個級別。

·  根據mLruProcesses中程式個數計算每個級別平均會存在多少程式。在這個計算過程中出現了一個魔數4令人極度費解。

·  然後利用一個迴圈從mLruProcesses末端開始對每個程式執行另一個updateOomAdjLocked函式。關於這個函式的內容,我們放到下一節再討論。

·  判斷處於Hidden狀態的程式數是否超過限制,如果超過限制,則會殺死一些程式。

接著來看updateOomAdjLocked下一階段的工作。

(2) updateOomAdjLocked分析之二

[-->ActivityManagerService.java::updateOomAdjLocked]

 mNumServiceProcs = mNewNumServiceProcs;

 //numHidden表示處於hidden狀態的程式個數

  //當Hidden程式個數小於7時候(15/2的整型值),執行if分支

  if(numHidden <= (ProcessList.MAX_HIDDEN_APPS/2)) {

       ......

     /*

      我們不討論這段缺乏文件及使用魔數的程式碼,但這裡有個知識點要注意:

      該知識點和Android 4.0新增介面ComponentCallbacks2有關,主要是通知應用程式進行

      記憶體清理,ComponentCallbacks2介面定了一個函式onTrimMemory(int level),

      而四大元件除BroadcastReceiver外,均實現了該介面。系統定義了4個level以通知程式

      做對應處理:

      TRIM_MEMORY_UI_HIDDEN,提示程式當前不處於前臺,故可釋放一些UI資源

      TRIM_MEMORY_BACKGROUND,表明該程式已加入LRU列表,此時程式可以對一些簡單的資源

      進行清理

      TRIM_MEMORY_MODERATE,提示程式可以釋放一些資源,這樣其他程式的日子會好過些。

      即所謂的“我為人人,人人為我”

      TRIM_MEMORY_COMPLETE,該程式需儘可能釋放一些資源,否則當記憶體不足時,它可能被殺死

      */

   } else {//假設hidden程式數超過7,

      finalint N = mLruProcesses.size();

      for(i=0; i<N; i++) {

       ProcessRecord app = mLruProcesses.get(i);

        if ((app.curAdj >ProcessList.VISIBLE_APP_ADJ || app.systemNoUi)

              && app.pendingUiClean) {

            if (app.trimMemoryLevel <

                        ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN

                        && app.thread!= null) {

                   try {//呼叫應用程式ApplicationThread的scheduleTrimMemory函式

                        app.thread.scheduleTrimMemory(

                                    ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);

                      }......

                }// if (app.trimMemoryLevel...)判斷結束

              app.trimMemoryLevel =

                             ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;

                   app.pendingUiClean = false;

         }else {

              app.trimMemoryLevel = 0;

          }

       }//for迴圈結束

        }//else結束

   //Android4.0中設定有一個開發人員選項,其中有一項用於控制是否銷燬後臺的Activity。

   //讀者可自行研究destroyActivitiesLocked函式

   if (mAlwaysFinishActivities)

       mMainStack.destroyActivitiesLocked(null, false,"always-finish");

 }

通過上述程式碼,可獲得兩個資訊:

·  Android 4.0增加了新的介面類ComponentCallbacks2,其中只定義了一個函式onTrimMemory。從以上描述中可知,它主要通知應用程式進行一定的記憶體釋放。

·  Android 4.0 Settings新增了一個開放人員選項,通過它可控制AMS對後臺Activity的操作。

這裡和讀者探討一下ComponentCallbacks2介面的意義。此介面的目的是通知應用程式根據情況做一些記憶體釋放,但筆者覺得,這種設計方案的優劣尚有待考證,主要是出於以下下幾種考慮:

第一,不是所有應用程式都會實現該函式。原因有很多,主要原因是,該介面只是SDK 14才有的,之前的版本沒有這個介面。另外,應用程式都會盡可能搶佔資源(在不超過允許範圍內)以保證執行速度,不應該考慮其他程式的事情。

第二個重要原因是無法區分在不同的level下到底要釋放什麼樣的記憶體。程式碼中的註釋也是含糊其辭。到底什麼樣的資源可以在TRIM_MEMORY_BACKGROUND級別下釋放,什麼樣的資源不可以在TRIM_MEMORY_BACKGROUND級別下釋放?

既然系統加了這些介面,讀者不妨參考原始碼中的使用案例來開發自己的程式。

建議真誠希望Google能給出一個明確的文件,說明這幾個函式該怎麼使用。

接下來分析在以上程式碼中出現的針對每個ProcessRecord都呼叫的updateOomAdjLocked函式。

3.  第二個updateOomAdjLocked分析

[-->ActivityManagerService.java::updateOomAdjLocked]

private final boolean updateOomAdjLocked( ProcessRecordapp, int hiddenAdj,

           ProcessRecord TOP_APP, boolean doingAll) {

  //設定該app的hiddenAdj

 app.hiddenAdj = hiddenAdj;

 

  if(app.thread == null)  return false;

 

  finalboolean wasKeeping = app.keeping;

  booleansuccess = true;

   //下面這個函式的呼叫極其關鍵。從名字上看,它會計算該程式的oom_adj及排程策略

  computeOomAdjLocked(app, hiddenAdj, TOP_APP, false, doingAll);

 

  if(app.curRawAdj != app.setRawAdj) {

      if (wasKeeping && !app.keeping) {

           .....//統計電量

          app.lastCpuTime = app.curCpuTime;

        }

 

     app.setRawAdj = app.curRawAdj;

  }

  //如果新舊oom_adj不同,則重新設定該程式的oom_adj

  if(app.curAdj != app.setAdj) {

       if(Process.setOomAdj(app.pid, app.curAdj)) //設定該程式的oom_adj

            app.setAdj = app.curAdj;

       .....

   }

  //如果新舊排程策略不同,則需重新設定該程式的排程策略

  if(app.setSchedGroup != app.curSchedGroup) {

      app.setSchedGroup = app.curSchedGroup;

        //waitingToKill是一個字串,用於描述殺掉該程式的原因

       if (app.waitingToKill != null &&

            app.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {

             Process.killProcessQuiet(app.pid);//

            success = false;

        } else{

          if (true) {//強制執行if分支

                long oldId = Binder.clearCallingIdentity();

                   try {//設定程式排程策略

                       Process.setProcessGroup(app.pid, app.curSchedGroup);

                   }......

               } ......

           }

        }

   returnsuccess;

 }

上面的程式碼還算簡單,主要完成兩項工作:

·  呼叫computeOomAdjLocked計算獲得某個程式的oom_adj和排程策略。

·  調整程式的排程策略和oom_adj。

建議思考一個問題:為何AMS只設定程式的排程策略,而不設定程式的排程優先順序?

看來AMS排程演算法的核心就在computeOomAdjLocked中。

4.  computeOomAdjLocked分析

這段程式碼較長,其核心思想是綜合考慮各種情況以計算程式的oom_adj和排程策略。建議讀者閱讀程式碼時聚焦到AMS關注的幾個因素上。computeOomAdjLocked的程式碼如下:

[-->ActivityManagerService.java::computeOomAdjLocked]

private final intcomputeOomAdjLocked(ProcessRecord app, int hiddenAdj,

           ProcessRecord TOP_APP, boolean recursed, boolean doingAll) {

  ......

  app.adjTypeCode= ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN;

 app.adjSource = null;

 app.adjTarget = null;

  app.empty= false;

  app.hidden= false;

 

  //該應用程式包含Activity的個數

  final intactivitiesSize = app.activities.size();

 

  //如果maxAdj小於FOREGROUND_APP_ADJ,基本上沒什麼工作可以做了。這類程式優先順序相當高

  if(app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {

       ......//讀者可自行閱讀這塊程式碼

      return(app.curAdj=app.maxAdj);

   }

 

  finalboolean hadForegroundActivities = app.foregroundActivities;

 

 app.foregroundActivities = false;

 app.keeping = false;

 app.systemNoUi = false;

 

  int adj;

  intschedGroup;

  //如果app為前臺Activity所在的那個應用程式

  if (app ==TOP_APP) {

       adj =ProcessList.FOREGROUND_APP_ADJ;

      schedGroup = Process.THREAD_GROUP_DEFAULT;

       app.adjType= "top-activity";

      app.foregroundActivities = true;

    } else if (app.instrumentationClass != null){

             ......//略過instrumentationClass不為null的情況

    } else if (app.curReceiver != null ||

         (mPendingBroadcast != null && mPendingBroadcast.curApp == app)){

         //此情況對應正在執行onReceive函式的廣播接收者所在程式,它的優先順序也很高

         adj = ProcessList.FOREGROUND_APP_ADJ;

         schedGroup = Process.THREAD_GROUP_DEFAULT;

         app.adjType = "broadcast";

   } else if(app.executingServices.size() > 0) {

        //正在執行Service生命週期函式的程式

        adj= ProcessList.FOREGROUND_APP_ADJ;

       schedGroup = Process.THREAD_GROUP_DEFAULT;

       app.adjType = "exec-service";

    } elseif (activitiesSize > 0) {

         adj = hiddenAdj;

        schedGroup =Process.THREAD_GROUP_BG_NONINTERACTIVE;

        app.hidden = true;

        app.adjType = "bg-activities";

    } else {//不含任何元件的程式,即所謂的Empty程式

         adj= hiddenAdj;

        schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;

         app.hidden= true;

         app.empty = true;

        app.adjType = "bg-empty";

   }

   //下面幾段程式碼將根據情況重新調整前面計算處理的adj和schedGroup,我們以後面的

   //mHomeProcess判斷為例

   if(!app.foregroundActivities && activitiesSize > 0) {

         //對無前臺Activity所在程式的處理

   }

 

   if (adj> ProcessList.PERCEPTIBLE_APP_ADJ) {

        .......

    }

   //如果前面計算出來的adj大於HOME_APP_ADJ,並且該程式又是Home程式,則需要重新調整

   if (adj> ProcessList.HOME_APP_ADJ && app == mHomeProcess) {

        //重新調整adj和schedGroupde的值

        adj = ProcessList.HOME_APP_ADJ;

       schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;

       app.hidden = false;

       app.adjType = "home";//描述調節adj的原因

   }

  

   if (adj> ProcessList.PREVIOUS_APP_ADJ && app == mPreviousProcess

           && app.activities.size() > 0) {

          ......

   }

 

  app.adjSeq = mAdjSeq;

  app.curRawAdj = adj;

 

    ......

   //下面這幾段程式碼處理那些程式中含有Service,ContentProvider元件情況下的adj調節

   if(app.services.size() != 0 && (adj > ProcessList.FOREGROUND_APP_ADJ

          || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {

   }

 

   if(s.connections.size() > 0 && (adj >ProcessList.FOREGROUND_APP_ADJ

             || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {

   }

 

   if(app.pubProviders.size() != 0 && (adj >ProcessList.FOREGROUND_APP_ADJ

              || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {

           ......

   }

 

   //終於計算完畢

  app.curRawAdj = adj;

       

   if (adj> app.maxAdj) {

        adj= app.maxAdj;

        if(app.maxAdj <= ProcessList.PERCEPTIBLE_APP_ADJ)

           schedGroup = Process.THREAD_GROUP_DEFAULT;

    }

 

    if (adj< ProcessList.HIDDEN_APP_MIN_ADJ)

        app.keeping = true;

     ......

   app.curAdj = adj;

   app.curSchedGroup = schedGroup;

   ......

   returnapp.curRawAdj;

}

computeOomAdjLocked的工作比較瑣碎,實際上也談不上什麼演算法,僅僅是簡單地根據各種情況來設定幾個值。隨著系統的改進和完善,這部分程式碼變動的可能性比較大。

5. updateOomAdjLocked呼叫點統計

updateOomAdjLocked呼叫點很多,這裡給出其中一個updateOomAdjLocked函式的呼叫點統計,如圖6-23所示。


圖6-23  updateOomAdjLocked函式的呼叫點統計圖

注意,圖6-23統計的是updateOomAdjLocked(ProcessRecord)函式的呼叫點。從該圖可知,此函式被呼叫的地方較多,這也說明AMS非常關注應用程式的狀況。

提示筆者覺得,AMS中這部分程式碼不是特別高效,不知各位讀者是否有同感,?

6.6.4  AMS程式管理總結

本節首先向讀者介紹了Linux平臺中和程式排程、OOM管理方面的API,然後介紹了AMS如何利用這些API完成Android平臺中程式管理方面的工作,從中可以發現,AMS設定的檢查點比較密集,也就是經常會進行程式排程方面的操作。

6.7  App的 Crash處理

在Android平臺中,應用程式fork出來後會為虛擬機器設定一個未截獲異常處理器,即在程式執行時,如果有任何一個執行緒丟擲了未被截獲的異常,那麼該異常最終會拋給未截獲異常處理器去處理。設定未截獲異常處理器的程式碼如下:

[-->RuntimeInit.java::commonInit]

private static final void commonInit() {

   //呼叫完畢後,該應用中所有執行緒丟擲的未處理異常都會由UncaughtHandler來處理

   Thread.setDefaultUncaughtExceptionHandler(newUncaughtHandler());

  ......

}

應用程式有問題是再平常不過的事情了,不過,當丟擲的異常沒有被截獲時,系統又會做什麼處理呢?來看UncaughtHandler的程式碼。

6.7.1  應用程式的Crash處理

[-->RuntimeInit.java::UncaughtHandler]

 privatestatic class UncaughtHandler implements

                                           Thread.UncaughtExceptionHandler{

   publicvoid uncaughtException(Thread t, Throwable e) {

    try {

          if (mCrashing) return;

           mCrashing = true;

         //呼叫AMS的handleApplicationCrash函式。第一個引數mApplicationObject其實

        //就是前面經常見到的ApplicationThread物件

          ActivityManagerNative.getDefault().handleApplicationCrash(

               mApplicationObject, new  ApplicationErrorReport.CrashInfo(e));

     } ......

    finally{

           //這裡有一句註釋很有意思:Try everything to make surethis process goes

          //away.從下面這兩句呼叫來看,該應用程式確實想方設法要離開Java世界

          Process.killProcess(Process.myPid());//把自己殺死

          System.exit(10);//如果上面那句話不成功,再嘗試exit方法

     }

   }

}

6.7.2  AMS的handleApplicationCrash分析

AMS handleApplicationCrash函式的程式碼如下:

[-->ActivityManagerService.java::handleApplicationCrash]

public void handleApplicationCrash(IBinder app,

                             ApplicationErrorReport.CrashInfocrashInfo) {

  //找到對應的ProcessRecord資訊,後面那個字串“Crash”用於列印輸出

 ProcessRecord r = findAppProcess(app, "Crash");

  finalString processName = app == null ? "system_server"

                        : (r == null ? "unknown": r.processName);

   //新增crash資訊到dropbox中

 ddErrorToDropBox("crash", r, processName, null, null, null,null, null,

                         crashInfo);

  //呼叫crashApplication函式

 crashApplication(r, crashInfo);

}

[-->ActivityManagerService.java::crashApplication]

private void crashApplication(ProcessRecord r,

                   ApplicationErrorReport.CrashInfocrashInfo) {

   longtimeMillis = System.currentTimeMillis();

   //從應用程式傳遞過來的crashInfo中獲取相關資訊

   StringshortMsg = crashInfo.exceptionClassName;

   StringlongMsg = crashInfo.exceptionMessage;

   StringstackTrace = crashInfo.stackTrace;

   if(shortMsg != null && longMsg != null) {

      longMsg = shortMsg + ": " + longMsg;

     } elseif (shortMsg != null) {

      longMsg = shortMsg;

   }

 

  AppErrorResult result = new AppErrorResult();

  synchronized (this) {

      if(mController != null) {

         //通知監視者。目前僅MonkeyTest中會為AMS設定監聽者。測試過程中可設定是否一檢測

         //到App Crash即停止測試。另外,Monkey測試也會將App Crash資訊儲存起來

        //供開發人員分析

       }

 

      final long origId =Binder.clearCallingIdentity();

 

      if (r!= null && r.instrumentationClass != null) {

       ......// instrumentationClass不為空的情況

      }

      //①呼叫makeAppCrashingLocked處理,如果返回false,則整個處理流程完畢

     if (r == null || !makeAppCrashingLocked(r,shortMsg, longMsg,

                        stackTrace)) {

         ......

       return;

      }

     Message msg = Message.obtain();

     msg.what = SHOW_ERROR_MSG;

     HashMap data = new HashMap();

     data.put("result", result);

     data.put("app", r);

     msg.obj = data;

    //傳送SHOW_ERROR_MSG訊息給mHandler,預設處理是彈出一個對話方塊,提示使用者某程式   //崩潰(Crash),使用者可以選擇“退出”或 “退出並報告”

     mHandler.sendMessage(msg);

      ......

   }//synchronized(this)結束

 

   //下面這個get函式是阻塞的,直到使用者處理了對話方塊為止。注意,此處涉及兩個執行緒:

 //handleApplicationCrash函式是在Binder呼叫執行緒中處理的,而對話方塊則是在mHandler所

  //線上程中處理的

   int res =result.get();

   IntentappErrorIntent = null;

  synchronized (this) {

     if (r!= null)

        mProcessCrashTimes.put(r.info.processName, r.info.uid,

                       SystemClock.uptimeMillis());

 

   if (res== AppErrorDialog.FORCE_QUIT_AND_REPORT)

      //createAppErrorIntentLocked返回一個Intent,該Intent的Action是

      //"android.intent.action.APP_ERROR",指定接收者是app. errorReportReceiver

      //成員,該成員變數在關鍵點makeAppCrashingLocked中被設定

       appErrorIntent =createAppErrorIntentLocked(r, timeMillis,

                       crashInfo);

   }//synchronized(this)結束

 

   if(appErrorIntent != null) {

       try {//啟動能處理APP_ERROR的應用程式,目前的原始碼中還沒有地方處理它

             mContext.startActivity(appErrorIntent);

       } ......

    }

 }

以上程式碼中還有一個關鍵函式makeAppCrashingLocked,其程式碼如下:

[-->ActivityManagerService.java::makeAppCrashingLocked]

private booleanmakeAppCrashingLocked(ProcessRecord app,

           String shortMsg, String longMsg, String stackTrace) {

  app.crashing = true;

   //生成一個錯誤報告,存放在crashingReport變數中

  app.crashingReport = generateProcessError(app,

               ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg,

               longMsg, stackTrace);

   /*

    在上邊程式碼中,我們知道系統會通過APP_ERROR Intent啟動一個Activity去處理錯誤報告,

    實際上在此之前,系統需要為它找到指定的接收者(如果有)。程式碼在startAppProblemLocked

    中,此處簡單描述該函式的處理過程如下:

    1 先查詢Settings Secure表中“send_action_app_error”是否使能,如果沒有使能,則

      不能設定指定接收者

    2 通過PKMS查詢安裝此Crash應用的應用是否能接收APP_ERROR Intent。必須注意

     安裝此應用的應用(例如通過“安卓市場”安裝了該Crash應用)。如果沒有,則轉下一步處理

    3 查詢系統屬性“ro.error.receiver.system.apps”是否指定了APP_ERROR處理者,如果

      沒有,則轉下一步處理

    4 查詢系統屬性“ro.error.receiver.default”是否指定了預設的處理者

    5 處理者的資訊儲存在app的errorReportReceiver變數中

    另外,如果該Crash應用正好是序列廣播傳送處理中的一員,則需要結束它的處理流程以

    保證後續廣播處理能正常執行。讀者可參考skipCurrentReceiverLocked函式

  */

  startAppProblemLocked(app);

  app.stopFreezingAllLocked();

   //呼叫handleAppCrashLocked做進一步處理。讀者可自行閱讀

   returnhandleAppCrashLocked(app);

 }

當App的Crash處理完後,事情並未就此結束,因為該應用程式退出後,之前AMS為它設定的訃告接收物件將被喚醒。接下來介紹AppDeathRecipientbinderDied的處理流程。

6.7.3  AppDeathRecipient binderDied分析

1.  binderDied函式分析

[-->ActvityManagerService.java::AppDeathRecipientbinderDied]

public void binderDied() {

  //注意,該函式也是通過Binder執行緒呼叫的,所以此處要加鎖

 synchronized(ActivityManagerService.this) {

       appDiedLocked(mApp, mPid, mAppThread);

   }

 }

最終的處理函式是appDiedLocked,其中所傳遞的3個引數儲存了對應死亡程式的資訊。來看appDiedLocked的程式碼::

[-->ActvityManagerService.java::appDiedLocked]

final void appDiedLocked(ProcessRecord app, intpid,

                                                IApplicationThread thread) {

   ......

   if(app.pid == pid && app.thread != null &&

          app.thread.asBinder() == thread.asBinder()) {

   //當記憶體低到水位線時,LMK模組也會殺死程式。對AMS來說,需要區分程式死亡是LMK導致的

   //還是其他原因導致的。App instrumentationClass一般都為空,故此處doLowMem為true

    booleandoLowMem = app.instrumentationClass == null;

 

    //①下面這個函式非常重要

   handleAppDiedLocked(app, false, true);

 

    if(doLowMem) {

      boolean haveBg = false;

      //如果系統中還存在oom_adj大於HIDDEN_APP_MIN_ADJ的程式,就表明不是LMK模組因

      //記憶體不夠而導致程式死亡的

       for(int i=mLruProcesses.size()-1; i>=0; i--) {

          ProcessRecord rec = mLruProcesses.get(i);

          if (rec.thread != null && rec.setAdj >=

                                           ProcessList.HIDDEN_APP_MIN_ADJ){

                haveBg = true;//還有後臺程式,故可確定系統記憶體尚未吃緊

                break;

            }

        }//for迴圈結束

      //如果沒有後臺程式,表明系統記憶體已吃緊

      if(!haveBg) {

         long now = SystemClock.uptimeMillis();

          for(int i=mLruProcesses.size()-1; i>=0; i--) {

               .....//將這些程式按一定規則加到mProcessesToGc中,儘量保證

              //heavy/important/visible/foreground的程式位於mProcessesToGc陣列

              //的前端

          }//for迴圈結束

          /*

           傳送GC_BACKGROUND_PROCESSES_MSG訊息給mHandler,該訊息的處理過程就是:

           呼叫應用程式的scheduleLowMemory或processInBackground函式。其中,

           scheduleLowMemory將觸發onLowMemory回撥被呼叫,而processInBackground將

           觸發應用程式進行一次垃圾回收

           讀者可自行閱讀該訊息的處理函式performAppGcsIfAppropriateLocked

          */

         scheduleAppGcsLocked();

       }// if(!haveBg)判斷結束

    }//if(doLowMem)判斷結束

 }

以上程式碼中有一個關鍵函式handleAppDiedLocked,下面來看它的處理過程。

2.  handleAppDiedLocked函式分析

[-->ActivityManagerService.java::handleAppDiedLocked]

private final voidhandleAppDiedLocked(ProcessRecord app,

           boolean restarting, boolean allowRestart) {

   //①在本例中,傳入的引數為restarting=false, allowRestart=true

  cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);

   if(!restarting) {

       mLruProcesses.remove(app);

   }

   ......//下面還有一部分程式碼處理和Activity相關的收尾工作,讀者可自行閱讀

}

重點看上邊程式碼中的cleanUpApplicationRecordLocked函式,該函式的主要功能就是處理Service、ContentProvider及BroadcastReceiver相關的收尾工作。先來看Service方面的工作。

(1) cleanUpApplicationRecordLocked之處理Service

[-->ActivityManagerService.java::cleanUpApplicationRecordLocked]

 privatefinal void cleanUpApplicationRecordLocked(ProcessRecord app,

       boolean restarting, boolean allowRestart, int index) {

   if (index>= 0) mLruProcesses.remove(index);

 

   mProcessesToGc.remove(app);

   //如果該Crash程式有對應開啟的對話方塊,則關閉它們,這些對話方塊包括crash、anr和wait等

   if(app.crashDialog != null) {

       app.crashDialog.dismiss();

       app.crashDialog = null;

   }

   ......//處理anrDialog、waitDialog

 

   //清理app的一些引數

  app.crashing = false;

  app.notResponding = false;

   ......

   //處理該程式中所駐留的Service或它和別的程式中的Service建立的Connection關係

   //該函式是AMS Service處理流程中很重要的一環,讀者要仔細閱讀

   killServicesLocked(app,allowRestart);

cleanUpApplicationRecordLocked函式首先處理幾個對話方塊(dialog),然後呼叫killServicesLocked函式做相關處理。作為Service流程的一部分,讀者需要深入研究。

(2) cleanUpApplicationRecordLocked之處理ContentProvider

再來看cleanUpApplicationRecordLocked下一階段的工作,主要和ContentProvider有關。

[-->ActivityManagerService.java::cleanUpApplicationRecordLocked]

   booleanrestart = false;

 

   int NL = mLaunchingProviders.size();

       

   if(!app.pubProviders.isEmpty()) {

       //得到該程式中釋出的ContentProvider資訊

       Iterator<ContentProviderRecord> it =

                          app.pubProviders.values().iterator();

       while(it.hasNext()) {

         ContentProviderRecord cpr = it.next();

          cpr.provider = null;

         cpr.proc = null;

         int i = 0;

          if(!app.bad && allowRestart) {

              for (; i<NL; i++) {

                 /*

                   如果有客戶端程式在等待這個已經死亡的ContentProvider,則系統會

                   嘗試重新啟動它,即設定restart變數為true

                */

                 if (mLaunchingProviders.get(i) == cpr) {

                       restart = true;

                        break;

                  }

              }//for迴圈結束

          } else  i = NL;

 

          if(i >= NL) {

             /*

              如果沒有客戶端程式等待這個ContentProvider,則呼叫下面這個函式處理它,我們

              在卷I的第10章曾提過一個問題,即ContentProvider程式被殺死

              後,系統該如何處理那些使用了該ContentProvider的客戶端程式。例如,Music和

              MediaProvider之間有互動,如果殺死了MediaProvider,Music會怎樣呢?

              答案是系統會殺死Music,證據就在removeDyingProviderLocked函式

              中,讀者可自行閱讀其內部處理流程

             */

             removeDyingProviderLocked(app, cpr);

             NL = mLaunchingProviders.size();

           }

      }// while(it.hasNext())迴圈結束

    app.pubProviders.clear();

   }

   //下面這個函式的功能是檢查本程式中的ContentProvider是否存在於

  // mLaunchingProviders中,如果存在,則表明有客戶端在等待,故需考慮是否重啟本程式或者

  //殺死客戶端(當死亡程式變成bad process的時,需要殺死客戶端)

   if(checkAppInLaunchingProvidersLocked(app, false)) restart = true;

   ......

從以上的描述中可知,ContentProvider所在程式和其客戶端程式實際上有著非常緊密而隱晦(之所以說其隱晦,是因為SDK中沒有任何說明)的關係。在目前軟體開發追求模組間儘量保持鬆耦合關係的大趨勢下,Android中的ContentProvider和其客戶端這種緊耦合的設計思路似乎不夠明智。不過,這種設計是否是不得已而為之呢?讀者不妨探討一下,如果有更合適的解決方案,期待能一起分享。

(3) cleanUpApplicationRecordLocked之處理BroadcastReceiver

[-->ActivityManagerService.java::cleanUpApplicationRecordLocked]

 

  skipCurrentReceiverLocked(app);

    //從AMS中去除接收者

   if(app.receivers.size() > 0) {

      Iterator<ReceiverList> it = app.receivers.iterator();

       while(it.hasNext()) {

          removeReceiverLocked(it.next());

        }

       app.receivers.clear();

   }

       

   if(mBackupTarget != null && app.pid == mBackupTarget.app.pid) {

         //處理Backup資訊

    }

 

    mHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid,

                                   app.info.uid,null).sendToTarget();

    //注意該變數名為restarting,前面設定為restart.

    if(restarting) return;

 

 

    if(!app.persistent) {

        mProcessNames.remove(app.processName, app.info.uid);

         if(mHeavyWeightProcess == app) {

             ......//處理HeavyWeightProcess

           }

      } elseif (!app.removed) {

         if(mPersistentStartingProcesses.indexOf(app) < 0) {

              mPersistentStartingProcesses.add(app);

               restart = true;

          }

      }

    mProcessesOnHold.remove(app);

     if (app== mHomeProcess)  mHomeProcess = null;

       

    

     if(restart) {//如果需要重啟,則呼叫startProcessLocked處理它

          mProcessNames.put(app.processName, app.info.uid, app);

         startProcessLocked(app, "restart", app.processName);

     } elseif (app.pid > 0 && app.pid != MY_PID) {

        synchronized (mPidsSelfLocked) {

             mPidsSelfLocked.remove(app.pid);

             mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);

          }

         app.setPid(0);

     }

 }

在這段程式碼中,除了處理BrodcastReceiver方面的工作外,還包括其他方面的收尾工作。最後,如果要重啟該應用,則需呼叫startProcessLocked函式進行處理。這部分程式碼不再詳述,讀者可自行閱讀。

6.7.4  App的Crash處理總結

分析完整個處理流程,有些讀者或許會咂舌。應用程式的誕生是一件很麻煩的事情,沒想到應用程式的善後工作居然也很費事,希望各個應用程式能活得更穩健點兒。

圖6-24展示了應用程式進行Crash處理的流程。


圖6-24  應用程式的Crash處理流程

 

6.7  本章學習指導

本章內容較為複雜,即使用了這麼長的篇幅來講解AMS,依然只能覆蓋其中一部分內容。讀者在閱讀本章時,一定要注意文中的分析脈絡,以搞清楚流程為主旨。以下是本章的思路總結:

·  首先搞清楚AMS初創時期的一系列流程,這對理解Android執行環境和系統啟動流程等很有幫助。

·  搞清楚一個最簡單的情形下Activity啟動所歷經的磨難。這部分流程最複雜,建議讀者在搞清書中所闡述內容的前提下結合具體問題進行分析。

·  BroadcastReceiver的處理流程相對簡單。讀者務必理解AMS傳送廣播的處理流程,這對實際工作非常有幫助。例如最近在處理一個廣播時,發現靜態註冊的廣播接收者收到廣播的時間較長,研究了AMS廣播傳送的流程後,將其改成了動態註冊,結果響應速度就快了很多。

·  關於Service的處理流程希望讀者根據流程圖自行分析和研究。

·  AMS中的程式管理這一部分內容最難看懂。此處有多方面的原因,筆者覺得和缺乏相關說明有重要關係。建議讀者只瞭解AMS程式管理的大概面貌即可。另外,建議讀者不要試圖通過修改這部分程式碼來優化Android的執行效率。程式排程規則向來比較複雜,只有在大量實驗的基礎上才能得到一個合適的模型。

·  AMS在處理應用程式的Crash及死亡的工作上也是不遺餘力的。這部分工作相對比較簡單,相信讀者能輕鬆掌握。

由於精力和篇幅的原因,AMS中還有很多精彩的內容未能涉及,建議讀者在本章學習基礎上,根據具體情況繼續深入研究。

6.8  本章小結

本章對AMS進行有針對性的分析:

·  先分析了AMS的建立及初始化過程。

·  以啟動一個Activity為例,分析了Activity的啟動,應用程式的建立等一系列比較複雜的處理流程。

·  介紹了廣播接收者及廣播傳送的處理流程。Service的處理流程讀者可根據流程圖並結合程式碼自行開展研究。

·  還介紹了AMS中的程式管理及相關的知識。重點是在AMS中對LRU、oom_adj及scheduleGroup的調整。

·  最後介紹了應用程式Crash及死亡後,AMS的處理流程。

 



[①] AMS較複雜,其中一個原因是其功能較多,另一個不可忽視的原因是它的程式碼質量及結構並不出彩。

[②] 實際上,SettingsProvider.apk也執行於SystemServer程式中。

[③] http://www.merriam-webster.com/dictionary/activity中第六條解釋

[④] 關於zygote的工作原理,請讀者閱讀卷I第4章“深入理解Zygote”。

[⑤] 由於系統內部儲存了Stikcy廣播,當一個惡意程式頻繁傳送這種廣播時,是否會把記憶體都耗光呢?

相關文章