4.3.2 构造函数分析之扫描Package
PKMS构造函数第二阶段的工作就是扫描系统中的APK了。由于需要逐个扫描文件,因此手机上装的程序越多,PKMS的工作量就越大,系统启动速度也就越慢。
1.系统库的dex优化
接着对PKMS构造函数进行分析,代码如下:
[—>vPackageManagerService.java]
……
mRestoredSettings=mSettings.readLPw();//接第一段的结尾
long startTime=SystemClock.uptimeMillis();//记录扫描开始的时间
//定义扫描参数
int scanMode=SCAN_MONITOR|SCAN_NO_PATHS|SCAN_DEFER_DEX;
if(mNoDexOpt){
scanMode|=SCAN_NO_DEX;//在控制扫描过程中是否对APK文件进行dex优化
}
final HashSet<String>libFiles=new HashSet<String>();
//mFrameworkDir指向/system/frameworks目录
mFrameworkDir=new File(Environment.getRootDirectory(),"framework");
//mDalvikCacheDir指向/data/dalvik-cache目录
mDalvikCacheDir=new File(dataDir,"dalvik-cache");
boolean didDexOpt=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所有核心库及文件位置
*/
String bootClassPath=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@")){
(new File(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目录下。
注意 本书把这3个目录称为系统package目录,以区分后面的非系统package目录。
PKMS调用scanDirLI函数进行扫描,下面来分析此函数。
(1)scanDirLI函数分析
scanDirLI函数的代码如下:
[—>PackageManagerService.java:scanDirLI]
private void scanDirLI(File dir, int flags, int scanMode, long currentTime){
String[]files=dir.list();//列举该目录下的文件
……
int i;
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:scanPackageLI]
private PackageParser.Package scanPackageLI(File scanFile, int parseFlags,
int scanMode, long currentTime)
{
mLastScanError=PackageManager.INSTALL_SUCCEEDED;
String scanPath=scanFile.getPath();
parseFlags|=mDefParseFlags;//默认的扫描标志,正常情况下其值为0
//创建一个PackageParser对象
PackageParser pp=new PackageParser(scanPath);
pp.setSeparateProcesses(mSeparateProcesses);//mSeparateProcesses为空
pp.setOnlyCoreApps(mOnlyCore);//mOnlyCore为false
/*
调用PackageParser的parsePackage函数解析APK文件。注意,这里把代表屏幕
信息的mMetrics对象也传了进去
*/
final PackageParser.Package pkg=pp.parsePackage(scanFile,
scanPath, mMetrics, parseFlags);
……
PackageSetting ps=null;
PackageSetting updatedPkg;
……
/*
这里略去一大段代码,主要是关于Package升级方面的工作。读者可能会比较好奇:既然是
升级,一定有新旧之分,如果这里刚解析后得到的Package信息是新的,那么旧Package
的信息从何得来?还记得4.3.1节中提到的package.xml文件吗?此文件中存储的就是
上一次扫描得到的Package信息。对比这两次的信息就知道是否需要做升级了。这部分代码
比较烦琐,但不影响我们正常分析。感兴趣的读者可自行研究
*/
//收集签名信息,这部分内容涉及signature,本书暂不讨论[1]。
if(!collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags))
return null;
//判断是否需要设置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:parsePackage]
public Package 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:parsePackage]
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);
……
int type;
……
//以pkgName名字为参数,创建一个Package对象。后面的工作就是解析XML并填充
//该Package信息
final Package pkg=new Package(pkgName);
boolean foundApp=false;
……//下面开始解析该文件中的标签,由于这段代码功能简单,所以这里仅列举相关函数
while(……/如果解析未完成/){
……
String tagName=parser.getName();//得到标签名
if(tagName.equals("application")){
……//解析application标签
parseApplication(pkg, res, parser, attrs, flags, outError);
}else if(tagName.equals("permission-group")){
……//解析permission-group标签
parsePermissionGroup(pkg, res, parser, attrs, outError);
}else if(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());
}
}else if(tagName.equals("uses-configuration")){
/*
该标签用于指明本Package对硬件的一些设置参数,目前主要针对输入设备(触摸屏、键盘
等)。游戏类的应用可能对此有特殊要求。
*/
ConfigurationInfo cPref=new ConfigurationInfo();
……//解析该标签所支持的各种属性
pkg.configPreferences.add(cPref);//保存到Package的configPreferences数组
}
……//对其他标签解析和处理
}
上面代码展示了AndroidManifest.xml解析的流程,其中比较重要的函数是parser-Application,它用于解析application标签及其子标签(Android的四大组件在application标签中已声明)。
图4-6表示出了PackageParser及其内部重要成员的信息。
图 4-6 PackageParser家族
由图4-6可知:
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。
注意 读者需要了解Java泛型类的相关知识。
(4)与scanPackageLI再相遇
在PackageParser扫描完一个APK后,此时系统已经根据该APK中AndroidManifest.xml,创建了一个完整的Package对象,下一步就是将该Package加入到系统中。此时调用的函数就是另外一个scanPackageLI,其代码如下:
[—>PackageManagerService.java:scanPackageLI]
private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
int parseFlags, int scanMode, long currentTime){
File scanFile=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-7所示为该APK的AndroidManifest.xml中的相关内容。
图 4-7 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=new File(pkg.applicationInfo.sourceDir);
File destResourceFile=new File(pkg.applicationInfo.publicSourceDir);
SharedUserSetting suid=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
*/
}
final long scanFileTime=scanFile.lastModified();
final boolean 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();
//从Android 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.Provider p=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-8所示为该函数的调用流程。
图 4-8 scanDirLI工作流程总结
图4-8比较简单,相关知识无须赘述。读者在自行分析代码时,只要注意区分两个scanPackageLI函数即可。
扫描完APK文件后,Package的私有财产就充公了。PKMS提供了好几个重要数据结构来保存这些财产,这些数据结构的相关信息如图4-9所示。
图 4-9 PKMS中重要的数据结构
图4-9借用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=new AppDirObserver(
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可以启动多个线程以扫描不同的目录,但是目前代码中还没有寻找到相关的蛛丝马迹。难道此处真的就不能优化了吗?读者如果有更好的解决方案,不妨和大家分享一下。
[1]Signature和Android安全机制有关。本系列书后续拟考虑编写有关Android安全方面的专题卷。感兴趣的读者也可先阅读《Application Security for the Android Platform》一书。