9.2.2 调用handleStartCopy方法处理安装操作

InstallParams的handleStartCopy方法执行步骤如下:

步骤1 检查InstallParams.flags判断安装位置。

如果flags中设置了PackageManager.INSTALL_EXTERNAL标记,安装位置为SD卡。如果flags中设置了PackageManager.INSTALL_INTERNAL标记,安装位置为内部存储区。如果这两个标记同时被设置,记录返回结果INSTALL_FAILED_INVALID_INSTALL_LOCATION到InstallParams.mRet中,否者执行后续步骤。

步骤2 查询最小可用存储空间的临界值。

通过DeviceStorageMonitorService服务获取最小可用存储空间的临界值,该值默认为存储容量的10%。

步骤3 为com.android.defcontainer包授予访问APK文件的临时权限。

调用mContext.grantUriPermission方法,为com.android.defcontainer包授予访问packageURI指定的APK文件的读权限。这个权限是临时的,使用后需要收回。

步骤4 处理加密参数和mPackageURI的Scheme。代码如下:


final File packageFile;

//如果指定了加密参数或者mPackageURI不是文件类型

if(encryptionParams!=null||!"file".equals(mPackageURI.getScheme())){

ParcelFileDescriptor out=null;

//在data/app-private目录下创建临时文件,该临时文件格式为vmdl<随机整数>.tmp

mTempPackage=createTempPackageFile(mDrmAppPrivateInstallDir);

if(mTempPackage!=null){

try{

//打开该临时文件

out=ParcelFileDescriptor.open(mTempPackage,

ParcelFileDescriptor.MODE_READ_WRITE);

}catch(FileNotFoundException e){

}

/*复制mPackageURI指定的文件到out,即临时文件中。由于

需要跨进程复制,因此使用ParcelFileDescriptor类型的out/

ret=mContainerService.copyResource(mPackageURI, encryptionParams, out);

packageFile=mTempPackage;//以临时文件代替包文件

FileUtils.setPermissions(packageFile.getAbsolutePath(),

FileUtils.S_IRUSR|FileUtils.S_IWUSR|FileUtils.S_IROTH,

-1,-1);

}else{

packageFile=null;//创建临时文件出错

}

}else{

//如果没有指定加密参数或者mPackageURI指定文件类型,则直接使用包文件

packageFile=new File(mPackageURI.getPath());

}


加密参数是Jelly Bean新添加的参数,通过PackageManagerService.installPackage调用installPackageWithVerification方法不需要指定加密参数,而通过adb install调用该方法则需要指定加密参数。

步骤5 计算推荐安装位置。

计算推荐安装位置通过调用mContainerService.getMinimalPackageInfo方法完成,该方法定义于DefaultContainerService中,返回PackageInfoLite类型的ret对象。

getMinimalPackageInfo方法首先解析包的AndroidManifest.xml文件,读取其package、installLocation和package-verifier的值,并将其分别存入ret.packageName、ret.installLocation、ret.verifiers中;然后调用recommendAppInstallLocation方法计算推荐安装位置,该方法返回结果存入ret.recommendedInstallLocation中。计算推荐安装位置的流程如下:

1)如果InstallParams.flags设置了INSTALL_INTERNAL标记,需要检查内部存储区是否有足够空间,检查成功返回RECOMMEND_INSTALL_INTERNAL。

2)如果InstallParams.flags设置了INSTALL_EXTERNAL标记,需要检查外部存储区是否有足够空间,检查成功返回RECOMMEND_INSTALL_EXTERNAL。

3)如果没有设置以上标记,根据AndroidManifest.xml的installLocation配置选择内部存储区或者外部存储区。默认配置为AUTO,表示内部存储区。

4)如果以上三步都没有配置,则查询Settings的Secure表读取用户自定义安装位置。

5)如果选择外部存储设备,需要判断该外部设备是否是模拟存储设备,如果不是模拟存储设备才能安装到外部存储区。

getMinimalPackageInfo方法返回后,调用mContext.revokeUriPermission取消com.android.defcontainer包对packageURI的读权限。然后调用installLocationPolicy(pkgLite, flags)方法对推荐安装位置做进一步处理。installLocationPolicy方法的主要工作是限制系统级应用不能安装在外部存储设备上。

步骤6 创建安装参数。

安装位置确认后,调用createInstallArgs方法创建InstallArgs类型的对象args,并将其存入InstallParams.mArgs。InstallArgs是一个抽象类,其子类有FileInstallArgs和AsecInstallArgs, createInstallArgs方法根据安装在内部存储区还是外部存储区,分别返回FileInstallArgs和AsecInstallArgs的对象,对于设置了INSTALL_FORWARD_LOCK标记的安装包也要返回AsecInstallArgs对象。

步骤7 验证包。

如果PackageManagerService.mRequiredVerifierPackage不为null,需要进行包的verification操作。mRequiredVerifierPackage是一个String类型的成员变量,在PackageManagerService启动阶段的最后一步由getRequiredVerifierLPr方法赋值。mRequiredVerifierPackage存储了可以处理Action类型为ACTION_PACKAGE_NEEDS_VERIFICATION的Intent的应用组件所在的包名。到目前为止,Android并未提供验证APK的组件。

步骤8 复制安装APK。

如果不需要验证包,则直接调用InstallArgs的copyApk方法复制安装APK。假设APK安装在内部存储区,那么这里将调用InstallArgs的子类FileInstallArgs的copyApk方法,其代码如下:


//调用参数为copyApk(mContainerService, true)

int copyApk(IMediaContainerService imcs, boolean temp)throws RemoteException{

if(temp){//temp为true

/*对于FORWARD_LOCK类型的包,在/data/app-private目录下创建

*其临时文件;对于其他类型的包,在/data/app目录下创建其临时文件。

临时文件的命名规则为vmdl<随机整数>.tmp,后续会分析该文件的用途/

createCopyFile();

}

/*codeFile即createCopyFile()方法在data/app-private

或者/data/app目录中创建的APK临时文件/

File codeFile=new File(codeFileName);

//createCopyFile方法会将created赋值为true

if(!created){

……

}

ParcelFileDescriptor out=null;

try{

//通过ParcelFileDescriptor打开APK临时文件实现跨进程复制

out=ParcelFileDescriptor.open(codeFile,

ParcelFileDescriptor.MODE_READ_WRITE);

}catch(FileNotFoundException e){

return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;

}

int ret=PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;

try{

//分配临时权限,令DEFAULT_CONTAINER_PACKAGE具体对APK的读权限

mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE,

packageURI, Intent.FLAG_GRANT_READ_URI_PERMISSION);

/*将packageURI指定的APK复制到/data/app-private或者/data/app

*目录中已创建的APK临时文件中。以adb install为例,APK文件会上传

*到设备的/data/local/tmp目录下,该目录即packageURI。此时相当于

*将该目录下的APK复制到createCopyFile创建的临时文件中。之所以这样

*做,是因为/data/app目录添加删除APK会被AppDirObserver监控,这

里以临时文件的形式回避监控/

ret=imcs.copyResource(packageURI, null, out);

}finally{

IoUtils.closeQuietly(out);

mContext.revokeUriPermission(packageURI,//回收临时权限

Intent.FLAG_GRANT_READ_URI_PERMISSION);

}

//对于FORWARD_LOCK类型的APK

if(isFwdLocked()){

final File destResourceFile=new File(getResourcePath());

/从APK文件中提取AndroidManifest.xml、resources.arsc和res目录到ZIP文件/

try{

PackageHelper.extractPublicFiles(codeFileName, destResourceFile);

}catch(IOException e){

destResourceFile.delete();//提取失败,删除

return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;

}

}

return ret;

}


FileInstallArgs. copyApk方法的执行流程归纳如下:

步骤1 首先调用createCopyFile方法,在该方法中为以下4个变量赋值。

installDir:代表APK安装路径。对于forward-lock类型的APK,其值为data/app-private;对于其他类型APK,其值为data/app。

codeFileName:表示APK的临时文件,内容为空,后续由实际APK填充。在installDir指定的目录下创建该临时文件,文件名的格式为“vmdl-随机整数.tmp”,即临时文件以vmdl为前缀,以tmp为后缀,中间拼接一个随机整数。

resourceFileName:对于forward-lock类型的APK,其值为data/app/<apk文件名>.zip;对于其他类型APK,其值与codeFileName相同。

created:成功获取上述三个文件,即为true。

对于非lock-forward应用程序,上述三个文件的路径类似以下格式:

installDir:/data/app

codeFileName:/data/app/vmdl-1111448183. tmp

resourceFileName:/data/app/vmdl-1111448183. tmp

对于lock-forward的应用程序,上述三个文件的路径类似以下格式:

installDir位于/data/app-private;

其临时codeFileName为/data/app-private/vmdl-1111448183.tmp;

其临时resourceFileName为data/app/vmdl-1111448183.zip。

注意 ZIP文件与APK文件的区别是:ZIP文件中只保留应用程序的res目录、Android Manifest.xml和resources.arsc文件,而assets目录、lib目录、META-INF目录、classes.dex目录,以及其他目录则不会出现在ZIP文件中。这样可以起到保护APK中资源的作用。

步骤2 生成ParcelFileDescriptor类型的out对象读写codeFileName指定的临时文件。

ParcelFileDescriptor. open方法返回ParcelFileDescriptor类型的对象用于跨进程访问文件,这里要跨越的进程便是DefaultContainerService。copyResource方法,最终调用DefaultContainerService.copyFile方法,在该方法中,将packageURI指定的APK文件跨进程复制到createCopyFile创建的临时文件中。操作成功后,返回PackageManager.INSTALL_SUCCEEDED,并将其赋值给InstallParmas的mRet成员变量。

以上步骤完成后,handleStartCopy方法执行完毕,进而返回到HandlerParams的startCopy方法,接下来继续执行handleReturnCode()方法。