詳細(xì)分析PackageManagerService
·??SystemServer.java
frameworks/base/services/java/com/android/server/SystemServer.java
·?? IPackageManager.aidl
frameworks/base/core/android/java/content/pm/IPackageManager.aidl
·??PackageManagerService.java
frameworks/base/services/java/com/android/server/pm/PackageManagerService.java
·??Settings.java
frameworks/base/services/java/com/android/server/pm/Settings.java
·??SystemUI的AndroidManifest.xml
frameworks/base/package/systemui/AndroidManifest.xml
·??PackageParser.java
frameworks/base/core/java/android/content/pm/PackageParser.java
·??commandline.c
system/core/adb/commandline.c
·??installd.c
frameworks/base/cmds/installd/installd.c
·??commands.c
frameworks/base/cmds/installd/commands.c
·??pm腳本文件
frameworks/base/cmds/pm/pm
·??Pm.java
frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.java
·??DefaultContainerService.java
frameworks/base/packages/defaultcontainerservice/src/com/android/defaultcontainerservice/DefaultContainerService.java
·??UserManager.java
frameworks/base/services/java/com/android/server/pm/UserManager.java
·??UserInfo.java
frameworks/base/core/android/java/content/pm/UserInfo.java
PackageManagerService是本書分析的第一個(gè)核心服務(wù),也是Android系統(tǒng)中最常用的服務(wù)之一。它負(fù)責(zé)系統(tǒng)中Package的管理,應(yīng)用程序的安裝、卸載、信息查詢等。圖4-1展示了PackageManagerService及客戶端的類家族。
http://wiki.jikexueyuan.com/project/deep-android-v2/images/chapter4/image001.png" alt="image" />
圖4-1? PackageManagerService及客戶端類家族
由圖4-1可知:
·??IPackageManager接口類中定義了服務(wù)端和客戶端通信的業(yè)務(wù)函數(shù),還定義了內(nèi)部類Stub,該類從Binder派生并實(shí)現(xiàn)了IPackageManager接口。
·??PackageManagerService繼承自IPackageManager.Stub類,由于Stub類從Binder派生,因此PackageManagerService將作為服務(wù)端參與Binder通信。
·??Stub類中定義了一個(gè)內(nèi)部類Proxy,該類有一個(gè)IBinder類型(實(shí)際類型為BinderProxy)的成員變量mRemote,根據(jù)第2章介紹的Binder系統(tǒng)的知識,mRemote用于和服務(wù)端PackageManagerService通信。
·??IPackageManager接口類中定義了許多業(yè)務(wù)函數(shù),但是出于安全等方面的考慮,Android對外(即SDK)提供的只是一個(gè)子集,該子集被封裝在抽象類PackageManager中??蛻舳艘话阃ㄟ^Context的getPackageManager函數(shù)返回一個(gè)類型為PackageManager的對象,該對象的實(shí)際類型是PackageManager的子類ApplicationPackageManager。這種基于接口編程的方式,雖然極大降低了模塊之間的耦合性,卻給代碼分析帶來了不小的麻煩。
·??ApplicationPackageManager類繼承自PackageManager類。它并沒有直接參與Binder通信,而是通過mPM成員變量指向一個(gè)IPackageManager.Stub.Proxy類型的對象。
提示讀者在源碼中可能找不到IPackageManager.java文件。該文件在編譯過程中是經(jīng)aidl工具處理IPackageManager.aidl后得到,最終的文件位置在Android源碼/out/target
/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/content/pm/目錄中。如果讀者沒有整體編譯過源碼,也可使用aidl工具單獨(dú)處理IPackageManager.aidl。
aidl工具生成的結(jié)果文件有著相似的代碼結(jié)構(gòu)。讀者不妨看看下面這個(gè)筆者通過編譯生成的IPackageManager.java文件。注意,aidl工具生成的結(jié)果文件沒有格式縮進(jìn),所以看起來慘不忍睹,讀者可用Eclipse中的源文件格式化命令處理它。
[-->IPackageManager.java]
public interface IPackageManager extendsandroid.os.IInterface {
???? //定義內(nèi)部類Stub,派生自Binder,實(shí)現(xiàn)IPackageManager接口
????? publicstatic abstract class Stub extends android.os.Binder
????????????????????? implements? android.content.pm.IPackageManager {
??????????? privatestatic final java.lang.String DESCRIPTOR =
???????????????????????????????????"android.content.pm.IPackageManager";
??????????? publicStub() {
???????????????? this.attachInterface(this,DESCRIPTOR);
?????????????}
?? ?????????......
???????????????//定義Stub的內(nèi)部類Proxy,實(shí)現(xiàn)IPackageManager接口
??????????? privatestatic class Proxy implements
?????????????????????????????????? android.content.pm.IPackageManager{
??????????????????//通過mRemote變量和服務(wù)端交互
?????????????????private android.os.IBinder mRemote;
???????????????????????? Proxy(android.os.IBinderremote) {
???????????????????????? mRemote = remote;
??????????????????????? }
?????????????????????????? ......
??????????????}
??????????......
}
接下來分析PackageManagerService,為書寫方便起見,以后將其簡稱為PKMS。
PKMS作為系統(tǒng)的核心服務(wù),由SystemServer創(chuàng)建,相關(guān)代碼如下:
[-->SystemServer.java]
......//ServerThread的run函數(shù)
?/*
?4.0新增的一個(gè)功能,即設(shè)備加密(encrypting the device),該功能由
?系統(tǒng)屬性vold.decrypt指定。這部分功能比較復(fù)雜,本書暫不討論。
?該功能對PKMS的影響就是通過onlyCore實(shí)現(xiàn)的,該變量用于判斷是否只掃描系統(tǒng)庫
?(包括APK和Jar包)
?*/
?StringcryptState = SystemProperties.get("vold.decrypt");
?booleanonlyCore = false;
?//ENCRYPTING_STATE的值為"trigger_restart_min_framework"
?if(ENCRYPTING_STATE.equals(cryptState)) {
????? ?......
??????onlyCore = true;
?} else if(ENCRYPTED_STATE.equals(cryptState)) {
? ?????......//ENCRYPTED_STATE的值為"1"
?????onlyCore = true;
?}
?//①調(diào)用PKMS的main函數(shù),第二個(gè)參數(shù)用于判斷是否為工廠測試,我們不討論的這種情況,
?//假定onlyCore的值為false
?pm =PackageManagerService.main(context,
????????? ?factoryTest !=SystemServer.FACTORY_TEST_OFF,onlyCore);
?booleanfirstBoot = false;
?try {
???????? //判斷本次是否為初次啟動(dòng)。當(dāng)Zygote或SystemServer退出時(shí),init會再次啟動(dòng)
??????? //它們,所以這里的FirstBoot是指開機(jī)后的第一次啟動(dòng)
????????firstBoot = pm.isFirstBoot();
?}
......
? try {
??????? //②做dex優(yōu)化。dex是Android上針對Java字節(jié)碼的一種優(yōu)化技術(shù),可提高運(yùn)行效率
???????pm.performBootDexOpt();
? }
?......
? try {
????????pm.systemReady();//③通知系統(tǒng)進(jìn)入就緒狀態(tài)
?}
......
}//run函數(shù)結(jié)束
以上代碼中共有4個(gè)關(guān)鍵調(diào)用,分別是:
·??PKMS的main函數(shù)。這個(gè)函數(shù)是PKMS的核心,稍后會重點(diǎn)分析它。
·??isFirstBoot、performBootDexOpt和systemReady。這3個(gè)函數(shù)比較簡單。學(xué)完本章后,讀者可完全自行分析它們,故這里不再贅述。
首先分析PKMS的main函數(shù),它是核心函數(shù),此處單獨(dú)用一節(jié)進(jìn)行分析。
PKMS的main函數(shù)代碼如下:
[-->PackageManagerService.java]
public static final IPackageManager main(Contextcontext, boolean factoryTest,
???????????boolean onlyCore) {
??????? //調(diào)用PKMS的構(gòu)造函數(shù),factoryTest和onlyCore的值均為false
???????PackageManagerService m = new PackageManagerService(context,
????????????????????????????????????? ??????factoryTest, onlyCore);
??????? //向ServiceManager注冊PKMS
???????ServiceManager.addService("package", m);
???????return m;
?}
main函數(shù)很簡單,只有短短幾行代碼,執(zhí)行時(shí)間卻較長,主要原因是PKMS在其構(gòu)造函數(shù)中做了很多“重體力活”,這也是Android啟動(dòng)速度慢的主要原因之一。在分析該函數(shù)前,先簡單介紹一下PKMS構(gòu)造函數(shù)的功能。
PKMS構(gòu)造函數(shù)的主要功能是,掃描Android系統(tǒng)中幾個(gè)目標(biāo)文件夾中的APK,從而建立合適的數(shù)據(jù)結(jié)構(gòu)以管理諸如Package信息、四大組件信息、權(quán)限信息等各種信息。抽象地看,PKMS像一個(gè)加工廠,它解析實(shí)際的物理文件(APK文件)以生成符合自己要求的產(chǎn)品。例如,PKMS將解析APK包中的AndroidManifest.xml,并根據(jù)其中聲明的Activity標(biāo)簽來創(chuàng)建與此對應(yīng)的對象并加以保管。
PKMS的工作流程相對簡單,復(fù)雜的是其中用于保存各種信息的數(shù)據(jù)結(jié)構(gòu)和它們之間的關(guān)系,以及影響最終結(jié)果的策略控制(例如前面代碼中的onlyCore變量,用于判斷是否只掃描系統(tǒng)目錄)。曾經(jīng)閱讀過PKMS的讀者可能會發(fā)現(xiàn),代碼中大量不同的數(shù)據(jù)結(jié)構(gòu)以及它們之間的關(guān)系會令人大為頭疼。所以,本章除了分析PKMS的工作流程外,也將關(guān)注重要的數(shù)據(jù)結(jié)構(gòu)及它們的作用。
PKMS構(gòu)造函數(shù)的工作流程大體可分三個(gè)階段:
·??掃描目標(biāo)文件夾之前的準(zhǔn)備工作。
·??掃描目標(biāo)文件夾。
·??掃描之后的工作。
該函數(shù)涉及到的知識點(diǎn)較多,代碼段也較長,因此我們將通過分段討論的方法,集中解決相關(guān)的重點(diǎn)問題。
下面開始分析構(gòu)造函數(shù)第一階段的工作,先看如下所示的代碼。
[-->PackageManagerService.java::構(gòu)造函數(shù)]
public PackageManagerService(Context context,boolean factoryTest,
????????????????????????????????? booleanonlyCore) {
?????? ......
??????? if(mSdkVersion <= 0) {
???????????/*
?????????????mSdkVersion是PKMS的成員變量,定義的時(shí)候進(jìn)行賦值,其值取自系統(tǒng)屬性
?????????????“ro.build.version.sdk”,即編譯的SDK版本。如果沒有定義,則APK
?????????????就無法知道自己運(yùn)行在Android哪個(gè)版本上
??????????*/
???????????Slog.w(TAG, "**** ro.build.version.sdk not set!");//打印一句警告
??????? }
???????mContext = context;
??????? mFactoryTest= factoryTest;//假定為false,即運(yùn)行在非工廠模式下
???????mOnlyCore = onlyCore;//假定為false,即運(yùn)行在普通模式下
?????? ?//如果此系統(tǒng)是eng版,則掃描Package后,不對package做dex優(yōu)化
???????mNoDexOpt ="eng".equals(SystemProperties.get("ro.build.type"));
???????//mMetrics用于存儲與顯示屏相關(guān)的一些屬性,例如屏幕的寬/高尺寸,分辨率等信息
???????mMetrics = new DisplayMetrics();
???????//Settings是一個(gè)非常重要的類,該類用于存儲系統(tǒng)運(yùn)行過程中的一些設(shè)置,
??????? //下面進(jìn)行詳細(xì)分析??????? mSettings = new Settings();
?????? //①addSharedUserLPw是什么?馬上來分析
???????mSettings.addSharedUserLPw("android.uid.system",
???????????????Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM);
???????mSettings.addSharedUserLPw("android.uid.phone",
???????????????MULTIPLE_APPLICATION_UIDS? //該變量的默認(rèn)值是true
??????????????????????? ? RADIO_UID :FIRST_APPLICATION_UID,
???????????????ApplicationInfo.FLAG_SYSTEM);
???????mSettings.addSharedUserLPw("android.uid.log",
???????????????MULTIPLE_APPLICATION_UIDS
??????????????????????? ? LOG_UID :FIRST_APPLICATION_UID,
???????????????ApplicationInfo.FLAG_SYSTEM);
???????mSettings.addSharedUserLPw("android.uid.nfc",
???????????????MULTIPLE_APPLICATION_UIDS
??????????????????????? ? NFC_UID :FIRST_APPLICATION_UID,
???????????????ApplicationInfo.FLAG_SYSTEM);
???????......//第一段結(jié)束
?
剛進(jìn)入構(gòu)造函數(shù),就會遇到第一個(gè)較為復(fù)雜的數(shù)據(jù)結(jié)構(gòu)Setting及它的addSharedUserLPw函數(shù)。Setting的作用是管理Android系統(tǒng)運(yùn)行過程中的一些設(shè)置信息。到底是哪些信息呢?來看下面的分析。
先分析addSharedUserLPw函數(shù)。此處截取該函數(shù)的調(diào)用代碼,如下所示:
mSettings.addSharedUserLPw("android.uid.system",//字符串
??????????????Process.SYSTEM_UID, //系統(tǒng)進(jìn)程使用的用戶id,值為1000
??????????????ApplicationInfo.FLAG_SYSTEM//標(biāo)志系統(tǒng)Package
);
以此處的函數(shù)調(diào)用為例,我們?yōu)閍ddSharedUserLPw傳遞了3個(gè)參數(shù):
第一個(gè)是字符串“android.uid.system“;第二個(gè)是SYSTEM_UID,其值為1000;第三個(gè)是FLAG_SYSTEM標(biāo)志,用于標(biāo)識系統(tǒng)Package。
在進(jìn)入對addSharedUserLPw函數(shù)的分析前,先介紹一下SYSTEM_UID 及相關(guān)知識。
UID為用戶ID的縮寫,GID為用戶組ID的縮寫,這兩個(gè)概念均與Linux系統(tǒng)中進(jìn)程的權(quán)限管理有關(guān)。一般說來,每一個(gè)進(jìn)程都會有一個(gè)對應(yīng)的UID(即表示該進(jìn)程屬于哪個(gè)user,不同user有不同權(quán)限)。一個(gè)進(jìn)程也可分屬不同的用戶組(每個(gè)用戶組都有對應(yīng)的權(quán)限)。
提示Linux的UID/GID還可細(xì)分為幾種類型,此處我們僅考慮普適意義的UID/GID。
如上所述,UID/GID和進(jìn)程的權(quán)限有關(guān)。在Android平臺中,系統(tǒng)定義的UID/GID在Process.java文件中,如下所示:
[-->Process.java]
?? //系統(tǒng)進(jìn)程使用的UID/GID,值為1000
?? publicstatic final int SYSTEM_UID = 1000;
?? //Phone進(jìn)程使用的UID/GID,值為1001
?? publicstatic final int PHONE_UID = 1001;
?? //shell進(jìn)程使用的UID/GID,值為2000
?? publicstatic final int SHELL_UID = 2000;
?? //使用LOG的進(jìn)程所在的組的UID/GID為1007
?? publicstatic final int LOG_UID = 1007;
?? //供WIF相關(guān)進(jìn)程使用的UID/GID為1010
?? publicstatic final int WIFI_UID = 1010;
??//mediaserver進(jìn)程使用的UID/GID為1013
?? publicstatic final int MEDIA_UID = 1013;
?? //設(shè)置能讀寫SD卡的進(jìn)程的GID為1015
?? publicstatic final int SDCARD_RW_GID = 1015;
?? //NFC相關(guān)的進(jìn)程的UID/GID為1025
?? publicstatic final int NFC_UID = 1025;
?? //有權(quán)限讀寫內(nèi)部存儲的進(jìn)程的GID為1023
?? publicstatic final int MEDIA_RW_GID = 1023;
?? //第一個(gè)應(yīng)用Package的起始UID為10000
?? publicstatic final int FIRST_APPLICATION_UID = 10000;
?? //系統(tǒng)所支持的最大的應(yīng)用Package的UID為99999
?? publicstatic final int LAST_APPLICATION_UID = 99999;
?? //和藍(lán)牙相關(guān)的進(jìn)程的GID為2000
?? publicstatic final int BLUETOOTH_GID = 2000;
對不同的UID/GID授予不同的權(quán)限,接下來就介紹和權(quán)限設(shè)置相關(guān)的代碼。
提示讀者可用adb shell(將什么?)登錄到自己的手機(jī),然后用busybox提供的ps命令查看進(jìn)程的UID。
下面分析addSharedUserLPw函數(shù),代碼如下:
[-->Settings.java]
SharedUserSetting addSharedUserLPw(String name,int uid, int pkgFlags) {
? ????/*
??????? 注意這里的參數(shù):name為字符串”android.uid.system”,uid為1000,pkgFlags為
??????? ApplicationInfo.FLAG_SYSETM(以后簡寫為FLAG_SYSTEM)
?? ???*/
???????//mSharedUsers是一個(gè)HashMap,key為字符串,值為SharedUserSetting對象
???????SharedUserSetting s = mSharedUsers.get(name);
??????? if(s != null) {
???????????if (s.userId == uid) {
???????????????return s;
???????????}......
???????????return null;
???? ???}
??????? //創(chuàng)建一個(gè)新的SharedUserSettings對象,并設(shè)置的userId為uid,
?????? //SharedUserSettings是什么?有什么作用?
??????? s =new SharedUserSetting(name, pkgFlags);
???????s.userId = uid;
??????? if(addUserIdLPw(uid, s, name)) {
???????????mSharedUsers.put(name, s);//將name與s鍵值對添加到mSharedUsers中保存
???????????return s;
??????? }
???????return null;
??? }
從以上代碼可知,Settings中有一個(gè)mSharedUsers成員,該成員存儲的是字符串與SharedUserSetting鍵值對,也就是說以字符串為key得到對應(yīng)的SharedUserSetting對象。
那么SharedUserSettings是什么?它的目的是什么?來看一個(gè)例子。
該例子來源于SystemUI的AndroidManifest.xml,如下所示:
[-->SystemUI的AndroidManifest.xml]
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
???????package="com.android.systemui"
???????coreApp="true"
??????? android:sharedUserId="android.uid.system"
???????android:process="system">
?......
在xml中,聲明了一個(gè)名為android:sharedUserId的屬性,其值為“android.uid.system”。sharedUserId看起來和UID有關(guān),確實(shí)如此,它有兩個(gè)作用:
·??兩個(gè)或多個(gè)聲明了同一種sharedUserIds的APK可共享彼此的數(shù)據(jù),并且可運(yùn)行在同一進(jìn)程中。
·??更重要的是,通過聲明特定的sharedUserId,該APK所在進(jìn)程將被賦予指定的UID。例如,本例中的SystemUI聲明了system的uid,運(yùn)行SystemUI的進(jìn)程就可享有system用戶所對應(yīng)的權(quán)限(實(shí)際上就是將該進(jìn)程的uid設(shè)置為system的uid)了。
提示除了在AndroidManifest.xml中聲明sharedUserId外,APK在編譯時(shí)還必須使用對應(yīng)的證書進(jìn)行簽名。例如本例的SystemUI,在其Android.mk中需要額外聲明LOCAL_CERTIFICATE := platform,如此,才可獲得指定的UID。
通過以上介紹,讀者能知道如何組織一種數(shù)據(jù)結(jié)構(gòu)來包括上面的內(nèi)容。此處有三個(gè)關(guān)鍵點(diǎn)需注意:
·??XML中sharedUserId屬性指定了一個(gè)字符串,它是UID的字符串描述,故對應(yīng)數(shù)據(jù)結(jié)構(gòu)中也應(yīng)該有這樣一個(gè)字符串,這樣就把代碼和XML中的屬性聯(lián)系起來了。
·??在Linux系統(tǒng)中,真正的UID是一個(gè)整數(shù),所以該數(shù)據(jù)結(jié)構(gòu)中必然有一個(gè)整型變量。
·??多個(gè)Package可聲明同一個(gè)sharedUserId,因此該數(shù)據(jù)結(jié)構(gòu)必然會保存那些聲明了相同sharedUserId的Package的某些信息。
了解了上面三個(gè)關(guān)鍵點(diǎn),再來看Android是如何設(shè)計(jì)相應(yīng)數(shù)據(jù)結(jié)構(gòu)的,如圖4-2所示。
http://wiki.jikexueyuan.com/project/deep-android-v2/images/chapter4/image002.png" alt="image" />
圖4-2? SharedUserSetting類的關(guān)系圖
由圖4-2可知:
·??Settings類定義了一個(gè)mSharedUsers成員,它是一個(gè)HashMap,以字符串(如“android.uid.system”)為Key,對應(yīng)的Value是一個(gè)SharedUserSettings對象。
·??SharedUserSetting派生自GrantedPermissions類,從GrantedPermissions類的命名可知,它和權(quán)限有關(guān)。SharedUserSetting定義了一個(gè)成員變量packages,類型為HashSet,用于保存聲明了相同sharedUserId的Package的權(quán)限設(shè)置信息。
·??每個(gè)Package有自己的權(quán)限設(shè)置。權(quán)限的概念由PackageSetting類表達(dá)。該類繼承自PackagesettingBase,而PackageSettingBase又繼承自GrantedPermissions。
·??Settings中還有兩個(gè)成員,一個(gè)是mUserIds,另一個(gè)是mOtherUserIds,這兩位成員的類型分別是ArrayList和SparseArray。其目的是以UID為索引,得到對應(yīng)的SharedUserSettings對象。在一般情況下,以索引獲取數(shù)組元素的速度,比以key獲取HashMap中元素的速度要快很多。
提示 根據(jù)以上對mUserIds和mOtherUserIds的描述,可知這是典型的以空間換時(shí)間的做法。
下邊來分析addUserIdLPw函數(shù),它的功能就是將SharedUserSettings對象保存到對應(yīng)的數(shù)組中,代碼如下:
[-->Settings.java]
private boolean addUserIdLPw(int uid, Object obj, Objectname) {
??? ?????//uid不能超出限制。Android對UID進(jìn)行了分類,應(yīng)用APK所在進(jìn)程的UID從10000開始,
??????? //而系統(tǒng)APK所在進(jìn)程小于10000
??????? if(uid >= PackageManagerService.FIRST_APPLICATION_UID +
??????????????????????????? PackageManagerService.MAX_APPLICATION_UIDS){
???????????return false;
??????? }
?
??????? if(uid >= PackageManagerService.FIRST_APPLICATION_UID) {
???????????int N = mUserIds.size();
??????????//計(jì)算索引,其值是uid和FIRST_APPLICATION_UID的差
???????????final int index = uid - PackageManagerService.FIRST_APPLICATION_UID;
???????????while (index >= N) {
???????????????mUserIds.add(null);
???????????????N++;
???????????}
??????????? ......//判斷該索引位置的內(nèi)容是否為空,為空才保存
???????????mUserIds.set(index, obj);//mUserIds保存應(yīng)用Package的UID
??????? }else {
?????????? ......
?????????? ?mOtherUserIds.put(uid, obj);//系統(tǒng)Package的UID由mOtherUserIds保存
??????? }
???????return true;
}
至此,對Settings的分析就告一段落了。在這次“行程”中,我們重點(diǎn)分析了UID/GID以及SharedUserId方面的知識,并見識好幾個(gè)重要的數(shù)據(jù)結(jié)構(gòu)。希望讀者通過SystemUI的實(shí)例能夠理解這些數(shù)據(jù)結(jié)構(gòu)存在的目的。
下面繼續(xù)分析PKMS的構(gòu)造函數(shù),代碼如下:
[-->PackageMangerService.java::構(gòu)造函數(shù)]
??? ???......//接前一段
???????String separateProcesses = //該值和調(diào)試有關(guān)。一般不設(shè)置該屬性
????? ????????????????????SystemProperties.get("debug.separate_processes");
??????? if(separateProcesses != null && separateProcesses.length() > 0) {
??????????......
??????? }else {
???????????mDefParseFlags = 0;
???????????mSeparateProcesses = null;
??????? }
??????? //創(chuàng)建一個(gè)Installer對象,該對象和Native進(jìn)程installd交互,以后分析installd
??????? //時(shí)再來討論它的作用
???????mInstaller = new Installer();
???????WindowManager wm =? //得到一個(gè)WindowManager對象
????????????? (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
?
???????Display d = wm.getDefaultDisplay();
???????d.getMetrics(mMetrics); //獲取當(dāng)前設(shè)備的顯示屏信息
???????synchronized (mInstallLock) {
???????synchronized (mPackages) {
???????????//創(chuàng)建一個(gè)ThreadHandler對象,實(shí)際就是創(chuàng)建一個(gè)帶消息循環(huán)處理的線程,該線程
???????????//的工作是:程序的和卸載等。以后分析程序安裝時(shí)會和它親密接觸
???????????mHandlerThread.start();
??????????//以ThreadHandler線程的消息循環(huán)(Looper對象)為參數(shù)創(chuàng)建一個(gè)PackageHandler,
???????????//可知該Handler的handleMessage函數(shù)將運(yùn)行在此線程上
???????????mHandler = new PackageHandler(mHandlerThread.getLooper());
?????????? ?File dataDir = Environment.getDataDirectory();
??????????// mAppDataDir指向/data/data目錄
???????????mAppDataDir = new File(dataDir, "data");
???????? ??// mUserAppDataDir指向/data/user目錄
???????????mUserAppDataDir = new File(dataDir, "user");
???????????// mDrmAppPrivateInstallDir指向/data/app-private目錄
???????????mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
???????????/*
????????????創(chuàng)建一個(gè)UserManager對象,目前沒有什么作用,但其前途將不可限量。
?????????????根據(jù)Google的設(shè)想,未來手機(jī)將支持多個(gè)User,每個(gè)User將安裝自己的應(yīng)用,
?????????????該功能為Andorid智能手機(jī)推向企業(yè)用戶打下堅(jiān)實(shí)基礎(chǔ)
?????????? ?*/
???????????mUserManager = new UserManager(mInstaller, mUserAppDataDir);
???????????//①從文件中讀權(quán)限
???????????readPermissions();
???????????//②readLPw分析
???????????mRestoredSettings = mSettings.readLPw();
???????????long startTime = SystemClock.uptimeMillis();
以上代碼中創(chuàng)建了幾個(gè)對象,此處暫可不去理會它們。另外,以上代碼中還調(diào)用了兩個(gè)函數(shù),分別是readPermission和Setttings的readLPw,它們有什么作用呢?下面就展開分析。
先來分析readPermissions函數(shù),從其函數(shù)名可猜測到它和權(quán)限有關(guān),代碼如下:
[-->PackageManagerService.java]
void readPermissions() {
??????? // 指向/system/etc/permission目錄,該目錄中存儲了和設(shè)備相關(guān)的一些權(quán)限信息
??????? FilelibraryDir = new File(Environment.getRootDirectory(),
?????????????????????????????????????? ?"etc/permissions");
??????? ......
??????? for(File f : libraryDir.listFiles()) {
???????????//先處理該目錄下的非platform.xml文件
???????????if (f.getPath().endsWith("etc/permissions/platform.xml")) {
???????????????continue;
???????????}
??????????? ......//調(diào)用readPermissionFromXml解析此XML文件
???????????readPermissionsFromXml(f);
??????? }
?????? finalFile permFile = new File(Environment.getRootDirectory(),
???????????????"etc/permissions/platform.xml");
??????? //解析platform.xml文件,看來該文件優(yōu)先級最高
???????readPermissionsFromXml(permFile);
}
懸著的心終于放了下來!readPermissions函數(shù)不就是調(diào)用readPermissionFromXml函數(shù)解析/system/etc/permissions目錄下的文件嗎?這些文件似乎都是XML文件。該目錄下都有哪些XML文件呢?這些XML文件中有些什么內(nèi)容呢?來看一個(gè)實(shí)際的例子,如圖4-3所示。
http://wiki.jikexueyuan.com/project/deep-android-v2/images/chapter4/image003.png" alt="image" />
圖4-3? /system/etc/permissions目錄下的內(nèi)容
圖4-3中列出的是本人G7手機(jī)上/system/etc/permissions目錄下的內(nèi)容。在上面的代碼中,雖然最后才解析platform.xml文件, 不過此處先分析此文件其內(nèi)容如下所示:
[-->platform.xml]
<permissions>
?? <!--建立權(quán)限名與gid的映射關(guān)系。如下面聲明的BLUTOOTH_ADMIN權(quán)限,它對應(yīng)的用戶組是
??? net_bt_admin。注意,該文件中的permission標(biāo)簽只對那些需要通過讀寫設(shè)備(藍(lán)牙/camera)
???? /創(chuàng)建socket等進(jìn)程劃分了gid。因?yàn)檫@些權(quán)限涉及和Linux內(nèi)核交互,所以需要在底層
???? 權(quán)限(由不同的用戶組界定)和Android層權(quán)限(由不同的字符串界定)之間建立映射關(guān)系
??-->
?<permission name="android.permission.BLUETOOTH_ADMIN" >
???????<group gid="net_bt_admin" />
?</permission>
?<permission name="android.permission.BLUETOOTH" >
???????<group gid="net_bt" />
? </permission>
? ......
?? <!--
?????賦予對應(yīng)uid相應(yīng)的權(quán)限。如果下面一行表示uid為shell,那么就賦予
?????? 它SEND_SMS的權(quán)限,其實(shí)就是把它加到對應(yīng)的用戶組中-->
???<assign-permission name="android.permission.SEND_SMS"uid="shell" />
???<assign-permission name="android.permission.CALL_PHONE"uid="shell" />
???<assign-permission name="android.permission.READ_CONTACTS"uid="shell" />
???<assign-permission name="android.permission.WRITE_CONTACTS"uid="shell" />
??? <assign-permissionname="android.permission.READ_CALENDAR" uid="shell" />
......
??? <!-- 系統(tǒng)提供的Java庫,應(yīng)用程序運(yùn)行時(shí)候必須要鏈接這些庫,該工作由系統(tǒng)自動(dòng)完成 -->
??? <libraryname="android.test.runner"
???????????file="/system/frameworks/android.test.runner.jar" />
?? ?<library name="javax.obex"
???????????file="/system/frameworks/javax.obex.jar"/>
</permissions>
platform.xml文件中主要使用了如下4個(gè)標(biāo)簽:
·??permission和group用于建立Linux層gid和Android層pemission之間的映射關(guān)系。
·??assign-permission用于向指定的uid賦予相應(yīng)的權(quán)限。這個(gè)權(quán)限由Android定義,用字符串表示。
·??library用于指定系統(tǒng)庫。當(dāng)應(yīng)用程序運(yùn)行時(shí),系統(tǒng)會自動(dòng)為這些進(jìn)程加載這些庫。
了解了platform.xml后,再看其他的XML文件,這里以handheld-core-hardware.xml為例進(jìn)行介紹,其內(nèi)容如下:
[-->handheld-core-hardware.xml]
<permissions>
???<feature name="android.hardware.camera" />
???<feature name="android.hardware.location" />
???<feature name="android.hardware.location.network" />
???<feature name="android.hardware.sensor.compass" />
???<feature name="android.hardware.sensor.accelerometer" />
???<feature name="android.hardware.bluetooth" />
???<feature name="android.hardware.touchscreen" />
???<feature name="android.hardware.microphone" />
???<feature name="android.hardware.screen.portrait" />
???<feature name="android.hardware.screen.landscape" />
???</permissions>
這個(gè)XML文件包含了許多feature標(biāo)簽。根據(jù)該文件中的注釋,這些feature用來描述一個(gè)手持終端(包括手機(jī)、平板電腦等)應(yīng)該支持的硬件特性,例如支持camera、支持藍(lán)牙等。
注意對于不同的硬件特性,還需要包含其他的xml文件。例如,要支持前置攝像頭,還需要包含android.hardware.camera.front.xml文件。這些文件內(nèi)容大體一樣,都通過feature標(biāo)簽表明自己的硬件特性。相關(guān)說明可參考handheld-core-hardware.xml中的注釋。
有讀者可能會好奇,真實(shí)設(shè)備上/system/etc/permission目錄中的文件是從哪里的呢?
答案是,在編譯階段由不同硬件平臺根據(jù)自己的配置信息復(fù)制相關(guān)文件到目標(biāo)目錄中得來的。
這里給出一個(gè)例子,如圖4-4所示。
http://wiki.jikexueyuan.com/project/deep-android-v2/images/chapter4/image004.png" alt="image" />
圖4-4? /system/etc/permission目錄中文件的來源
由圖4-4可知,當(dāng)編譯的設(shè)備目標(biāo)為htc-passion時(shí),就會將Android源碼目錄/frameworks/base/data/etc/下某些和該目標(biāo)設(shè)備硬件特性匹配的XML文件復(fù)制到最終輸出目錄/system/etc/permissions下。編譯完成后,將生成system鏡像。把該鏡像文件燒到手機(jī)中,就成了目標(biāo)設(shè)備使用的情況了。
注意4.0源碼中并沒有htc相關(guān)的文件,這是筆者從2.3源碼中移植過去的。讀者可參考筆者一篇關(guān)于如何移植4.0到G7的博文,地址為http://blog.csdn.net/innost/article/details/6977167。
了解了與XML相關(guān)的知識后,再來分析readPermissionFromXml函數(shù)。相信聰明的讀者已經(jīng)知道它的作用了,就是將XML文件中的標(biāo)簽以及它們之間的關(guān)系轉(zhuǎn)換成代碼中的相應(yīng)數(shù)據(jù)結(jié)構(gòu),代碼如下:
[-->PackageManagerService.java]
private void readPermissionsFromXml(File permFile){
???????FileReader permReader = null;
??????? try{
???????????permReader = new FileReader(permFile);
??????? } ......
??????? try{
???????????XmlPullParser parser = Xml.newPullParser();
???????????parser.setInput(permReader);
???????????XmlUtils.beginDocument(parser, "permissions");
???????????while (true) {
???????????????......
???????????????String name = parser.getName();
???????????????//解析group標(biāo)簽,前面介紹的XML文件中沒有單獨(dú)使用該標(biāo)簽的地方
???????????????if ("group".equals(name)) {
???????????????????String gidStr = parser.getAttributeValue(null, "gid");
???????????????????if (gidStr != null) {
??????????????????????? int gid =Integer.parseInt(gidStr);
??????????????????????? //轉(zhuǎn)換XML中的gid字符串為整型,并保存到mGlobalGids中
??????????????????????? mGlobalGids =appendInt(mGlobalGids, gid);
???????????????????} ......
???????????????} else if ("permission".equals(name)) {//解析permission標(biāo)簽
???????????????????String perm = parser.getAttributeValue(null, "name");
??????????????????......
???????????????????perm = perm.intern();
???????????????????? //調(diào)用readPermission處理
???????????????????readPermission(parser, perm);
?????????????????//下面解析的是assign-permission標(biāo)簽
??? ????????????} else if("assign-permission".equals(name)) {
???????????????????String perm = parser.getAttributeValue(null, "name");
???????????????????......
???????????????????String uidStr = parser.getAttributeValue(null, "uid");
???????????????????......
???????????????????//如果是assign-permission,則取出uid字符串,然后獲得Linux平臺上
???????????????????//的整型uid值
???????????????????int uid = Process.getUidForName(uidStr);
??????????????????......
???????????????????perm = perm.intern();
???????????????????//和assign相關(guān)的信息保存在mSystemPermissions中
???????????????????HashSet<String> perms = mSystemPermissions.get(uid);
???????????????????if (perms == null) {
??????????????????????? perms = newHashSet<String>();
???????????????????????mSystemPermissions.put(uid, perms);
???????????????????}
???????????????????perms.add(perm);......
??????????????????} else if ("library".equals(name)) {//解析library標(biāo)簽
???????????????????String lname = parser.getAttributeValue(null, "name");
???????????????????String lfile = parser.getAttributeValue(null, "file");
???????????????????if (lname == null) {
??????????????????????? ......
???????????????????} else if (lfile == null) {
??????????????????????? ......
???????????????????} else {
??????????????????????? //將XML中的name和library屬性值存儲到mSharedLibraries中
?????????????????????? mSharedLibraries.put(lname,lfile);
???????????????????} ......
???????????????} else if ("feature".equals(name)) {//解析feature標(biāo)簽
???????????????????String fname = parser.getAttributeValue(null, "name");
????????????????????......{
??????????????????????? //在XML中定義的feature由FeatureInfo表達(dá)
??????????????????????? FeatureInfo fi = newFeatureInfo();
??????????????????????? fi.name = fname;
??????????????????????? //存儲feature名和對應(yīng)的FeatureInfo到mAvailableFeatures中
???????????????????????mAvailableFeatures.put(fname, fi);
???????????????????}......
??????????? ????} ......
??????? } ......
??? }
readPermissions函數(shù)果然將XML中的標(biāo)簽轉(zhuǎn)換成對應(yīng)的數(shù)據(jù)結(jié)構(gòu)??偨Y(jié)相關(guān)的數(shù)據(jù)結(jié)構(gòu),如圖4-4所示,此處借用了UML類圖。在每個(gè)類圖中,首行是數(shù)據(jù)結(jié)構(gòu)名,第二行是數(shù)據(jù)結(jié)構(gòu)的類型,第三行是注釋。
http://wiki.jikexueyuan.com/project/deep-android-v2/images/chapter4/image005.png" alt="image" />
圖4-4? 通過readPermissions函數(shù)建立的數(shù)據(jù)結(jié)構(gòu)及其關(guān)系
這里必須再次強(qiáng)調(diào):圖4-4中各種數(shù)據(jù)結(jié)構(gòu)的目的是為了保存XML中各種標(biāo)簽及它們之間的關(guān)系。在分析過程中,最重要的是理解各種標(biāo)簽的作用,而不是它們所使用的數(shù)據(jù)結(jié)構(gòu)。
readLPw函數(shù)的功能也是解析文件,不過這些文件的內(nèi)容卻是在PKMS正常啟動(dòng)后生成的。這里僅介紹作為readLPw“佐料”的文件的信息。文件的具體位置在Settings構(gòu)造函數(shù)中指明,其代碼如下:
[-->Settings.java]
Settings() {
??????? FiledataDir = Environment.getDataDirectory();
??????? FilesystemDir = new File(dataDir, "system");//指向/data/system目錄
???????systemDir.mkdirs();//創(chuàng)建該目錄
??????? ......
??????? /*
??????? 一共有5個(gè)文件,packages.xml和packages-backup.xml為一組,用于描述系統(tǒng)中
??????? 所安裝的Package的信息,其中backup是臨時(shí)文件。PKMS先把數(shù)據(jù)寫到backup中,
??????? 信息都寫成功后再改名成非backup的文件。其目的是防止在寫文件過程中出錯(cuò),導(dǎo)致信息丟失。
???????? packages-stopped.xml和packages-stopped-backup.xml為一組,用于描述系統(tǒng)中
????? ???強(qiáng)制停止運(yùn)行的pakcage的信息,backup也是臨時(shí)文件。如果此處存在該臨時(shí)文件,表明
??????? 此前系統(tǒng)因?yàn)槟撤N原因中斷了正常流程
??????? packages.list列出當(dāng)前系統(tǒng)中應(yīng)用級(即UID大于10000)Package的信息
??????? */
???????mSettingsFilename = new File(systemDir, "packages.xml");
???????mBackupSettingsFilename = new File(systemDir,"packages-backup.xml");
???????mPackageListFilename = new File(systemDir, "packages.list");
???????mStoppedPackagesFilename = new File(systemDir,"packages-stopped.xml");
???????mBackupStoppedPackagesFilename = new File(systemDir,
?????????????????????????????????????????? ?"packages-stopped-backup.xml");
}
上面5個(gè)文件共分為三組,這里簡單介紹一下這些文件的來歷(不考慮臨時(shí)的backup文件)。
·??packages.xml: PKMS掃描完目標(biāo)文件夾后會創(chuàng)建該文件。當(dāng)系統(tǒng)進(jìn)行程序安裝、卸載和更新等操作時(shí),均會更新該文件。該文件保存了系統(tǒng)中與package相關(guān)的一些信息。
·??packages.list:描述系統(tǒng)中存在的所有非系統(tǒng)自帶的APK的信息。當(dāng)這些程序有變動(dòng)時(shí),PKMS就會更新該文件。
·??packages-stopped.xml:從系統(tǒng)自帶的設(shè)置程序中進(jìn)入應(yīng)用程序頁面,然后在選擇強(qiáng)制停止(ForceStop)某個(gè)應(yīng)用時(shí),系統(tǒng)會將該應(yīng)用的相關(guān)信息記錄到此文件中。也就是該文件保存系統(tǒng)中被用戶強(qiáng)制停止的Package的信息。
readLPw的函數(shù)功能就是解析其中的XML文件的內(nèi)容,然后建立并更新對應(yīng)的數(shù)據(jù)結(jié)構(gòu),例如停止的package重啟之后依然是stopped狀態(tài)。
提示讀者看完本章后,可自行分析該函數(shù)。在此之前,建議讀者不必關(guān)注該函數(shù)。
在繼續(xù)征程前,先總結(jié)一下PKMS構(gòu)造函數(shù)在第一階段的工作,千言萬語匯成一句話:掃描并解析XML文件,將其中的信息保存到特定的數(shù)據(jù)結(jié)構(gòu)中。
第一階段掃描的XML文件與權(quán)限及上一次掃描得到的Package信息有關(guān),它為PKMS下一階段的工作提供了重要的參考信息。
PKMS構(gòu)造函數(shù)第二階段的工作就是掃描系統(tǒng)中的APK了。由于需要逐個(gè)掃描文件,因此手機(jī)上裝的程序越多,PKMS的工作量越大,系統(tǒng)啟動(dòng)速度也就越慢。
接著對PKMS構(gòu)造函數(shù)進(jìn)行分析,代碼如下:
[-->PackageManagerService.java]
......
?mRestoredSettings= mSettings.readLPw();//接第一段的結(jié)尾
?longstartTime = SystemClock.uptimeMillis();//記錄掃描開始的時(shí)間
//定義掃描參數(shù)
?intscanMode = SCAN_MONITOR | SCAN_NO_PATHS | SCAN_DEFER_DEX;
?if(mNoDexOpt) {
??? scanMode|= SCAN_NO_DEX; //在控制掃描過程中是否對APK文件進(jìn)行dex優(yōu)化
? }
?finalHashSet<String> libFiles = new HashSet<String>();
?// mFrameworkDir指向/system/frameworks目錄
?mFrameworkDir = newFile(Environment.getRootDirectory(),"framework");
?// mDalvikCacheDir指向/data/dalvik-cache目錄
?mDalvikCacheDir= new File(dataDir, "dalvik-cache");
?booleandidDexOpt = false;
?/*
??獲取Java啟動(dòng)類庫的路徑,在init.rc文件中通過BOOTCLASSPATH環(huán)境變量輸出,該值如下
??/system/framework/core.jar:/system/frameworks/core-junit.jar:
??/system/frameworks/bouncycastle.jar:/system/frameworks/ext.jar:
??/system/frameworks/framework.jar:/system/frameworks/android.policy.jar:
??/system/frameworks/services.jar:/system/frameworks/apache-xml.jar:
??/system/frameworks/filterfw.jar
??該變量指明了framework所有核心庫及文件位置
?*/
?StringbootClassPath = System.getProperty("java.boot.class.path");
?if(bootClassPath != null) {
?????String[] paths = splitString(bootClassPath, ':');
????? for(int i=0; i<paths.length; i++) {
??????? try{? //判斷該jar包是否需要重新做dex優(yōu)化
?????????????if (dalvik.system.DexFile.isDexOptNeeded(paths[i])) {
??????????????????/*
?????????????????? 將該jar包文件路徑保存到libFiles中,然后通過mInstall對象發(fā)送
?????????????????? 命令給installd,讓其對該jar包進(jìn)行dex優(yōu)化
??????????????????*/
??????????????????libFiles.add(paths[i]);
??????????????????mInstaller.dexopt(paths[i], P