🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
installPackageWithVerification的代码如下: **PackageManagerService.java::installPackageWithVerification函数** ~~~ public void installPackageWithVerification(UripackageURI, IPackageInstallObserverobserver, int flags, String installerPackageName, Uri verificationURI, ManifestDigest manifestDigest) { //检查客户端进程是否具有安装Package的权限。在本例中,该客户端进程是shell mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INSTALL_PACKAGES,null); final int uid = Binder.getCallingUid(); final int filteredFlags; if(uid == Process.SHELL_UID || uid == 0) { ......//如果通过shell pm的方式安装,则增加INSTALL_FROM_ADB标志 filteredFlags = flags | PackageManager.INSTALL_FROM_ADB; }else { filteredFlags = flags & ~PackageManager.INSTALL_FROM_ADB; } //创建一个Message,code为INIT_COPY,将该消息发送给之前在PKMS构造函数中 //创建的mHandler对象,将在另外一个工作线程中处理此消息 final Message msg = mHandler.obtainMessage(INIT_COPY); //创建一个InstallParams,其基类是HandlerParams msg.obj = new InstallParams(packageURI, observer, filteredFlags,installerPackageName, verificationURI,manifestDigest); mHandler.sendMessage(msg); } ~~~ installPackageWithVerification函数倒是蛮清闲,简简单单创建几个对象,然后发送INIT_COPY消息给mHandler,就甩手退出了。根据之前在PKMS构造函数中介绍的知识可知,mHandler被绑定到另外一个工作线程(借助ThreadHandler对象的Looper)中,所以该INIT_COPY消息也将在那个工作线程中进行处理。我们马上转战到那。 1. INIT_COPY处理 INIT_COPY只是安装流程的第一步。先来看相关代码: **PackageManagerService.java::handleMesssage** ~~~ public void handleMessage(Message msg) { try { doHandleMessage(msg);//调用doHandleMessage函数 } ...... } voiddoHandleMessage(Message msg) { switch(msg.what) { caseINIT_COPY: { //①这里记录的是params的基类类型HandlerParams,实际类型为InstallParams HandlerParams params = (HandlerParams) msg.obj; //idx为当前等待处理的安装请求的个数 intidx = mPendingInstalls.size(); if(!mBound) { /* 很多读者可能想不到,APK的安装居然需要使用另外一个APK提供的服务,该服务就是 DefaultContainerService,由DefaultCotainerService.apk提供, 下面的connectToService函数将调用bindService来启动该服务 */ if(!connectToService()) { return; }else {//如果已经连上,则以idx为索引,将params保存到mPendingInstalls中 mPendingInstalls.add(idx, params); } } else { mPendingInstalls.add(idx, params); if(idx == 0) { //如果安装请求队列之前的状态为空,则表明要启动安装 mHandler.sendEmptyMessage(MCS_BOUND); } } break; } ......//后续再分析 ~~~ 这里假设之前已经成功启动了DefaultContainerService(以后简称DCS),并且idx为零,所以这是PKMS首次处理安装请求,也就是说,下一个将要处理的是MCS_BOUND消息。 >[info] **注意**:connectToService在调用bindService时会传递一个DefaultContainerConnection类型的对象,以接收服务启动的结果。当该服务成功启动后,此对象的onServiceConnected被调用,其内部也将发送MCS_BOUND消息给mHandler。 2. MCS_BOUND处理 现在,安装请求的状态从INIT_COPY变成MCS_BOUND了,此时的处理流程时怎样的呢?依然在doHandleMessage函数中,直接从对应的case开始,代码如下: **PackageManagerService.java::** ~~~ ......//接doHandleMesage中的switch/case case MCS_BOUND: { if(msg.obj != null) { mContainerService= (IMediaContainerService) msg.obj; } if(mContainerService == null) { ......//如果没法启动该service,则不能安装程序 mPendingInstalls.clear(); } else if(mPendingInstalls.size() > 0) { HandlerParamsparams = mPendingInstalls.get(0); if(params != null) { //调用params对象的startCopy函数,该函数由基类HandlerParams定义 if(params.startCopy()) { ...... if(mPendingInstalls.size() > 0) { mPendingInstalls.remove(0);//删除队列头 } if (mPendingInstalls.size() == 0) { if (mBound) { ......//如果安装请求都处理完了,则需要和Service断绝联系, //通过发送MSC_UNB消息处理断交请求。读者可自行研究此情况的处理流程 removeMessages(MCS_UNBIND); Message ubmsg = obtainMessage(MCS_UNBIND); sendMessageDelayed(ubmsg, 10000); } }else { //如果还有未处理的请求,则继续发送MCS_BOUND消息。 //为什么不通过一个循环来处理所有请求呢 mHandler.sendEmptyMessage(MCS_BOUND); } } } ...... break; ~~~ MCS_BOUND的处理还算简单,就是调用HandlerParams的startCopy函数。在深入分析前,应先认识一下HandlerParams及相关的对象。 (1) HandlerParams和InstallArgs介绍 除了HandlerParams家族外,这里提前请出另外一个家族InstallArgs及其成员,如图4-8所示。 :-: ![](http://img.blog.csdn.net/20150803110954000?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图4-8 HandlerParams及InstallArgs家族成员 由图4-8可知: - HandlerParams和InstallArgs均为抽象类。 - HandlerParams有三个子类,分别是InstallParams、MoveParams和MeasureParams。其中,InstallParams用于处理APK的安装,MoveParams用于处理某个已安装APK的搬家请求(例如从内部存储移动到SD卡上),MeasureParams用于查询某个已安装的APK占据存储空间的大小(例如在设置程序中得到的某个APK使用的缓存文件的大小)。 - 对于InstallParams来说,它还有两个伴儿,即InstallArgs的派生类FileInstallArgs和SdInstallArgs。其中,FileInstallArgs针对的是安装在内部存储的APK,而SdInstallArgs针对的是那些安装在SD卡上的APK。 本节将讨论用于内部存储安装的FileInstallArgs。 * * * * * **提示**:读者可以在介绍完MountService后,结合本章知识点,自行研究SdInstallArgs的处理流程。 * * * * * 在前面MCS_BOUND的处理中,首先调用InstallParams的startCopy函数,该函数由其基类HandlerParams实现,代码如下: **PackageManagerService.java::HandlerParams.startCopy函数** ~~~ final boolean startCopy() { booleanres; try { //MAX_RETIRES目前为4,表示尝试4次安装,如果还不成功,则认为安装失败 if(++mRetries > MAX_RETRIES) { mHandler.sendEmptyMessage(MCS_GIVE_UP); handleServiceError(); return false; } else { handleStartCopy();//①调用派生类的handleStartCopy函数 res= true; } } ...... handleReturnCode();//②调用派生类的handleReturnCode,返回处理结果 returnres; } ~~~ 在上述代码中,基类的startCopy将调用子类实现的handleStartCopy和handleReturnCode函数。下面来看InstallParams是如何实现这两个函数的。 (2) InstallParams分析 先来看派生类InstallParams的handleStartCopy函数,代码如下: **PackageManagerService::InstallParams.handleStartCopy** ~~~ public void handleStartCopy() throwsRemoteException { int ret= PackageManager.INSTALL_SUCCEEDED; finalboolean fwdLocked = //本书不考虑fwdLocked的情况 (flags &PackageManager.INSTALL_FORWARD_LOCK) != 0; //根据adb install的参数,判断安装位置 finalboolean onSd = (flags & PackageManager.INSTALL_EXTERNAL) != 0; finalboolean onInt = (flags & PackageManager.INSTALL_INTERNAL) != 0; PackageInfoLite pkgLite = null; if(onInt && onSd) { //APK不能同时安装在内部存储和SD卡上 ret =PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } elseif (fwdLocked && onSd) { //fwdLocked的应用不能安装在SD卡上 ret =PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else { finallong lowThreshold; //获取DeviceStorageMonitorService的binder客户端 finalDeviceStorageMonitorService dsm = (DeviceStorageMonitorService) ServiceManager.getService( DeviceStorageMonitorService.SERVICE); if(dsm == null) { lowThreshold = 0L; }else { //从DSMS查询内部空间最小余量,默认是总空间的10% lowThreshold = dsm.getMemoryLowThreshold(); } try { //授权DefContainerService URI读权限 mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,Intent.FLAG_GRANT_READ_URI_PERMISSION); //①调用DCS的getMinimalPackageInfo函数,得到一个PackageLite对象 pkgLite =mContainerService.getMinimalPackageInfo(packageURI, flags,lowThreshold); }finally ......//撤销URI授权 //PacakgeLite的recommendedInstallLocation成员变量保存该APK推荐的安装路径 int loc =pkgLite.recommendedInstallLocation; if (loc== PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) { ret= PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION; } else if......{ } else { //②根据DCS返回的安装路径,还需要调用installLocationPolicy进行检查 loc =installLocationPolicy(pkgLite, flags); if(!onSd && !onInt) { if(loc == PackageHelper.RECOMMEND_INSTALL_EXTERNAL) { flags |= PackageManager.INSTALL_EXTERNAL; flags &=~PackageManager.INSTALL_INTERNAL; } ......//处理安装位置为内部存储的情况 } } } //③创建一个安装参数对象,对于安装位置为内部存储的情况,args的真实类型为FileInstallArgs finalInstallArgs args = createInstallArgs(this); mArgs =args; if (ret== PackageManager.INSTALL_SUCCEEDED) { final int requiredUid = mRequiredVerifierPackage == null ? -1 :getPackageUid(mRequiredVerifierPackage); if(requiredUid != -1 && isVerificationEnabled()) { ......//④待会再讨论verification的处理 }else { //⑤调用args的copyApk函数 ret= args.copyApk(mContainerService, true); } } mRet =ret;//确定返回值 } ~~~ 在以上代码中,一共列出了五个关键点,总结如下: - 调用DCS的getMinimalPackageInfo函数,将得到一个PackageLite对象,该对象是一个轻量级的用于描述APK的结构(相比PackageParser.Package来说)。在这段代码逻辑中,主要想取得其recommendedInstallLocation的值。此值表示该APK推荐的安装路径。 - 调用installLocationPolicy检查推荐的安装路径。例如系统Package不允许安装在SD卡上。 - createInstallArgs将根据安装位置创建不同的InstallArgs。如果是内部存储,则返回FileInstallArgs,否则为SdInstallArgs。 - 在正式安装前,应先对该APK进行必要的检查。这部分代码后续再介绍。 - 调用InstallArgs的copyApk。对本例来说,将调用FileInstallArgs的copyApk函数。 下面围绕这五个基本关键点展开分析,其中installLocationPolicy和createInstallArgs比较简单,读者可自行研究。 3. handleStartCopy分析 (1) DefaultContainerService分析 首先分析DCS的getMinimalPackageInfo函数,其代码如下: **DefaultContainerService.java::getMinimalPackageInfo函数** ~~~ public PackageInfoLite getMinimalPackageInfo(finalUri fileUri, int flags, longthreshold) { //注意该函数的参数:fileUri指向该APK的文件路径(此时还在/data/local/tmp下) PackageInfoLite ret = new PackageInfoLite(); ...... Stringscheme = fileUri.getScheme(); ...... StringarchiveFilePath = fileUri.getPath(); DisplayMetrics metrics = new DisplayMetrics(); metrics.setToDefaults(); //调用PackageParser的parsePackageLite解析该APK文件 PackageParser.PackageLite pkg = PackageParser.parsePackageLite(archiveFilePath,0); if (pkg== null) {//解析失败 ......//设置错误值 returnret; } ret.packageName = pkg.packageName; ret.installLocation = pkg.installLocation; ret.verifiers = pkg.verifiers; //调用recommendAppInstallLocation,取得一个合理的安装位置 ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation,archiveFilePath, flags, threshold); returnret; } ~~~ APK可在AndroidManifest.xml中声明一个安装位置,不过DCS除了解析该位置外,还需要做进一步检查,这个工作由recommendAppInstallLocation函数完成,代码如下: **DefaultContainerService.java::recommendAppInstallLocation函数** ~~~ private int recommendAppInstallLocation(intinstallLocation, StringarchiveFilePath, int flags,long threshold) { int prefer; booleancheckBoth = false; check_inner: { if((flags & PackageManager.INSTALL_FORWARD_LOCK) != 0) { prefer = PREFER_INTERNAL; break check_inner; //根据FOWRAD_LOCK的情况,只能安装在内部存储 } elseif ((flags & PackageManager.INSTALL_INTERNAL) != 0) { prefer = PREFER_INTERNAL; break check_inner; } ......//检查各种情况 } else if(installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { prefer= PREFER_INTERNAL;//一般设定的位置为AUTO,默认是内部空间 checkBoth = true; //设置checkBoth为true breakcheck_inner; } //查询settings数据库中的secure表,获取用户设置的安装路径 intinstallPreference = Settings.System.getInt(getApplicationContext() .getContentResolver(), Settings.Secure.DEFAULT_INSTALL_LOCATION, PackageHelper.APP_INSTALL_AUTO); if(installPreference == PackageHelper.APP_INSTALL_INTERNAL) { prefer = PREFER_INTERNAL; break check_inner; } else if(installPreference == PackageHelper.APP_INSTALL_EXTERNAL) { prefer= PREFER_EXTERNAL; breakcheck_inner; } prefer =PREFER_INTERNAL; } //判断外部存储空间是否为模拟的,这部分内容我们以后再介绍 finalboolean emulated = Environment.isExternalStorageEmulated(); final FileapkFile = new File(archiveFilePath); booleanfitsOnInternal = false; if(checkBoth || prefer == PREFER_INTERNAL) { try {//检查内部存储空间是否足够大 fitsOnInternal = isUnderInternalThreshold(apkFile, threshold); } ...... } booleanfitsOnSd = false; if(!emulated && (checkBoth || prefer == PREFER_EXTERNAL)) { try{ //检查外部存储空间是否足够大 fitsOnSd = isUnderExternalThreshold(apkFile); } ...... } if (prefer== PREFER_INTERNAL) { if(fitsOnInternal) {//返回推荐安装路径为内部空间 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; } } elseif (!emulated && prefer == PREFER_EXTERNAL) { if(fitsOnSd) {//返回推荐安装路径为外部空间 returnPackageHelper.RECOMMEND_INSTALL_EXTERNAL; } } if(checkBoth) { if(fitsOnInternal) {//如果内部存储满足条件,先返回内部空间 return PackageHelper.RECOMMEND_INSTALL_INTERNAL; }else if (!emulated && fitsOnSd) { return PackageHelper.RECOMMEND_INSTALL_EXTERNAL; } } ...... //到此,前几个条件都不满足,此处将根据情况返回一个明确的错误值 returnPackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE; } } ~~~ DCS的getMinimalPackageInfo函数为了得到一个推荐的安装路径做了不少工作,其中,各种安装策略交叉影响。这里总结一下相关的知识点: - APK在AndroidManifest.xml中设置的安装点默认为AUTO,在具体对应时倾向内部空间。 - 用户在Settings数据库中设置的安装位置。 - 检查外部存储或内部存储是否有足够空间。 (2) InstallArgs的copyApk函数分析 至此,我们已经得到了一个合适的安装位置(先略过Verification这一步)。下一步工作就由copyApk来完成。根据函数名可知该函数将完成APK文件的复制工作,此中会有蹊跷吗?来看下面的代码。 **PackageManagerService.java::InstallArgs.copyApk函数** ~~~ int copyApk(IMediaContainerService imcs, booleantemp) throws RemoteException { if (temp){ /* 本例中temp参数为true,createCopyFile将在/data/app下创建一个临时文件。 临时文件名为vmdl-随机数.tmp。为什么会用这样的文件名呢? 因为PKMS通过Linux的inotify机制监控了/data/app,目录,如果新复制生成的文件名后缀 为apk,将触发PKMS扫描。为了防止发生这种情况,这里复制生成的文件才有了 如此奇怪的名字 */ createCopyFile(); } FilecodeFile = new File(codeFileName); ...... ParcelFileDescriptor out = null; try { out =ParcelFileDescriptor.open(codeFile, ParcelFileDescriptor.MODE_READ_WRITE); }...... int ret= PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; try { mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, packageURI,Intent.FLAG_GRANT_READ_URI_PERMISSION); //调用DCS的copyResource,该函数将执行复制操作,最终结果是/data/local/tmp //下的APK文件被复制到/data/app下,文件名也被换成vmdl-随机数.tmp ret= imcs.copyResource(packageURI, out); }finally { ......//关闭out,撤销URI授权 } returnret; } ~~~ 关于临时文件,这里提供一个示例,如图4-9所示。 :-: ![](http://img.blog.csdn.net/20150803111034657?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图4-9 createCopyFile生成的临时文件 由图4-9可知:/data/app下有两个文件,第一个是正常的APK文件,第二个是createCopyFile生成的临时文件。 4. handleReturnCode分析 在HandlerParams的startCopy函数中,handleStartCopy执行完之后,将调用handleReturnCode开展后续工作,代码如下: **PackageManagerService.java::InstallParams.HandleParams** void handleReturnCode() { if(mArgs != null) { //调用processPendingInstall函数,mArgs指向之前创建的FileInstallArgs对象 processPendingInstall(mArgs, mRet); } } **PackageManagerService.java::** ~~~ private void processPendingInstall(finalInstallArgs args, final intcurrentStatus) { //向mHandler中抛一个Runnable对象 mHandler.post(new Runnable() { publicvoid run() { mHandler.removeCallbacks(this); //创建一个PackageInstalledInfo对象, PackageInstalledInfo res = new PackageInstalledInfo(); res.returnCode = currentStatus; res.uid = -1; res.pkg = null; res.removedInfo = new PackageRemovedInfo(); if(res.returnCode == PackageManager.INSTALL_SUCCEEDED) { //①调用FileInstallArgs的doPreInstall args.doPreInstall(res.returnCode); synchronized (mInstallLock) { //②调用installPackageLI进行安装 installPackageLI(args, true, res); } //③调用FileInstallArgs的doPostInstall args.doPostInstall(res.returnCode); } final boolean update = res.removedInfo.removedPackage != null; boolean doRestore = (!update&& res.pkg != null && res.pkg.applicationInfo.backupAgentName!= null); int token;//计算一个ID号 if(mNextInstallToken < 0) mNextInstallToken = 1; token = mNextInstallToken++; //创建一个PostInstallData对象 PostInstallData data = new PostInstallData(args, res); //保存到mRunningInstalls结构中,以token为key mRunningInstalls.put(token, data); if (res.returnCode ==PackageManager.INSTALL_SUCCEEDED && doRestore) { ......//备份恢复的情况暂时不考虑 } if(!doRestore) { //④抛一个POST_INSTALL消息给mHandler进行处理 Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); mHandler.sendMessage(msg); } } }); } ~~~ 由上面代码可知,handleReturnCode主要做了4件事情: - 调用InstallArgs的doPreInstall函数,在本例中是FileInstallArgs的doPreInstall函数。 - 调用PKMS的installPackageLI函数进行APK安装,该函数内部将调用InstallArgs的doRename对临时文件进行改名。另外,还需要扫描此APK文件。此过程和之前介绍的“扫描系统Package”一节的内容类似。至此,该APK中的私有财产就全部被登记到PKMS内部进行保存了。 - 调用InstallArgs的doPostInstall函数,在本例中是FileInstallArgs的doPostInstall函数。 - 此时,该APK已经安装完成(不论失败还是成功),继续向mHandler抛送一个POST_INSTALL消息,该消息携带一个token,通过它可从mRunningInstalls数组中取得一个PostInstallData对象。 * * * * * **提示**:对于FileInstallArgs来说,其doPreInstall和doPostInstall都比较简单,读者可自行阅读相关代码。另外,读者也可自行研究PKMS的installPackageLI函数。 * * * * * 这里介绍一下FileInstallArgs的doRename函数,它的功能是将临时文件改名,最终的文件的名称一般为“包名-数字.apk”。其中,数字是一个index,从1开始。读者可参考图4-9中/data/app目录下第一个文件的文件名。 5. POST_INSTALL处理 现在需要处理POST_INSTALL消息,因为adb install还等着安装结果呢。相关代码如下: **PackageManagerService.java::doHandleMessage函数** ~~~ ......//接前面的switch/case case POST_INSTALL: { PostInstallData data = mRunningInstalls.get(msg.arg1); mRunningInstalls.delete(msg.arg1); booleandeleteOld = false; if (data!= null) { InstallArgs args = data.args; PackageInstalledInfo res = data.res; if(res.returnCode == PackageManager.INSTALL_SUCCEEDED) { res.removedInfo.sendBroadcast(false, true); Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, res.uid); final boolean update = res.removedInfo.removedPackage != null; if (update) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } //发送PACKAGE_ADDED广播 sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, res.pkg.applicationInfo.packageName,extras, null, null); if (update) { /* 如果是APK升级,那么发送PACKAGE_REPLACE和MY_PACKAGE_REPLACED广播。 二者不同之处在于PACKAGE_REPLACE将携带一个extra信息 */ } Runtime.getRuntime().gc(); if(deleteOld) { synchronized (mInstallLock) { //调用FileInstallArgs的doPostDeleteLI进行资源清理 res.removedInfo.args.doPostDeleteLI(true); } } if(args.observer != null) { try { // 向pm通知安装的结果 args.observer.packageInstalled(res.name, res.returnCode); } ...... } break; ~~~