本篇主要分析了系統啓動階段包管理服務的啓動流程,其中的幾個接口在 apk 安裝時也會被調用。包管理服務啓動時主要作的工做大體有以下幾方面:
java
1. 創建 java 層的 installer 與 c 層的 installd 的 socket 聯接,使得在上層的 install,remove,dexopt等功能最終由 installd 在底層實現android
2. 建 立 PackageHandler 消 息 循 環 , 用 於 處 理 外 部 的 apk 安 裝 請 求 消 息 , 如 adbinstall,packageinstaller 安裝 apk 時會發送消息
shell
3. 解析/system/etc/permission 下 xml 文件(framework/base/data/etc/),包括 platform.xml 和系統支持的各類硬件模塊的 feature.主要工做:cookie
(1) 創建底層 user ids 和 group ids 同上層 permissions 之間的映射;能夠指定一個權限與幾個組 ID 對應。當一個 APK 被授予這個權限時,它也同時屬於這幾個組。
(2) 給一些底層用戶分配權限,如給 shell 授予各類 permission 權限;把一個權限賦予一個UID,當進程使用這個 UID 運行時,就具有了這個權限。
(3) library,系統增長的一些應用須要 link 的擴展 jar 庫;
(4) feature, 系 統 每 增 加 一 個 硬 件 , 都 要 添 加 相 應 的 feature. 將 解 析 結 果mSystemPermissions,mSharedLibraries,mSettings.mPermissions,mAvailableFeatures 等幾個集合中供系統查詢和權限配置使用。
4. 檢查/data/system/packages.xml 是否存在,這個文件是在解析 apk 時由writeLP()建立的,裏面記錄了系統的 permissions,以及每一個 apk name,codePath,flags,ts,version,uesrid 等信息,這些信息主要經過 apk 的AndroidManifest.xml 解析獲取,解析完 apk 後將更新信息寫入這個文件並保存到 flash,下次開機直接從裏面讀取相關信息添加到內存相關列表中。當有 apk升級,安裝或刪除時會更新這個文件。
5. 檢查 BootClassPath,mSharedLibraries 及/system/framework 下的 jar是否須要 dexopt,須要的則經過 dexopt 進行優化
6. 啓動 AppDirObserver 線程監測/system/framework,/system/app,/data/app,/data/app-private 目錄的事件,主要監聽 add 和 remove 事件。對於目錄監聽底層經過inotify 機制實現,inotify 是一種文件系統的變化通知機制,如文件增長、刪除等事件能夠馬上讓用戶態得知,它爲用戶態監視文件系統的變化提供了強大的支持。當有 add event 時調用 scanPackageLI(File , int , int)處理;當有 remove event 時調用 removePackageLI()處理;
7. 對於以上幾個目錄下的 apk 逐個解析,主要是解析每一個 apk 的 AndroidMa-nifest.xml 文件,處理 asset/res 等資源文件,創建起每一個 apk 的配置結構信息,並將每一個 apk 的配置信息添加到全局列表進行管理。調用 installer.install()進行安裝工做,檢查 apk 裏的 dex 文件是否須要再優化,若是須要優化則經過輔助工具 dexopt 進行優化處理;將解析出的 componet 添加到 pkg 的對應列表裏;對 apk 進行簽名和證書校驗,進行完整性驗證。
8. 將解析的每一個 apk 的信息保存到 packages.xml 和 packages.list 文件裏,
packages.list 記錄了以下數據:pkgName,userId,debugFlag,dataPath(包的數據路徑)
app
圖 1 主流程圖
詳細分析
在 systemserver.java 中啓動包管理服務
pm=PackageManagerService.main(context,factoryTest != SystemServer.FACTORY_TEST_OFF);
main 函數主要功能是構造 PackageManagerService 實例,而後添加到 ServiceManager 中。
public static final IPackageManager main(Context context, boolean factoryTest) {
PackageManagerService m = new PackageManagerService(context, factoryTest);
ServiceManager.addService("package", m);
return m;
}
PackageManagerService(context, factoryTest) 是 包 管 理 服 務 的 主 進 程 。 它 完 成 了對/system/app,/data/app,/system/framework,/data/app-private 下的 apk 文件的解析。詳細流程以下:
初始化過程:
判斷 ro.build.type 是否等於 eng;
建立系統顯示像素實例 mMetrics = new DisplayMetrics();
建立 mSettings 實例 mSettings = new Settings(),Settings 類是 PackageManagerService 的一個
靜態子類,它的做用主要是保持動態設置的信息,經過 Settings()構造函數在/data/system 下
建立了三個文件名:packages.xml,packages-backup.xml(這個文件在 mSettings.writeLP()裏被刪除了),packages.list。
mSettings 增長 android.uid.system,android.uid.phone,android.uid.log 三個共享用戶 ID,同時授予其系統權限。
//創建 installer 與 installd 的 socket 聯接
Installer installer = new Installer();
installer.ping() && Process.supportsProcesses();
installd 完成如下一些命令
struct cmdinfo cmds[] = {
{ "ping", 0, do_ping },
{ "install", 3, do_install },
{ "dexopt", 3, do_dexopt },
{ "movedex", 2, do_move_dex },
{ "rmdex", 1, do_rm_dex },
{ "remove", 1, do_remove },
{ "rename", 2, do_rename },
{ "freecache", 1, do_free_cache },
{ "rmcache", 1, do_rm_cache },
{ "protect", 2, do_protect },
{ "getsize", 3, do_get_size },
{ "rmuserdata", 1, do_rm_user_data },
{ "movefiles", 0, do_movefiles },
};
//獲取當前缺省的顯示像素
WindowManager wm=(WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Display d = wm.getDefaultDisplay();
d.getMetrics(mMetrics);
創建一個消息循環,用於處理 apk 安裝時的請求消息處理(這些請求來自 adb install/push,包安裝器,android market 下載安裝 apk 時發送的)
mHandlerThread.start();
mHandler = new PackageHandler(mHandlerThread.getLooper());
這個消息循環處理的消息事件以下:
static final int SEND_PENDING_BROADCAST = 1;
static final int MCS_BOUND = 3;
static final int END_COPY = 4;
static final int INIT_COPY = 5;
static final int MCS_UNBIND = 6;
static final int START_CLEANING_PACKAGE = 7;
static final int FIND_INSTALL_LOC = 8;
static final int POST_INSTALL = 9;
static final int MCS_RECONNECT = 10;
static final int MCS_GIVE_UP = 11;
static final int UPDATED_MEDIA_STATUS = 12;
static final int WRITE_SETTINGS = 13;
//建立/data/data 和/data/app-private 目錄
File dataDir = Environment.getDataDirectory();//得到/data 目錄
mAppDataDir = new File(dataDir, "data");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
// Read permissions from /system/etc/permission directory.
//這些文件在 framework/base/data/etc
Void readPermissions()
{
//解析/system/etc/permission/下的*.xml 文件,獲取權限信息
//最後解析該目錄下的 platform.xml 文件,使該文件裏的權限在棧頂出現,以便預先處理
//這個文件記錄了系統級應用的 uid 及其擁有的權限
File permFile = new File(Environment.getRootDirectory(),"etc/permissions/platform.xml");
readPermissionsFromXml(permFile);
//該函數的功能以下:
經過 xml 解 析 器 解 釋 *.xml 文 件 , 提 取 標 籤 名 「 group」 , "permission" , "assign-permission","library","feature"並進行相應處理。在 platform.xml 中對底層的系統用戶和組ID(group ids)同上層的由平臺管理的 permission 名字之間進行了關係映射,使它們關聯起來。當一個應用被授予某個權限後,同時屬於已知的組 ID,這個應用就能夠進行容許這個組的文件系統操做,如(read,write,execute )。這裏記錄了一些系統級的應用的 uid 對應的permission
//每一個標籤的含義:
group:安裝到系統中的全部 APK 都具有的組 ID。
permission:能夠指定一個權限與幾個組 ID 對應。當一個 APK 被授予這個權限時,它也同時屬於這幾個組。
assign-permission:把一個權限賦予一個 UID,當進程使用這個 UID 運行時,就具有了這個權限。
library:爲系統添加一些擴展庫用的。對應的.jar 文件放在/system/framework/目錄下。好比Google Map 相關的庫。
feature : 每 添 加 一 個 硬 件 , 都 要 增 加 對 應 的 feature 。 將 以 上 解 析 的 結 果 對 應 放 入mGlobalGids,mSettings.mPermissions,mSystemPermissions,mSharedLibraries,,mAvailableFeatures 等幾個 list 中供系統查詢和權限配置使用。
}
//readLP()會判斷/data/system/packages.xml 文件是否存在,若是不存在則返回 false,若是存在則進行解析,在系統第一次啓動時 packages.xml 文件是不存在的,由 writeLP()建立該文件,並將該文件寫到 nand 上,下次開機會直接讀取並解析這個文件。解析的過程便是按照 xml定義的標籤,將對應的屬性和值添加到全局列表中。packages.xml 文件中記錄了系統安裝的全部 apk 的屬性權限的信息,當系統中的 apk 安裝,刪除或升級時,改文件就會被更新。
標 籤 定 義 了 目 前 系 統 中 定 義 的 所 有 權 限 。 主 要 分 爲 兩 類 : 系 統 定 義 的(package 屬性爲 Android)和 APK 定義的(package 屬性爲 APK 的包名)
sharedUserId/userId:Android 系統啓動一個普通的 APK 時,會爲這個 APK 分配一個獨立的UID , 這 就 是 userId 。 如 果 APK 要 和 系 統 中 其 它 APK 使 用 相 同 的 UID 的 話 , 那 就 是sharedUserId。perms:APK 的 AndroidManifest.xml 文件中,每使用一個標籤,標籤中就會增長一項。
代 表 一 個 共 享 UID , 通 常 , 共 同 實 現 一 系 列 相 似 功 能 的 APK 共 享 一 個UID。標籤中的 權限表明了這個共享 UID 的權限,全部使用的同一個共享 UID 的APK 運行在同一進程中,這個進程的 UID 就是這個共享 UID,這些 APK 都具備這個共享UID 的權限。
name:共享 UID 的名字,在 APK 的 Android:sharedUserId 屬性中使用。
userId:使用這個共享 UID 的全部 APK 運行時所在的進程的 UID。
mRestoredSettings = mSettings.readLP();
// 判 斷 boot class path 裏 的 文 件 ( jar 文 件 ) 是 否 需 要 dexopt( 判 斷 標 準 是 檢 查DvmGlobals.bootClassPath 是否已經包含這個文件),若是須要先將文件添加到 libFiles 裏,同時 進 行 dexopt : 由 Installer 通 過 socket 將 命 令 傳 給 installd 的 run_dexopt, 最 終 調 用 的是/system/bin/dexopt 對 jar 包進行處理。若是已經進行了 dexopt 動做,則將/data/dalvik-cache下的以 data 開頭的文件刪除,後續從新創建。若是外部庫 mSharedLibraries 列表存在,也要檢 查 列 表 中 的 元 素 是 否 需 要 dexopt, 如 果 需 要 則 和 boot class path 進 行 相 同 處 理 。 對於/system/framework 下 apk 和 jar 文件檢查是否須要 dexopt.
String bootClassPath = System.getProperty("java.boot.class.path");
if (bootClassPath != null) {
String[] paths = splitString(bootClassPath, ':');
for (int i=0; i try {
if (dalvik.system.DexFile.isDexOptNeeded(paths[i])) {//是否須要 dexopt
libFiles.add(lib);//添加到 libFiles 列表
mInstaller.dexopt(paths[i], Process.SYSTEM_UID, true);//進行 dexopt
}
}
}
}
//對 framework-res.apk 不進行 dexopt 直接添加到 libFiles
libFiles.add(mFrameworkDir.getPath() + "/framework-res.apk");
// 啓 動 AppDirObserver 線 程 監 測 /system/framework , /system/app , /data/app , /data/app-private 幾個目錄的事件,主要監聽的是 add 和 remove 事件。對於目錄監聽底層經過 inotify機制實現,inotify 是在 2.6.13 中引入的新功能,它爲用戶態監視文件系統的變化提供了強大的支持;inotify 是一種文件系統的變化通知機制,如文件增長 ,刪除等事件能夠馬上讓用戶態得知,當監測到事件發生時該線程作何處理呢?
MframeworkInstallObserver =
new AppDirObserver(mFrameworkDir.getPath(),OBSERVER_EVENTS, true);
mFrameworkInstallObserver.startWatching();
//調用 scanDirLI 解析以上目錄下的 apk 文件,該函數是包管理服務的重要函數,在後面有詳細的分析
private void scanDirLI(File dir, int flags, int scanMode) {
String[] files = dir.list();
for (i=0; i {
File file = new File(dir, files[i]);
PackageParser.Package pkg =
scanPackageLI(file,flags|PackageParser.PARSE_MUST_BE_APK, scanMode);
if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {
// Delete the apk
file.delete();
}
}
}
//對於不存在的 system apk 調用如下函數刪除掉
Iterator psit = mSettings.mPackages.values().iterator();
PackageSetting ps = psit.next();
if ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0
&&!mPackages.containsKey(ps.name)
&& !mSettings.mDisabledSysPackages.containsKey(ps.name))
{
psit.remove();
mInstaller.remove(ps.name);
}
//在解析完以上目錄下的 apk 後,更新應用的權限
updatePermissionsLP(null, null, true, regrantPermissions, regrantPermissions);
//writeLP 會 生 成 packages.xml 和 packages.list 文 件 ,packages.list 的 數 據 格 式 是 :pkgName,userId,debugFlag,dataPath(包的數據路徑),packages.xml 保存了每一個已經安
裝 apk 的詳盡的信息
mSettings.writeLP();
以上是包管理服務在系統啓動時作的所有工做。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
下面解析其中一個比較重要的函數 scanDirLI:
private void scanDirLI(File dir, int flags, int scanMode) {
String[] files = dir.list();
for (i=0; i {
File file = new File(dir, files[i]);
PackageParser.Package pkg =
scanPackageLI(file,flags|PackageParser.PARSE_MUST_BE_APK, scanMode);
if (pkg == null && (flags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
mLastScanError == PackageManager.INSTALL_FAILED_INVALID_APK) {
// Delete the apk
file.delete();
}
}
}
這 個 函 數 結 構 比 較 簡 單 , 對 監 測 的 幾 個 目 錄 下 的 每 一 個 apk 文 件 繼 續 通 過scanPackageLI(file,flags|PackageParser.PARSE_MUST_BE_APK, scanMode) 進 行 解 析 , 對 不存在且安裝失敗已經無效的非系統 apk 直接刪除。
socket
圖 2 scanDirLI 流程分析ide
scanPackageLI 是 一 個 重 定 義 函 數 , 它 的 做 用 是 : 用 PackageParser 的 兩 個 重 定 義 函 數parsePackage 解析 package 的 asset,res,創建 asset 資源文件路徑;解析 AndroidManifest.xml文件,創建 PackageParser.Package 結構,這個結構保存了從 AndroidManifest.xml 解析出的package 的信息。對 package 進行數字簽名及完整性校驗,
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanMode)
{
//實例化一個 PackageParser 對象
PackageParser pp = new PackageParser(scanPath);
// parsePackage 也是一個重定義函數,它主要作了三件事,一個是解析 apk 中的 asset下 的 文 件 , 一 個 是 解 析 res 下 的 文 件 。 然 後 通 過 重 定 義 函 數 parsePackage(Resources res, XmlResourceParser parser, int flags, String[] outError) 對 apk 的AndroidManifest.xml 進行解析,將每一個標籤對應的信息添加到每一個包的相關列表中,如將標籤 application 下 的 activity 通 過 pkg.activities.add(a) 添 加 到 package 的 activities 列 表 , 將service 添加到 owner.services.add(s)。
PackageParser.Package pkg = pp.parsePackage(scanFile, scanPath, mMetrics, parseFlags);
//檢查這個 package 是否已經存在,以及是否重命名過,以及該系統 package 是否能夠被更新,若是能夠被更新,則對比系統分區和 data 分區的 package 版本,若是系統分區的
package 高於 data 分區的版本,則保留系統分區的 package
//對 package 進行簽名認證,若是是 system img 裏的,只是經過 AndroidManifest.xml 得到簽名,對簽名校驗,不會對所有文件進行有效性檢查;不然,就要結合 META-INF/進行
簽名和有效性校驗collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags);
//調用重定義函數繼續進行解析,將每一個 apk 解析出的標籤信息添加到全局的列表裏。如將 每 個 apk 的 recervers 列 表 裏 的 元 素 pkg.receivers.get(i), 通 過 mReceivers.addActivity(a,"receiver")添加到全局列表 mReceivers 裏
return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE);
}
<補充知識>
res/raw 和 assets 區別
*res/raw 和 assets 的相同點:
1. 二者目錄下的文件在打包後會原封不動的保存在 apk 包中,不會被編譯成二進制。
*res/raw 和 assets 的不一樣點:
1. res/raw 中 的 文 件 會 被 映 射 到 R.java 文 件 中 , 訪 問 的 時 候 直 接 使 用 資 源 ID 即R.id.filename ; assets 文 件 夾 下 的 文 件 不 會 被 映 射 到 R.java 中 , 訪 問 的 時 候 需 要AssetManager 類。
2.res/raw 不能夠有目錄結構,而 assets 則能夠有目錄結構,也就是 assets 目錄下能夠再創建文件夾
*讀取文件資源:
1.讀取 res/raw 下的文件資源,經過如下方式獲取輸入流來進行寫操做InputStream is = getResources().openRawResource(R.id.filename);
2.讀取 assets 下的文件資源,經過如下方式獲取輸入流來進行寫操做
AssetManager am = null;
am = getAssets();
InputStream is = am.open("filename");
(用於內置文件但不知道文件名稱,須要篩選出想要的文件而後拷貝到目標目錄中,推薦內置在 assets 文件夾中)
1.res/raw 目錄:
經過反射的方式獲得 R.java 裏面 raw 內部類裏面全部的資源 ID 的名稱,而後經過名稱獲取資源 ID 的值來讀取咱們想要的文件。
2.assets 目錄:
getAssets().list("");來獲取 assets 目錄下全部文件夾和文件的名稱,再經過這些名稱讀取咱們想要的文件。另,在處理 asset 時,android 限制最大的數據是 1M,超出後會報錯誤。
public Package parsePackage(File sourceFile, String destCodePath, DisplayMetrics metrics, int
flags)
{
//解析/asset 下的文件
assmgr = new AssetManager();
int cookie = assmgr.addAssetPath(mArchiveSourcePath);
parser = assmgr.openXmlResourceParser(cookie, "AndroidManifest.xml");
//解析/Res 下的文件,經過 parsePackage 函數解析 AndroidManifest.xml 文件
Resources res = new Resources(assmgr, metrics, null);
pkg = parsePackage(res, parser, flags, errorText);
// 設置代碼路徑和資源路徑
pkg.mPath = destCodePath;
pkg.mScanPath = mArchiveSourcePath;
}
Package parsePackage(Resources res, XmlResourceParser parser, int flags, String[] outError)
{
解析 AndroidManifest.xml 裏的各個標籤,並對 pkg 的 mVersionCode,mSharedUserId,mSharedUserLabel,installLocation 等 變 量 賦 值 。 對 於 application , permission-
group , permission , permission-tree , uses-permission , uses-configuration , uses-feature , uses-sdk , supports-screens , protected-broadcast , instrumentation , original-
package,adopt-permissions,eat-comment 等標籤調用相關函數進行處理 解析出每一個標籤下的子標籤的信息,而後將這些信息添加到每一個 package 的對應列表中,如將 application 下的activity 經過 pkg.activities.add(a)添加到 package 的 activities 列表。
//將 pkg 返回
return pkg;
}
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags, int
scanMode) {
(1)Check all shared libraries and map to their actual file path.
(2)check pkg.reqFeatures in mAvailableFeatures
(3)Check and note if we are renaming from an original package name
(4)Check if we are renaming from an original package name.
對於 original package 不是太瞭解,還須要繼續研究
判斷新裝應用的 content providers 是否與已經安裝應用的產生衝突。
if (mPlatformPackage == pkg) {//若是包名以 android 開頭的,則將應用的 dataDir 設爲/data/system
// The system package is special.
dataPath = new File (Environment.getDataDirectory(), "system");
pkg.applicationInfo.dataDir = dataPath.getPath();
}else {
// This is a normal package, need to make its data directory.
dataPath = getDataPathForPackage(pkg);
if (dataPath.exists()) {//若是路徑存在,使用 FileUtils.getPermissions 獲取 dataPath 的權限,
if (mOutPermissions[1] == pkg.applicationInfo.uid || !Process.supportsProcesses())
{
pkg.applicationInfo.dataDir = dataPath.getPath();
} else {
mInstaller.remove(pkgName);//將該包刪除
mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid);
//從新安裝該應用
}
pkg.applicationInfo.dataDir = dataPath.getPath();//dataDir 從新賦值
}
else
//若是路徑不存在則直接 install 安裝
{
mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid);
}
// Perform shared library installation and dex validation and
// optimization, if this is not a system app.
performDexOptLI(pkg, forceDex);
//若是新的應用已經安裝,請求 ActivityManager 將舊的 kill 掉,以避免使用時形成混亂
if ((parseFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
killApplication(pkg.applicationInfo.packageName,pkg.applicationInfo.uid);
}
// Add the new setting to mSettings
mSettings.insertPackageSettingLP(pkgSetting, pkg);
// Add the new setting to mPackages
mPackages.put(pkg.applicationInfo.packageName, pkg);
// Make sure we don't accidentally delete its data.
mSettings.mPackagesToBeCleaned.remove(pkgName);
如下將每一個包的 provider,service,activity 等信息添加到全局列表中
mServices.addService(s);
mReceivers.addActivity(a, "receiver");
mActivities.addActivity(a, "activity");
mPermissionGroups.put(pg.info.name, pg);
permissionMap.put(p.info.name, bp);
mInstrumentation.put(a.getComponentName(), a);
mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i));
}
來在:http://blog.csdn.net/andy_android/article/details/7245602函數