adb install 流程分析

本文详细解析了Android系统中ADB命令安装APK的过程。从命令行调用adb install开始,深入介绍了如何通过PM脚本启动app_process执行pm.jar,再到IPackageManager的交互过程,直至最终完成安装。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在Android系统中,应用的文件是以 .apk为结尾的文件,这个文件是如何被安装到Android系统中的? 那就是利用 adb install 这个命令, 大家可以在命令行内 打入 adb install <.apk文件路径> 进行安装。下面我们来分析它的原理。

首先该命令对应代码在commandline.cpp内,内容如下:

int adb_commandline(int argc, const char **argv) {
....
....
   else if (!strcmp(argv[0], "install")) {
        if (argc < 2) return usage();
        if (_use_legacy_install()) {
            return install_app_legacy(transport_type, serial, argc, argv);
        }
        return install_app(transport_type, serial, argc, argv);
    }

接下来会调用到 install_app函数. 在其内,会执行一个pm脚本,该脚本主要功能就是通过app_process来执行pm.jar 的main函数(有关于app_procee相关信息大家可以去查阅一下), pm.jar的main函数就相当于java进程的入口函数,下面对它进行分析(PM.java)

    public static void main(String[] args) {
        int exitCode = 1;
        try {
            exitCode = new Pm().run(args);
        } catch (Exception e) {
            Log.e(TAG, "Error", e);
            System.err.println("Error: " + e);
            if (e instanceof RemoteException) {
                System.err.println(PM_NOT_RUNNING_ERR);
            }
        }
        System.exit(exitCode);
    }

可以看到,直接new Pm() 然后执行了 run()函数.

public int run(String[] args) throws RemoteException {
        boolean validCommand = false;
        if (args.length < 1) {
            return showUsage();
        }
        mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));
        mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));
        mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));


        mInstaller = mPm.getPackageInstaller();

        mArgs = args;
        String op = args[0];
        mNextArg = 1;
        ....
        if ("install".equals(op)) {
            return runInstall();
        }

      ....

可以看到,上面获取了一个IPackageManager,它实际是一个PackageManagerService 的binder客户端,通过它调用pkms的方法,接下来处理 install 指令.

    private int runInstall() throws RemoteException {
        final InstallParams params = makeInstallParams();
        final String inPath = nextArg();
        ....
        final int sessionId     =doCreateSession(params.sessionParams,params.installerPacka.    geName, params.userId);
            ....
           if (doCommitSession(sessionId, false 
                    != PackageInstaller.STATUS_SUCCESS) {
                return 1;
            }
            ....

由上面代码可以看到

  1. 调用makeInstallParams()获取 InstallParams
  2. 调用doCreateSession 并一 InstallParams为参数 来获取sessionId
  3. 通过sessionId 调用 doCommitSession

下面我们来看doCommitSession

    private int doCommitSession(int sessionId, boolean logSuccess) throws RemoteException {
        PackageInstaller.Session session = null;
        try {
            session = new PackageInstaller.Session(
                    mInstaller.openSession(sessionId));
            final LocalIntentReceiver receiver = new LocalIntentReceiver();
            session.commit(receiver.getIntentSender());
            ....
            ....

可以看到,通过 session 来创建了一个PackageInstall.Session对象(注意mInstall.openSession()), 然后通过该对象调用 commit方法,我们继续深入。

   public void commit(@NonNull IntentSender statusReceiver) {
            try {
                mSession.commit(statusReceiver);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

可以看到直接在其内直接调用了 成员变量mSession的commit, 它的类型是IPackageInstallerSession对象,是通过上面的mInstaller.openSession来获取的,相当于PackageInstallerSession binder的客户端,既然客户端调用,那么就必然会调用到服务端相同名字的代码,下面我们来看PackageInstallerSession.commit()

    @Override
    public void commit(IntentSender statusReceiver) {
    ····
        //原子操作,防止并发
        mActiveCount.incrementAndGet();

        final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
                statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
        mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
    }

可以看到,它创建了一个PackageInstallObserverAdapter,它的作用是监听PKMS的安装结果。 然后通过mHandler发送了一个MSG_COMMIT的消息,该hander是通过传入callback方式来处理的,其callback为mHandlerCallback,

    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            // Cache package manager data without the lock held
            final PackageInfo pkgInfo = mPm.getPackageInfo(
                    params.appPackageName, PackageManager.GET_SIGNATURES /*flags*/, userId);
            final ApplicationInfo appInfo = mPm.getApplicationInfo(
                    params.appPackageName, 0, userId);

            synchronized (mLock) {
             ....
                try {
                    commitLocked(pkgInfo, appInfo);
                }
                ....
                return true;
            }
        }
    };

上面代码首先获取pkg与application 的信息,然后调用 commitLocked

    private void commitLocked(PackageInfo pkgInfo, ApplicationInfo appInfo)
            throws PackageManagerException {
        ····
         mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
                installerPackageName, installerUid, user, mCertificates);
               }

在上面方法中,我们直接到了最后一行,也就是通过PKMS的 binder客户端来调用了 installStage,最终会到PKMS内的installStage。

    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);//
        final InstallParams params = new InstallParams(origin, null, observer,
                sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
                verificationInfo, user, sessionParams.abiOverride,
                sessionParams.grantedRuntimePermissions, certificates);//
            ····
             mHandler.sendMessage(msg);

可以看到上面代码首先获取了一个 INIT_COPY消息,然后创建了一个InstallParams, 接着发送消息。

 public void handleMessage(Message msg) {
            try {
                doHandleMessage(msg);
            } finally {
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            }
        }


  void doHandleMessage(Message msg) {
            switch (msg.what) {
                case INIT_COPY: {
                ....
                if (!mBound) {
                       if (!connectToService()) {
                            params.serviceError();
                             ....
                        } else {
                       mPendingInstalls.add(idx, params);
                        }
                }else {
                        mPendingInstalls.add(idx, params);
                        if (idx == 0) {   mHandler.sendEmptyMessage(MCS_BOUND);
                        }
                    }

可以看到在doHandleMessage 县调用了connectToService(),其内主要是开启一个服务(DefalutContainerService),它的主要作用就是安装apk。所以,安装apk是由系统提供的服务来完成的。最后会接着发送一个MCS_BOUND的消息。

  case MCS_BOUND: {
  ....
  if (params.startCopy()) {
  ....
  }
  ....
  }

在其内,最重要的一句就是 startCopy这句代码,下面我们来看

        final boolean startCopy() {
            boolean res;
            try {
                 // max_retries =4, 最大可重试安装4次,超过则错误
                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;
        }

可以看到,上面判断了安装失败次数,系统默认允许最大4次安装失败。
接着依次调用了。handleStartCopy 与 handleReturnCode(). 这两个方法都是abstract的,所以我们应该去找它的子类来查看。由前面代码可知,HandlerParams实际类型是 InstallerParams/

  public void handleStartCopy() throws RemoteException {
            final boolean onSd = (installFlags & PackageManager.INSTALL_EXTERNAL) != 0;
            final boolean onInt = (installFlags & PackageManager.INSTALL_INTERNAL) != 0;
            final boolean ephemeral = (installFlags & PackageManager.INSTALL_EPHEMERAL) != 0;
          if (onInt && onSd) {
                .....
            } else if (onSd && ephemeral) {
                .....
            } else {
            //1.
            pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags, packageAbiOverride);
            .....
}
        .....
          loc = installLocationPolicy(pkgLite);
        .....
          final InstallArgs args = createInstallArgs(this);
        ....
        ret = args.copyApk(mContainerService, true);
}

以上省去了大量代码,只留了我们分析流程的代码,以上代码主要干了以下几件事

  1. 调用mContainerService.getMinimalPackageInfo 获取一个用于描述apk结构的类
  2. 调用installLocationPolicy 获取推荐安装路径
  3. 根据params参数的不同创建不同的args,这里创建的是FileInstallArgs
  4. 调用FileInstallArgs 的 copyApk函数 (该函数完成会在data/app 下面多出一个.tmp为结尾的临时文件)

下面看handleReturnCode

        @Override
        void handleReturnCode() {

            if (mArgs != null) {
                processPendingInstall(mArgs, mRet);
            }
        }
    private void processPendingInstall(final InstallArgs args, final int currentStatus) {
        mHandler.post(new Runnable() {
            public void run() {
            ····
                 if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                    args.doPreInstall(res.returnCode);
                    synchronized (mInstallLock) {
                        installPackageTracedLI(args, res);
                    }
                    args.doPostInstall(res.returnCode, res.uid);
                }
                ····
                Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
                    mHandler.sendMessage(msg);

             }
          }

由上面代码可知

  1. 调用FileInstallArgs的 doPreInstall
  2. 调用 installPackageTracedLI进行安装
  3. 调用FileInstallArgs的 doPostInstall
  4. 此时已安装完apk,无论成功还是失败都会发送POST_INSTALL消息,该消息携带一个token,通过该token可以取出mRunningInstalls数组中的一个PostInstallData对象

关于安装的代码,其内容由dex优化等内容,由于篇幅较长,所以大家只需要知道该函数是安装的代码,如果有想知道具体细节的,可以自行查阅。在POST_INSTALL中,会通知PM安装完成。

到这里安装的流程就分析完了,其中还有很多细节没有分析,这里就留给大家了。
### 关于 `adb install -p` 参数的说明 经过查询,未发现官方文档或其他权威资料中提及 `adb install -p` 的具体功能或用途。以下是基于现有知识和其他可能相关的 ADB 命令参数的推测和补充: #### 已知 ADB 安装命令常用参数 在 ADB 官方文档以及常见参考资料中,提供了多个用于控制应用安装行为的选项[^1]。这些参数包括但不限于: - `-r`: 替换已存在的应用程序,即强制重新安装并保留原有数据。 - `-l`: 锁定应用程序,防止其被卸载。 - `-t`: 允许安装测试版 APK 文件。 - `-s`: 将应用程序安装到外部存储(如 SD 卡)而非内部存储。 - `-d`: 允许降级安装,即使目标 APK 版本低于当前设备上的版本。 - `-g`: 自动授予应用程序所需的所有运行时权限。 然而,在上述列举的内容以及其他公开资源中,并未明确提到 `-p` 参数的具体含义及其作用[^2]。 #### 对 `-p` 参数的功能猜测 尽管缺乏直接证据支持,但可以尝试从其他角度分析此标志位的作用: 1. **可能是历史遗留或者实验性质**: 部分工具随着版本迭代会引入新特性同时废弃旧接口;如果某个早期实现里定义过特殊逻辑,则后续维护者未必更新文档来同步最新状态。 2. **关联特定平台扩展**: 某些定制化 ROM 或厂商修改后的 Android 系统可能会额外增加独有的 ADB 功能标记,其中就包含了尚未广泛传播的小众选项比如这里讨论的 "-p"。 需要注意的是以上两点均属于假设范畴并无实际依据证实它们确实对应真实情况下的"-p"表现形式。 鉴于目前可获得的信息有限,建议采取以下措施进一步确认该参数的实际意义: - 查阅最新的Android开发者指南以获取最精确的消息; - 实验性操作——通过向不同类型的终端发送包含此标签在内的完整指令串观察反馈结果从而推断潜在规律。 ```bash adb install -p your_app_file.apk ``` 执行上述脚本前最好先备份重要数据以防万一出现问题影响正常使用体验。 ### 示例代码片段展示如何调用基本的ADB安装流程而不涉及争议性的'-p' 下面给出一段标准Python脚本来演示怎样利用subprocess模块远程操控安卓模拟器完成常规意义上的APK部署工作而无需依赖不确定因素: ```python import subprocess def run_adb_command(device_id, apk_path): command = ["adb", "-s", device_id, "install", "-r", "-d", apk_path] process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, error = process.communicate() if process.returncode != 0: print(f"Error occurred: {error.decode('utf-8')}") else: print(f"Output: {output.decode('utf-8')}") # Example usage device_serial_number = "emulator-5554" path_to_apk = "/path/to/your/app-debug.apk" run_adb_command(device_serial_number, path_to_apk) ```
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值