##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图。

两个ResourceManager 都是备用状态_PKMS源码

以上流程图只是扫描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