##1、背景##
前一篇文章中我们分析了PKMS构造器的第一阶段,其主要的工作就是扫描XML文件。今天我们就来分析PKMS构造器的第二阶段,其主要工作是扫描系统中的APK,由于需要逐个扫描APK文件,所以系统安装的应用越多PKMS工作量就越大,从而导致系统启动就越慢。
##2、源码分析##
我们紧接着上一篇文章的最后开始分析。
1、系统库的dex优化
final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING;
//用于保存已做过Dex优化的字符串
final ArraySet<String> alreadyDexOpted = new ArraySet<String>();
final String bootClassPath = System.getenv("BOOTCLASSPATH");
final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");
if (bootClassPath != null) {
String[] bootClassPathElements = splitString(bootClassPath, ':');
for (String element : bootClassPathElements) {
alreadyDexOpted.add(element);
}
}
if (systemServerClassPath != null) {
String[] systemServerClassPathElements = splitString(systemServerClassPath, ':');
for (String element : systemServerClassPathElements) {
alreadyDexOpted.add(element);
}
}
final List<String> allInstructionSets = getAllInstructionSets();
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(allInstructionSets.toArray(new String[allInstructionSets.size()]));
//共享库mSharedLibraries进行Dex优化
if (mSharedLibraries.size() > 0) {
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {
final String lib = libEntry.path;
if (lib == null) {
continue;
}
try {
//判断共享库是否需要Dex优化
byte dexoptRequired = DexFile.isDexOptNeededInternal(lib, null,dexCodeInstructionSet,false);
if (dexoptRequired != DexFile.UP_TO_DATE) {
alreadyDexOpted.add(lib);
//表示需要优化
if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
} else {
mInstaller.patchoat(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet);
}
}
}
}
}
}
//system/framework
File frameworkDir = new File(Environment.getRootDirectory(), "framework");
//system/framework/framework-res.apk
//framework-res.apk定义了系统常用的资源和重要的Activity,比如按Power键弹出的对话框
alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk");
//system/framework/core-libart.jar
alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");
//对system/framework目录下的文件进行扫描
String[] frameworkFiles = frameworkDir.list();
if (frameworkFiles != null) {
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 we already did it.
if (alreadyDexOpted.contains(path)) {
continue;
}
// Skip the file if it is not a type we want to dexopt.
if (!path.endsWith(".apk") && !path.endsWith(".jar")) {
continue;
}
try {
//和之前一样判断是否需要Dex优化
byte dexoptRequired = DexFile.isDexOptNeededInternal(path, null,dexCodeInstructionSet,false);
if (dexoptRequired == DexFile.DEXOPT_NEEDED) {
mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
} else if (dexoptRequired == DexFile.PATCHOAT_NEEDED) {
mInstaller.patchoat(path, Process.SYSTEM_UID, true, dexCodeInstructionSet);
}
}
}
}
}
以上代码主要对系统库(BOOTCLASSPATH、SYSTEMSERVERCLASSPATH、以及platform.xml共享库和system/framework下的jar包和Apk)进行一次扫描,将需要优化的一定进行优化。
2、扫描系统package
我们进入PKMS的重点核心部分就是扫描系统package,我们先看一下有哪些Package会被扫描,经过统计后有以下路径的Package:
系统package:system/framework,system/priv-app,system/app,vendor/overlay,vendor/app,productinfo/apklist.txt
非系统package:system/preloadapp,system/vital-app,data/app-private,data/app
在代码中区分系统package和非系统package主要依靠mOnlyCore字段,而解析这些目录apk的方法主要就两个scanDirLI()和scanPartDir()方法,其中scanPartDir()只使用在system/app时,应该是该目录下包含的apk比较多,所以Android在扫描时使用多线程扫描,这两个方法调用的代码逻辑都是相同的,我们这里就以scanDirLI为例进行讲解。
我们进入scanDirLI()方法。
private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
//获取文件夹文件列表
final File[] files = dir.listFiles();
if (ArrayUtils.isEmpty(files)) {
return;
}
//循环遍历这些文件
for (File file : files) {
final boolean isPackage = (isApkFile(file) || file.isDirectory())
&& !PackageInstallerService.isStageName(file.getName());
if (!isPackage) {
continue;
}
try {
//调用scanPackageLI方法用于扫描单个apk文件或目录
scanPackageLI(file, parseFlags |PackageParser.PARSE_MUST_BE_APK,
scanFlags, currentTime, null);
} catch (PackageManagerException e) {
// Delete invalid userdata apps
//删除无效的用户数据,注意此处只有非系统Package才会删除
if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
if (file.isDirectory()) {
FileUtils.deleteContents(file);
}
file.delete();
}
}
}
}
从以上代码中可以看出会循环遍历某个目录下所有的文件和目录,然后调用scanPackageLI方法进行处理,在这里你会发现两个重载的scanPackageLI方法, 这两个方法我们在后面都会遇到,首先我们进入第一个参数是File的scanPackageLI方法查看其源码。
/*
* Scan a package and return the newly parsed package.
* Returns null in case of errors and the error code is stored in mLastScanError
*/
//返回值为PackageParser.Package对象,该对象表示为一个Apk文件对应的数据结构
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,long currentTime, UserHandle user) throws PackageManagerException {
//创建一个PackageParser对象,
PackageParser pp = new PackageParser();
pp.setSeparateProcesses(mSeparateProcesses);
pp.setOnlyCoreApps(mOnlyCore);
//传入之前创建的屏幕相关的mMetrics对象
pp.setDisplayMetrics(mMetrics);
final PackageParser.Package pkg;
try {
//调用PackageParser对象的parsePackage方法,并将对应的目录或文件作为参数传入后返回PackageParser.Package对象
pkg = pp.parsePackage(scanFile, parseFlags);
} catch (PackageParserException e) {
throw PackageManagerException.from(e);
}
//......
//最后在调用以PackageParser.Package为参数的scanPackageLI方法
PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags| SCAN_UPDATE_SIGNATURE, currentTime, user);
}
scanPackageLI方法首先会调用parsePackage方法对apk文件进行解析,即将apk从物理文件到数据结构的转换,然后获得apk对应的数据结构PackageParser.Package对象,并最终调用参数为PackageParser.Package的scanPackageLI方法,在这里我们先分析PackageParser对象的parsePackage方法。
public Package parsePackage(File packageFile, int flags) throws PackageParserException {
//如果当前传入的是目录则调用parseClusterPackage方法,而传入的是apk文件则调用parseMonolithicPackage方法
if (packageFile.isDirectory()) {
return parseClusterPackage(packageFile, flags);
} else {
return parseMonolithicPackage(packageFile, flags);
}
}
其实这两个方法的处理的逻辑都差不多,我们先看一下parseClusterPackage方法。
private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
//首先根据路径获得PackageLite对象
final PackageLite lite = parseClusterPackageLite(packageDir, 0);
final File baseApk = new File(lite.baseCodePath);
//然后调用parseBaseApk方法
final Package pkg = parseBaseApk(baseApk, assets, flags);
if (pkg == null) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
"Failed to parse base APK: " + baseApk);
}
if (!ArrayUtils.isEmpty(lite.splitNames)) {
final int num = lite.splitNames.length;
pkg.splitNames = lite.splitNames;
pkg.splitCodePaths = lite.splitCodePaths;
pkg.splitRevisionCodes = lite.splitRevisionCodes;
pkg.splitFlags = new int[num];
for (int i = 0; i < num; i++) {
//循环调用parseSplitApk方法
parseSplitApk(pkg, i, assets, flags);
}
}
pkg.codePath = packageDir.getAbsolutePath();
return pkg;
}
我们在进入parseBaseApk方法来分析一下其解析APK的什么文件。
/** File name in an APK for the Android manifest. */
private static final String ANDROID_MANIFEST_FILENAME = "AndroidManifest.xml";
private Package parseBaseApk(File apkFile, AssetManager assets, int flags)throws PackageParserException {
//......
final String apkPath = apkFile.getAbsolutePath();
XmlResourceParser parser = null;
//这里通过AssetManager的openXmlResourceParser方法打开AndroidManifest.xml文件并返回XmlResourceParser解析器
parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
final Package pkg = parseBaseApk(res, parser, flags, outError);
//最终返回一个Package对象
return pkg;
}
看到这里相信大家应该都明白了吧,其实PKMS扫描APK就是扫描其AndroidManifest.xml文件,这里的parseSplitApk方法也是同样的逻辑扫描AndroidManifest.xml文件,这是二者扫描的标签不同。
当扫描的是Apk文件时其调用了parseMonolithicPackage方法,这个方法的逻辑和之前分析的差不多,在这里我们就不深入分析了,感兴趣的同学可以自己去研究一下。
当PackageParser的parsePackage方法分析完成后将返回一个PackageParser.Package对象,我们来看一下Package类的属性值。
public class PackageParser {
public final static class Package {
public String packageName;
/** Names of any split APKs, ordered by parsed splitName */
public String[] splitNames;
public String codePath;
/** Path of base APK */
public String baseCodePath;
/** Paths of any split APKs, ordered by parsed splitName */
public String[] splitCodePaths;
//权限标签存放位置
public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
//四大组件标签存放位置
public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
public final ArrayList<Service> services = new ArrayList<Service>(0);
public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);
public final ArrayList<String> requestedPermissions = new ArrayList<String>();
public final ArrayList<Boolean> requestedPermissionsRequired = new ArrayList<Boolean>();
}
//......
}
当Apk文件的AndroidManifest.xml文件被解析完成后,Package 类的各属性值也将被填充完毕,以供后续使用。
所有的Apk文件扫描完成后将调用参数为Package的scanPackageLI方法,我们进入该方法。
private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags,int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
boolean success = false;
try {
//这里直接将操作转发给scanPackageDirtyLI方法
final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags,currentTime, user);
success = true;
return res;
} finally {
if (!success && (scanFlags & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
removeDataDirsLI(pkg.packageName);
}
}
}
我们这里直接进入scanPackageDirtyLI方法,这个方法有一千多行,在这里就不一一分析,我们主要看几个重点代码段。
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
//此处进行包名判断,当包名是android的Package将进行单独处理
if (pkg.packageName.equals("android")) {
synchronized (mPackages) {
if (mAndroidApplication != null) {
throw new PackageManagerException(INSTALL_FAILED_DUPLICATE_PACKAGE,
"Core android package being redefined. Skipping.");
}
// Set up information for our fall-back user intent resolution activity.
mPlatformPackage = pkg;
pkg.mVersionCode = mSdkVersion;
mAndroidApplication = pkg.applicationInfo;
if (!mResolverReplaced) {
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.activityInfo = mResolveActivity;
mResolveInfo.priority = 0;
mResolveInfo.preferredOrder = 0;
mResolveInfo.match = 0;
mResolveComponentName = new ComponentName(
mAndroidApplication.packageName, mResolveActivity.name);
}
}
}
}
scanPackageDirtyLI方法刚开始我们就遇到一个重要的条件判断,即单独处理packageName为android的Package。其实此包就是framework-res.apk,此apk包含以下常用的Activity。
ChooserActivity:当多个Activity符合某个Intent时,系统会弹出此Activity由用户选择合适的应用进行处理。
RingtonePickerActivity:铃声选择的Activity。
ShutdownActivity:关键前弹出的选择对话框。
在此处保存这些信息的主要原因是Android认为这些信息比较常用,以便使用时提高效率。
我们继续来分析scanPackageDirtyLI方法。
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
//解析Package中的providers并保存到PKMS的mProviders变量中
int N = pkg.providers.size();
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);
//......
}
//解析Package中的services并保存到PKMS的mServices变量中
N = pkg.services.size();
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);
//......
}
//解析Package中的receivers并保存到PKMS的mReceivers变量中
N = pkg.receivers.size();
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");
//......
}
//解析Package中的activities并保存到PKMS的mActivities变量中
N = pkg.activities.size();
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");
//......
}
N = pkg.permissionGroups.size();
for (i=0; i<N; i++) {
PackageParser.PermissionGroup pg = pkg.permissionGroups.get(i);
PackageParser.PermissionGroup cur = mPermissionGroups.get(pg.info.name);
mPermissionGroups.put(pg.info.name, pg);
//......
}
N = pkg.permissions.size();
for (i=0; i<N; i++) {
//......
}
N = pkg.instrumentation.size();
for (i=0; i<N; i++) {
//......
}
}
以上代码主要就是将Package中的数据整理到PKMS的成员变量中,通过以上操作就将Package的私有化数据变成了PKMS的公有化数据,从而提供给系统使用。
其实scanPackageDirtyLI方法主要进行了以下操作:
1、建立ResolverActivity的内存对象,就是当发出一个Intent,如果有多个Activity响应该Intent的Activity,会弹出一个对话框让用户选择,这个对话框就是ResolverActivity。
2、处理带有original-package标签的应用。
3、校验签名。
4、检查ContentProvider名称。
5、确定应用将来的进程名称。
6、创建应用的数据目录。
7、安装动态库。
8、重新优化dex。
9、提取应用中的组件信息,把应用的Activity、Service、Provider、Receiver、Permission、PermissionGroup信息都提取出来加入到PKMS的成员变量中。
final ActivityIntentResolver mActivities;
final ActivityIntentResolver mReceivers;
final ActivityIntentResolver mServices;
final ActivityIntentResolver mProviders;
系统中所有的组件信息都会加入到这4个变量中。Android系统运行时对Intent的解析就是通过这些变量查找的。
3、文件扫描后总结
scanDirLI()方法主要就是对指定目录下的Apk文件扫描,以下是其调用方法的UML图。
以上流程图只是扫描App的大致方法流程(具体请查看源码),Apk文件扫描完成后,PKMS提供了几个重要的数据结构来保存这些扫描信息。
final ArrayMap<String, PackageParser.Package> mPackages =
new ArrayMap<String, PackageParser.Package>();
final ArrayMap<String, SharedLibraryEntry> mSharedLibraries =
new ArrayMap<String, SharedLibraryEntry>();
final ActivityIntentResolver mActivities = new ActivityIntentResolver();
final ActivityIntentResolver mReceivers =new ActivityIntentResolver();
final ServiceIntentResolver mServices = new ServiceIntentResolver();
final ProviderIntentResolver mProviders = new ProviderIntentResolver();
final ArrayMap<ComponentName, PackageParser.Instrumentation> mInstrumentation =
new ArrayMap<ComponentName, PackageParser.Instrumentation>();
final ArrayMap<String, PackageParser.PermissionGroup> mPermissionGroups =
new ArrayMap<String, PackageParser.PermissionGroup>();
//......
由此可见PKMS的第二阶段的任务还是相当繁重的,需要创建很多对象同时用对应的数据结构保存数据,接下来我们就简单的分析一下其第三阶段的扫尾工作。
##3、PKMS的第三阶段##
在scanPackageDirtyLI方法中我们来看一下mSettings.getPackageLPw方法。
pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
destResourceFile, pkg.applicationInfo.nativeLibraryRootDir,
pkg.applicationInfo.primaryCpuAbi,
pkg.applicationInfo.secondaryCpuAbi,
pkg.applicationInfo.flags, user, false);
我们进入Settings类的getPackageLPw()方法。
private PackageSetting getPackageLPw(String name, PackageSetting origPackage,.......)
PackageSetting p = mPackages.get(name);
//......
if (add) {
// Finish adding new package by adding it and updating shared
// user preferences
addPackageSettingLPw(p, name, sharedUser);
}
private void addPackageSettingLPw(PackageSetting p, String name,
SharedUserSetting sharedUser) {
mPackages.put(name, p);
//......
}
可以看出当整个文件都扫描完成后,mSettings的mPackages也会赋值。
接下来我们回到PKMS的构造函数继续往下分析。
//对共享库进行操作
updateAllSharedLibrariesLPw();
//设置应用权限
updatePermissionsLPw(null, null, UPDATE_PERMISSIONS_ALL
| (regrantPermissions ? (UPDATE_PERMISSIONS_REPLACE_PKG|UPDATE_PERMISSIONS_REPLACE_ALL)
: 0));
// can downgrade to reader
//将数据写入到packages.xml文件
mSettings.writeLPr();
mAsyncScanThread.start();
mInstallerService = new PackageInstallerService(context, this, mAppInstallDir);
Runtime.getRuntime().gc();
updatePermissionsLPw这个方法用于循环遍历mSettings.mPackages集合来将所有的应用赋予权限,而writeLPr方法则是将一些应用信息保存到packages.xml文件中。
好啦,到此PKMS中比较繁重的工作任务我们也就分析完毕啦,其实主要就是对系统目录下的App进行扫描保存,后面我们将看到PKMS的另外一个任务—安装应用P