本文主要内容
静默安装 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://juejin.im/entry/5b30e9586fb9a00e4966f19a