💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
PKMS构造函数第二阶段的工作就是扫描系统中的APK了。由于需要逐个扫描文件,因此手机上装的程序越多,PKMS的工作量越大,系统启动速度也就越慢。 1. 系统库的dex优化 接着对PKMS构造函数进行分析,代码如下: **PackageManagerService.java** ~~~ ...... mRestoredSettings= mSettings.readLPw();//接第一段的结尾 longstartTime = SystemClock.uptimeMillis();//记录扫描开始的时间 //定义扫描参数 intscanMode = SCAN_MONITOR | SCAN_NO_PATHS | SCAN_DEFER_DEX; if(mNoDexOpt) { scanMode|= SCAN_NO_DEX; //在控制扫描过程中是否对APK文件进行dex优化 } finalHashSet<String> libFiles = new HashSet<String>(); // mFrameworkDir指向/system/frameworks目录 mFrameworkDir = newFile(Environment.getRootDirectory(),"framework"); // mDalvikCacheDir指向/data/dalvik-cache目录 mDalvikCacheDir= new File(dataDir, "dalvik-cache"); booleandidDexOpt = false; /* 获取Java启动类库的路径,在init.rc文件中通过BOOTCLASSPATH环境变量输出,该值如下 /system/framework/core.jar:/system/frameworks/core-junit.jar: /system/frameworks/bouncycastle.jar:/system/frameworks/ext.jar: /system/frameworks/framework.jar:/system/frameworks/android.policy.jar: /system/frameworks/services.jar:/system/frameworks/apache-xml.jar: /system/frameworks/filterfw.jar 该变量指明了framework所有核心库及文件位置 */ StringbootClassPath = System.getProperty("java.boot.class.path"); if(bootClassPath != null) { String[] paths = splitString(bootClassPath, ':'); for(int i=0; i<paths.length; i++) { try{ //判断该jar包是否需要重新做dex优化 if (dalvik.system.DexFile.isDexOptNeeded(paths[i])) { /* 将该jar包文件路径保存到libFiles中,然后通过mInstall对象发送 命令给installd,让其对该jar包进行dex优化 */ libFiles.add(paths[i]); mInstaller.dexopt(paths[i], Process.SYSTEM_UID, true); didDexOpt = true; } } ...... } } ...... /* 读者还记得mSharedLibrarires的作用吗?它保存的是platform.xml中声明的系统库的信息。 这里也要判断系统库是否需要做dex优化。处理方式同上 */ if (mSharedLibraries.size() > 0) { ...... } //将framework-res.apk添加到libFiles中。framework-res.apk定义了系统常用的 //资源,还有几个重要的Activity,如长按Power键后弹出的选择框 libFiles.add(mFrameworkDir.getPath() + "/framework-res.apk"); //列举/system/frameworks目录中的文件 String[] frameworkFiles = mFrameworkDir.list(); if(frameworkFiles != null) { ......//判断该目录下的apk或jar文件是否需要做dex优化。处理方式同上 } /* 上面代码对系统库(BOOTCLASSPATH指定,或 platform.xml定义,或 /system/frameworks目录下的jar包与apk文件)进行一次仔细检查,该优化的一定要优化。 如果发现期间对任何一个文件进行了优化,则设置didDexOpt为true */ if (didDexOpt) { String[] files = mDalvikCacheDir.list(); if (files != null) { /* 如果前面对任意一个系统库重新做过dex优化,就需要删除cache文件。原因和 dalvik虚拟机的运行机制有关。本书暂不探讨dex及cache文件的作用。 从删除cache文件这个操作来看,这些cache文件应该使用了dex优化后的系统库 所以当系统库重新做dex优化后,就需要删除旧的cache文件。可简单理解为缓存失效 */ for (int i=0; i<files.length; i++) { String fn = files[i]; if(fn.startsWith("data@app@") ||fn.startsWith("data@app-private@")) { (newFile(mDalvikCacheDir, fn)).delete(); ...... } ~~~ 2. 扫描系统Package 清空cache文件后,PKMS终于进入重点段了。接下来看PKMS第二阶段工作的核心内容,即扫描Package,相关代码如下: **PackageManagerService.java** ~~~ //创建文件夹监控对象,监视/system/frameworks目录。利用了Linux平台的inotify机制 mFrameworkInstallObserver = new AppDirObserver( mFrameworkDir.getPath(),OBSERVER_EVENTS, true); mFrameworkInstallObserver.startWatching(); /* 调用scanDirLI函数扫描/system/frameworks目录,这个函数很重要,稍后会再分析。 注意,在第三个参数中设置了SCAN_NO_DEX标志,因为该目录下的package在前面的流程 中已经过判断并根据需要做过dex优化了 */ scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR,scanMode | SCAN_NO_DEX, 0); //创建文件夹监控对象,监视/system/app目录 mSystemAppDir = new File(Environment.getRootDirectory(),"app"); mSystemInstallObserver = new AppDirObserver( mSystemAppDir.getPath(), OBSERVER_EVENTS, true); mSystemInstallObserver.startWatching(); //扫描/system/app下的package scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0); //监视并扫描/vendor/app目录 mVendorAppDir = new File("/vendor/app"); mVendorInstallObserver = new AppDirObserver( mVendorAppDir.getPath(), OBSERVER_EVENTS, true); mVendorInstallObserver.startWatching(); //扫描/vendor/app下的package scanDirLI(mVendorAppDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0); //和installd交互。以后单独分析installd mInstaller.moveFiles(); ~~~ 由以上代码可知,PKMS将扫描以下几个目录。 - /system/frameworks:该目录中的文件都是系统库,例如framework.jar、services.jar、framework-res.apk。不过scanDirLI只扫描APK文件,所以framework-res.apk是该目录中唯一“受宠”的文件。 - /system/app:该目录下全是默认的系统应用,例如Browser.apk、SettingsProvider.apk等。 - /vendor/app:该目录中的文件由厂商提供,即厂商特定的APK文件,不过目前市面上的厂商都把自己的应用放在/system/app目录下。 >[warning] **注意**:本书把这三个目录称为系统Package目录,以区分后面的非系统Package目录。 PKMS调用scanDirLI函数进行扫描,下面来分析此函数。 (1) scanDirLI函数分析 scanDirLI函数的代码如下: **PackageManagerService.java** ~~~ private void scanDirLI(File dir, int flags, intscanMode, long currentTime) { String[] files = dir.list();//列举该目录下的文件 ...... inti; for(i=0; i<files.length; i++) { File file = new File(dir, files[i]); if (!isPackageFilename(files[i])) { continue; //根据文件名后缀,判断是否为APK 文件。这里只扫描APK 文件 } /* 调用scanPackageLI函数扫描一个特定的文件,返回值是PackageParser的内部类 Package,该类的实例代表一个APK文件,所以它就是和APK文件对应的数据结构 */ PackageParser.Package pkg = scanPackageLI(file, flags|PackageParser.PARSE_MUST_BE_APK, scanMode, currentTime); if (pkg == null && (flags &PackageParser.PARSE_IS_SYSTEM) == 0 && mLastScanError ==PackageManager.INSTALL_FAILED_INVALID_APK) { //注意此处flags的作用,只有非系统Package扫描失败,才会删除该文件 file.delete(); } } } ~~~ 接着来分析scanPackageLI函数。PKMS中有两个同名的scanPackageLI函数,后面会一一见到。先来看第一个也是最先碰到的scanPackageLI函数。 (2) 初会scanPackageLI函数 首次相遇的scanPackageLI函数的代码如下: **PackageManagerService.java** ~~~ private PackageParser.Package scanPackageLI(FilescanFile, int parseFlags, int scanMode, long currentTime) { mLastScanError = PackageManager.INSTALL_SUCCEEDED; StringscanPath = scanFile.getPath(); parseFlags |= mDefParseFlags;//默认的扫描标志,正常情况下为0 //创建一个PackageParser对象 PackageParser pp = new PackageParser(scanPath); pp.setSeparateProcesses(mSeparateProcesses);// mSeparateProcesses为空 pp.setOnlyCoreApps(mOnlyCore);// mOnlyCore为false /* 调用PackageParser的parsePackage函数解析APK文件。注意,这里把代表屏幕 信息的mMetrics对象也传了进去 */ finalPackageParser.Package pkg = pp.parsePackage(scanFile,scanPath, mMetrics, parseFlags); ...... PackageSetting ps = null; PackageSetting updatedPkg; ...... /* 这里略去一大段代码,主要是关于Package升级方面的工作。读者可能会比较好奇:既然是 升级,一定有新旧之分,如果这里刚解析后得到的Package信息是新,那么旧Package 的信息从何得来?还记得”readLPw的‘佐料’”这一小节提到的package.xml文件吗?此 文件中存储的就是上一次扫描得到的Package信息。对比这两次的信息就知道是否需要做 升级了。这部分代码比较繁琐,但不影响我们正常分析。感兴趣的读者可自行研究 */ //收集签名信息,这部分内容涉及signature,本书暂不拟讨论(Signature和Android安全机制有关)。 if (!collectCertificatesLI(pp, ps, pkg,scanFile, parseFlags)) returnnull; //判断是否需要设置PARSE_FORWARD_LOCK标志,这个标志针对资源文件和Class文件 //不在同一个目录的情况。目前只有/vendor/app目录下的扫描会使用该标志。这里不讨论 //这种情况。 if (ps != null &&!ps.codePath.equals(ps.resourcePath)) parseFlags|= PackageParser.PARSE_FORWARD_LOCK; String codePath = null; String resPath = null; if((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0) { ......//这里不考虑PARSE_FORWARD_LOCK的情况。 }else { resPath = pkg.mScanPath; } codePath = pkg.mScanPath;//mScanPath指向该APK文件所在位置 //设置文件路径信息,codePath和resPath都指向APK文件所在位置 setApplicationInfoPaths(pkg, codePath, resPath); //调用第二个scanPackageLI函数 return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE, currentTime); } ~~~ scanPackageLI函数首先调用PackageParser对APK文件进行解析。根据前面的介绍可知,PackageParser完成了从物理文件到对应数据结构的转换。下面来分析这个PackageParser。 (3) PackageParser分析 PackageParser主要负责APK文件的解析,即解析APK文件中的AndroidManifest.xml代码如下: **PackageParser.java** ~~~ publicPackage parsePackage(File sourceFile, String destCodePath, DisplayMetrics metrics, int flags) { mParseError = PackageManager.INSTALL_SUCCEEDED; mArchiveSourcePath = sourceFile.getPath(); ......//检查是否为APK文件 XmlResourceParser parser = null; AssetManager assmgr = null; Resources res = null; boolean assetError = true; try{ assmgr = new AssetManager(); int cookie = assmgr.addAssetPath(mArchiveSourcePath); if (cookie != 0) { res = new Resources(assmgr, metrics, null); assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,Build.VERSION.RESOURCES_SDK_INT); /* 获得一个XML资源解析对象,该对象解析的是APK中的AndroidManifest.xml文件。 以后再讨论AssetManager、Resource及相关的知识 */ parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); assetError = false; } ......//出错处理 String[] errorText = new String[1]; Package pkg = null; Exception errorException = null; try { //调用另外一个parsePackage函数 pkg = parsePackage(res, parser, flags, errorText); } ...... ......//错误处理 parser.close(); assmgr.close(); //保存文件路径,都指向APK文件所在的路径 pkg.mPath = destCodePath; pkg.mScanPath = mArchiveSourcePath; pkg.mSignatures = null; return pkg; } ~~~ 以上代码中调用了另一个同名的PackageParser函数,此函数内容较长,但功能单一,就是解析AndroidManifest.xml中的各种标签,这里只提取其中相关的代码: **PackageParser.java** ~~~ private Package parsePackage( Resources res, XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException, IOException { AttributeSet attrs = parser; mParseInstrumentationArgs = null; mParseActivityArgs = null; mParseServiceArgs= null; mParseProviderArgs = null; //得到Package的名字,其实就是得到AndroidManifest.xml中package属性的值, //每个APK都必须定义该属性 String pkgName = parsePackageName(parser, attrs, flags, outError); ...... inttype; ...... //以pkgName名字为参数,创建一个Package对象。后面的工作就是解析XML并填充 //该Package信息 finalPackage pkg = new Package(pkgName); boolean foundApp = false; ......//下面开始解析该文件中的标签,由于这段代码功能简单,所以这里仅列举相关函数 while(如果解析未完成){ ...... StringtagName = parser.getName(); //得到标签名 if(tagName.equals("application")){ ......//解析application标签 parseApplication(pkg,res, parser, attrs, flags, outError); } elseif (tagName.equals("permission-group")) { ......//解析permission-group标签 parsePermissionGroup(pkg, res, parser, attrs, outError); } elseif (tagName.equals("permission")) { ......//解析permission标签 parsePermission(pkg, res, parser, attrs, outError); } else if(tagName.equals("uses-permission")){ //从XML文件中获取uses-permission标签的属性 sa= res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestUsesPermission); //取出属性值,也就是对应的权限使用声明 String name = sa.getNonResourceString(com.android.internal. R.styleable.AndroidManifestUsesPermission_name); //添加到Package的requestedPermissions数组 if(name != null && !pkg.requestedPermissions.contains(name)) { pkg.requestedPermissions.add(name.intern()); } }elseif (tagName.equals("uses-configuration")){ /* 该标签用于指明本package对硬件的一些设置参数,目前主要针对输入设备(触摸屏、键盘 等)。游戏类的应用可能对此有特殊要求。 */ ConfigurationInfocPref = new ConfigurationInfo(); ......//解析该标签所支持的各种属性 pkg.configPreferences.add(cPref);//保存到Package的configPreferences数组 } ......//对其他标签解析和处理 } ~~~ 上面代码展示了AndroidManifest.xml解析的流程,其中比较重要的函数是parserApplication,它用于解析application标签及其子标签(Android的四大组件在application标签中已声明)。 图4-5表示了PackageParser及其内部重要成员的信息。 :-: ![](http://img.blog.csdn.net/20150803110542379?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图4-5 PackageParser大家族 由图4-5可知: - PackageParser定了相当多的内部类,这些内部类的作用就是保存对应的信息。解析AndroidManifest.xml文件得到的信息由Package保存。从该类的成员变量可看出,和Android四大组件相关的信息分别由activites、receivers、providers、services保存。由于一个APK可声明多个组件,因此activites和receivers等均声明为ArrayList。 - 以PackageParser.Activity为例,它从Component<ActivityIntentInfo>派生。Component是一个模板类,元素类型是ActivityIntentInfo,此类的顶层基类是IntentFilter。PackageParser.Activity内部有一个ActivityInfo类型的成员变量,该变量保存的就是四大组件中Activity的信息。细心的读者可能会有疑问,为什么不直接使用ActivityInfo,而是通过IntentFilter构造出一个使用模板的复杂类型PackageParser.Activity呢?原来,Package除了保存信息外,还需要支持Intent匹配查询。例如,设置Intent的Action为某个特定值,然后查找匹配该Intent的Activity。由于ActivityIntentInfo是从IntentFilter派生的,因此它它能判断自己是否满足该Intent的要求,如果满足,则返回对应的ActivityInfo。在后续章节会详细讨论根据Intent查询特定Activity的工作流程。 - PackageParser定了一个轻量级的数据结构PackageLite,该类仅存储Package的一些简单信息。我们在介绍Package安装的时候,会遇到PackageLite。 >[warning] **注意**:读者需要了解Java泛型类的相关知识。 (4) 与scanPackageLI再相遇 在PackageParser扫描完一个APK后,此时系统已经根据该APK中AndroidManifest.xm,创建了一个完整的Package对象,下一步就是将该Package加入到系统中。此时调用的函数就是另外一个scanPackageLI,其代码如下: **PackageManagerService.java::scanPackageLI函数** ~~~ private PackageParser.PackagescanPackageLI(PackageParser.Package pkg, int parseFlags, int scanMode, long currentTime) { FilescanFile = new File(pkg.mScanPath); ...... mScanningPath = scanFile; //设置package对象中applicationInfo的flags标签,用于标示该Package为系统 //Package if((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; } //①下面这句if判断极为重要,见下面的解释 if(pkg.packageName.equals("android")) { synchronized (mPackages) { if (mAndroidApplication != null) { ...... mPlatformPackage = pkg; pkg.mVersionCode = mSdkVersion; mAndroidApplication = pkg.applicationInfo; mResolveActivity.applicationInfo = mAndroidApplication; mResolveActivity.name = ResolverActivity.class.getName(); mResolveActivity.packageName = mAndroidApplication.packageName; mResolveActivity.processName = mAndroidApplication.processName; mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; mResolveActivity.theme = com.android.internal.R.style.Theme_Holo_Dialog_Alert; mResolveActivity.exported = true; mResolveActivity.enabled = true; //mResoveInfo的activityInfo成员指向mResolveActivity mResolveInfo.activityInfo = mResolveActivity; mResolveInfo.priority = 0; mResolveInfo.preferredOrder = 0; mResolveInfo.match = 0; mResolveComponentName = new ComponentName( mAndroidApplication.packageName, mResolveActivity.name); } } ~~~ 刚进入scanPackageLI函数,我们就发现了一个极为重要的内容,即单独判断并处理packageName为“android”的Package。和该Package对应的APK是framework-res.apk,有图为证,如图4-6所示为该APK的AndroidManifest.xml中的相关内容。 :-: ![](http://img.blog.csdn.net/20150803110628247?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图4-6 framework-res.apk的AndroidManifest.xml 实际上,framework-res.apk还包含了以下几个常用的Activity。 - ChooserActivity:当多个Activity符合某个Intent的时候,系统会弹出此Activity,由用户选择合适的应用来处理。 - RingtonePickerActivity:铃声选择Activity。 - ShutdownActivity:关机前弹出的选择对话框。 由前述知识可知,该Package和系统息息相关,因此它得到了PKMS的特别青睐,主要体现在以下几点。 - mPlatformPackage成员用于保存该Package信息。 - mAndroidApplication用于保存此Package中的ApplicationInfo。 - mResolveActivity指向用于表示ChooserActivity信息的ActivityInfo。 - mResolveInfo为ResolveInfo类型,它用于存储系统解析Intent(经IntentFilter的过滤)后得到的结果信息,例如满足某个Intent的Activity的信息。由前面的代码可知,mResolveInfo的activityInfo其实指向的就是mResolveActivity。 * * * * * **注意**:在从PKMS中查询满足某个Intent的Activity时,返回的就是ResolveInfo,再根据ResolveInfo的信息得到具体的Activity。 此处保存这些信息,主要是为了提高运行过程中的效率。Goolge工程师可能觉得ChooserActivity使用的地方比较多,所以这里单独保存了此Activity的信息。 * * * * * 好,继续对scanPackageLI函数的分析。 **PackageManagerService::scanPackageLI函数** ~~~ ......//mPackages用于保存系统内的所有Package,以packageName为key if(mPackages.containsKey(pkg.packageName) || mSharedLibraries.containsKey(pkg.packageName)) { return null; } File destCodeFile = newFile(pkg.applicationInfo.sourceDir); FiledestResourceFile = new File(pkg.applicationInfo.publicSourceDir); SharedUserSettingsuid = null;//代表该Package的SharedUserSetting对象 PackageSetting pkgSetting = null;//代表该Package的PackageSetting对象 synchronized(mPackages) { ......//此段代码大约有300行左右,主要做了以下几方面工作 /* ①如果该Packge声明了” uses-librarie”话,那么系统要判断该library是否 在mSharedLibraries中 ②如果package声明了SharedUser,则需要处理SharedUserSettings相关内容, 由Settings的getSharedUserLPw函数处理 ③处理pkgSetting,通过调用Settings的getPackageLPw函数完成 ④调用verifySignaturesLP函数,检查该Package的signature */ } finallong scanFileTime = scanFile.lastModified(); finalboolean forceDex = (scanMode&SCAN_FORCE_DEX) != 0; //确定运行该package的进程的进程名,一般用packageName作为进程名 pkg.applicationInfo.processName = fixProcessName( pkg.applicationInfo.packageName, pkg.applicationInfo.processName, pkg.applicationInfo.uid); if(mPlatformPackage == pkg) { dataPath = new File (Environment.getDataDirectory(),"system"); pkg.applicationInfo.dataDir = dataPath.getPath(); }else { /* getDataPathForPackage函数返回该package的目录 一般是/data/data/packageName/ */ dataPath = getDataPathForPackage(pkg.packageName, 0); if(dataPath.exists()){ ......//如果该目录已经存在,则要处理uid的问题 } else { ......//向installd发送install命令,实际上就是在/data/data下 //建立packageName目录。后续将分析installd相关知识 int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid); //为系统所有user安装此程序 mUserManager.installPackageForAllUsers(pkgName, pkg.applicationInfo.uid); if (dataPath.exists()) { pkg.applicationInfo.dataDir = dataPath.getPath(); } ...... if (pkg.applicationInfo.nativeLibraryDir == null && pkg.applicationInfo.dataDir!= null) { ......//为该Package确定native library所在目录 //一般是/data/data/packagename/lib } } //如果该APK包含了native动态库,则需要将它们从APK文件中解压并复制到对应目录中 if(pkg.applicationInfo.nativeLibraryDir != null) { try { final File nativeLibraryDir = new File(pkg.applicationInfo.nativeLibraryDir); final String dataPathString = dataPath.getCanonicalPath(); //从2.3开始,系统package的native库统一放在/system/lib下。所以 //系统不会提取系统Package目录下APK包中的native库 if (isSystemApp(pkg) && !isUpdatedSystemApp(pkg)) { NativeLibraryHelper.removeNativeBinariesFromDirLI( nativeLibraryDir)){ } else if (nativeLibraryDir.getParentFile().getCanonicalPath() .equals(dataPathString)) { boolean isSymLink; try { isSymLink = S_ISLNK(Libcore.os.lstat( nativeLibraryDir.getPath()).st_mode); } ......//判断是否为链接,如果是,需要删除该链接 if (isSymLink) { mInstaller.unlinkNativeLibraryDirectory(dataPathString); } //在lib下建立和CPU类型对应的目录,例如ARM平台的是arm/,MIPS平台的是mips/ NativeLibraryHelper.copyNativeBinariesIfNeededLI(scanFile, nativeLibraryDir); } else { mInstaller.linkNativeLibraryDirectory(dataPathString, pkg.applicationInfo.nativeLibraryDir); } } ...... } pkg.mScanPath= path; if((scanMode&SCAN_NO_DEX) == 0) { ......//对该APK做dex优化 performDexOptLI(pkg,forceDex, (scanMode&SCAN_DEFER_DEX); } //如果该APK已经存在,要先杀掉运行该APK的进程 if((parseFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { killApplication(pkg.applicationInfo.packageName, pkg.applicationInfo.uid); } ...... /* 在此之前,四大组件信息都属于Package的私有财产,现在需要把它们登记注册到PKMS内部的 财产管理对象中。这样,PKMS就可对外提供统一的组件信息,而不必拘泥于具体的Package */ synchronized(mPackages) { if ((scanMode&SCAN_MONITOR) != 0) { mAppDirs.put(pkg.mPath, pkg); } mSettings.insertPackageSettingLPw(pkgSetting, pkg); mPackages.put(pkg.applicationInfo.packageName,pkg); //处理该Package中的Provider信息 int N =pkg.providers.size(); int i; for (i=0;i<N; i++) { PackageParser.Providerp = pkg.providers.get(i); p.info.processName=fixProcessName( pkg.applicationInfo.processName, p.info.processName, pkg.applicationInfo.uid); //mProvidersByComponent提供基于ComponentName的Provider信息查询 mProvidersByComponent.put(new ComponentName( p.info.packageName,p.info.name), p); ...... } //处理该Package中的Service信息 N =pkg.services.size(); r = null; for (i=0;i<N; i++) { PackageParser.Service s =pkg.services.get(i); mServices.addService(s); } //处理该Package中的BroadcastReceiver信息 N =pkg.receivers.size(); r = null; for (i=0;i<N; i++) { PackageParser.Activity a =pkg.receivers.get(i); mReceivers.addActivity(a,"receiver"); ...... } //处理该Package中的Activity信息 N = pkg.activities.size(); r =null; for (i=0; i<N; i++) { PackageParser.Activity a =pkg.activities.get(i); mActivities.addActivity(a,"activity");//后续将详细分析该调用 } //处理该Package中的PermissionGroups信息 N = pkg.permissionGroups.size(); ......//permissionGroups处理 N =pkg.permissions.size(); ......//permissions处理 N =pkg.instrumentation.size(); ......//instrumentation处理 if(pkg.protectedBroadcasts != null) { N = pkg.protectedBroadcasts.size(); for(i=0; i<N; i++) { mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i)); } } ......//Package的私有财产终于完成了公有化改造 return pkg; } ~~~ 到此这个长达800行的代码就分析完了,下面总结一下Package扫描的流程。 (5) scanDirLI函数总结 scanDirLI用于对指定目录下的APK文件进行扫描,如图4-7所示为该函数的调用流程。 :-: ![](http://img.blog.csdn.net/20150803110657112?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图4-7 scanDirLI工作流程总结 图4-7比较简单,相关知识无须赘述。读者在自行分析代码时,只要注意区分这两个同名scanPackageLI函数即可。 扫描完APK文件后,Package的私有财产就充公了。PKMS提供了好几个重要数据结构来保存这些财产,这些数据结构的相关信息如图4-8所示。 :-: ![](http://img.blog.csdn.net/20150803110726269?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图4-8 PKMS中重要的数据结构 图4-8借用UML的类图来表示PKMS中重要的数据结构。每个类图的第一行为成员变量名,第二行为数据类型,第三行为注释说明。 3. 扫描非系统Package 非系统Package就是指那些不存储在系统目录下的APK文件,这部分代码如下: **PackageManagerService.java::构造函数第三部分** ~~~ if (!mOnlyCore) {//mOnlyCore用于控制是否扫描非系统Package Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator(); while (psit.hasNext()) { ......//删除系统package中那些不存在的APK } mAppInstallDir = new File(dataDir,"app"); ......//删除安装不成功的文件及临时文件 if (!mOnlyCore) { //在普通模式下,还需要扫描/data/app以及/data/app_private目录 mAppInstallObserver = new AppDirObserver( mAppInstallDir.getPath(), OBSERVER_EVENTS, false); mAppInstallObserver.startWatching(); scanDirLI(mAppInstallDir, 0, scanMode, 0); mDrmAppInstallObserver = newAppDirObserver( mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false); mDrmAppInstallObserver.startWatching(); scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,scanMode,0); } else { mAppInstallObserver = null; mDrmAppInstallObserver = null; } ~~~ 结合前述代码,这里总结几个存放APK文件的目录。 - 系统Package目录包括:/system/frameworks、/system/app和/vendor/app。 - 非系统Package目录包括:/data/app、/data/app-private。 4. 第二阶段工作总结 PKMS构造函数第二阶段的工作任务非常繁重,要创建比较多的对象,所以它是一个耗时耗内存的操作。在工作中,我们一直想优化该流程以加快启动速度,例如延时扫描不重要的APK,或者保存Package信息到文件中,然后在启动时从文件中恢复这些信息以减少APK文件读取并解析XML的工作量。但是一直没有一个比较完满的解决方案,原因有很多。比如APK之间有着比较微妙的依赖关系,因此到底延时扫描哪些APK,尚不能确定。另外,笔者感到比较疑惑的一个问题是:对于多核CPU架构,PKMS可以启动多个线程以扫描不同的目录,但是目前代码中还没有寻找到相关的蛛丝马迹。难道此处真的就不能优化了吗?读者如果有更好的解决方案,不妨和大家分享一下。