Android PMS处理APK的复制

前言

在上一篇文章Android包管理机制之PackageInstaller安装APK中,我们学习了PackageInstaller是如何安装APK的,最后会将APK的信息交由PMS处理。那么PMS是如何处理的呢?主要是APK的复制和安装,由于公号文章字数的限制,这篇文章只能介绍 PMS处理APK的复制,APK安装过程会在后续文章讲解。

1.PackageHandler处理安装消息

APK的信息交由PMS后,PMS通过向PackageHandler发送消息来驱动APK的复制和安装工作。 先来查看PackageHandler处理安装消息的调用时序图。

接着上一篇文章的代码逻辑来查看PMS的installStage方法。 frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

 void installStage(String packageName, File stagedDir, String stagedCid,
            IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
            String installerPackageName, int installerUid, UserHandle user,
            Certificate[][] certificates) {
        ...
        final Message msg = mHandler.obtainMessage(INIT_COPY);//1
        final int installReason = fixUpInstallReason(installerPackageName, installerUid,
                sessionParams.installReason);
        final InstallParams params = new InstallParams(origin, null, observer,
                sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
                verificationInfo, user, sessionParams.abiOverride,
                sessionParams.grantedRuntimePermissions, certificates, installReason);//2
        params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
        msg.obj = params;
        ...
        mHandler.sendMessage(msg);//3
    }

注释2处创建InstallParams,它对应于包的安装数据。注释1处创建了类型为INIT_COPY的消息,在注释3处将InstallParams通过消息发送出去。

1.1 对INIT_COPY的消息的处理

处理INIT_COPY类型的消息的代码如下所示。 frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#PackageHandler

 void doHandleMessage(Message msg) {
     switch (msg.what) {
         case INIT_COPY: {
            HandlerParams params = (HandlerParams) msg.obj;
            int idx = mPendingInstalls.size();
            if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params);
                 //mBound用于标识是否绑定了服务,默认值为false
               if (!mBound) {//1
                   Trace.asyncTraceBegin(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                      System.identityHashCode(mHandler));
                  //如果没有绑定服务,重新绑定,connectToService方法内部如果绑定成功会将mBound置为true
                  if (!connectToService()) {//2
                      Slog.e(TAG, "Failed to bind to media container service");
                      params.serviceError();
                      Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                                    System.identityHashCode(mHandler));
                     if (params.traceMethod != null) {
                         Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, params.traceMethod,
                                        params.traceCookie);
                        }
                            //绑定服务失败则return
                       return;
                      } else {
                            //绑定服务成功,将请求添加到ArrayList类型的mPendingInstalls中,等待处理
                        mPendingInstalls.add(idx, params);
                        }
                    } else {
                    //已经绑定服务
                      mPendingInstalls.add(idx, params);
                       if (idx == 0) {
                          mHandler.sendEmptyMessage(MCS_BOUND);//3
                        }
                    }
                    break;
                }
                ....
        }
    }
 }

PackageHandler继承自Handler,它被定义在PMS中,doHandleMessage方法用于处理各个类型的消息,来查看对INIT_COPY类型消息的处理。注释1处的mBound用于标识是否绑定了DefaultContainerService,默认值为false。DefaultContainerService是用于检查和复制可移动文件的服务,这是一个比较耗时的操作,因此DefaultContainerService没有和PMS运行在同一进程中,它运行在com.android.defcontainer进程,通过IMediaContainerService和PMS进行IPC通信,如下图所示。

注释2处的connectToService方法用来绑定DefaultContainerService,注释3处发送MCS_BOUND类型的消息,触发处理第一个安装请求。 查看注释2处的connectToService方法: frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#PackageHandler

  private boolean connectToService() {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" +
                    " DefaultContainerService");
            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
            Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
            if (mContext.bindServiceAsUser(service, mDefContainerConn,
                    Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) {//1
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                mBound = true;//2
                return true;
            }
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            return false;
        }

注释2处如果绑定DefaultContainerService成功,mBound会置为ture 。注释1处的bindServiceAsUser方法会传入mDefContainerConn,bindServiceAsUser方法的处理逻辑和我们调用bindService是类似的,服务建立连接后,会调用onServiceConnected方法: frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

  class DefaultContainerConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName name, IBinder service) {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
            final IMediaContainerService imcs = IMediaContainerService.Stub
                    .asInterface(Binder.allowBlocking(service));
            mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, Object));//1
        }
        public void onServiceDisconnected(ComponentName name) {
            if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected");
        }
    }

注释1处发送了MCS_BOUND类型的消息,与PackageHandler.doHandleMessage方法的注释3处不同的是,这里发送消息带了Object类型的参数,这里会对这两种情况来进行讲解,一种是消息不带Object类型的参数,一种是消息带Object类型的参数。

1.2 对MCS_BOUND类型的消息的处理

消息不带Object类型的参数

查看对MCS_BOUND类型消息的处理:

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

case MCS_BOUND: {
            if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
            if (msg.obj != null) {//1
                mContainerService = (IMediaContainerService) msg.obj;//2
                Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "bindingMCS",
                        System.identityHashCode(mHandler));
            }
            if (mContainerService == null) {//3
                if (!mBound) {//4
                      Slog.e(TAG, "Cannot bind to media container service");
                      for (HandlerParams params : mPendingInstalls) {
                          params.serviceError();//5
                          Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
                                        System.identityHashCode(params));
                          if (params.traceMethod != null) {
                          Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER,
                           params.traceMethod, params.traceCookie);
                          }
                          return;
                      }   
                          //绑定失败,清空安装请求队列
                          mPendingInstalls.clear();
                   } else {
                          //继续等待绑定服务
                          Slog.w(TAG, "Waiting to connect to media container service");
                   }
            } else if (mPendingInstalls.size() > 0) {
              ...
              else {
                   Slog.w(TAG, "Empty queue");
                   }
            break;
        }

如果消息不带Object类型的参数,就无法满足注释1处的条件,注释2处的IMediaContainerService类型的mContainerService也无法被赋值,这样就满足了注释3处的条件。 如果满足注释4处的条件,说明还没有绑定服务,而此前已经在PackageHandler.doHandleMessage方法的注释2处调用绑定服务的方法了,这显然是不正常的,因此在注释5处负责处理服务发生错误的情况。如果不满足注释4处的条件,说明已经绑定服务了,就会打印出系统log,告知用户等待系统绑定服务。

消息带Object类型的参数 frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

case MCS_BOUND: {
            if (DEBUG_INSTALL) Slog.i(TAG, "mcs_bound");
            if (msg.obj != null) {
            ...
            }
            if (mContainerService == null) {//1
             ...
            } else if (mPendingInstalls.size() > 0) {//2
                          HandlerParams params = mPendingInstalls.get(0);//3
                        if (params != null) {
                            Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
                                    System.identityHashCode(params));
                            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startCopy");
                            if (params.startCopy()) {//4
                                if (DEBUG_SD_INSTALL) Log.i(TAG,
                                        "Checking for more work or unbind...");
                                 //如果APK安装成功,删除本次安装请求
                                if (mPendingInstalls.size() > 0) {
                                    mPendingInstalls.remove(0);
                                }
                                if (mPendingInstalls.size() == 0) {
                                    if (mBound) {
                                    //如果没有安装请求了,发送解绑服务的请求
                                        if (DEBUG_SD_INSTALL) Log.i(TAG,
                                                "Posting delayed MCS_UNBIND");
                                        removeMessages(MCS_UNBIND);
                                        Message ubmsg = obtainMessage(MCS_UNBIND);
                                        sendMessageDelayed(ubmsg, 10000);
                                    }
                                } else {
                                    if (DEBUG_SD_INSTALL) Log.i(TAG,
                                            "Posting MCS_BOUND for next work");
                                   //如果还有其他的安装请求,接着发送MCS_BOUND消息继续处理剩余的安装请求       
                                    mHandler.sendEmptyMessage(MCS_BOUND);//5
                                }
                            }
                            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                        }else {
                        Slog.w(TAG, "Empty queue");//6
                    }
            break;
        }

如果MCS_BOUND类型消息带Object类型的参数就不会满足注释1处的条件,就会调用注释2处的判断,如果安装请求数不大于0就会打印出注释6处的log,说明安装请求队列是空的。安装完一个APK后,就会在注释5处发出MSC_BOUND消息,继续处理剩下的安装请求直到安装请求队列为空。 注释3处得到安装请求队列第一个请求HandlerParams ,如果HandlerParams 不为null就会调用注释4处的HandlerParams的startCopy方法,用于开始复制APK的流程。

2.复制APK

先来查看复制APK的时序图。

HandlerParams是PMS中的抽象类,它的实现类为PMS的内部类InstallParams。HandlerParams的startCopy方法如下所示。 frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#HandlerParams

 final boolean startCopy() {
            boolean res;
            try {
                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
                //startCopy方法尝试的次数,超过了4次,就放弃这个安装请求
                if (++mRetries > MAX_RETRIES) {//1
                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                    mHandler.sendEmptyMessage(MCS_GIVE_UP);//2
                    handleServiceError();
                    return false;
                } else {
                    handleStartCopy();//3
                    res = true;
                }
            } catch (RemoteException e) {
                if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
                mHandler.sendEmptyMessage(MCS_RECONNECT);
                res = false;
            }
            handleReturnCode();//4
            return res;
        }

注释1处的mRetries用于记录startCopy方法调用的次数,调用startCopy方法时会先自动加1,如果次数大于4次就放弃这个安装请求:在注释2处发送MCS_GIVE_UP类型消息,将第一个安装请求(本次安装请求)从安装请求队列mPendingInstalls中移除掉。注释4处用于处理复制APK后的安装APK逻辑,第3小节中会再次提到它。注释3处调用了抽象方法handleStartCopy,它的实现在InstallParams中,如下所示。 frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#InstallParams

     public void handleStartCopy() throws RemoteException {
            ...
            //确定APK的安装位置。onSd:安装到SD卡, onInt:内部存储即Data分区,ephemeral:安装到临时存储(Instant Apps安装)            
            final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
            final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
            final boolean ephemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
            PackageInfoLite pkgLite = null;
            if (onInt && onSd) {
              // APK不能同时安装在SD卡和Data分区
                Slog.w(TAG, "Conflicting flags specified for installing on both internal and external");
                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
              //安装标志冲突,Instant Apps不能安装到SD卡中
            } else if (onSd && ephemeral) {
                Slog.w(TAG,  "Conflicting flags specified for installing ephemeral on external");
                ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
            } else {
                 //获取APK的少量的信息
                pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags,
                        packageAbiOverride);//1
                if (DEBUG_EPHEMERAL && ephemeral) {
                    Slog.v(TAG, "pkgLite for install: " + pkgLite);
                }
            ...
            if (ret == PackageManager.INSTALL_SUCCEEDED) {
                 //判断安装的位置
                int loc = pkgLite.recommendedInstallLocation;
                if (loc == PackageHelper.RECOMMEND_FAILED_INVALID_LOCATION) {
                    ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
                } else if (loc == PackageHelper.RECOMMEND_FAILED_ALREADY_EXISTS) {
                    ret = PackageManager.INSTALL_FAILED_ALREADY_EXISTS;
                } 
                ...
                }else{
                  loc = installLocationPolicy(pkgLite);//2
                  ...
                }
            }
            //根据InstallParams创建InstallArgs对象
            final InstallArgs args = createInstallArgs(this);//3
            mArgs = args;
            if (ret == PackageManager.INSTALL_SUCCEEDED) {
                   ...
                if (!origin.existing && requiredUid != -1
                        && isVerificationEnabled(
                              verifierUser.getIdentifier(), installFlags, installerUid)) {
                      ...
                } else{
                    ret = args.copyApk(mContainerService, true);//4
                }
            }
            mRet = ret;
        }

handleStartCopy方法的代码很多,这里截取关键的部分。 注释1处通过IMediaContainerService跨进程调用DefaultContainerService的getMinimalPackageInfo方法,该方法轻量解析APK并得到APK的少量信息,轻量解析的原因是这里不需要得到APK的全部信息,APK的少量信息会封装到PackageInfoLite中。接着在注释2处确定APK的安装位置。注释3处创建了InstallArgs,InstallArgs 是一个抽象类,定义了APK的安装逻辑,比如复制和重命名APK等,它有3个子类,都被定义在PMS中,如下图所示。

其中FileInstallArgs用于处理安装到非ASEC的存储空间的APK,也就是内部存储空间(Data分区),AsecInstallArgs用于处理安装到ASEC中(mnt/asec)即SD卡中的APK。MoveInstallArgs用于处理已安装APK的移动的逻辑。 对APK进行检查后就会在注释4处调用InstallArgs的copyApk方法进行安装。 不同的InstallArgs子类会有着不同的处理,这里以FileInstallArgs为例。FileInstallArgs的copyApk方法中会直接return FileInstallArgs的doCopyApk方法: frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java#FileInstallArgs

   private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
           ...
            try {
                final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
                //创建临时文件存储目录
                final File tempDir =
                        mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);//1
                codeFile = tempDir;
                resourceFile = tempDir;
            } catch (IOException e) {
                Slog.w(TAG, "Failed to create copy file: " + e);
                return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
            }
            ...
            int ret = PackageManager.INSTALL_SUCCEEDED;
            ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);//2
            ...
            return ret;
        }

注释1处用于创建临时存储目录,比如/data/app/vmdl18300388.tmp,其中18300388是安装的sessionId。注释2处通过IMediaContainerService跨进程调用DefaultContainerService的copyPackage方法,这个方法会在DefaultContainerService所在的进程中将APK复制到临时存储目录,比如/data/app/vmdl18300388.tmp/base.apk。目前为止APK的复制工作就完成了,接着就是APK的安装过程了。

3.总结

本文主要讲解了PMS是如何处理APK复制的,主要有两个步骤:

  1. PackageInstaller安装APK时会将APK的信息交由PMS处理,PMS通过向PackageHandler发送消息来驱动APK的复制和安装工作。
  2. PMS发送INIT_COPY和MCS_BOUND类型的消息,控制PackageHandler来绑定DefaultContainerService,完成复制APK等工作。

原文发布于微信公众号 - 刘望舒(liuwangshuAndroid)

原文发表时间:2018-07-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java与Android技术栈

Android App安全防范措施的小结

关闭打印的日志,防止日志中的调试信息被看到。如果在网络框架中使用了日志,那就更加需要关闭了。

1102
来自专栏yang0range

MVVM的数据持久化(二)——ROOM的使用

MVVM框架的搭建(一)——背景 MVVM框架的搭建(二)——项目搭建 MVVM框架的搭建(三)——网络请求 MVVM的数据持久化(一)——ROOM的集成

1055
来自专栏智能大石头

线程池ThreadPool及Task调度机制分析

近1年,偶尔发生应用系统启动时某些操作超时的问题,特别在使用4核心Surface以后。笔记本和台式机比较少遇到,服务器则基本上没有遇到过。

1220
来自专栏Spark生态圈

[spark] 调度模式(FIFO&FAIR)

spark应用程序的调度体现在两个地方,第一个是Yarn对spark应用间的调度,第二个是spark应用内(同一个SparkContext)的多个TaskSet...

1162
来自专栏高性能服务器开发

从零学习开源项目系列(四)LogServer源码探究

这是从零学习开源项目的第四篇,上一篇是《从零学习开源项目系列(三) CSBattleMgr服务源码研究》,这篇文章我们一起来学习LogServer,中文意思可能...

2632
来自专栏码匠的流水账

jvm排查工具箱jvm-tools

本文主要介绍的是一款jvm排查工具箱:jvm-tools。除了对基本jvm封装外,还提供了jmx访问以及火焰图的生成。

1151
来自专栏Android 研究

APK安装流程详解7——PackageManagerService的启动流程(上)

我们看到在SystemServer无参构造函数里面就是初始化mFactoryTestMode

2011
来自专栏Android 研究

APK安装流程详解2——PackageManager简介

俗话说的好,得中原者,得天下,那么想要了解Android的安装了流程就不得不提及一个重要的类"PackageManager"我们就先来了解这两个类

3893
来自专栏马洪彪

Java生成条码二维码

一、概述 可用barcode4j或zxing等第三方库,推荐zxing。 barcode4j资料链接:http://barcode4j.sourceforge....

5018
来自专栏Kubernetes

深度解析Kubernetes Local Persistent Volume(二)

摘要:上一篇博客”深度解析Kubernetes Local Persistent Volume(一)“对local volume的基本原理和注意事项进行了分析,...

1.5K3

扫码关注云+社区

领取腾讯云代金券