PKMS構造函數第二階段的工作就是掃描系統中的APK了。由于需要逐個掃描文件,因此手機上裝的程序越多,PKMS的工作量越大,系統啟動速度也就越慢。
1. 系統庫的dex優化
接著對PKMS構造函數進行分析,代碼如下:
**PackageManagerService.java**
~~~
......
mRestoredSettings= mSettings.readLPw();//接第一段的結尾
longstartTime = SystemClock.uptimeMillis();//記錄掃描開始的時間
//定義掃描參數
intscanMode = SCAN_MONITOR | SCAN_NO_PATHS | SCAN_DEFER_DEX;
if(mNoDexOpt) {
scanMode|= SCAN_NO_DEX; //在控制掃描過程中是否對APK文件進行dex優化
}
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啟動類庫的路徑,在init.rc文件中通過BOOTCLASSPATH環境變量輸出,該值如下
/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優化
if (dalvik.system.DexFile.isDexOptNeeded(paths[i])) {
/*
將該jar包文件路徑保存到libFiles中,然后通過mInstall對象發送
命令給installd,讓其對該jar包進行dex優化
*/
libFiles.add(paths[i]);
mInstaller.dexopt(paths[i], Process.SYSTEM_UID, true);
didDexOpt = true;
}
} ......
}
} ......
/*
讀者還記得mSharedLibrarires的作用嗎?它保存的是platform.xml中聲明的系統庫的信息。
這里也要判斷系統庫是否需要做dex優化。處理方式同上
*/
if (mSharedLibraries.size() > 0) {
......
}
//將framework-res.apk添加到libFiles中。framework-res.apk定義了系統常用的
//資源,還有幾個重要的Activity,如長按Power鍵后彈出的選擇框
libFiles.add(mFrameworkDir.getPath() + "/framework-res.apk");
//列舉/system/frameworks目錄中的文件
String[] frameworkFiles = mFrameworkDir.list();
if(frameworkFiles != null) {
......//判斷該目錄下的apk或jar文件是否需要做dex優化。處理方式同上
}
/*
上面代碼對系統庫(BOOTCLASSPATH指定,或 platform.xml定義,或
/system/frameworks目錄下的jar包與apk文件)進行一次仔細檢查,該優化的一定要優化。
如果發現期間對任何一個文件進行了優化,則設置didDexOpt為true
*/
if (didDexOpt) {
String[] files = mDalvikCacheDir.list();
if (files != null) {
/*
如果前面對任意一個系統庫重新做過dex優化,就需要刪除cache文件。原因和
dalvik虛擬機的運行機制有關。本書暫不探討dex及cache文件的作用。
從刪除cache文件這個操作來看,這些cache文件應該使用了dex優化后的系統庫
所以當系統庫重新做dex優化后,就需要刪除舊的cache文件。可簡單理解為緩存失效
*/
for (int i=0; i<files.length; i++) {
String fn = files[i];
if(fn.startsWith("data@app@")
||fn.startsWith("data@app-private@")) {
(newFile(mDalvikCacheDir, fn)).delete();
......
}
~~~
2. 掃描系統Package
清空cache文件后,PKMS終于進入重點段了。接下來看PKMS第二階段工作的核心內容,即掃描Package,相關代碼如下:
**PackageManagerService.java**
~~~
//創建文件夾監控對象,監視/system/frameworks目錄。利用了Linux平臺的inotify機制
mFrameworkInstallObserver = new AppDirObserver(
mFrameworkDir.getPath(),OBSERVER_EVENTS, true);
mFrameworkInstallObserver.startWatching();
/*
調用scanDirLI函數掃描/system/frameworks目錄,這個函數很重要,稍后會再分析。
注意,在第三個參數中設置了SCAN_NO_DEX標志,因為該目錄下的package在前面的流程
中已經過判斷并根據需要做過dex優化了
*/
scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR,scanMode | SCAN_NO_DEX, 0);
//創建文件夾監控對象,監視/system/app目錄
mSystemAppDir = new File(Environment.getRootDirectory(),"app");
mSystemInstallObserver = new AppDirObserver(
mSystemAppDir.getPath(), OBSERVER_EVENTS, true);
mSystemInstallObserver.startWatching();
//掃描/system/app下的package
scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);
//監視并掃描/vendor/app目錄
mVendorAppDir = new File("/vendor/app");
mVendorInstallObserver = new AppDirObserver(
mVendorAppDir.getPath(), OBSERVER_EVENTS, true);
mVendorInstallObserver.startWatching();
//掃描/vendor/app下的package
scanDirLI(mVendorAppDir, PackageParser.PARSE_IS_SYSTEM
| PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);
//和installd交互。以后單獨分析installd
mInstaller.moveFiles();
~~~
由以上代碼可知,PKMS將掃描以下幾個目錄。
- /system/frameworks:該目錄中的文件都是系統庫,例如framework.jar、services.jar、framework-res.apk。不過scanDirLI只掃描APK文件,所以framework-res.apk是該目錄中唯一“受寵”的文件。
- /system/app:該目錄下全是默認的系統應用,例如Browser.apk、SettingsProvider.apk等。
- /vendor/app:該目錄中的文件由廠商提供,即廠商特定的APK文件,不過目前市面上的廠商都把自己的應用放在/system/app目錄下。
>[warning] **注意**:本書把這三個目錄稱為系統Package目錄,以區分后面的非系統Package目錄。
PKMS調用scanDirLI函數進行掃描,下面來分析此函數。
(1) scanDirLI函數分析
scanDirLI函數的代碼如下:
**PackageManagerService.java**
~~~
private void scanDirLI(File dir, int flags, intscanMode, long currentTime) {
String[] files = dir.list();//列舉該目錄下的文件
......
inti;
for(i=0; i<files.length; i++) {
File file = new File(dir, files[i]);
if (!isPackageFilename(files[i])) {
continue; //根據文件名后綴,判斷是否為APK 文件。這里只掃描APK 文件
}
/*
調用scanPackageLI函數掃描一個特定的文件,返回值是PackageParser的內部類
Package,該類的實例代表一個APK文件,所以它就是和APK文件對應的數據結構
*/
PackageParser.Package pkg = scanPackageLI(file,
flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime);
if (pkg == null && (flags &PackageParser.PARSE_IS_SYSTEM) == 0 &&
mLastScanError ==PackageManager.INSTALL_FAILED_INVALID_APK) {
//注意此處flags的作用,只有非系統Package掃描失敗,才會刪除該文件
file.delete();
}
}
}
~~~
接著來分析scanPackageLI函數。PKMS中有兩個同名的scanPackageLI函數,后面會一一見到。先來看第一個也是最先碰到的scanPackageLI函數。
(2) 初會scanPackageLI函數
首次相遇的scanPackageLI函數的代碼如下:
**PackageManagerService.java**
~~~
private PackageParser.Package scanPackageLI(FilescanFile, int parseFlags,
int scanMode, long currentTime)
{
mLastScanError = PackageManager.INSTALL_SUCCEEDED;
StringscanPath = scanFile.getPath();
parseFlags |= mDefParseFlags;//默認的掃描標志,正常情況下為0
//創建一個PackageParser對象
PackageParser pp = new PackageParser(scanPath);
pp.setSeparateProcesses(mSeparateProcesses);// mSeparateProcesses為空
pp.setOnlyCoreApps(mOnlyCore);// mOnlyCore為false
/*
調用PackageParser的parsePackage函數解析APK文件。注意,這里把代表屏幕
信息的mMetrics對象也傳了進去
*/
finalPackageParser.Package pkg = pp.parsePackage(scanFile,scanPath, mMetrics, parseFlags);
......
PackageSetting ps = null;
PackageSetting updatedPkg;
......
/*
這里略去一大段代碼,主要是關于Package升級方面的工作。讀者可能會比較好奇:既然是
升級,一定有新舊之分,如果這里剛解析后得到的Package信息是新,那么舊Package
的信息從何得來?還記得”readLPw的‘佐料’”這一小節提到的package.xml文件嗎?此
文件中存儲的就是上一次掃描得到的Package信息。對比這兩次的信息就知道是否需要做
升級了。這部分代碼比較繁瑣,但不影響我們正常分析。感興趣的讀者可自行研究
*/
//收集簽名信息,這部分內容涉及signature,本書暫不擬討論(Signature和Android安全機制有關)。
if (!collectCertificatesLI(pp, ps, pkg,scanFile, parseFlags))
returnnull;
//判斷是否需要設置PARSE_FORWARD_LOCK標志,這個標志針對資源文件和Class文件
//不在同一個目錄的情況。目前只有/vendor/app目錄下的掃描會使用該標志。這里不討論
//這種情況。
if (ps != null &&!ps.codePath.equals(ps.resourcePath))
parseFlags|= PackageParser.PARSE_FORWARD_LOCK;
String codePath = null;
String resPath = null;
if((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0) {
......//這里不考慮PARSE_FORWARD_LOCK的情況。
}else {
resPath = pkg.mScanPath;
}
codePath = pkg.mScanPath;//mScanPath指向該APK文件所在位置
//設置文件路徑信息,codePath和resPath都指向APK文件所在位置
setApplicationInfoPaths(pkg, codePath, resPath);
//調用第二個scanPackageLI函數
return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE,
currentTime);
}
~~~
scanPackageLI函數首先調用PackageParser對APK文件進行解析。根據前面的介紹可知,PackageParser完成了從物理文件到對應數據結構的轉換。下面來分析這個PackageParser。
(3) PackageParser分析
PackageParser主要負責APK文件的解析,即解析APK文件中的AndroidManifest.xml代碼如下:
**PackageParser.java**
~~~
publicPackage parsePackage(File sourceFile, String destCodePath,
DisplayMetrics metrics, int flags) {
mParseError = PackageManager.INSTALL_SUCCEEDED;
mArchiveSourcePath = sourceFile.getPath();
......//檢查是否為APK文件
XmlResourceParser parser = null;
AssetManager assmgr = null;
Resources res = null;
boolean assetError = true;
try{
assmgr = new AssetManager();
int cookie = assmgr.addAssetPath(mArchiveSourcePath);
if (cookie != 0) {
res = new Resources(assmgr, metrics, null);
assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0,Build.VERSION.RESOURCES_SDK_INT);
/*
獲得一個XML資源解析對象,該對象解析的是APK中的AndroidManifest.xml文件。
以后再討論AssetManager、Resource及相關的知識
*/
parser = assmgr.openXmlResourceParser(cookie,
ANDROID_MANIFEST_FILENAME);
assetError = false;
} ......//出錯處理
String[] errorText = new String[1];
Package pkg = null;
Exception errorException = null;
try {
//調用另外一個parsePackage函數
pkg = parsePackage(res, parser, flags, errorText);
} ......
......//錯誤處理
parser.close();
assmgr.close();
//保存文件路徑,都指向APK文件所在的路徑
pkg.mPath = destCodePath;
pkg.mScanPath = mArchiveSourcePath;
pkg.mSignatures = null;
return pkg;
}
~~~
以上代碼中調用了另一個同名的PackageParser函數,此函數內容較長,但功能單一,就是解析AndroidManifest.xml中的各種標簽,這里只提取其中相關的代碼:
**PackageParser.java**
~~~
private Package parsePackage(
Resources res, XmlResourceParser parser, int flags, String[] outError)
throws XmlPullParserException, IOException {
AttributeSet attrs = parser;
mParseInstrumentationArgs = null;
mParseActivityArgs = null;
mParseServiceArgs= null;
mParseProviderArgs = null;
//得到Package的名字,其實就是得到AndroidManifest.xml中package屬性的值,
//每個APK都必須定義該屬性
String pkgName = parsePackageName(parser, attrs, flags, outError);
......
inttype;
......
//以pkgName名字為參數,創建一個Package對象。后面的工作就是解析XML并填充
//該Package信息
finalPackage pkg = new Package(pkgName);
boolean foundApp = false;
......//下面開始解析該文件中的標簽,由于這段代碼功能簡單,所以這里僅列舉相關函數
while(如果解析未完成){
......
StringtagName = parser.getName(); //得到標簽名
if(tagName.equals("application")){
......//解析application標簽
parseApplication(pkg,res, parser, attrs, flags, outError);
} elseif (tagName.equals("permission-group")) {
......//解析permission-group標簽
parsePermissionGroup(pkg, res, parser, attrs, outError);
} elseif (tagName.equals("permission")) {
......//解析permission標簽
parsePermission(pkg, res, parser, attrs, outError);
} else if(tagName.equals("uses-permission")){
//從XML文件中獲取uses-permission標簽的屬性
sa= res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestUsesPermission);
//取出屬性值,也就是對應的權限使用聲明
String name = sa.getNonResourceString(com.android.internal.
R.styleable.AndroidManifestUsesPermission_name);
//添加到Package的requestedPermissions數組
if(name != null && !pkg.requestedPermissions.contains(name)) {
pkg.requestedPermissions.add(name.intern());
}
}elseif (tagName.equals("uses-configuration")){
/*
該標簽用于指明本package對硬件的一些設置參數,目前主要針對輸入設備(觸摸屏、鍵盤
等)。游戲類的應用可能對此有特殊要求。
*/
ConfigurationInfocPref = new ConfigurationInfo();
......//解析該標簽所支持的各種屬性
pkg.configPreferences.add(cPref);//保存到Package的configPreferences數組
}
......//對其他標簽解析和處理
}
~~~
上面代碼展示了AndroidManifest.xml解析的流程,其中比較重要的函數是parserApplication,它用于解析application標簽及其子標簽(Android的四大組件在application標簽中已聲明)。
圖4-5表示了PackageParser及其內部重要成員的信息。
:-: 
圖4-5 PackageParser大家族
由圖4-5可知:
- PackageParser定了相當多的內部類,這些內部類的作用就是保存對應的信息。解析AndroidManifest.xml文件得到的信息由Package保存。從該類的成員變量可看出,和Android四大組件相關的信息分別由activites、receivers、providers、services保存。由于一個APK可聲明多個組件,因此activites和receivers等均聲明為ArrayList。
- 以PackageParser.Activity為例,它從Component<ActivityIntentInfo>派生。Component是一個模板類,元素類型是ActivityIntentInfo,此類的頂層基類是IntentFilter。PackageParser.Activity內部有一個ActivityInfo類型的成員變量,該變量保存的就是四大組件中Activity的信息。細心的讀者可能會有疑問,為什么不直接使用ActivityInfo,而是通過IntentFilter構造出一個使用模板的復雜類型PackageParser.Activity呢?原來,Package除了保存信息外,還需要支持Intent匹配查詢。例如,設置Intent的Action為某個特定值,然后查找匹配該Intent的Activity。由于ActivityIntentInfo是從IntentFilter派生的,因此它它能判斷自己是否滿足該Intent的要求,如果滿足,則返回對應的ActivityInfo。在后續章節會詳細討論根據Intent查詢特定Activity的工作流程。
- PackageParser定了一個輕量級的數據結構PackageLite,該類僅存儲Package的一些簡單信息。我們在介紹Package安裝的時候,會遇到PackageLite。
>[warning] **注意**:讀者需要了解Java泛型類的相關知識。
(4) 與scanPackageLI再相遇
在PackageParser掃描完一個APK后,此時系統已經根據該APK中AndroidManifest.xm,創建了一個完整的Package對象,下一步就是將該Package加入到系統中。此時調用的函數就是另外一個scanPackageLI,其代碼如下:
**PackageManagerService.java::scanPackageLI函數**
~~~
private PackageParser.PackagescanPackageLI(PackageParser.Package pkg,
int parseFlags, int scanMode, long currentTime) {
FilescanFile = new File(pkg.mScanPath);
......
mScanningPath = scanFile;
//設置package對象中applicationInfo的flags標簽,用于標示該Package為系統
//Package
if((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
}
//①下面這句if判斷極為重要,見下面的解釋
if(pkg.packageName.equals("android")) {
synchronized (mPackages) {
if (mAndroidApplication != null) {
......
mPlatformPackage = pkg;
pkg.mVersionCode = mSdkVersion;
mAndroidApplication = pkg.applicationInfo;
mResolveActivity.applicationInfo = mAndroidApplication;
mResolveActivity.name = ResolverActivity.class.getName();
mResolveActivity.packageName = mAndroidApplication.packageName;
mResolveActivity.processName = mAndroidApplication.processName;
mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
mResolveActivity.theme =
com.android.internal.R.style.Theme_Holo_Dialog_Alert;
mResolveActivity.exported = true;
mResolveActivity.enabled = true;
//mResoveInfo的activityInfo成員指向mResolveActivity
mResolveInfo.activityInfo = mResolveActivity;
mResolveInfo.priority = 0;
mResolveInfo.preferredOrder = 0;
mResolveInfo.match = 0;
mResolveComponentName = new ComponentName(
mAndroidApplication.packageName, mResolveActivity.name);
}
}
~~~
剛進入scanPackageLI函數,我們就發現了一個極為重要的內容,即單獨判斷并處理packageName為“android”的Package。和該Package對應的APK是framework-res.apk,有圖為證,如圖4-6所示為該APK的AndroidManifest.xml中的相關內容。
:-: 
圖4-6 framework-res.apk的AndroidManifest.xml
實際上,framework-res.apk還包含了以下幾個常用的Activity。
- ChooserActivity:當多個Activity符合某個Intent的時候,系統會彈出此Activity,由用戶選擇合適的應用來處理。
- RingtonePickerActivity:鈴聲選擇Activity。
- ShutdownActivity:關機前彈出的選擇對話框。
由前述知識可知,該Package和系統息息相關,因此它得到了PKMS的特別青睞,主要體現在以下幾點。
- mPlatformPackage成員用于保存該Package信息。
- mAndroidApplication用于保存此Package中的ApplicationInfo。
- mResolveActivity指向用于表示ChooserActivity信息的ActivityInfo。
- mResolveInfo為ResolveInfo類型,它用于存儲系統解析Intent(經IntentFilter的過濾)后得到的結果信息,例如滿足某個Intent的Activity的信息。由前面的代碼可知,mResolveInfo的activityInfo其實指向的就是mResolveActivity。
* * * * *
**注意**:在從PKMS中查詢滿足某個Intent的Activity時,返回的就是ResolveInfo,再根據ResolveInfo的信息得到具體的Activity。
此處保存這些信息,主要是為了提高運行過程中的效率。Goolge工程師可能覺得ChooserActivity使用的地方比較多,所以這里單獨保存了此Activity的信息。
* * * * *
好,繼續對scanPackageLI函數的分析。
**PackageManagerService::scanPackageLI函數**
~~~
......//mPackages用于保存系統內的所有Package,以packageName為key
if(mPackages.containsKey(pkg.packageName)
|| mSharedLibraries.containsKey(pkg.packageName)) {
return null;
}
File destCodeFile = newFile(pkg.applicationInfo.sourceDir);
FiledestResourceFile = new File(pkg.applicationInfo.publicSourceDir);
SharedUserSettingsuid = null;//代表該Package的SharedUserSetting對象
PackageSetting pkgSetting = null;//代表該Package的PackageSetting對象
synchronized(mPackages) {
......//此段代碼大約有300行左右,主要做了以下幾方面工作
/*
①如果該Packge聲明了” uses-librarie”話,那么系統要判斷該library是否
在mSharedLibraries中
②如果package聲明了SharedUser,則需要處理SharedUserSettings相關內容,
由Settings的getSharedUserLPw函數處理
③處理pkgSetting,通過調用Settings的getPackageLPw函數完成
④調用verifySignaturesLP函數,檢查該Package的signature
*/
}
finallong scanFileTime = scanFile.lastModified();
finalboolean forceDex = (scanMode&SCAN_FORCE_DEX) != 0;
//確定運行該package的進程的進程名,一般用packageName作為進程名
pkg.applicationInfo.processName = fixProcessName(
pkg.applicationInfo.packageName,
pkg.applicationInfo.processName,
pkg.applicationInfo.uid);
if(mPlatformPackage == pkg) {
dataPath = new File (Environment.getDataDirectory(),"system");
pkg.applicationInfo.dataDir = dataPath.getPath();
}else {
/*
getDataPathForPackage函數返回該package的目錄
一般是/data/data/packageName/
*/
dataPath = getDataPathForPackage(pkg.packageName, 0);
if(dataPath.exists()){
......//如果該目錄已經存在,則要處理uid的問題
} else {
......//向installd發送install命令,實際上就是在/data/data下
//建立packageName目錄。后續將分析installd相關知識
int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid,
pkg.applicationInfo.uid);
//為系統所有user安裝此程序
mUserManager.installPackageForAllUsers(pkgName,
pkg.applicationInfo.uid);
if (dataPath.exists()) {
pkg.applicationInfo.dataDir = dataPath.getPath();
} ......
if (pkg.applicationInfo.nativeLibraryDir == null &&
pkg.applicationInfo.dataDir!= null) {
......//為該Package確定native library所在目錄
//一般是/data/data/packagename/lib
}
}
//如果該APK包含了native動態庫,則需要將它們從APK文件中解壓并復制到對應目錄中
if(pkg.applicationInfo.nativeLibraryDir != null) {
try {
final File nativeLibraryDir = new
File(pkg.applicationInfo.nativeLibraryDir);
final String dataPathString = dataPath.getCanonicalPath();
//從2.3開始,系統package的native庫統一放在/system/lib下。所以
//系統不會提取系統Package目錄下APK包中的native庫
if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) {
NativeLibraryHelper.removeNativeBinariesFromDirLI(
nativeLibraryDir)){
} else if (nativeLibraryDir.getParentFile().getCanonicalPath()
.equals(dataPathString)) {
boolean isSymLink;
try {
isSymLink = S_ISLNK(Libcore.os.lstat(
nativeLibraryDir.getPath()).st_mode);
} ......//判斷是否為鏈接,如果是,需要刪除該鏈接
if (isSymLink) {
mInstaller.unlinkNativeLibraryDirectory(dataPathString);
}
//在lib下建立和CPU類型對應的目錄,例如ARM平臺的是arm/,MIPS平臺的是mips/
NativeLibraryHelper.copyNativeBinariesIfNeededLI(scanFile,
nativeLibraryDir);
} else {
mInstaller.linkNativeLibraryDirectory(dataPathString,
pkg.applicationInfo.nativeLibraryDir);
}
} ......
}
pkg.mScanPath= path;
if((scanMode&SCAN_NO_DEX) == 0) {
......//對該APK做dex優化
performDexOptLI(pkg,forceDex, (scanMode&SCAN_DEFER_DEX);
}
//如果該APK已經存在,要先殺掉運行該APK的進程
if((parseFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
killApplication(pkg.applicationInfo.packageName,
pkg.applicationInfo.uid);
}
......
/*
在此之前,四大組件信息都屬于Package的私有財產,現在需要把它們登記注冊到PKMS內部的
財產管理對象中。這樣,PKMS就可對外提供統一的組件信息,而不必拘泥于具體的Package
*/
synchronized(mPackages) {
if ((scanMode&SCAN_MONITOR) != 0) {
mAppDirs.put(pkg.mPath, pkg);
}
mSettings.insertPackageSettingLPw(pkgSetting, pkg);
mPackages.put(pkg.applicationInfo.packageName,pkg);
//處理該Package中的Provider信息
int N =pkg.providers.size();
int i;
for (i=0;i<N; i++) {
PackageParser.Providerp = pkg.providers.get(i);
p.info.processName=fixProcessName(
pkg.applicationInfo.processName,
p.info.processName, pkg.applicationInfo.uid);
//mProvidersByComponent提供基于ComponentName的Provider信息查詢
mProvidersByComponent.put(new ComponentName(
p.info.packageName,p.info.name), p);
......
}
//處理該Package中的Service信息
N =pkg.services.size();
r = null;
for (i=0;i<N; i++) {
PackageParser.Service s =pkg.services.get(i);
mServices.addService(s);
}
//處理該Package中的BroadcastReceiver信息
N =pkg.receivers.size();
r = null;
for (i=0;i<N; i++) {
PackageParser.Activity a =pkg.receivers.get(i);
mReceivers.addActivity(a,"receiver");
......
}
//處理該Package中的Activity信息
N = pkg.activities.size();
r =null;
for (i=0; i<N; i++) {
PackageParser.Activity a =pkg.activities.get(i);
mActivities.addActivity(a,"activity");//后續將詳細分析該調用
}
//處理該Package中的PermissionGroups信息
N = pkg.permissionGroups.size();
......//permissionGroups處理
N =pkg.permissions.size();
......//permissions處理
N =pkg.instrumentation.size();
......//instrumentation處理
if(pkg.protectedBroadcasts != null) {
N = pkg.protectedBroadcasts.size();
for(i=0; i<N; i++) {
mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i));
}
}
......//Package的私有財產終于完成了公有化改造
return pkg;
}
~~~
到此這個長達800行的代碼就分析完了,下面總結一下Package掃描的流程。
(5) scanDirLI函數總結
scanDirLI用于對指定目錄下的APK文件進行掃描,如圖4-7所示為該函數的調用流程。
:-: 
圖4-7 scanDirLI工作流程總結
圖4-7比較簡單,相關知識無須贅述。讀者在自行分析代碼時,只要注意區分這兩個同名scanPackageLI函數即可。
掃描完APK文件后,Package的私有財產就充公了。PKMS提供了好幾個重要數據結構來保存這些財產,這些數據結構的相關信息如圖4-8所示。
:-: 
圖4-8 PKMS中重要的數據結構
圖4-8借用UML的類圖來表示PKMS中重要的數據結構。每個類圖的第一行為成員變量名,第二行為數據類型,第三行為注釋說明。
3. 掃描非系統Package
非系統Package就是指那些不存儲在系統目錄下的APK文件,這部分代碼如下:
**PackageManagerService.java::構造函數第三部分**
~~~
if (!mOnlyCore) {//mOnlyCore用于控制是否掃描非系統Package
Iterator<PackageSetting> psit =
mSettings.mPackages.values().iterator();
while (psit.hasNext()) {
......//刪除系統package中那些不存在的APK
}
mAppInstallDir = new File(dataDir,"app");
......//刪除安裝不成功的文件及臨時文件
if (!mOnlyCore) {
//在普通模式下,還需要掃描/data/app以及/data/app_private目錄
mAppInstallObserver = new AppDirObserver(
mAppInstallDir.getPath(), OBSERVER_EVENTS, false);
mAppInstallObserver.startWatching();
scanDirLI(mAppInstallDir, 0, scanMode, 0);
mDrmAppInstallObserver = newAppDirObserver(
mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false);
mDrmAppInstallObserver.startWatching();
scanDirLI(mDrmAppPrivateInstallDir,
PackageParser.PARSE_FORWARD_LOCK,scanMode,0);
} else {
mAppInstallObserver = null;
mDrmAppInstallObserver = null;
}
~~~
結合前述代碼,這里總結幾個存放APK文件的目錄。
- 系統Package目錄包括:/system/frameworks、/system/app和/vendor/app。
- 非系統Package目錄包括:/data/app、/data/app-private。
4. 第二階段工作總結
PKMS構造函數第二階段的工作任務非常繁重,要創建比較多的對象,所以它是一個耗時耗內存的操作。在工作中,我們一直想優化該流程以加快啟動速度,例如延時掃描不重要的APK,或者保存Package信息到文件中,然后在啟動時從文件中恢復這些信息以減少APK文件讀取并解析XML的工作量。但是一直沒有一個比較完滿的解決方案,原因有很多。比如APK之間有著比較微妙的依賴關系,因此到底延時掃描哪些APK,尚不能確定。另外,筆者感到比較疑惑的一個問題是:對于多核CPU架構,PKMS可以啟動多個線程以掃描不同的目錄,但是目前代碼中還沒有尋找到相關的蛛絲馬跡。難道此處真的就不能優化了嗎?讀者如果有更好的解決方案,不妨和大家分享一下。
- 前言
- 第1章 搭建Android源碼工作環境
- 1.1 Android系統架構
- 1.2 搭建開發環境
- 1.2.1 下載源碼
- 1.2.2 編譯源碼
- 1.2.3 利用Eclipse調試system_process
- 1.3 本章小結
- 第2章 深入理解Java Binder和MessageQueue
- 2.1 概述
- 2.2 Java層中的Binder架構分析
- 2.2.1 Binder架構總覽
- 2.2.2 初始化Java層Binder框架
- 2.2.3 addService實例分析
- 2.2.4 Java層Binder架構總結
- 2.3 心系兩界的MessageQueue
- 2.3.1 MessageQueue的創建
- 2.3.2 提取消息
- 2.3.3 nativePollOnce函數分析
- 2.3.4 MessageQueue總結
- 2.4 本章小結
- 第3章 深入理解SystemServer
- 3.1 概述
- 3.2 SystemServer分析
- 3.2.1 main函數分析
- 3.2.2 Service群英會
- 3.3 EntropyService分析
- 3.4 DropBoxManagerService分析
- 3.4.1 DBMS構造函數分析
- 3.4.2 dropbox日志文件的添加
- 3.4.3 DBMS和settings數據庫
- 3.5 DiskStatsService和DeviceStorageMonitorService分析
- 3.5.1 DiskStatsService分析
- 3.5.2 DeviceStorageManagerService分析
- 3.6 SamplingProfilerService分析
- 3.6.1 SamplingProfilerService構造函數分析
- 3.6.2 SamplingProfilerIntegration分析
- 3.7 ClipboardService分析
- 3.7.1 復制數據到剪貼板
- 3.7.2 從剪切板粘貼數據
- 3.7.3 CBS中的權限管理
- 3.8 本章小結
- 第4章 深入理解PackageManagerService
- 4.1 概述
- 4.2 初識PackageManagerService
- 4.3 PKMS的main函數分析
- 4.3.1 構造函數分析之前期準備工作
- 4.3.2 構造函數分析之掃描Package
- 4.3.3 構造函數分析之掃尾工作
- 4.3.4 PKMS構造函數總結
- 4.4 APK Installation分析
- 4.4.1 adb install分析
- 4.4.2 pm分析
- 4.4.3 installPackageWithVerification函數分析
- 4.4.4 APK 安裝流程總結
- 4.4.5 Verification介紹
- 4.5 queryIntentActivities分析
- 4.5.1 Intent及IntentFilter介紹
- 4.5.2 Activity信息的管理
- 4.5.3 Intent 匹配查詢分析
- 4.5.4 queryIntentActivities總結
- 4.6 installd及UserManager介紹
- 4.6.1 installd介紹
- 4.6.2 UserManager介紹
- 4.7 本章學習指導
- 4.8 本章小結
- 第5章 深入理解PowerManagerService
- 5.1 概述
- 5.2 初識PowerManagerService
- 5.2.1 PMS構造函數分析
- 5.2.2 init分析
- 5.2.3 systemReady分析
- 5.2.4 BootComplete處理
- 5.2.5 初識PowerManagerService總結
- 5.3 PMS WakeLock分析
- 5.3.1 WakeLock客戶端分析
- 5.3.2 PMS acquireWakeLock分析
- 5.3.3 Power類及LightService類介紹
- 5.3.4 WakeLock總結
- 5.4 userActivity及Power按鍵處理分析
- 5.4.1 userActivity分析
- 5.4.2 Power按鍵處理分析
- 5.5 BatteryService及BatteryStatsService分析
- 5.5.1 BatteryService分析
- 5.5.2 BatteryStatsService分析
- 5.5.3 BatteryService及BatteryStatsService總結
- 5.6 本章學習指導
- 5.7 本章小結
- 第6章 深入理解ActivityManagerService
- 6.1 概述
- 6.2 初識ActivityManagerService
- 6.2.1 ActivityManagerService的main函數分析
- 6.2.2 AMS的 setSystemProcess分析
- 6.2.3 AMS的 installSystemProviders函數分析
- 6.2.4 AMS的 systemReady分析
- 6.2.5 初識ActivityManagerService總結
- 6.3 startActivity分析
- 6.3.1 從am說起
- 6.3.2 AMS的startActivityAndWait函數分析
- 6.3.3 startActivityLocked分析
- 6.4 Broadcast和BroadcastReceiver分析
- 6.4.1 registerReceiver流程分析
- 6.4.2 sendBroadcast流程分析
- 6.4.3 BROADCAST_INTENT_MSG消息處理函數
- 6.4.4 應用進程處理廣播分析
- 6.4.5 廣播處理總結
- 6.5 startService之按圖索驥
- 6.5.1 Service知識介紹
- 6.5.2 startService流程圖
- 6.6 AMS中的進程管理
- 6.6.1 Linux進程管理介紹
- 6.6.2 關于Android中的進程管理的介紹
- 6.6.3 AMS進程管理函數分析
- 6.6.4 AMS進程管理總結
- 6.7 App的 Crash處理
- 6.7.1 應用進程的Crash處理
- 6.7.2 AMS的handleApplicationCrash分析
- 6.7.3 AppDeathRecipient binderDied分析
- 6.7.4 App的Crash處理總結
- 6.8 本章學習指導
- 6.9 本章小結
- 第7章 深入理解ContentProvider
- 7.1 概述
- 7.2 MediaProvider的啟動及創建
- 7.2.1 Context的getContentResolver函數分析
- 7.2.2 MediaStore.Image.Media的query函數分析
- 7.2.3 MediaProvider的啟動及創建總結
- 7.3 SQLite創建數據庫分析
- 7.3.1 SQLite及SQLiteDatabase家族
- 7.3.2 MediaProvider創建數據庫分析
- 7.3.3 SQLiteDatabase創建數據庫的分析總結
- 7.4 Cursor 的query函數的實現分析
- 7.4.1 提取query關鍵點
- 7.4.2 MediaProvider 的query分析
- 7.4.3 query關鍵點分析
- 7.4.4 Cursor query實現分析總結
- 7.5 Cursor close函數實現分析
- 7.5.1 客戶端close的分析
- 7.5.2 服務端close的分析
- 7.5.3 finalize函數分析
- 7.5.4 Cursor close函數總結
- 7.6 ContentResolver openAssetFileDescriptor函數分析
- 7.6.1 openAssetFileDescriptor之客戶端調用分析
- 7.6.2 ContentProvider的 openTypedAssetFile函數分析
- 7.6.3 跨進程傳遞文件描述符的探討
- 7.6.4 openAssetFileDescriptor函數分析總結
- 7.7 本章學習指導
- 7.8 本章小結
- 第8章 深入理解ContentService和AccountManagerService
- 8.1 概述
- 8.2 數據更新通知機制分析
- 8.2.1 初識ContentService
- 8.2.2 ContentResovler 的registerContentObserver分析
- 8.2.3 ContentResolver的 notifyChange分析
- 8.2.4 數據更新通知機制總結和深入探討
- 8.3 AccountManagerService分析
- 8.3.1 初識AccountManagerService
- 8.3.2 AccountManager addAccount分析
- 8.3.3 AccountManagerService的分析總結
- 8.4 數據同步管理SyncManager分析
- 8.4.1 初識SyncManager
- 8.4.2 ContentResolver 的requestSync分析
- 8.4.3 數據同步管理SyncManager分析總結
- 8.5 本章學習指導
- 8.6 本章小結