9.1.3 过滤PackageParser.Package类型的pkg对象

scanPackageLI(File,……)接收到第二阶段返回的pkg后,便进入第三阶段,代码如下:


private PackageParser.Package scanPackageLI(File scanFile,

int parseFlags, int scanMode, long currentTime){

……//省略内容

PackageSetting ps=null;

PackageSetting updatedPkg;

//reader

synchronized(mPackages){

/*mRenamedPackages的类型是HashMap,以键值对的形式存储

已更名应用程序的包信息,其中键为新的包名,值为旧的包名/

String oldName=mSettings.mRenamedPackages.get(pkg.packageName);

//查看mOriginalPackages中是否存在该包的原始包信息

if(pkg.mOriginalPackages!=null&&

pkg.mOriginalPackages.contains(oldName)){

//从Settings.mPackages中查找原始包对应的PackageSetting信息

ps=mSettings.peekPackageLPr(oldName);

}

//无原始包信息,查询Settings.mPackages中新包名对应的PackageSetting

if(ps==null){

ps=mSettings.peekPackageLPr(pkg.packageName);

}

/*查询Settings.mDisabledSysPackages是否存在该包的信息

该变量存储了被替换或升级了的系统应用程序的PackageSetting/

updatedPkg=mSettings.getDisabledSystemPkgLPr(ps!=null?

ps.name:pkg.packageName);

}

//①当前解析的包信息是需要升级的系统级应用

if(updatedPkg!=null&&(parseFlags&PackageParser.PARSE_IS_SYSTEM)!=0){

//②如果APK包的路径发生变化

if(ps!=null&&!ps.codePath.equals(scanFile)){

//系统包路径发生变化且版本低于已安装版本,则直接返回null

if(pkg.mVersionCode<ps.versionCode){

……

return null;

}else{

//否则,先从PackageManagerService的mPackages中删除原始包信息

synchronized(mPackages){

mPackages.remove(ps.name);

}

//创建安装参数

InstallArgs args=createInstallArgs(

packageFlagsToInstallFlags(ps),ps.codePathString,

ps.resourcePathString, ps.nativeLibraryPathString);

synchronized(mInstaller){

args.cleanUpResourcesLI();

}

synchronized(mPackages){

//将当前包由disabled修改为enable

mSettings.enableSystemPackageLPw(ps.name);

}

}

}//if②结束

}//if①结束

if(updatedPkg!=null){

//升级的系统级APK初始没有设置PARSE_IS_SYSTEM标记,这里追加该标记

parseFlags|=PackageParser.PARSE_IS_SYSTEM;

}

//验证certificates

if(!collectCertificatesLI(pp, ps, pkg, scanFile, parseFlags)){

return null;//验证失败,直接返回

}

boolean shouldHideSystemApp=false;

//当前扫描的是系统包,与已安装的非系统包重名

if(updatedPkg==null&&ps!=null

&&(parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR)!=0

&&!isSystemApp(ps)){

//比较签名信息,如果不同则删除已安装的APK

if(compareSignatures(ps.signatures.mSignatures, pkg.mSignatures)

!=PackageManager.SIGNATURE_MATCH){

deletePackageLI(pkg.packageName, true,0,null, false);

ps=null;

}else{

//当前扫描的系统APK版本比已安装版本低,则需要将其隐藏

if(pkg.mVersionCode<ps.versionCode){

shouldHideSystemApp=true;

}else{

//当前扫描的系统APK比已安装版本高,则删除已安装应用,但保留其数据

InstallArgs args=createInstallArgs(

packageFlagsToInstallFlags(ps),ps.codePathString,

ps.resourcePathString, ps.nativeLibraryPathString);

synchronized(mInstaller){

args.cleanUpResourcesLI();

}

}

}

}

……//省略第三阶段一部分内容,后续分析


可见,scanPackageLI第三阶段的主要工作是过滤第二阶段返回的PackageParser.Package类型的pkg对象。scanPackageLI扫描到的APK文件可能是已更名的包、disabled的包、需要升级的包、已安装并且签名冲突的包、被非系统级包替代的系统包等情况。scanPackageLI第三阶段将针对上述情况一一进行处理:要么删除,要么更新。具体操作参考源码中的注释,这里不赘述。

scanPackageLI第三阶段有一个重要操作:验证certificates,该操作由collectCertificatesLI方法完成。其代码如下:


private boolean collectCertificatesLI(PackageParser pp, PackageSetting ps,

PackageParser.Package pkg, File srcFile, int parseFlags){

if(GET_CERTIFICATES){//默认为true

if(ps!=null

&&ps.codePath.equals(srcFile)

&&ps.timeStamp==srcFile.lastModified()){

//如果包路径未变,文件时间戳未修改,说明包没有变化

if(ps.signatures.mSignatures!=null

&&ps.signatures.mSignatures.length!=0){

//复用已有的certificates

pkg.mSignatures=ps.signatures.mSignatures;

return true;

}

}else{

Log.i(TAG, srcFile.toString()+"changed;collecting certs");

}

//包文件有改动,需要重新获取certificates

if(!pp.collectCertificates(pkg, parseFlags)){

mLastScanError=pp.getParseError();

return false;

}

}

return true;

}


由以上代码可知,如果包没有变化,会复用已有的certificates。如果包发生变化,需要调用collectCertificates方法重新获取certificates。接下来分析certificates的获取过程,代码如下:


public boolean collectCertificates(Package pkg, int flags){

pkg.mSignatures=null;

byte[]readBuffer=null;

……

try{

JarFile jarFile=new JarFile(mArchiveSourcePath);

Certificate[]certs=null;

if((flags&PARSE_IS_SYSTEM)!=0){

/系统级APK是可信的,通过AndroidManifest.xml获取签名信息/

JarEntry jarEntry=jarFile.getJarEntry(ANDROID_MANIFEST_FILENAME);

certs=loadCertificates(jarFile, jarEntry, readBuffer);

……

}

}else{//非系统级应用

Enumeration<JarEntry>entries=jarFile.entries();

final Manifest manifest=jarFile.getManifest();

while(entries.hasMoreElements()){

/*遍历APK文件获取签名信息,也是调用loadCertificates方法

这里都是标准算法,有兴趣的读者可以自行研究/

}

}

//将签名信息存入pkg.mSignatures

if(certs!=null&&certs.length>0){

final int N=certs.length;

pkg.mSignatures=new Signature[certs.length];

……

}


collectCertificates方法会根据APK的类型做出不同处理。如果是系统级APK,直接利用AndroidManifest.xml获取签名信息,而不需要验证APK中所有文件。如果是非系统级APK,则需要验证APK中所有文件以获取certificates信息。最终签名信息被存入pkg.mSignatures中。

签名信息验证通过后,返回到scanPackageLI(File,……)方法,继续执行第三阶段后续部分,代码如下:


private PackageParser.Package scanPackageLI(File scanFile,

int parseFlags, int scanMode, long currentTime){

……//省略第一阶段内容

……//省略第二阶段已分析内容

//codePath与resourcePath不同时,需要设置PARSE_FORWARD_LOCK标记

if(ps!=null&&!ps.codePath.equals(ps.resourcePath)){

parseFlags|=PackageParser.PARSE_FORWARD_LOCK;

}

String codePath=null;

String resPath=null;

//如果设置了PARSE_FORWARD_LOCK标记

if((parseFlags&PackageParser.PARSE_FORWARD_LOCK)!=0){

if(ps!=null&&ps.resourcePathString!=null){

resPath=ps.resourcePathString;//设置resPath

}else{//错误处理

Slog.e(TAG,"Resource path not set for pkg:"+pkg.packageName);

}

}else{//如果没有设置PARSE_FORWARD_LOCK标记

resPath=pkg.mScanPath;//resPath与APK文件路径一样

}

codePath=pkg.mScanPath;

/*以codePath设置pkg.mPath、pkg.mScanPath和

*pkg.applicationInfo.sourceDir,以resPath设置

pkg.applicationInfo.publicSourceDir/

setApplicationInfoPaths(pkg, codePath, resPath);

……//省略第四阶段内容


可见scanPackageLI(File,……)方法第三阶段后续处理部分主要处理FORWARD_LOCK类型的安装包的resourcePath和codePath。因为FORWARD_LOCK类型APK的resourcePath和codePath并不相同,需要将这两个变量设为相应值,即resourcePath=/data/app/<pkgname>.zip;codePath=/data/app-private/<pkgname>.apk。其中pkgname为包名。resourcePath和codePath设置成功后调用setApplicationInfoPaths方法更新pkg的pkg.mPath、pkg.mScanPath、pkg.applicationInfo.sourceDir和pkg.applicationInfo.publicSourceDir信息。FORWARD_LOCK类型APK放在后续小节中分析。

接下来开始分析scanPackageLI第四阶段。