Android Apk安装过程解析

3,460 阅读14分钟
原文链接: mp.weixin.qq.com

本文主要内容

静默安装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

如果想进技术群交流,关注公众号并在后台回复 “加群”

学历和技术固然重要,但更重要的是圈子。微信中长按识别如下二维码加入我们吧!