鍍金池/ 教程/ Android/ 第4章? 深入理解 PackageManagerService
第2章? 深入理解 Java Binder 和 MessageQueue
第7章? 深入理解 ContentProvider
第5章? 深入理解 PowerManagerService
第3章? 深入理解 SystemServer
第8章? 深入理解ContentService 和 AccountManagerService
第1章?開發(fā)環(huán)境部署
第4章? 深入理解 PackageManagerService
第6章?深入理解ActivityManagerService

第4章? 深入理解 PackageManagerService

本章主要內(nèi)容:

詳細(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

4.1 ?概述

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。

4.2? 初識PackageManagerService

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)行分析。

4.3? PKMS的main函數(shù)分析

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)問題。

4.3.1? 構(gòu)造函數(shù)分析之前期準(zhǔ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è)置信息。到底是哪些信息呢?來看下面的分析。

1.? 初識Settings

先分析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)知識。

(1) Android系統(tǒng)中UID/GID介紹

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è)例子。

(2) SharedUserSetting分析

該例子來源于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)存在的目的。

2.? XML文件掃描

下面繼續(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,它們有什么作用呢?下面就展開分析。

(1) readPermissions函數(shù)分析

先來分析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)。

(2) readLPw的“佐料”

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ù)。

3.? 第一階段工作總結(jié)

在繼續(xù)征程前,先總結(jié)一下PKMS構(gòu)造函數(shù)在第一階段的工作,千言萬語匯成一句話:掃描并解析XML文件,將其中的信息保存到特定的數(shù)據(jù)結(jié)構(gòu)中。

第一階段掃描的XML文件與權(quán)限及上一次掃描得到的Package信息有關(guān),它為PKMS下一階段的工作提供了重要的參考信息。

4.3.2? 構(gòu)造函數(shù)分析之掃描Package

PKMS構(gòu)造函數(shù)第二階段的工作就是掃描系統(tǒng)中的APK了。由于需要逐個(gè)掃描文件,因此手機(jī)上裝的程序越多,PKMS的工作量越大,系統(tǒng)啟動(dòng)速度也就越慢。

1.? 系統(tǒng)庫的dex優(yōu)化

接著對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