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.3.2 构造函数分析之扫描Package - 图1

图 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.3.2 构造函数分析之扫描Package - 图2

图 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.3.2 构造函数分析之扫描Package - 图3

图 4-8 scanDirLI工作流程总结

图4-8比较简单,相关知识无须赘述。读者在自行分析代码时,只要注意区分两个scanPackageLI函数即可。

扫描完APK文件后,Package的私有财产就充公了。PKMS提供了好几个重要数据结构来保存这些财产,这些数据结构的相关信息如图4-9所示。

4.3.2 构造函数分析之扫描Package - 图4

图 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》一书。