Android Framework之PMS篇
1. PMS運行時的一些規則
PMS相關的目錄與文件,以及PMS操作它們的規則。
/data/app
用戶安裝的第三方apk,以及app所依賴的native library都放在這里。在Android 6.0時,此目錄增加了一個文件夾“oat”,用來存放此app第一次運行時由dex2oat生成的此app的oat文件。在之前的Android版本中,用戶安裝的app的oat文件存儲在/data/dalvik-cache中。6.0時,此目錄只存放系統自帶的apk的oat文件。
/data/data
是系統當前用戶安裝的所有app的沙箱目錄。該目錄實際上是data/user/用戶ID目錄的引用。隨著用戶的切換,”/data/data/“也會映射為不同的用戶。
PMS的配置文件
PMS會產生一些配置文件,用來記錄系統當前安裝的app,這些文件存儲在:data/system/users/userId/
- packages.xml------->記錄系統中所有已經安裝的應用信息,包括基本信息,簽名和權限。
packages內容之Package標簽.png
Package標簽
- Name:程序包名稱
- codePath:程序包所在路徑
- nativeLibraryPath:該程序所使用的native庫文件路徑。
- primaryCpuAbi:apk支持的abi類型(優先)
- userId:應用程序對應的Linux用戶Id
- sharedUserId:若在androidManifest.xml中定義了sharedUserId,則此處使用它而非userId。
-
Sigs:簽名信息。一個應用程序只能有一個簽名。
-
Perms:一個應用程序所申請的權限列表。androidManifest.xml中每使用一個<uses-permission>,則packages.xml中<perms>標簽就會增加一項。
packages內容之share-user標簽.png
Shared-user標簽
-
定義了共享用戶id對應的簽名和權限
當操作該文件的時候,總會創建備份文件packages-backup.xml。當正常操作完成的時候,會刪除該備份。否則,當PMS下次啟動的時候,一旦發現有backup文件,就會優先解析備份文件。當一個app被升級覆蓋安裝時,會使用<updated-packages>表示,當新舊版本app的包名發生改變時,會使用<renamed-package>記錄。
2.packages-stoped.xml------->記錄系統中被強制停止運行的app的信息。它同樣可能存在一個packages-stoped-backup.xml的備份文件,當備份文件存在的時候,優先使用備份文件。因為原文件可能已經損壞了。
3.packages.list------->保存應用的數據目錄和uid信息。
如:
com.qihoo.appstore 10067 0 /data/data/com.qihoo.appstore default 3002,3003,3001
第一列為app的包名,第二列為10067為此app的用戶ID,第三列中的0,表示此app所屬的系統用戶ID,第四列為此app的數據文件目錄。
default為seinfo,SEAndroid相關機制會使用該字段。
最后一列記錄了該app所在的權限組,也就是說擁有哪些權限。
系統硬件特性和權限
PMS啟動的時候會從/system/etc/permissions/中讀取當前Android設備的硬件特性和設定的相關權限。
xxx.xml 包含很多feature,用來描述手機應該支持的硬件特性,如支持camera,藍牙等
Platform.xml 建立上層permission同底層uid/gid的關系
正是因為解析了該目錄中的文件,所以可以通過pm命令查看features和permissions等信息。
多用戶管理
PMS還要對多用戶進行管理。因為安裝apk的時候,可以PMS可以指定給某個特定的用戶,也可以安裝給全部的用戶。
權限動態管理
Android M 中 允許動態授權和取消App中申請的權限。修改后會相應的寫入runtime-permissions.xml
2. PMS的機制與實現
2.1 PMS體系結構
PMS體系結構圖
2.2 PMS相關代碼路徑
frameworks/base/core/java/android/content/pm/PackageManager.java
frameworks/base/core/java/android/content/pm/PackageParser.java
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
frameworks/base/services/core/java/com/android/server/pm/Settings.java
frameworks/base/services/core/java/com/android/server/pm/Installer.java
//Runtime-Permission相關
frameworks/base/services/core/java/com/android/server/pm/ DefaultPermissionGrantPolicy.java
frameworks/base/core/java/android/os/FileObserver.java
frameworks/base/core/java/android/app/ApplicationPackageManager.java
frameworks/base/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
frameworks/native/cmds/installd/commands.c
frameworks/native/cmds/installd/installd.c
frameworks/native/cmds/installd/utils.c</code></pre>
2.3 PMS的類關系圖

PMS類關系圖
2.4PMS的啟動流程

PMS的啟動流程圖
2.4.1 獲取系統默認配置
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
……
mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
long dexOptLRUThresholdInMinutes;
if (mLazyDexOpt) {
dexOptLRUThresholdInMinutes = 30; // only last 30 minutes of apps for eng builds.
} else {
dexOptLRUThresholdInMinutes = 7 * 24 * 60; // apps used in the 7 days for users.
}
mDexOptLRUThresholdInMills = dexOptLRUThresholdInMinutes * 60 * 1000;
mMetrics = new DisplayMetrics();
String separateProcesses = SystemProperties.get("debug.separate_processes");
……
- PMS啟動階段主要獲取ro.build.type和debug.separate_processes兩個值。
- ro.build.type:用于標記版本是eng還是usr。如果為eng,則mLazyDexOpt為true。通過mLazyDexOpt來設定dexOptLRUThresholdInMinutes的值(該值在filterRecentlyUsedApps方法中用于過濾最近使用的apps,如果大于mDexOptLRUThresholdInMills則不進行dexopt操作。usr版本為7 days,eng版本為30 mins) 。
- debug.separate_processes:用于標記是否在獨立進程中運行某個程序。根據該值設置PMS.mDefParseFlags和PMS.mSeparateProcesses兩個全局變量,后續scanDirLi掃描并安裝APK的時候用到。
- 獲取系統默認顯示參數
通過new DisplayMetrics()獲取系統默認顯示參數,存入PMS.mMetrics變量中,主要用于匹配APK中的asset和resource。 2.4.2 創建并初始化Settings對象
public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) {
……
mSettings = new Settings(context);
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);
Setting初始化階段包括以下兩部分工作:
1) 調用構造函數初始化new Settings(context)
2) 調用addSharedUserLPw方法添加6個默認共享用戶ID
1.調用構造函數初始化
Settings(Context context) {
this(context, Environment.getDataDirectory());
}
Settings(Context context, File dataDir) {
mSystemDir = new File(dataDir, "system");
mSystemDir.mkdirs();
FileUtils.setPermissions(mSystemDir.toString(),
FileUtils.S_IRWXU|FileUtils.S_IRWXG
|FileUtils.S_IROTH|FileUtils.S_IXOTH,
-1, -1);
mSettingsFilename = new File(mSystemDir, "packages.xml");//記錄系統中所有已安裝的apk信息,安裝和卸載時會更新
mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
mPackageListFilename = new File(mSystemDir, "packages.list");//記錄系統中所有已安裝APK的簡略信息
FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml");
mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml");
}
2.4.3 創建dexopt優化器對象
mInstaller = installer;
mPackageDexOptimizer = new PackageDexOptimizer(this);
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
mOnPermissionChangeListeners = new OnPermissionChangeListeners(FgThread.get().getLooper());
創建PackageDexOptimizer對象,該類主要用來執行ART中的patchoat命令,用來對oat文件的偏移值進行隨機化。該類是Android M 中才有的。創建監聽權限更改的監聽者。因為Android M中允許動態修改App權限。
2.4.4 解析系統Permissions和Feature信息
public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) {
SystemConfig systemConfig = SystemConfig.getInstance();
mGlobalGids = systemConfig.getGlobalGids();
mSystemPermissions = systemConfig.getSystemPermissions();
mAvailableFeatures = systemConfig.getAvailableFeatures();
……
SystemConfig負責解析系統Permissions和Feature信息,路經為system/etc/sysconfig和system/etc/permissions。其流程如下:

SystemConfig解析Permissions和Feature流程
解析完成后,Pemissions文件中各標簽和SystemConfig及PMS變量的對應關系如下:

Paste_Image.png
2.4.5 啟動PackageHandler
進入mPackages同步塊后首先啟動PackageHandler
public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) {
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
mHandlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
mHandlerThread.start();
mHandler = new PackageHandler(mHandlerThread.getLooper());
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
……
PackageHandler是PMS的內部類,用于接收并處理其他模塊程序發送的消息,這些消息可以來自adb或者其他應用程序
在PackageHandler的handleMessage方法中調用doHandleMessage方法處理消息,代碼如下:
class PackageHandler extends Handler {
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY:
case MCS_BOUND:
case MCS_CHECK:
case MCS_RECONNECT:
case MCS_UNBIND:
case MCS_GIVE_UP:
case SEND_PENDING_BROADCAST:
case START_CLEANING_PACKAGE:
case POST_INSTALL:
case UPDATED_MEDIA_STATUS:
case WRITE_SETTINGS:
case WRITE_PACKAGE_RESTRICTIONS:
case CHECK_PENDING_VERIFICATION:
case PACKAGE_VERIFIED:
……
以上消息主要用于APK的復制和更名操作,但這些操作并不是在PackageHandler中完成的。而是在PackageHandler的消息處理函數中會通過connectToService方法綁定到MCS服務,即DefaultContainerService。
2.4.6 創建data目錄并初始化UserManagerService
public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) {
File dataDir = Environment.getDataDirectory();//即“/data”目錄
mAppDataDir = new File(dataDir, "data");// data/data
mAppInstallDir = new File(dataDir, "app");// data/app
mAppLib32InstallDir = new File(dataDir, "app-lib");// data/app-lib
mAsecInternalPath = new File(dataDir, "app-asec").getPath();// data/app-asec
mUserAppDataDir = new File(dataDir, "user");// data/user
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");// data/app-private
sUserManager = new UserManagerService(context, this,mInstallLock, mPackages);
……</code></pre>
由以上代碼可知,首先初始化成員變量來存放數據目錄信息,然后創建UserManagerService。
在UserManagerService的構造函數中,創建data/system/users、data/system/users/0目錄和data/system/users/userlist.xml文件,然后調用readUserListLocked()方法解析userlist.xml
2.4.7 解析packages文件
public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) {
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),mSdkVersion, mOnlyCore);

readLPw流程
2.4.8 Dexopt優化判定
public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) {
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
final ArraySet<String>alreadyDexOpted = new ArraySet<String>();
final String bootClassPath = System.getenv("BOOTCLASSPATH");
/BOOTCLASSPATH是Android Linux的一個環境變量,可以在adb shell下用$BOOTCLASSPATH看到。BOOTCLASSPATH即系統核心JAR包的路徑,直接加入alreadyDexOpted列表,表示不需要進行dexopt操作
/system/framework/core-libart.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/mms-common.jar:/system/framework/android.policy.jar:/system/framework/apache-xml.jar:/system/framework/mediatek-common.jar:/system/framework/mediatek-framework.jar:/system/framework/mediatek-telephony-common.jar/
final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");
//system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/wifi-service.jar/
if (bootClassPath != null) {
String[] bootClassPathElements = splitString(bootClassPath, ':');
for (String element : bootClassPathElements) {
alreadyDexOpted.add(element);
}
} else {
Slog.w(TAG, "No BOOTCLASSPATH found!");
}
if (systemServerClassPath != null) {
……//同bootClassPath處理流程
}
/該段代碼主要是把BOOTCLASSPATH和SYSTEMSERVERCLASSPATH里面的文件添加到alreadyDexOpted這個HashSet中,因為它們在zygote啟動時已經進過Dex優化了。/
final List<String> allInstructionSets = getAllInstructionSets();//arm arm64
final String[] dexCodeInstructionSets =
getDexCodeInstructionSets(allInstructionSets.toArray(new String[allInstructionSets.size()]));//arm arm64
/**
* Ensure all external libraries have had dexopt run on them.
*/
if (mSharedLibraries.size() > 0) {
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {
final String lib = libEntry.path;
try {
int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false);
if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
alreadyDexOpted.add(lib);
mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
}
}
}
File frameworkDir = new File(Environment.getRootDirectory(), "framework");
// Gross hack for now: we know this file doesn't contain any
// code, so don't dexopt it to avoid the resulting log spew.
alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk");
alreadyDexOpted.add(frameworkDir.getPath() + "/mediatek-res/mediatek-res.apk");
File customFrameworkDir = new File("/custom/framework");
alreadyDexOpted.add(customFrameworkDir.getPath() + "/framework-res.apk");
alreadyDexOpted.add(customFrameworkDir.getPath() + "/mediatek-res.apk");
alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");
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();
// Skip the file if it is not a type we want to dexopt.
if (!path.endsWith(".apk") && !path.endsWith(".jar")) {
continue;
}
//同mSharedLibraries處理流程
……</code></pre>
system/framework/framework-res.apk 、mediatek-res.apk中沒有代碼,不需要進行dexopt操作,直接存入alreadyDexOpted
如果system/framework、custom/framework、system/plugin、custom/plugin目錄下的APK和JAR包需要優化,調用dexopt流程進行優化

Dexopt流程
2.4.8 Dexopt優化判定
public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) {
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
final ArraySet<String>alreadyDexOpted = new ArraySet<String>();
final String bootClassPath = System.getenv("BOOTCLASSPATH");
/BOOTCLASSPATH是Android Linux的一個環境變量,可以在adb shell下用$BOOTCLASSPATH看到。BOOTCLASSPATH即系統核心JAR包的路徑,直接加入alreadyDexOpted列表,表示不需要進行dexopt操作
/system/framework/core-libart.jar:/system/framework/conscrypt.jar:/system/framework/okhttp.jar:/system/framework/core-junit.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/telephony-common.jar:/system/framework/voip-common.jar:/system/framework/ims-common.jar:/system/framework/mms-common.jar:/system/framework/android.policy.jar:/system/framework/apache-xml.jar:/system/framework/mediatek-common.jar:/system/framework/mediatek-framework.jar:/system/framework/mediatek-telephony-common.jar/
final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");
//system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/wifi-service.jar/
if (bootClassPath != null) {
String[] bootClassPathElements = splitString(bootClassPath, ':');
for (String element : bootClassPathElements) {
alreadyDexOpted.add(element);
}
} else {
Slog.w(TAG, "No BOOTCLASSPATH found!");
}
if (systemServerClassPath != null) {
……//同bootClassPath處理流程
}
/該段代碼主要是把BOOTCLASSPATH和SYSTEMSERVERCLASSPATH里面的文件添加到alreadyDexOpted這個HashSet中,因為它們在zygote啟動時已經進過Dex優化了。/
final List<String> allInstructionSets = getAllInstructionSets();//arm arm64
final String[] dexCodeInstructionSets =
getDexCodeInstructionSets(allInstructionSets.toArray(new String[allInstructionSets.size()]));//arm arm64
/**
* Ensure all external libraries have had dexopt run on them.
*/
if (mSharedLibraries.size() > 0) {
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {
final String lib = libEntry.path;
try {
int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false);
if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
alreadyDexOpted.add(lib);
mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
}
}
}
}
File frameworkDir = new File(Environment.getRootDirectory(), "framework");
// Gross hack for now: we know this file doesn't contain any
// code, so don't dexopt it to avoid the resulting log spew.
alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk");
alreadyDexOpted.add(frameworkDir.getPath() + "/mediatek-res/mediatek-res.apk");
File customFrameworkDir = new File("/custom/framework");
alreadyDexOpted.add(customFrameworkDir.getPath() + "/framework-res.apk");
alreadyDexOpted.add(customFrameworkDir.getPath() + "/mediatek-res.apk");
alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");
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();
// Skip the file if it is not a type we want to dexopt.
if (!path.endsWith(".apk") && !path.endsWith(".jar")) {
continue;
}
//同mSharedLibraries處理流程
……</code></pre>
system/framework/framework-res.apk 、mediatek-res.apk中沒有代碼,不需要進行dexopt操作,直接存入alreadyDexOpted
如果system/framework、custom/framework、system/plugin、custom/plugin目錄下的APK和JAR包需要優化,調用dexopt流程進行優化

Dexopt流程

dexopt優化后生成內容
如果是升級系統時,則進行如下處理,如果沒有進行系統升級,則忽略這段代碼。
public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) {
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {
final VersionInfo ver = mSettings.getInternalVersion();
mIsUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint);//根據fingerprint判斷是否是OTA升級
// when upgrading from pre-M, promote system app permissions from install to runtime
mPromoteSystemApps = mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;
// save off the names of pre-existing system packages prior to scanning; we don't
// want to automatically grant runtime permissions for new system apps
if (mPromoteSystemApps) {
Iterator<PackageSetting> pkgSettingIter = mSettings.mPackages.values().iterator();
while (pkgSettingIter.hasNext()) {
PackageSetting ps = pkgSettingIter.next();
if (isSystemApp(ps)) {
mExistingSystemPackages.add(ps.name);
}
}
}
2.4.9 調用scanDirLI方法掃描并安裝APK包
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
long currentTime, UserHandle user) throws PackageManagerException {
……
PackageSetting ps = null;
PackageSetting updatedPkg;
// reader
synchronized (mPackages) {
String oldName = mSettings.mRenamedPackages.get(pkg.packageName);
if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {
ps = mSettings.peekPackageLPr(oldName);
}
if (ps == null) {
ps = mSettings.peekPackageLPr(pkg.packageName);
}
/這里主要是處理應用升級后包名不一致的情況,當設備第一次開機時,不存在這樣的情況。其他情況下,開機會解析packages.xml,當前后有apk的包名發生變化時,該app在packages.xml中會以標簽標記。而且還會把這些包名更改了的信息計入 PMS的mSettings變量的ArrayMap 類型的變量mRenamedPackages中,key是newname./
// Check to see if this package could be hiding/updating a system package. Must look for it either under the original or real
// package name depending on our state.
updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
}
boolean updatedPkgBetter = false;
// First check if this is a system package that may involve an update
if (updatedPkg != null) {
/處理系統更新后,檢查是否對系統app有影響。即是否將系統app更新為更高的新版本了。是的話,要處理。/
}
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,
long currentTime, UserHandle user) throws PackageManagerException {
……
/A new system app appeared, but we already had a non-system one of thesame name installed earlier./
boolean shouldHideSystemApp = false;
if (updatedPkg == null && ps != null
&& (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
/Check to make sure the signatures match first. If they don't, wipe the installed application and its data. /
if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)!= PackageManager.SIGNATURE_MATCH) {
deletePackageLI(pkg.packageName, null, true, null, null, 0, null, false);
ps = null;
} else {
/If the newly-added system app is an older version than thealready installed version, hide it. It will be scanned later
and re-added like an update. /
if (pkg.mVersionCode <= ps.versionCode) {
shouldHideSystemApp = true;
} else {
/*
* The newly found system app is a newer version that the one previously installed. Simply remove the
* already-installed application and replace it with our own while keeping the application data.
*/
InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
ps.codePathString, ps.resourcePathString, getAppDexInstructionSets(ps));
synchronized (mInstallLock) {
args.cleanUpResourcesLI();
}
}
}
}
/此處主要處理系統升級后,系統多出一些system app與已安裝的非系統包重名的情況/
……
if (shouldHideSystemApp) {
synchronized (mPackages) {
mSettings.disableSystemPackageLPw(pkg.packageName);
}
}
/如果掃描的系統app需要被隱藏,那么通過mSettings.disableSystemPackageLPw方法將其信息記錄在mSettings的mDisabledSysPackages中。/</code></pre>
解析一個app有多個apk的情況
Split APK是Google為解決65536上限,以及APK安裝包越來越大等問題,在Android L中引入的機制。
Split APK可以將一個龐大的APK,按屏幕密度,ABI等形式拆分成多個獨立的APK,在應用程序更新時,不必下載整個APK,只需單獨下載某個模塊即可安裝更新。
Split APK將原來一個APK中多個模塊共享同一份資源的模型分離成多個APK使用各自的資源,并且可以繼承Base APK中的資源,多個APK有相同的data,cache目錄,多個dex文件,相同的進程,在Settings.apk中只顯示一個APK,并且使用相同的包名。

image011.jpg
想了解更多關于Split APK機制,請參考如下文章
Android動態部署一:Google原生Split APK淺析
解析app時序圖1

解析app時序圖2
2.4.10 掃描用戶安裝的app
/處理有升級包的系統應用,也就是執行過OTA升級后,第一次啟動時,需要關心的邏輯/
// Prune any system packages that no longer exist.
final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<String>();
if (!mOnlyCore) {
Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
while (psit.hasNext()) {
PackageSetting ps = psit.next();
/ If this is not a system app, it can't be a disable system app. /
if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
continue;//忽略普通應用
}
/ If the package is scanned, it's not erased. /
final PackageParser.Package scannedPkg = mPackages.get(ps.name);
if (scannedPkg != null) {
// packages.xml中<updated-package>修飾的package會被記錄到mSettings中的disable列表中去
// 這說明掃描的系統app是帶有升級包的
if (mSettings.isDisabledSystemPackageLPr(ps.name)) {
removePackageLI(ps, true);//將其從mPackages中移除
mExpectingBetter.put(ps.name, ps.codePath);// 將其添加到mExpectingBetter,后續處理
}
continue;
}
// 運行到這里說明packages.xml中記錄的app,但此時還未掃描到。
if (!mSettings.isDisabledSystemPackageLPr(ps.name)) {
// 如果這個app在packages.xml也不屬于<updated-package>
// 意味著這個應用是殘留在packages.xml中的,可能還會剩下沙箱數據,因此也要刪掉
psit.remove();
removeDataDirsLI(null, ps.name);
} else {
// 如果這個app在packages.xml屬于<updated-package>
// 將其加入possiblyDeletedUpdatedSystemApps
final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name);
if (disabledPs.codePath == null || !disabledPs.codePath.exists()) {
possiblyDeletedUpdatedSystemApps.add(ps.name);
}
}
}
}
//掃描并刪除未成功安裝的apk包(針對第三方app)
//look for any incomplete package installations
ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr();
//clean up list
for(int i = 0; i < deletePkgsList.size(); i++) {
cleanupInstallFailedPackage(deletePkgsList.get(i));
}
// 刪除臨時文件
deleteTempPackageFiles();
// 把從mSettings中沒有關聯任何應用的SharedUserSetting對象刪掉
mSettings.pruneSharedUsersLPw();
//開始掃描用戶安裝的app,即data/app和data/priv-app
scanDirLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
scanFlags | SCAN_REQUIRE_KNOWN, 0);
處理possiblyDeletedUpdatedSystemApps。它里面存儲的是在packages.xml中被標記為,但是前面又沒有掃描到的其apk文件的app。
for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {
// 在掃描了用戶app目錄之后,再次嘗試查找是否有這些app
PackageParser.Package deletedPkg = mPackages.get(deletedAppName);
mSettings.removeDisabledSystemPackageLPw(deletedAppName);
String msg;
// 依舊沒有,那么就刪除他們的數據目錄
if (deletedPkg == null) {
msg = "Updated system package " + deletedAppName
+ " no longer exists; wiping its data";
removeDataDirsLI(null, deletedAppName);
} else {
// 找到了,說明是在用戶app目錄中找到的,那么移除系統權限
msg = "Updated system app + " + deletedAppName
+ " no longer present; removing system privileges for "
+ deletedAppName;
deletedPkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
PackageSetting deletedPs = mSettings.mPackages.get(deletedAppName);
deletedPs.pkgFlags &= ~ApplicationInfo.FLAG_SYSTEM;
}
logCriticalInfo(Log.WARN, msg);
}
// 處理存放到mExpectingBetter是那些帶有升級包的系統應用
for (int i = 0; i < mExpectingBetter.size(); i++) {
final String packageName = mExpectingBetter.keyAt(i);
if (!mPackages.containsKey(packageName)) {
final File scanFile = mExpectingBetter.valueAt(i);
logCriticalInfo(Log.WARN, "Expected better " + packageName
+ " but never showed up; reverting to system");
//確保是在
///system/priv-app、system/app、vendor/app、oem/app這四個目錄中。
final int reparseFlags;
if (FileUtils.contains(privilegedAppDir, scanFile)) {
reparseFlags = PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR
| PackageParser.PARSE_IS_PRIVILEGED;
} else if (FileUtils.contains(systemAppDir, scanFile)) {
reparseFlags = PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR;
} else if (FileUtils.contains(vendorAppDir, scanFile)) {
reparseFlags = PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR;
} else if (FileUtils.contains(oemAppDir, scanFile)) {
reparseFlags = PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR;
} else {
Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
continue;
}
// 會將其加入mSettings的mPackages中
mSettings.enableSystemPackageLPw(packageName);
/// 重新掃描這些文件
try {
scanPackageLI(scanFile, reparseFlags, scanFlags, 0, null);
} catch (PackageManagerException e) {
Slog.e(TAG, "Failed to parse original system package: "
+ e.getMessage());
}
}
}
}
// 清除mExpectingBetter
mExpectingBetter.clear();</code></pre>
接下來的代碼作用是更新所有應用的動態庫路徑,如果是OTA升級導致前后SDK版本不一致,還要進行權限重新檢查,并且刪除app oat cache目錄。更新數據庫版本,調用mSettings.writeLPr更新package.xml、package.list、runtime-permission.xml等文件。
2.4.11 調用mSettings.writeLPr更新package.xml

ddfefff.jpg
最后創建mInstallerService對象:
mInstallerService = new PackageInstallerService(context, this);
PMS構造方法的執行過程就是先讀取保存在packages.xml中記錄的系統關機前記錄所有安裝的app信息,保存在mSettings中的mPackages中。
然后掃描指定的若干目錄中的app,并把信息記錄在PMS的mPackages中。最后對兩者進行對比,看是否能發現有升級的app,然后進行相關處理,最后在寫入packages.xml中。
2.5 PMS類圖補充

PMS類圖補充
2.6 PMS啟動過程中使用的核心組件Installer
PMS啟動過程中使用了Installer的多個方法。Android APK的安裝和卸載主要是由Installer和Installd完成的。
Installer是Java層提供的Java API接口,Installd則是init進程啟動的Daemon Service。Installer與Installd通過Socket通信,Installer是Socket的Client端,Installd則是Socket的Server端。通過Socket通信,將Installer的API調用轉化為Installd中具體命令,這種轉化關系通過cmds[]數組配置和映射。Installer和Installd的關系如圖所示:

Installer方法映射圖
2.7 Dexopt優化原則
PMS的啟動流程執行完后,將會進入到mPackageManagerService.performBootDexOpt()流程,此處PMS會按一定的順序對app做dexopt,規則流程如下:

Dexopt優化原則
2.8 首次開機Runtime-permission生成機制

Runtime Permission時序圖
3. APK的安裝過程
當Android存儲中的一個apk文件時,實際上是調用了Packageinstaller來完成的。安裝過程中具體界面如下:

apk安裝過程界面
Packageinstaller內部也是對PMS的調用,安裝時,會先調用PMS相關接口,解析APK文件,也就是其AndroidMainifest.xml文件,這樣就得到了該app的組件,權限,包名等信息。然后以包名為key,檢查該app是否已經安裝,安裝的話,設置replace的flag:INSTALL_REPLACE_EXISTING。如果是之前沒安裝過的,那么會彈出一個activity,顯示該app有哪些權限,底部有兩個Button:”取消”和“安裝”。點擊”安裝”,就開始安裝了。如果該app之前安裝過了,彈出的Activity中會提示:“你要安裝此應用的新版本嗎?。。。。”,最后還會羅列出新app相比已經安裝在設備上的app的權限有哪些變化,比如新添加了哪些權限等等。底部同樣會提供兩個Button:”取消”和“安裝”。點擊”安裝”,就開始安裝了。
當點擊”安裝”button之后,實際上跳轉到PackageInstaller的InstallAppProgress這個activity了。其真正安裝開始于
pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
installerPackageName, verificationParams, null);
其代碼實現:Android6.0/frameworks/base/core/java/android/app/ApplicationPackageManager.java
public void installPackageWithVerificationAndEncryption(Uri packageURI,
PackageInstallObserver observer, int flags, String installerPackageName,
VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
installCommon(packageURI, observer, flags, installerPackageName, verificationParams,
encryptionParams);
}
內部直接又調用了installCommon方法:
private void installCommon(Uri packageURI, PackageInstallObserver observer, int flags, String installerPackageName,
VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
if (!"file".equals(packageURI.getScheme())) {
throw new UnsupportedOperationException("Only file:// URIs are supported");
}
if (encryptionParams != null) {
throw new UnsupportedOperationException("ContainerEncryptionParams not supported");
}
final String originPath = packageURI.getPath();
try {
mPM.installPackage(originPath, observer.getBinder(), flags, installerPackageName, verificationParams, null);
} catch (RemoteException ignored) {
}
}
做了一系列判斷后,接著調用mPM的installPackage方法。mPM就是PMS的一個代理。也就是說這里實際會調用PMS的installPackage方法:
public void installPackage(String originPath, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, VerificationParams verificationParams, String packageAbiOverride) {
installPackageAsUser(originPath, observer, installFlags, installerPackageName,
verificationParams, packageAbiOverride, UserHandle.getCallingUserId());
}
整個安裝過程很復雜,大體上可分為三個過程:
- 權限檢查
- 復制文件
- 裝載應用
3.1 權限檢查
public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, VerificationParams verificationParams, String packageAbiOverride, int userId) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
//利用binder機制,獲取安裝發起進程的uid
final int callingUid = Binder.getCallingUid();
enforceCrossUserPermission(callingUid, userId, true, true, "installPackageAsUser");
……
先檢查權限:
void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission, boolean checkShell, String message) {
if (userId < 0) {
throw new IllegalArgumentException("Invalid userId " + userId);
}
if (checkShell) {
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
}
if (userId == UserHandle.getUserId(callingUid)) return;
if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
if (requireFullPermission) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
} else {
try {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
} catch (SecurityException se) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS, message);
}
}
}
}
這里的權限檢查主要是檢查進程是否有權限安裝。
繼續installPackageAsUser代碼:
public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
int installFlags, String installerPackageName, VerificationParams verificationParams, String packageAbiOverride, int userId) {
……..
if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {//檢查當前系統用戶是否具備安裝app的權限
try {
if (observer != null) {
observer.onPackageInstalled("", INSTALL_FAILED_USER_RESTRICTED, null, null);
}
} catch (RemoteException re) {
}
return;
}
//如果是發起端進程是shell或者root,那么添加flags:PackageManager.INSTALL_FROM_ADB
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
installFlags |= PackageManager.INSTALL_FROM_ADB;
} else {
// 從flags中去掉INSTALL_FROM_ADB和INSTALL_ALL_USERS
installFlags &= ~PackageManager.INSTALL_FROM_ADB;
installFlags &= ~PackageManager.INSTALL_ALL_USERS;
}
UserHandle user; //創建一個當前用戶的handle
if ((installFlags & PackageManager.INSTALL_ALL_USERS) != 0) {
user = UserHandle.ALL;
} else {
user = new UserHandle(userId);
}
// Android 6.0 當權限屬于運行時權限時,需要彈出框,讓用戶授權,對于system app,應該取消運行時權限彈框授權,而是直接授權。
// 那么就要在system app中加入INSTALL_GRANT_RUNTIME_PERMISSIONS
// 我們安裝第三方app,沒有INSTALL_GRANT_RUNTIME_PERMISSIONS
if ((installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
&& mContext.checkCallingOrSelfPermission(Manifest.permission
.INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
throw new SecurityException("You need the "
+ "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
+ "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
}
verificationParams.setInstallerUid(callingUid);
//發送INIT_COPY消息</code></pre>
這里主要是對當前用戶是否有權限安裝app進行檢查,以及安裝的app是僅僅為當前用戶安裝,還是給所有的用戶安裝。從以上代碼可以得出,當安裝進程是shell或者root時,flags中又包含了INSTALL_ALL_USERS時,才會給所有用戶安裝,否則大多數情況下,僅僅安裝給當前的用戶。當我們使用pm命令安裝的時候,可以選擇安裝給哪個用戶,也可以是全部用戶,就是這個原因。
final File originFile = new File(originPath);
final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile);
final Message msg = mHandler.obtainMessage(INIT_COPY);
msg.obj = new InstallParams(origin, null, observer, installFlags, installerPackageName,null, verificationParams, user, packageAbiOverride, null);
mHandler.sendMessage(msg);
構造InstallParams,注意packageAbiOverride為null,然后利用Android中的Handler機制,發送給相關的線程進行安裝。
installPackageAsUser整個執行邏輯如下圖所示所示。

installPackageAsUser流程

ccc.jpg
.jpg
3.2 復制文件
前面發送了INIT_COPY消息,接下來看如何處理:
void doHandleMessage(Message msg) {
switch (msg.what) {
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
int idx = mPendingInstalls.size();
if (!mBound) {
//將綁定DefaultContainerService服務
if (!connectToService()) {
Slog.e(TAG, "Failed to bind to media container service");
params.serviceError();
return;
} else {
mPendingInstalls.add(idx, params);
}
}
……
break;
}
INIT_COPY消息的處理中將綁定DefaultContainerService,因為這是一個異步的過程,要等待的綁定的結果通過onServiceConnected()返回,所以這里就將安裝的參數信息放到了mPendingInstalls列表中,如果這個Service之前就綁定好了,現在就不要再次綁定了,安裝信息同樣要放到mPendingInstalls中。如果有多個安裝請求同時到達,就可以通過mPendingInstalls列表對它們進行排隊。如果列表中只有一項,說明沒有更多的安裝請求,因此這種情況下,需要立即發出MCS_BOUND消息,進入下一步的處理。
private boolean connectToService() {
//("com.android.defcontainer", "com.android.defcontainer.DefaultContainerService")
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
if (mContext.bindServiceAsUser(service, mDefContainerConn, Context.BIND_AUTO_CREATE, UserHandle.OWNER)) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mBound = true;
//此處為MTK為解決adb install cannot response after run MTK-MTBF for a period of time而添加。當綁定未成功時,再重新綁定3次
final long DEFCONTAINER_CHECK = 1 * 1000;
final Message msg = mHandler.obtainMessage(MCS_CHECK);
mHandler.sendMessageDelayed(msg, DEFCONTAINER_CHECK);
return true;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return false;
}
final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection();
class DefaultContainerConnection implements ServiceConnection {
public void onServiceConnected(ComponentName name, IBinder service) {
mServiceConnected = true;
mServiceCheck = 0;
//此處返回的service為DefaultContainerService中定義的IMediaContainerService.Stub,使用了AIDL進行的進程間通信。
//其定義于:frameworks/base/core/java/com/android/internal/app/IMediaContainerService.aidl
IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
}
……
可以看到當綁定成功后在onServiceConnected中將一個IBinder轉換成了一個IMediaContainerService.這個就是在onServiceConnected回調函數中根據參數傳進來的IMediaContainerService.Stub的對象引用創建的一個遠程代理對象。以后PMS務通過該代理對象訪問DefaultContainerService服務。
接下來分析MCS_BOUND消息:
void doHandleMessage(Message msg) {
switch (msg.what) {
……
case MCS_BOUND: {
if (msg.obj != null) {
mContainerService = (IMediaContainerService) msg.obj;
}
if (mContainerService == null) {
if (!mBound) {
for (HandlerParams params : mPendingInstalls) {
params.serviceError();
}
mPendingInstalls.clear();
} else {
Slog.w(TAG, "Waiting to connect to media container service");
}
} else if (mPendingInstalls.size() > 0) {
HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
if (params.startCopy()) {
if (mPendingInstalls.size() > 0) {
mPendingInstalls.remove(0);
}
if (mPendingInstalls.size() == 0) {
if (mBound) {
removeMessages(MCS_UNBIND);
Message ubmsg = obtainMessage(MCS_UNBIND);
sendMessageDelayed(ubmsg, 10000);
}
} else {
mHandler.sendEmptyMessage(MCS_BOUND);
}
}
}
break;
}
MCS_BOUND消息的處理過程就是調用InstallParams類的startCopy()方法來執行拷貝操作。只要mPendingInstalls中還有安裝信息,就會重復發送MCS_BOUND消息,直到所有的應用都安裝完畢,然后在發送一個延時10秒的MCS_UNBIND消息。
case MCS_UNBIND: {
if (mPendingInstalls.size() == 0 && mPendingVerification.size() == 0) {
if (mBound) {
disconnectService();
}
} else if (mPendingInstalls.size() > 0) {
mHandler.sendEmptyMessage(MCS_BOUND);
}
break;
}
MCS_UNBIND消息的處理就簡單了,當mPendingInstalls中沒有安裝信息的時候,就調用disconnectService斷開與DefaultContainerService的連接。如果發現還有安裝信息,則繼續發送MCS_BOUND消息。
接下來分析真正的拷貝方法:startCopy
final boolean startCopy() {
boolean res;
try {
if (++mRetries > MAX_RETRIES) {
mHandler.sendEmptyMessage(MCS_GIVE_UP);
handleServiceError();
return false;
} else {
handleStartCopy();
res = true;
}
} catch (RemoteException e) {
mHandler.sendEmptyMessage(MCS_RECONNECT);
res = false;
}
handleReturnCode();
return res;
}
startCopy()方法通過調用其子類InstallParams的handleStartCopy()來完成拷貝操作。考慮到安裝過程的不確定性,startCopy主要工作是進行錯誤處理,當捕獲到handleStartCopy跑出的異常時,startCopy將發送MCS_RECONNECT.在MCS_RECONNECT消息的處理中,將會重新綁定DefaultContainerService,如果綁定成功,那么安裝過程將會重新開始。startCopy也將會再次被調用,重試的次數記錄在mRetries中,當累計重試超過4次時,安裝將失。如果安裝失敗,那么startCopy將會調用handleReturnCode()來繼續處理。
handleStartCopy操作流程如下:

InstallParms.handleStartCopy流程

InstallParams和InstallArgs關系
createInstallArgs傳入的params,在本例中就是InstallParams,在它的handleStartCopy()中已經確定了安裝在哪里。
private InstallArgs createInstallArgs(InstallParams params) {
if (params.move != null) {
return new MoveInstallArgs(params);//移動app
} else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
return new AsecInstallArgs(params);//安裝在SD卡
} else {
return new FileInstallArgs(params);//安裝在內部存儲
}
}
此處我們是安裝在內部存儲的,所以創建的就是FileInstallArgs了,那么調用的copyApk,自然就是FileInstallArgs的了。FileInstallArgs. copyApk主要是對apk內容進行拷貝,其數據流如下:

數據流向
3.3 裝載應用
主要完成將dex轉換為ART虛擬機的oat格式的執行文件,并為應用創建數據沙箱目錄,最后把應用的信息裝載進PMS的數據結構中去。
在前面的處理MCS_BOUND時調用的HandlerParams的startCopy方法中當復制完文件之后,會調用InstallParams的handleReturnCode方法:
void handleReturnCode() {
if (mArgs != null) {
processPendingInstall(mArgs, mRet);
}
}
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
mHandler.post(new Runnable() {
public void run() {
mHandler.removeCallbacks(this);
PackageInstalledInfo res = new PackageInstalledInfo();
res.returnCode = currentStatus;
res.uid = -1;
res.pkg = null;
res.removedInfo = new PackageRemovedInfo();
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
args.doPreInstall(res.returnCode);
synchronized (mInstallLock) {
installPackageLI(args, res);
}
args.doPostInstall(res.returnCode, res.uid);
}
//省略備份代碼
if (!doRestore) {
Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
mHandler.sendMessage(msg);
}
}
});
}
processPendingInstall()方法中post了一個消息,這樣安裝過程將以異步的方式繼續執行。在post消息中,首先是調用installPackageLI()來裝載應用,接下來的一大段代碼是在執行設備備份操作,備份是通過BackupManagerService來完成的,這里就不分析了。備份完成之后,通過發送POST_INSTALL消息繼續處理。
接著看installPackageLI()方法
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
final int installFlags = args.installFlags;//得到installFlags,里面記錄了app需要安裝到哪里
final String installerPackageName = args.installerPackageName; // 安裝程序的包名
final String volumeUuid = args.volumeUuid; // 與sd卡安裝有關,一般為null
final File tmpPackageFile = new File(args.getCodePath());// 前面已經把apk拷貝到了臨時階段性文件夾/data/app/vmdl<安裝回話id>.tmp/這個目錄了
final boolean forwardLocked = ((installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0);
final boolean onExternal = (((installFlags & PackageManager.INSTALL_EXTERNAL) != 0)
|| (args.volumeUuid != null)); // 是否安裝到外部存儲
boolean replace = false; // 初始化替換flag
int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
if (args.move != null) {
// moving a complete application; perfom an initial scan on the new install location
scanFlags |= SCAN_INITIAL;
}
res.returnCode = PackageManager.INSTALL_SUCCEEDED;
final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY| (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0);
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setDisplayMetrics(mMetrics);
final PackageParser.Package pkg;
try {
//解析APK,也就是解析AndroidMainifest.xml文件,將結果記錄在PackageParser.Package中
pkg = pp.parsePackage(tmpPackageFile, parseFlags);
} catch (PackageParserException e) {
res.setError("Failed parse during installPackageLI", e);
return;
}
// Mark that we have an install time CPU ABI override.
pkg.cpuAbiOverride = args.abiOverride;
……
try {//收集apk的簽名信息
pp.collectCertificates(pkg, parseFlags);
pp.collectManifestDigest(pkg);
} catch (PackageParserException e) {
res.setError("Failed collect during installPackageLI", e);
return;
}
//如果安裝程序此前傳入了一個清單文件,那么將解析到的清單文件與傳入的進行對比。安裝器的確傳入了一個清單,PackageInstallerActivity中也解析了apk,那時記錄了這個清單,并一并傳入到這里了。這里又做了一步判斷,判斷兩者是同一個apk.
if (args.manifestDigest != null) {
if (!args.manifestDigest.equals(pkg.manifestDigest)) {
res.setError(INSTALL_FAILED_PACKAGE_CHANGED, "Manifest digest changed");
return;
}
} else if (DEBUG_INSTALL) {
final String parsedManifest = pkg.manifestDigest == null
? "null" : pkg.manifestDigest.toString();
Slog.d(TAG, "manifestDigest was not present, but parser got: " + parsedManifest);
}
synchronized (mPackages) {
// Check if installing already existing package
if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
……//處理安裝已經存在的應用,將replace置為true
}
// 如果ps不為null,同樣說明,已經存在一個同包名的程序被安裝,也就是還是處理覆蓋安裝的情況
// 這里主要是驗證包名的簽名,不一致的話,是不能覆蓋安裝的,另外版本號也不能比安裝的低,否則也不能安裝
PackageSetting ps = mSettings.mPackages.get(pkgName);
if (ps != null) {
if (shouldCheckUpgradeKeySetLP(ps, scanFlags)) {
if (!checkUpgradeKeySetLP(ps, pkg)) {
res.setError(INSTALL_FAILED_UPDATE_INCOMPATIBLE, "Package " + pkg.packageName + " upgrade keys do not match the "+ "previously installed version");
return;
}
} else {
try {
verifySignaturesLP(ps, pkg);
} catch (PackageManagerException e) {
res.setError(e.error, e.getMessage());
return;
}
}
……
}
//檢查apk中定義的所有的權限是否已經被其他應用定義了,如果重定義的是系統應用定義的權限,那么忽略本app定義的這個權限。如果重定義的是非系統應用的權限,那么本次安裝就以失敗返回
int N = pkg.permissions.size();
for (int i = N-1; i >= 0; i--) {
}
}
if (args.move != null) {
// 移動app走該分支
} else if (!forwardLocked && !pkg.applicationInfo.isExternalAsec()) {
scanFlags |= SCAN_NO_DEX;
try {
derivePackageAbi(pkg, new File(pkg.codePath), args.abiOverride, true);//設置apk的so庫路徑,以及主次abi的值
} catch (PackageManagerException pme) {
return;
}
//實際為dex2oat操作,用來將apk中的dex文件轉換為oat文件。需要注意的是:
//Android M中,/data/dalvik_cache/只存放系統內置應用的oat文件,
//用戶安裝的app的oat文件在,最終會在/data/app/包名/oat/<isa>/
int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instruction sets */, false /* forceDex */,
false /* defer */, false /* inclDependencies */);
if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
res.setError(INSTALL_FAILED_DEXOPT, "Dexopt failed for " + pkg.codePath);
return;
}
}
//重命名,將/data/app/vmdl<安裝會話id>.tmp重命名為/data/app/包名-suffix suffix為1,2……
if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
return;
}
startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
if (replace) {//覆蓋安裝
replacePackageLI(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
installerPackageName, volumeUuid, res);
} else {//首次安裝
installNewPackageLI(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
args.user, installerPackageName, volumeUuid, res);
}
執行完installPackageLI之后,返回processPendingInstall方法中,繼續發送POST_INSTALL消息繼續處理。代碼如下:
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, extras, null, null, firstUsers);
final boolean update = res.removedInfo.removedPackage != null;
if (update) {
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, extras, null, null, updateUsers);
if (update) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, extras, null, null, updateUsers);
sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, packageName, null, updateUsers);
}
該消息的處理主要就是在發送廣播,應用安裝完成之后要通知系統中的其他應用開始處理,比如在launcher需要增加app的圖標等。等發完廣播,安裝也就結束了,最后通過最初安裝是傳入的安裝觀察者observer返回最初的調用者。
4. PMS問題調試方法
4.1 如何打開PMS的log開關
方法一
- adb shell dumpsys package log a on
- 只對當次開機有效
方法二
- PackageManagerService.java中修改DEBUG_XXX為true
- 長久有效
4.2 pm指令的使用
利用adb shell命令,進入Android設備的終端,pm工具在/system/bin中,所以可以直接使用:pm <cmd>
包名信息查詢
pm list packages [options] [FILTER]
打印所有的已經安裝的應用的包名,如果設置了文件過濾則值顯示包含過濾文字的內容.
參數:
-f 顯示每個包的文件位置
-d 使用過濾器,只顯示禁用的應用的包名
-e 使用過濾器,只顯示可用的應用的包名
-s 使用過濾器,只顯示系統應用的包名
-3 使用過濾器,只顯示第三方應用的包名
-i 查看應用的安裝者
權限信息查詢
打印所有已知的權限組
pm list permission-groups
打印權限:
pm list permissions [options] [GROUP]
參數:
g 按組進行列出權限
-f 打印所有信息
-s 簡短的摘要
-d 只有危險的權限列表
-u 只有權限的用戶將看到列表用戶自定義權限
Android 6.0之后,允許授權和取消權限:
pm grant <package_name> <permission>
pm revoke <package_name> <permission>
授權和取消是針對APK中申請的權限的來說的。即APK中沒有申請的權限,是沒辦法通過此命令添加的。
包路徑
pm path package_name
系統硬件特性
pm list features
設備依賴的java庫
pm list libraries
dump包信息
pm dump package_name
安裝與卸載apk
安裝apk
pm install [-lrtsfd] [-i PACKAGE] [PATH]
adb install實際上就是對pm install的封裝調用。
參數:
-l 鎖定應用程序
-r 重新安裝應用,且保留應用數據
-t 允許測試apk被安裝
-i INSTALLER_PACKAGE_NAME 指定安裝包的包名
-s 安裝到sd卡
-f 安裝到系統內置存儲中(默認安裝位置)
-d 允許降級安裝(同一應用低級換高級)
-g 授予應用程序清單中列出的所有權限(只有6.0系統可用)
卸載apk:
pm uninstall [options] <PACKAGE>
參數:
-k 卸載應用且保留數據與緩存(如果不加-k則全部刪除)
清除應用數據
pm clear package_name
禁用和啟用系統應用
pm enable <PACKAGE_OR_COMPONENT> 使package或component可用
pm disenable <PACKAGE_OR_COMPONENT> 使package或component不可用(直接就找不到應用了)
pm disenable-user [options] <PACKAGE_OR_COMPONENT> 使package或component不可用(會顯示已停用)
隱藏與恢復應用
pm hide PACKAGE_OR_COMPONENT
pm unhide PACKAGE_OR_COMPONENT
被隱藏應用在應用管理中變得不可見,桌面圖標也會消失
設置和查看應用的安裝位置
pm set-install-location package_name
pm get-install-location package_name
參數:
0:自動-讓系統決定最好的位置
1:內部存儲-安裝在內部設備上的存儲
2:外部存儲-安裝在外部媒體
查看當前系統user信息
pm list users
可以指的apk安裝在某個user下,這樣只有切換到該user時,才能顯示和使用該apk。
來自:http://www.jianshu.com/p/6769d1a9f4ad