本文主要内容
静默安装apk安装流程简析installd进程意义最近工作上遇到静默安装相关的内容,顺便学习一下apk安装的知识
静默安装
静默安装是指apk无感安装,不需要用户确认。目前一般来说有两种方法可以实现:
类似adb install指令使用PackageManager的installPackage接口,需要权限且是系统应用才行第一种方法的示例代码为:
public static int installSilent(String filePath) { File file = new File(filePath); if (filePath == null || filePath.length() == 0 || file == null || file.length() <= 0 || !file.exists() || !file.isFile()) { return 1; } String[] args = { "pm", "install", "-r", filePath }; ProcessBuilder processBuilder = new ProcessBuilder(args); Process process = null; BufferedReader successResult = null; BufferedReader errorResult = null; StringBuilder successMsg = new StringBuilder(); StringBuilder errorMsg = new StringBuilder(); int result; try { process = processBuilder.start(); successResult = new BufferedReader(new InputStreamReader(process.getInputStream())); errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream())); String s; while ((s = successResult.readLine()) != null) { successMsg.append(s); } while ((s = errorResult.readLine()) != null) { errorMsg.append(s); } } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } finally { try { if (successResult != null) { successResult.close(); } if (errorResult != null) { errorResult.close(); } } catch (IOException e) { e.printStackTrace(); } if (process != null) { process.destroy(); } } if (successMsg.toString().contains("Success") || successMsg.toString().contains("success")) { result = 0; } else { result = 2; } Log.d("test-test", "successMsg:" + successMsg + ", ErrorMsg:" + errorMsg); return result; }
使用installPackage接口,是需要系统应用,且需要申明权限的。
有一点需要注意,在安装中pms会检查另一种权限,类似于应用能否发通知,如果isUserRestricted返回为true,安装会失败,那么需要调用相关接口,重新设置下
if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) { try { if (observer != null) { observer.onPackageInstalled("", INSTALL_FAILED_USER_RESTRICTED, null, null); } } catch (RemoteException re) { } return; }
apk安装流程简析
PackageManager是一个抽象类,应用调用pm安装apk,这中间会发生跨进程调用,因为pms是运行在system进程中的。
为了更方便用户调用,于是Android封装了pm类供用户调用。在ContextImpl中,获取pm,实质上是获得了pm的实现类,ApplicationPackageManager。

查看pm的installPackage类
public abstract void installPackage( Uri packageURI, PackageInstallObserver observer, int flags, String installerPackageName);查看pm的子类ApplicationPackageManager,发现最终将调用pms的installPackage方法public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer, int installFlags, String installerPackageName, VerificationParams verificationParams, String packageAbiOverride, int userId) { //权限检查,INSTALL_PACKAGES权限及其它权限检查,权限并不只有一种 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null); final int callingUid = Binder.getCallingUid(); enforceCrossUserPermission(callingUid, userId, true, true, "installPackageAsUser"); if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) { try { if (observer != null) { observer.onPackageInstalled("", INSTALL_FAILED_USER_RESTRICTED, null, null); } } catch (RemoteException re) { } return; } //使用各参数,构建InstallParams,发送msg,交由handler处理 final File originFile = new File(originPath); final OriginInfo origin = OriginInfo.fromUntrustedFile(originFile); final Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = new InstallParams(origin, observer, installFlags, installerPackageName, verificationParams, user, packageAbiOverride); mHandler.sendMessage(msg);}
installPackageAsUser方法中主要完成两件事情,1是权限检查,2是构建InstallParams,然后发送INIT_COPY的msg。
此处比较有意思,因为pms是跨进程调用,所以installPackageAsUser方法运行在binder线程中,为什么还要将耗时的工作交给mHandler(mHandler在pms的构造方法中初始化,也是运行在一个单独的线程中,不是主线程)呢?
客户端调用binder服务端方法,客户端是会阻塞的,如果服务端的方法耗时较长,不利于客户端的流畅性。所以虽然服务端自己的主线程没问题,但客户端有问题,个人也经常这样考虑,使用handler处理,handler是异步,完全不会阻塞。不知道android的设计人员是不是也这样考虑的
void doHandleMessage(Message msg) { switch (msg.what) { case INIT_COPY: { HandlerParams params = (HandlerParams) msg.obj; int idx = mPendingInstalls.size(); //如果没有绑定IMediaContainerService服务,则先绑定服务 if (!mBound) { //绑定服务 if (!connectToService()) { params.serviceError(); return; } else { //绑定服务成功后,将params添加到mPendingInstalls队列当中 mPendingInstalls.add(idx, params); } } else { mPendingInstalls.add(idx, params); if (idx == 0) { mHandler.sendEmptyMessage(MCS_BOUND); } } break; } }}
在INIT_COPY的消息处理中,先查看当前有没有绑定IMediaContainerService服务,当服务绑定成功的时候
public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected"); IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service); mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs)); }
mHandler将发送MCS_BOUND消息,同时也会将params添加到mPendingInstalls队列当中。mPendingInstalls添加元素比较有意思,先查看mPendingInstalls的size,然后在size位置添加新元素,当元素使用完以后,则删除0位置上的元素,这就保证了先入先出。
void doHandleMessage(Message msg) { switch (msg.what) { case MCS_BOUND: { if (mContainerService == null) { //出錯 } else if (mPendingInstalls.size() > 0) { HandlerParams params = mPendingInstalls.get(0); if (params != null) { if (params.startCopy()) { // 删除已经完成的任务 if (mPendingInstalls.size() > 0) { mPendingInstalls.remove(0); } if (mPendingInstalls.size() == 0) { if (mBound) { //如果任务队列中没有任务了,则发送MCS_UNBIND,解绑服务 removeMessages(MCS_UNBIND); Message ubmsg = obtainMessage(MCS_UNBIND); sendMessageDelayed(ubmsg, 10000); } } else { //如果任务队列中还有新任务,则继续发送MCS_BOUND消息, 循环执行新消息 mHandler.sendEmptyMessage(MCS_BOUND); } } } } break; } }}
MCS_BOUND消息的处理中,调用startCopy方法,完成apk的拷贝。
final boolean startCopy() { boolean res; try { if (++mRetries > MAX_RETRIES) { //如果重试超过三次,则直接放弃 mHandler.sendEmptyMessage(MCS_GIVE_UP); handleServiceError(); return false; } else { //真正开始复制的地方 handleStartCopy(); res = true; } } catch (RemoteException e) { mHandler.sendEmptyMessage(MCS_RECONNECT); res = false; } //复制完成后后续事宜 handleReturnCode(); return res;}
继续查看handleStartCopy方法。之前绑定的服务,在此处主要有两个功能,一是解析apk中的基本信息,比如包名、版本号、安装位置等
pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags, packageAbiOverride);
另外则是执行拷贝。当拷贝执行完以后,handleStartCopy方法也就告一段落了。我们继续看handleReturnCode方法
private void processPendingInstall(final InstallArgs args, final int currentStatus) { // Queue up an async operation since the package installation may take a little while. mHandler.post(new Runnable() { public void run() { mHandler.removeCallbacks(this); // Result object to be returned PackageInstalledInfo res = new PackageInstalledInfo(); res.returnCode = currentStatus; res.uid = -1; res.pkg = null; res.removedInfo = new PackageRemovedInfo(); if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { //预安装 args.doPreInstall(res.returnCode); synchronized (mInstallLock) { //安装 installPackageLI(args, res); } //结束安装 args.doPostInstall(res.returnCode, res.uid); } if (!doRestore) { //如果完成安装的msg,package add的广播将在此处发送 Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); mHandler.sendMessage(msg); } } });}
如上所示,最重要的4个步骤已经标明,预安装,安装应用以及完成安装,并发送package add等。其中doPreInstall和doPostInstall方法较简单,不再复述,主要查看installPackageLI方法。
installPackageLI方法非常长,它需要验证apk的签名文件,并且详细解析apk中的所有activity、service等信息并加以保存,方法非常非常的长
//收集签名并验证try { pp.collectCertificates(pkg, parseFlags); pp.collectManifestDigest(pkg); } catch (PackageParserException e) { res.setError("Failed collect during installPackageLI", e); return; }//详细解析apkPackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanFlags, System.currentTimeMillis(), user);
代码实在是太长了,读起来非常非常累,以后再详细解析
当handleReturnCode也完成后,mHandler将处理POST_INSTALL消息,完成安装,发送package add 广播
installd进程意义
这一小节将完全是个人的猜测,首先pms是运行在system的进程中的,而android中使用system的uid,并没有访问应用程序目录的权限(不能访问/data/data/包名目录)。所以在pms之外还有一个进程存在,installd,它的代码位置是:/frameworks/native/cmds/installd/installd.cpp
它也有install或者odex优化的方法,但在install方法中只看到创建data/data/包名 等目录,并没有其它操作。这点需要后续去验证,installd与java端的installer类以socket通信。
作者:某昆https://www.jianshu.com/p/39402a37f705
如果想进技术群交流,关注公众号并在后台回复 “加群”
学历和技术固然重要,但更重要的是圈子。微信中长按识别如下二维码加入我们吧!