android系統啟動之PMS啟動原始碼解析
PakageManagerService的啟動流程圖
1.PakageManagerService概述
PakageManagerService是android系統中一個核心的服務,它負責系統中Package的管理,應該程式的安裝、解除安裝等。後面PakageManagerService簡稱PMS。
2.SystemServer啟動PackageManagerService
我之前的ATA文章有說到,SystemServer程式是Zygote孵化出的第一個程式,該程式主要的工作是啟動android系統服務程式,其中包括PackageManagerService服務,SystemServer啟動PMS關鍵原始碼如下:
private void startBootstrapServices() {
//...
//呼叫PMS的main函式
mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
//判斷本次是否為初次啟動,當Zygote或者SystemServer退出時,init會再次啟動它們,所以這裡
//的firstBoot指的是開機後的第一次啟動
mFirstBoot = mPackageManagerService.isFirstBoot();
mPackageManager = mSystemContext.getPackageManager();
//...
}
關鍵點
- PMS的main函式,該函式是PKM的核心。
3.PMS的main方法
PackageManagerService的主要功能是,掃描Android系統中幾個目標資料夾的APK,建立對應的資料結構來管理Package資訊、四大元件資訊、許可權資訊等各種資訊。例如PKMS解析APK包中的AndroidMainfest.xml,並根據其中宣告的Activity標籤來建立對應的物件並加以保管。PMS的main方法的程式碼如下:
public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
//new 一個PackageManagerService物件
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
//PKM註冊到ServiceManager上。ServiceManager相當於安卓系統服務的DNS伺服器
ServiceManager.addService("package", m);
return m;
}
該方法看似很簡單,只有幾行程式碼,然而執行事件卻比較長,這是因為PMS在其建構函式中做了很多的“重體力活”,這也是android啟動速度慢的主要因素之一。安裝的應用越多,系統啟動開機時間越長。
PMS建構函式的主要工作流程
- 掃描目標資料夾之前的準備工作。
- 掃描目標資料夾。
- 掃描之後的工作。
4.PMS的前期準備工作
4.1探究Setting
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,
SystemClock.uptimeMillis());
if (mSdkVersion <= 0) {
Slog.w(TAG, "**** ro.build.version.sdk not set!");
}
mContext = context;
//是否在工廠測試模式下,假定為false
mFactoryTest = factoryTest;
mOnlyCore = onlyCore;
//如果此係統是“eng”版,掃描Package後,不對package做dex優化
mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
//用於儲存與螢幕相關的一些屬性,例如螢幕的寬高解析度等。
mMetrics = new DisplayMetrics();
mSettings = new Settings(mPackages);
//第一個引數是字串“android.uid.system”;第二個是SYSTEM_UID,其值為1000,
//第三個是FLAG_SYSTEM標誌,用於標識系統Package。
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
4.1.1Android系統中UID/GID
UID為使用者ID的縮寫,GID為使用者組ID的縮寫,這兩個概念均與Linux系統中程式的許可權管理有關。一般來說,每一個程式都有一個對應的UID,表示該程式屬於哪個使用者,不同使用者有不同許可權。一個程式也可分屬不同的使用者組,每個使用者組都有對應的許可權。
在android系統中,系統定義的UID/GID在Process.java檔案中,關鍵原始碼如下所示
/**
* Defines the UID/GID under which system code runs.
*/
public static final int SYSTEM_UID = 1000;
/**
* Defines the UID/GID under which the telephony code runs.
*/
public static final int PHONE_UID = 1001;
/**
* Defines the UID/GID for the user shell.
* @hide
*/
public static final int SHELL_UID = 2000;
/**
* Defines the UID/GID for the log group.
* @hide
*/
public static final int LOG_UID = 1007;
/**
* Defines the UID/GID for the WIFI supplicant process.
* @hide
*/
public static final int WIFI_UID = 1010;
/**
* Defines the UID/GID for the mediaserver process.
* @hide
*/
public static final int MEDIA_UID = 1013;
/**
* Defines the UID/GID for the DRM process.
* @hide
*/
public static final int DRM_UID = 1019;
/**
* Defines the UID/GID for the group that controls VPN services.
* @hide
*/
public static final int VPN_UID = 1016;
/**
* Defines the UID/GID for the NFC service process.
* @hide
*/
public static final int NFC_UID = 1027;
/**
* Defines the UID/GID for the Bluetooth service process.
* @hide
*/
public static final int BLUETOOTH_UID = 1002;
/**
* Defines the GID for the group that allows write access to the internal media storage.
* @hide
*/
public static final int MEDIA_RW_GID = 1023;
/**
* Access to installed package details
* @hide
*/
public static final int PACKAGE_INFO_GID = 1032;
/**
* Defines the UID/GID for the shared RELRO file updater process.
* @hide
*/
public static final int SHARED_RELRO_UID = 1037;
/**
* Defines the start of a range of UIDs (and GIDs), going from this
* number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
* to applications.
*/
public static final int FIRST_APPLICATION_UID = 10000;
/**
* Last of application-specific UIDs starting at
* {@link #FIRST_APPLICATION_UID}.
*/
public static final int LAST_APPLICATION_UID = 19999;
/**
* First uid used for fully isolated sandboxed processes (with no permissions of their own)
* @hide
*/
public static final int FIRST_ISOLATED_UID = 99000;
/**
* Last uid used for fully isolated sandboxed processes (with no permissions of their own)
* @hide
*/
public static final int LAST_ISOLATED_UID = 99999;
4.1.2 探究SharedUserSetting
Setting中有一個mShareUsers成員,該成員儲存的是字串變數name與SharedUserSetting健值對。
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
//mSharedUsers是一個HashMap.key為字串,值為ShareUserSetting物件
SharedUserSetting s = mSharedUsers.get(name);
if (s != null) {
if (s.userId == uid) {
return s;
}
//...
return null;
}
建立一個SharedUserSetting物件,並設定為userid為uid
s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
s.userId = uid;
if (addUserIdLPw(uid, s, name)) {
mSharedUsers.put(name, s);
return s;
}
return null;
}
例如在SystemUI.apk的AndroidManifest.xml檔案中,有關鍵程式碼:
<mainfest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.systemui"
coreApp="true"
android:sharedUserId="android.uid.system"
android:process="system">
....
在該標籤中,宣告瞭一個android:sharedUserId的屬性,其值為“android.uid.system”。sharedUserId和UID有關,它的作用是
- 兩個或者多個宣告瞭同一種sharedUserid的APK可共享彼此的資料,並且可執行在同一程式中。
- 通過宣告特定的sharedUserId,該APK所在的程式將被賦予指定UID。
例如SystemUI宣告瞭system的uid,執行SystemUI的程式就可享有system使用者所對應的許可權了,實際上就是將該程式的UID設定為system的uid了
接下來分析addUserIdLPw的功能,它主要就是將SharedUserSettings物件儲存到對應的陣列中,程式碼如下
private boolean addUserIdLPw(int uid, Object obj, Object name) {
//uid不能超出限制,Android對uid進行歸納,系統APK所在程式小於10000
//應用APK所在程式的uid從10000開始
if (uid > Process.LAST_APPLICATION_UID) {
return false;
}
//FIRST_APPLICATION_UID = 10000,屬於應用APK
if (uid >= Process.FIRST_APPLICATION_UID) {
int N = mUserIds.size();
//計算索引,其值是uid和FIRST_APPLICATION_UID的差
final int index = uid - Process.FIRST_APPLICATION_UID;
while (index >= N) {
mUserIds.add(null);
N++;
}
//如果索引位置不為空,返回
if (mUserIds.get(index) != null) {
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate user id: " + uid
+ " name=" + name);
return false;
}
//mUserIds儲存應用Package的uid,obj是SharedUserSettings
mUserIds.set(index, obj);
} else {
if (mOtherUserIds.get(uid) != null) {
PackageManagerService.reportSettingsProblem(Log.ERROR,
"Adding duplicate shared id: " + uid
+ " name=" + name);
return false;
}
mOtherUserIds.put(uid, obj);
}
return true;
}
4.2 XML檔案掃描
接下來是掃描系統目錄下與系統許可權相關的xml檔案,將其存放到PKM中,關鍵原始碼如下:
// 獲取系統相關的許可權,它主要是解析系統目錄下xml檔案,獲得裝置相關的許可權
SystemConfig systemConfig = SystemConfig.getInstance();
mGlobalGids = systemConfig.getGlobalGids();
mSystemPermissions = systemConfig.getSystemPermissions();
mAvailableFeatures = systemConfig.getAvailableFeatures();
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
//建立一個ThreadHandler物件,實際就是建立一個帶訊息佇列迴圈處理的執行緒,
//該執行緒的工作是:程式的安裝和解除安裝等。
mHandlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
mHandlerThread.start();
//以ThreadHandler執行緒的訊息迴圈(Looper物件)作為引數new一個
//PackageHandler,因此該Handler的handlemessage方法將執行在此執行緒上
mHandler = new PackageHandler(mHandlerThread.getLooper());
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
// /data目錄
File dataDir = Environment.getDataDirectory();
// /data/data目錄
mAppDataDir = new File(dataDir, "data");
// /data/app目錄
mAppInstallDir = new File(dataDir, "app");
// /data/app-lib目錄
mAppLib32InstallDir = new File(dataDir, "app-lib");
// /data/app-asec目錄
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
// /data/user目錄
mUserAppDataDir = new File(dataDir, "user");
// /data/app-private目錄
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
//new一個UserManager物件,目前沒有什麼作用,但其前途不可限量。
//google設想,未來手機將支援多個User,每個User安裝自己的應用
//該功能為android智慧手機推向企業使用者打下基礎
sUserManager = new UserManagerService(context, this,
mInstallLock, mPackages);
// 獲取系統相關的許可權,它主要是解析系統目錄下xml檔案,獲得裝置相關的許可權
ArrayMap<String, SystemConfig.PermissionEntry> permConfig
= systemConfig.getPermissions();
for (int i=0; i<permConfig.size(); i++) {
SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
BasePermission bp = mSettings.mPermissions.get(perm.name);
if (bp == null) {
bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
mSettings.mPermissions.put(perm.name, bp);
}
if (perm.gids != null) {
bp.setGids(perm.gids, perm.perUser);
}
}
//獲得系統的Libraries,也就是系統的一些jar
ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
for (int i=0; i<libConfig.size(); i++) {
mSharedLibraries.put(libConfig.keyAt(i),
new SharedLibraryEntry(libConfig.valueAt(i), null));
}
mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();
mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
mSdkVersion, mOnlyCore);
String customResolverActivity = Resources.getSystem().getString(
R.string.config_customResolverActivity);
if (TextUtils.isEmpty(customResolverActivity)) {
customResolverActivity = null;
} else {
mCustomResolverComponentName = ComponentName.unflattenFromString(
customResolverActivity);
}
long startTime = SystemClock.uptimeMillis();
進一步我們再觀察SystemConfig是如何解析系統許可權xml檔案的,在SystemConfig的建構函式中,它會去分別讀取etc目錄下的sysconfig,permissions,sysconfig目錄下的檔案。
SystemConfig() {
// Read configuration from system
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "sysconfig"), false);
// Read configuration from the old permissions dir
readPermissions(Environment.buildPath(
Environment.getRootDirectory(), "etc", "permissions"), false);
// Only read features from OEM config
readPermissions(Environment.buildPath(
Environment.getOemDirectory(), "etc", "sysconfig"), true);
readPermissions(Environment.buildPath(
Environment.getOemDirectory(), "etc", "permissions"), true);
}
我們看看到底這些目錄下放著什麼樣的檔案,例如/etc/permissions目錄下的檔案如下圖:
我們再開啟第一個檔案來探究,沒錯,這個檔案代表藍芽許可權,表示該裝置支援藍芽。具體程式碼如下
<?xml version="1.0" encoding="utf-8"?>
<permissions>
<feature name="android.hardware.bluetooth" />
</permissions>
總結一下PMS的前期工作,其實就是掃描並解析XML檔案,將其中的資訊儲存到特定的資料結構中。
5.PMS掃描Package
第二個階段的工作主要是掃描系統中的APK,由於需要逐個掃描apk檔案,因此手機上安裝的程式越多,PKM的工作量越大,系統啟動速度越慢,也就是開機時間越長。
5.1系統庫的dex優化
以下的程式碼主要是對系統庫BOOTCLASSPATH指定,或platform.xml定義,或者/system/frameworks目錄下的jar
和apk包進行一次檢查,該dex優化的優化.dex優化後會在相應的目錄生成.odex檔案。/system/frameworks如下圖:
關鍵原始碼如下:
// Set flag to monitor and not change apk file paths when
// scanning install directories.
//定義掃描引數
final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;
//用於儲存已經dex優化過或者不需要優化的包
final ArraySet<String> alreadyDexOpted = new ArraySet<String>();
/**
* Add everything in the in the boot class path to the
* list of process files because dexopt will have been run
* if necessary during zygote startup.
*/
//獲取java啟動類庫的路徑,在init.rc檔案中通過BOOTCLASSPATH環境變數輸出
//主要是/system/framework/下的系統jar包
final String bootClassPath = System.getenv("BOOTCLASSPATH");
final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");
if (bootClassPath != null) {
String[] bootClassPathElements = splitString(bootClassPath, `:`);
//迴圈遍歷/system/framework/下的系統jar包的絕對路徑,新增到alreadyDexOpted
for (String element : bootClassPathElements) {
alreadyDexOpted.add(element);
}
}
if (mSharedLibraries.size() > 0) {
//...
if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
alreadyDexOpted.add(lib);
//dex優化
mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
}
//...
File frameworkDir = new File(Environment.getRootDirectory(), "framework");
//framework-res.apk定義了系統常用的資源,還有幾個重要的Activity,如長按Power鍵彈出選擇框
//不需要dex優化
alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk");
alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");
//掃描framework/下的apk或者jar進行dex優化
String[] frameworkFiles = frameworkDir.list();
if (frameworkFiles != null) {
// TODO: We could compile these only for the most preferred ABI. We should
// first double check that the dex files for these commands are not referenced
// by other system apps.
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
for (int i=0; i<frameworkFiles.length; i++) {
File libPath = new File(frameworkDir, frameworkFiles[i]);
String path = libPath.getPath();
// 跳過已經存在的包
if (alreadyDexOpted.contains(path)) {
continue;
}
// 不是apk或者jar的不做處理
if (!path.endsWith(".apk") && !path.endsWith(".jar")) {
continue;
}
int dexoptNeeded = DexFile.getDexOptNeeded(path, null, dexCodeInstructionSet, false);
if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
//dex優化
mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
}
}
}
5.2掃描系統的APK
對apk或者jar進行dex優化後,現在PKM進入了重點階段,掃描系統的APK,每一個APK對應一個Package物件,主要是掃描APK的AndroidManifest.xml,解析application標籤及其子標籤actvity、service、recever等,也就是android的四大元件,解析後將它們儲存到Package對應的資料結構中,最後將它們註冊到PKM中,要掃描以下幾個目錄:
- /system/frameworks:該目錄下的檔案都是系統庫,例如service.jar、framework.jar、framework-res.apk。不過只掃描framework-res.apk檔案
- /system/app:該目錄下全是預設的系統應用和廠商特定的APK檔案,例如Buletooth.apk、和SystemUI.apk等
解析AndroidManifest.xml關鍵的原始碼如下:
private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
//解析<application>
if (tagName.equals("application")) {
if (foundApp) {
if (RIGID_PARSER) {
outError[0] = "<manifest> has more than one <application>";
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return null;
} else {
Slog.w(TAG, "<manifest> has more than one <application>");
XmlUtils.skipCurrentTag(parser);
continue;
}
}
foundApp = true;
//解析<application>及其子標籤
if (!parseBaseApplication(pkg, res, parser, attrs, flags, outError)) {
return null;
}
//解析<application>
} else if (tagName.equals("overlay")) {
} else if (tagName.equals("key-sets")) {
} else if (tagName.equals("permission-group")) {
} else if (tagName.equals("permission")) {
} else if (tagName.equals("permission-tree")) {
//解析<uses-permission>
} else if (tagName.equals("uses-permission")) {
} else if (tagName.equals("uses-permission-sdk-m")
|| tagName.equals("uses-permission-sdk-23")) {
} else if (tagName.equals("uses-configuration")) {
} else if (tagName.equals("uses-feature")) {
} else if (tagName.equals("feature-group")) {
} else if (tagName.equals("uses-sdk")) {
} else if (tagName.equals("supports-screens")) {
} else if (tagName.equals("instrumentation")) {
} else if (tagName.equals("original-package")) {
} else if (tagName.equals("adopt-permissions")) {
} else if (tagName.equals("uses-gl-texture")) {
} else if (tagName.equals("compatible-screens")) {
} else if (tagName.equals("supports-input")) {
} else if (tagName.equals("eat-comment")) {
} else if (RIGID_PARSER) {
} else {
}
}
}
具體看一下怎麼解析application標籤下的四大元件的,依次解析activity,receiver,service,provider,其中可以發現,receiver被當成activity來解析了,PKM通過PackageParser類將解析後的四大元件儲存到對應資料結構中,也就是存放到PackageParser的activities,receivers,providers,services物件中。關鍵原始碼如下:
private boolean parseBaseApplication(Package owner, Resources res,
XmlPullParser parser, AttributeSet attrs, int flags, String[] outError){
//...
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String tagName = parser.getName();
//activity標籤
if (tagName.equals("activity")) {
//解析activity標籤
Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
owner.baseHardwareAccelerated);
//新增activity到owner.activities中
owner.activities.add(a);
//receiver標籤
} else if (tagName.equals("receiver")) {
//解析receiver標籤,receiver其實被當成Activity來解析了。
Activity a = parseActivity(owner, res, parser, attrs, flags, outError, true, false);
if (a == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
//新增activity到owner.activities中
owner.receivers.add(a);
//service標籤
} else if (tagName.equals("service")) {
//解析service標籤
Service s = parseService(owner, res, parser, attrs, flags, outError);
if (s == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
//新增service到owner.services中
owner.services.add(s);
//provider標籤
} else if (tagName.equals("provider")) {
Provider p = parseProvider(owner, res, parser, attrs, flags, outError);
if (p == null) {
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return false;
}
owner.providers.add(p);
} else if (tagName.equals("activity-alias")) {
} else if (parser.getName().equals("meta-data")) {
} else if (tagName.equals("uses-library")) {
} else if (tagName.equals("uses-package")) {
}
}
}
在PackageParser掃描完一個APK後,此時系統已經根據APK中的AndroidMainifest.xml,建立了一個Package物件,下一步是將該Package加入到系統中。此時呼叫scanPackageDirtyLI方法,scanPackageDirtyLI首先會對packageName為“android”的apk做單獨的處理,該apk其實就是framework-res.apk,它包含了幾個常見的activity
- ChooserActivity:當startActivity有多個Acitvity符合時,系統會彈出此Acitivity,由使用者選擇合適的應用來處理
- ShutDownActivity:關機前彈出的選擇對話方塊
- RingtonePickerAcitivity:鈴聲選擇Activity
scanPackageDirtyLI關鍵程式碼如下:
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
final File scanFile = new File(pkg.codePath);
if (pkg.applicationInfo.getCodePath() == null ||
pkg.applicationInfo.getResourcePath() == null) {
// Bail out. The resource and code paths haven`t been set.
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Code and resource paths haven`t been set correctly");
}
if ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
} else {
// Only allow system apps to be flagged as core apps.
pkg.coreApp = false;
}
if ((parseFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) {
pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
}
if (mCustomResolverComponentName != null &&
mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) {
setUpCustomResolverActivity(pkg);
}
if (pkg.packageName.equals("android")) {
synchronized (mPackages) {
if (mAndroidApplication != null) {
//...
}
//儲存該package資訊
mPlatformPackage = pkg;
pkg.mVersionCode = mSdkVersion;
//儲存該package的ApplicationInfo
mAndroidApplication = pkg.applicationInfo;
if (!mResolverReplaced) {
//mResolveActivity為ChooserActivity資訊的ActivityInfo
mResolveActivity.applicationInfo = mAndroidApplication;
mResolveActivity.name = ResolverActivity.class.getName();
mResolveActivity.packageName = mAndroidApplication.packageName;
mResolveActivity.processName = "system:ui";
mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
mResolveActivity.documentLaunchMode = ActivityInfo.DOCUMENT_LAUNCH_NEVER;
mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
mResolveActivity.theme = R.style.Theme_Holo_Dialog_Alert;
mResolveActivity.exported = true;
mResolveActivity.enabled = true;
//mResolveInfo用於儲存系統解析Intent後得到的結果資訊,在從PKM查詢滿足某個Intent的
//Activity時,返回的就是ResolveInfo,再根據ResolveInfo的activityInfo的資訊得到 //Activity
mResolveInfo.activityInfo = mResolveActivity;
mResolveInfo.priority = 0;
mResolveInfo.preferredOrder = 0;
mResolveInfo.match = 0;
mResolveComponentName = new ComponentName(
mAndroidApplication.packageName, mResolveActivity.name);
}
}
}
}
“android“該Package與系統有非常重要的作用,這裡儲存特殊處理儲存該Package的資訊,主要是為了提高執行過程中的效率,例如ChooserActivity使用的地方非常多。
接下里scanPackageDirtyLI方法會對系統其它的Package做處理,關鍵原始碼如下:
//mPackages用於儲存系統內的所有Package,以packageName為key
if (mPackages.containsKey(pkg.packageName)
|| mSharedLibraries.containsKey(pkg.packageName)) {
//...
}
if ((scanFlags & SCAN_REQUIRE_KNOWN) != 0) {
if (mExpectingBetter.containsKey(pkg.packageName)) {
logCriticalInfo(Log.WARN,
"Relax SCAN_REQUIRE_KNOWN requirement for package " + pkg.packageName);
} else {
PackageSetting known = mSettings.peekPackageLPr(pkg.packageName);
if (known != null) {
if (DEBUG_PACKAGE_SCANNING) {
Log.d(TAG, "Examining " + pkg.codePath
+ " and requiring known paths " + known.codePathString
+ " & " + known.resourcePathString);
}
if (!pkg.applicationInfo.getCodePath().equals(known.codePathString)
|| !pkg.applicationInfo.getResourcePath().equals(known.resourcePathString)) {
throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
"Application package " + pkg.packageName
+ " found at " + pkg.applicationInfo.getCodePath()
+ " but expected at " + known.codePathString + "; ignoring.");
}
}
}
}
// Initialize package source and resource directories
File destCodeFile = new File(pkg.applicationInfo.getCodePath());
File destResourceFile = new File(pkg.applicationInfo.getResourcePath());
SharedUserSetting suid = null;
PackageSetting pkgSetting = null;
if (!isSystemApp(pkg)) {
// Only system apps can use these features.
pkg.mOriginalPackages = null;
pkg.mRealPackage = null;
pkg.mAdoptPermissions = null;
}
//...
final String pkgName = pkg.packageName;
final long scanFileTime = scanFile.lastModified();
final boolean forceDex = (scanFlags & SCAN_FORCE_DEX) != 0;
//確定執行該package的程式名,一般用package作為程式名
pkg.applicationInfo.processName = fixProcessName(
pkg.applicationInfo.packageName,
pkg.applicationInfo.processName,
pkg.applicationInfo.uid);
File dataPath;
if (mPlatformPackage == pkg) {
// 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.
//該函式返回data/data/packageName
dataPath = Environment.getDataUserPackageDirectory(pkg.volumeUuid,
UserHandle.USER_OWNER, pkg.packageName);
boolean uidError = false;
if (dataPath.exists()) {
//..
} else {
if (DEBUG_PACKAGE_SCANNING) {
if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
Log.v(TAG, "Want this data dir: " + dataPath);
}
//該方法呼叫installer傳送install命令,其實就是在/data/data/目錄下建立packageName目錄
//然後為系統所有的user安裝此apk
int ret = createDataDirsLI(pkg.volumeUuid, pkgName, pkg.applicationInfo.uid,
pkg.applicationInfo.seinfo);
//安裝錯誤
if (ret < 0) {
// Error from installer
throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
"Unable to create data dirs [errorCode=" + ret + "]");
}
}
pkgSetting.uidError = uidError;
}
final String path = scanFile.getPath();
final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
//在/data/data/pageName/lib下建立和CPU型別對應的目錄,例如ARM平臺的事arm/,MIP平臺的事mips/
if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
derivePackageAbi(pkg, scanFile, cpuAbiOverride, true /* extract libs */);
//系統package的native庫統一放在/system/lib下,
//所以系統不會提取系統package目錄apk包中的native庫
if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp() &&
pkg.applicationInfo.primaryCpuAbi == null) {
setBundledAppAbisAndRoots(pkg, pkgSetting);
setNativeLibraryPaths(pkg);
}
} else {
if ((scanFlags & SCAN_MOVE) != 0) {
//
setNativeLibraryPaths(pkg);
}
//...
if ((scanFlags & SCAN_NO_DEX) == 0) {
//對該APK做dex優化
int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instruction sets */,
forceDex, (scanFlags & SCAN_DEFER_DEX) != 0, false /* inclDependencies */);
//如果該apk已經存在,要先殺掉該APK的程式
if ((scanFlags & SCAN_REPLACING) != 0) {
killApplication(pkg.applicationInfo.packageName,
pkg.applicationInfo.uid, "replace pkg");
}
//在此之前,四大元件資訊都是Package物件的私有的,在這裡把它們註冊到PKM內部的財產管理物件中。
//這樣,PKMS就可對外提供統一的元件資訊。
synchronized (mPackages) {
...
//註冊該Package中的provider到PKM的mProviders上
int N = pkg.providers.size();
StringBuilder r = null;
int i;
for (i=0; i<N; i++) {
PackageParser.Provider p = pkg.providers.get(i);
p.info.processName = fixProcessName(pkg.applicationInfo.processName,
p.info.processName, pkg.applicationInfo.uid);
mProviders.addProvider(p);
p.syncable = p.info.isSyncable;
if (p.info.authority != null) {
//...
}
//註冊該Package中的service到PKM的mServices上
N = pkg.services.size();
r = null;
for (i=0; i<N; i++) {
PackageParser.Service s = pkg.services.get(i);
s.info.processName = fixProcessName(pkg.applicationInfo.processName,
s.info.processName, pkg.applicationInfo.uid);
mServices.addService(s);
if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
if (r == null) {
r = new StringBuilder(256);
} else {
r.append(` `);
}
r.append(s.info.name);
}
}
if (r != null) {
if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Services: " + r);
}
//註冊該Package中的receiver到PKM的mReceivers上
N = pkg.receivers.size();
r = null;
for (i=0; i<N; i++) {
PackageParser.Activity a = pkg.receivers.get(i);
a.info.processName = fixProcessName(pkg.applicationInfo.processName,
a.info.processName, pkg.applicationInfo.uid);
mReceivers.addActivity(a, "receiver");
if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
if (r == null) {
r = new StringBuilder(256);
} else {
r.append(` `);
}
r.append(a.info.name);
}
}
if (r != null) {
if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Receivers: " + r);
}
//註冊該Package中的activity到PKM的mActivities上
N = pkg.activities.size();
r = null;
for (i=0; i<N; i++) {
PackageParser.Activity a = pkg.activities.get(i);
a.info.processName = fixProcessName(pkg.applicationInfo.processName,
a.info.processName, pkg.applicationInfo.uid);
mActivities.addActivity(a, "activity");
if ((parseFlags&PackageParser.PARSE_CHATTY) != 0) {
if (r == null) {
r = new StringBuilder(256);
} else {
r.append(` `);
}
r.append(a.info.name);
}
}
if (r != null) {
if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, " Activities: " + r);
}
//...
return pkg;
}
5.3掃描非系統apk
在PackageManagerService建構函式掃描完系統apk後,接下來就是掃描非系統apk,這些apk在/data/app或者/data/app-private中。如下圖:
下面是關鍵原始碼,scanDirLI已經在前面分析過了。跟系統apk的呼叫過程差不多。
if (!mOnlyCore) {
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
SystemClock.uptimeMillis());
scanDirLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
scanFlags | SCAN_REQUIRE_KNOWN, 0);
}
5.4掃描結果儲存到檔案中
在PackageManagerService建構函式收尾階段,PMS將前面收集的資訊再整理一次,將已安裝的apk資訊寫到package.xml、pacakage.list和package-stopped.xml中
//整理更新Permisssion的資訊
updatePermissionsLPw(null, null, updateFlags);
//...
//將資訊寫到package.xml,pacakage.list和package-stopped.xml中
mSettings.writeLPr();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
SystemClock.uptimeMillis());
mRequiredVerifierPackage = getRequiredVerifierLPr();
mRequiredInstallerPackage = getRequiredInstallerLPr();
} // synchronized (mPackages)
} // synchronized (mInstallLock)
//gc
Runtime.getRuntime().gc();
- packages.xml:系統對程式安裝,解除安裝和更新等操作時會更新該檔案,PMS掃描完目標資料夾後建立該檔案,儲存了Package相關的資訊。
- packages.list:儲存著系統中所有的非系統自帶的APK資訊,程式安裝,解除安裝和更新會更新該檔案。
- packages-stoped.xml:儲存系統中被使用者強制停止的Package的資訊。
5.4掃描系統和非系統apk總結
PKM在這個過程中工作任務非常繁重,要建立很多的物件,所以它是一個耗時耗記憶體的操作,從流程來看,PKM在這個過程中無非是掃描XML或者APK檔案,但是其中涉及的資料結構及它們的關係較為複雜。
相關文章
- Android 系統原始碼-1:Android 系統啟動流程原始碼分析Android原始碼
- Android系統原始碼分析--Service啟動流程Android原始碼
- Android系統原始碼分析–Service啟動流程Android原始碼
- Android系統原始碼分析--Activity啟動過程Android原始碼
- 原始碼閱讀之Activity啟動與App啟動流程 – Android 9.0原始碼APPAndroid
- 原始碼閱讀之Activity啟動與App啟動流程 - Android 9.0原始碼APPAndroid
- Android系統啟動流程(二)解析Zygote程式AndroidGo
- Android系統啟動自動開啟mtklogAndroid
- Android系統原始碼分析–Zygote和SystemServer啟動過程Android原始碼GoServer
- Android系統啟動流程(四)Launcher啟動過程與系統啟動流程Android
- NioEventLoop啟動流程原始碼解析OOP原始碼
- Android 系統啟動流程Android
- 【android 7.1.2】系統啟動Android
- React Native Android 原始碼分析之啟動過程React NativeAndroid原始碼
- SpringBoot原始碼解析-啟動流程(二)Spring Boot原始碼
- SpringBoot原始碼解析-啟動流程(一)Spring Boot原始碼
- Android Activity啟動流程原始碼分析Android原始碼
- Android原始碼分析:Activity啟動流程Android原始碼
- netty原始碼分析之服務端啟動全解析Netty原始碼服務端
- Spring原始碼解析02:Spring IOC容器之XmlBeanFactory啟動流程分析和原始碼解析Spring原始碼XMLBean
- 以太坊啟動過程原始碼解析原始碼
- SpringMVC原始碼解析(1)-啟動過程SpringMVC原始碼
- Android原始碼(二)應用程式啟動Android原始碼
- Android 8.0 原始碼分析 (六) BroadcastReceiver 啟動Android原始碼AST
- Android 8.0 原始碼分析 (五) Service 啟動Android原始碼
- Android 8.0 原始碼分析 (四) Activity 啟動Android原始碼
- Node.js原始碼解析-啟動-js部分Node.js原始碼
- 追蹤解析Spring ioc啟動原始碼(2)Spring原始碼
- [原始碼解析] 並行分散式框架 Celery 之 worker 啟動 (2)原始碼並行分散式框架
- [原始碼解析] 分散式任務佇列 Celery 之啟動 Consumer原始碼分散式佇列
- [原始碼解析] 並行分散式框架 Celery 之 worker 啟動 (1)原始碼並行分散式框架
- Android 8.0 原始碼分析 (一) SystemServer 程式啟動Android原始碼Server
- Android效能優化之啟動過程(冷啟動和熱啟動)Android優化
- 原始碼|HDFS之NameNode:啟動過程原始碼
- es原始碼啟動原始碼
- 線上直播系統原始碼,簡單實現Android應用的啟動頁原始碼Android
- Androd 系統原始碼-3:應用啟動過程的原始碼分析原始碼
- SpringBoot原始碼解析-內嵌Tomcat容器的啟動Spring Boot原始碼Tomcat
- Netty原始碼解析 -- 服務端啟動過程Netty原始碼服務端