Android APK安装与卸载机制

本文主要介绍Android系统如何实现APK安装、卸载、更新等操作。主要内容包括以下内容:

  1. 安装和卸载APK的方法有哪些,每种方法实现的原理是什么?
  2. APK安装和卸载过程中,系统数据发生了哪些变化?
  3. Android App端常用的Package Manager使用方法简介。

注:本文是在本人阅读<Android的设计与实现I>、<深入理解Android卷II>、以及网络中关于Android安装方式的基础上完成的。本人的主要工作在于阅读相关部分的Android源代码,并制作相应代码调用的时序图;尝试将所阅读的文章中的安装方法付诸实施。

1. APK安装卸载方法及原理浅析

1.1 APK安装方式

常见的APK安装方式有以下几种:

安装方式触发时机面向的用户类型
系统自带和厂商预装系统首次启动System Designer
adb命令安装使用adb push命令Programmer
adb命令安装使用adb (shell pm) install命令Programmer
网络下载应用安装下载完成之后安装User
第三方应用安装(PackageInstaller.apk等)通过SdCard中的APK安装User

1.2 APK安装、卸载原理浅析

Android系统中,APK安装、卸载的原理,与Windows系统软件安装(具体原理参考软件安装原理MSI文件简介)的基本原理类似。

  • APK安装可以归结为以下几个过程:
    1. 将APK文件复制到指定的目录下;
    2. 解压APK,拷贝文件,创建应用的数据目录;
    3. 把dex文件(Dalvik字节码)保存到dalvik-cache目录;
    4. 解析APK的AndroidManifest.xml文件,将其中声明的组件等信息保存在PMS中;更新PMS中相应的数据结构。
    5. 其他操作;
  • APK卸载:将APK文件、data/data/pacakgeName/、dalvik-cache目录中相应的文件删除;并更新PMS中相应的数据结构。

1.3 APK几种安装方式异同点

  1. 系统自带的应用以及厂商预装的应用等,在手机首次启动时,会通过扫描/system/app/、/system/framework/、/vendor/app/等目录下面的APK文件,完成安装;如果是Android的原生系统,或者cm等系统,则没有/vendor/app/目录。
  2. 通过adb push命令可以将APK文件推送到/system/app/、/system/framework/、/vendor/app/、/data/app/等目录下面,然后再次启动手机设备,便可以完成安装过程。具体命令如下:
    adb root
     adb push flyflow-release.apk /system/app
     adb reboot
    使用该方式安装APK,具有以下特点:
    • 使用这种方式安装APK时,要求手机被完全root,可以通过PC端获取root权限。
    • 如果将APK文件推送到/system/app/、/system/framework/、/vendor/app/目录下,该App便成为系统预装App,没有root权限不能卸载该应用。
    • 使用该种方式安装的APK,于系统自带的App以及厂商预装的App安装方式一样,也是通过PMS扫描相应的文件夹完成。
  3. 使用adb install命令安装APK。常用的命令如下:
    adb install flyflow-release.apk
    adb shell pm install flyflow-release.apk
    这两种命令最终都会调用Android端的/bin/pm的install命令完成安装。
  4. 网络应用下载安装APK之后,会调用PackageManager的installPackage(*)方法完成安装工作;该方法最终会调用PackageManagerService中的installPackage来完成APK安装。可通过以下形式实现验证: a) 将APK文件实现放入sdcard中; b) 设计一个Android InstallDemo,包含一个按钮,点击按钮便会调用installPackage方法进行安装。其中installPackage安装的文件为步骤a中的APK; 注意事项有:

    • 该实验在安装的过程中,要求InstallDemo工程具有INSTALL_PACKAGES权限。
  5. 使用PackageInstaller.apk进行安装时,其启动PackageInstallerActivity来完成安装包解析;在解析完成并得到用户的安装确认之后,启动 InstallAppProgress并调用安装接口pm.installPackage进行安装。(该方式未进行深入调研,道听途说)

2. PMS安装APK的过程解析

本节详细介绍APK的几种安装方式,主要包括开机扫描安装、adb install命令安装等。

2.1 扫描安装过程解析

Android设备在启动的过程中,会扫描本地安装的所有的App。整个扫描过程可以通过以下流程图来说明: APK开机扫描安装扫描安装过程分析如下:

  1. 在PackageManagerService构造函数中,扫描各个APK目录之前会创建一个Settings对象mSettings。其中Settings类负责读取系统的配置信息,主要解析的文件有packages.xml、packages-stopped.xml等文件。会将解析的信息分别存入mPackages、mRenamedPackages、mDisableSysPackages等数据结构中。重要的函数调用有:
    mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),mSdkVersion, mOnlyCore);
  2. 开机启动时会以此扫描目录:/system/app、/system/framework、 /vendor/app、/data/app等。具体调用有: APK目录扫描调用
  3. 扫描每个APK目录的核心代码如下:
    // scanDirLI(**)
    for (File file : files) {
      final boolean isPackage = (isApkFile(file) || file.isDirectory())
              && !PackageInstallerService.isStageName(file.getName());
      if (!isPackage) { // Ignore entries which are not packages
           continue;
       }
       try {
           scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
                   scanFlags, currentTime, null);
       } catch (PackageManagerException e) {
           // exception process
           .....
    }
    
  4. scanPackageLI(File scanFile,...)该函数负责完成APK的扫描工作,APK的扫描工作具体有PackageParser.parsePackage(*)完成。在扫描过程中,会解析AndroidManifiest.xml文件等信息,并将扫描结果存放在PackageParser.Package对象中。
  5. 在完成包的信息解析之后需要完成包信息同步工作,主要因为:scanPackageLI扫描到的APK可能是已经更名的包、disable的包、需要升级的包、已经安装并且签名冲突的包、被非系统级包替代系统包的情况,需要对这些情况一一处理,保证信息的正确性
  6. 如果Package需要Rename或者Update则会进行签名比较,以防止签名不一致的情况;
  7. 最终会调用scanPackageLI(PackageParser.Package,....)函数完成实际的Package安装或更新操作。
  8. 在扫描过程中,Package的Rename、Update、Install操作都是由scanPackage(PackageParser,int,int,long,UserHandler)来完成.该函数会调用scanPackageDirtyLI(...)完成具体的操作。scanPackageDirtyLI函数在后续章节会分析。

2.2 adb命令安装过程解析

Android adb的实现原理见adb原理与adb常用命令。在Console中输入adb的命令,最终会使用android /platform/system/core/adb/commandline.c 进行命令解析。

2.2.1 adb install命令

adb命令的intall命令在commandline.c中对应的实现函数为install_app(....),该函数的代码有如下调用:

if (!(err = do_sync_push(filename, to, 1 /* verify APK */))) {
        /* file in place; tell the Package Manager to install it */
        argv[argc - 1] = to;      
        /* destination name, not source location */
        pm_command(transport, serial, argc, argv);
        delete_file(transport, serial, to);
}
pm_command(...)函数实现如下:

static int pm_command(transport_type transport, char* serial,
                      int argc, char** argv)
{
    char buf[4096];
    snprintf(buf, sizeof(buf), "shell:pm");
    while(argc-- > 0) {
        char *quoted;
        quoted = dupAndQuote (*argv++);
        strncat(buf, " ", sizeof(buf)-1);
        strncat(buf, quoted, sizeof(buf)-1);
        free(quoted);
    }
    send_shellcommand(transport, serial, buf);
    return 0;
}

pm_command通过最终通过send_shellcommand(...)将数据发送到手机端的adbd守护进程中;adbd在收到PC的Console发来的数据之后,会启动一个Shell,然后执行pm。

2.2.2 adb shell pm命令

Android设备端pm命令位于/system/bin目录下,其是一个脚本,具体内容如下:

#!/system/bin/sh
base=/system
export CLASSPATH=$base/framework/pm.jar
exec app_process $base/bin com.android.commands.pm.Pm "$@"
1. 从上面脚本可以看出,pm命令通过app_process执行pm.jar包的main函数;
2. Android系统中常用的monkey、pm、am等脚本都是使用该方式来运行;
3. pm.jar对应的类为com.android.commands.pm.Pm,具体的安装函数由runInstall来实现。该函数解析剩余的install命令参数,并最终调用以下代码完成安装:
// Populate verificationURI, optionally present
 final String verificationFilePath = nextArg();
 if (verificationFilePath != null) {
     System.err.println("\tver: " + verificationFilePath);
     verificationURI = Uri.fromFile(new File(verificationFilePath));
 } else {
     verificationURI = null;
 }

 LocalPackageInstallObserver obs = new LocalPackageInstallObserver();
 try {
     VerificationParams verificationParams = new VerificationParams(verificationURI,originatingURI, 
        referrerURI, VerificationParams.NO_UID, null);
     // 调用PMS完成安装
       mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags,
             installerPackageName, verificationParams, abi, userId);
      // 安装结果后续处理
    ....
 } catch (RemoteException e) {
     System.err.println(e.toString());
     System.err.println(PM_NOT_RUNNING_ERR);
     return 1;
 }

2.3 PMS中相关代码分析

adb安装命令最终调用PMS中的installPackageAsUser函数进行APK安装。整个过程的时序图如下图所示: adb命令安装过程时序图安装APK的过程中具有以下特征:

  • installPackageAsUser函数检查客户端进程是否具有安装Package的权限,其中Shell和root进程都是具有该权限的。
  • installPackageAsUser函数会向PackageHandler发送一个INIT_COPY的消息,由PackageHandler来完成APK安装;
  • PackageHandler会调用InstallParms中的handleStartCopy()和handleReturnCode函数完成相应的操作。
  • 在handleStartCopy函数里完成的事情有:
    • 根据adb install的参数,判断安装位置;
    • 调用DeviceStorageMonitorService判断是否有足够的空间完成APK安装,并给出APK推荐的安装路径;
    • 创建一个安装参数(如果安装在内部存储空间时,该参数是一个FileInstallArgs对象)以便后续安装使用;
    • 调用InstallArgs的copyApk函数将APK从临时目录复制到指定的目录中;
  • 通过adb命令安装APK时,最终也是调用scanPackageDirLI函数完成APK安装。
  • 在该过程中,会判断是新安装一个APK还是覆盖更新安装APK。针对不同的情况会进行特殊的处理。
  • 在安装完成之后,processPendingInstall函数会想PackageHandler发送一个POST_INSTALL消息。然后在该消息的处理中,会根据APK的安装情况,发送相应的广播信息。

3. APK安装核心函数ScanPackageDirtyLI函数分析

从上述过程可以看出,不论是开机扫描安装APK,还是通过adb命令安装APK,最终都会调用scanPackageDirtyLI函数进行APK安装。该函数的流程图如下图所示: scanPackageDirtyLI流程图 该函数完成的主要工作有:

  • 初始化Package的数据目录和资源目录;
  • 如果需要则更新已有的Package的共享代码库;
  • 如果安装时传递了签名信息,则验证签名信息的合法性;
  • 验证新安装的APK中的Provider是否与系统中现有的Provider有冲突,并进行相应的处理;
  • 如果新安装APK需要使用其他Package的权限,则进行相应处理;
  • 调用createDataDirsLI()安装APK;
  • 设置本地lib路径;
  • 安装成功之后将Package信息更新到PMS和Setting相应的数据结构中;
  • 设置APK安装时间;
  • 设置APK的Provider信息,将Provider添加到相应的数据结构中;
  • 设置权限组和权限信息;
  • 该函数的主要工作便是将安装的APK的信息添加到PMS中,比如讲Provider、Activity、Service、Receiver等组件信息添加到相应的数据结构中,以便其他函数能够查询到。
  • 在该函数中还对framework-res.apk进行特殊的信息处理。framework-res.apk中主要包含以下信息:
    • 几个常用的Activity:ChooserActivity、ShutdownActivity、RingtonePckerActivity;
    • framework-res.apk与PMS联系紧密,其中PMS中的mPlatformPackage成员存储该Package信息;mAndroidApplicatioin保存该Package的ApplicationInfo信息;mResolveActivity表示ChooserActivity信息的ActivityInfo;mResolveInfo存储系统解析的Intent后得到的结果信息。

3.2 createDataDirsLI分析

上个小节已经说明,scanPackageDirLI函数会调用createDataDirsLI的函数来完成安装。该函数主要做了两件事情:

  • 调用mInstaller.install()函数完成APK安装;
  • 调用mInstaller.createUserData()函数创建用户信息。 其中mInstaller是在PMS.main()函数中传递进来的Installer对象。该函数代码如下:
    private int createDataDirsLI(String packageName, int uid, String seinfo) {
          int[] users = sUserManager.getUserIds();
          int res = mInstaller.install(packageName, uid, uid, seinfo);
          if (res < 0) {
              return res;
          }
          for (int user : users) {
              if (user != 0) {
                  res = mInstaller.createUserData(packageName,
                          UserHandle.getUid(user, uid), user, seinfo);
                  if (res < 0) {
                      return res;
                  }
              }
          }
          return res;
    }

3.3 Installer介绍

通过Installer类中install函数代码如下:

public int install(String name, int uid, int gid, String seinfo) {
    StringBuilder builder = new StringBuilder("install");
    builder.append(' ');
    builder.append(name);
    builder.append(' ');
    builder.append(uid);
    builder.append(' ');
    builder.append(gid);
    builder.append(' ');
    builder.append(seinfo != null ? seinfo : "!");
    return mInstaller.execute(builder.toString());
}
分析得出以下结论: 1. Installer.install()函数和createUserData()进行完成了命令组装工作,在组装完命令之后,将命令传递给InstallerConnection处理。 通过分析InstallerConnection.java得到以下结论: 1. InstallerConnection连接一个名为Installd的服务 2. Install具体的命令有Installd完成。 其中InstallerConnnection.connect()函数代码如下:
private boolean connect() {
        if (mSocket != null) {
            return true;
        }
        Slog.i(TAG, "connecting...");
        try {
            mSocket = new LocalSocket();
            LocalSocketAddress address = new LocalSocketAddress("installd",
                    LocalSocketAddress.Namespace.RESERVED);
            mSocket.connect(address);
            mIn = mSocket.getInputStream();
            mOut = mSocket.getOutputStream();
        } catch (IOException ex) {
            disconnect();
            return false;
        }
        return true;
}

3.4 Installed介绍

Installd是一个native进程,该进程启动一个socket,然后处理来之Installer的命令。Installd的实现原理可以参考博文Android安装服务installd源码分析。 installd源码位于frameworks/base/cmds/installd目录下,其中install操作对应的源代码在frameworks/base/cmds/installd/commands.c中,具体代码如下:

int install(const char *pkgname, uid_t uid, gid_t gid)
{
    char pkgdir[PKG_PATH_MAX];//程序目录路径最长为256
    char libdir[PKG_PATH_MAX];//程序lib路径最长为256
    //权限判断
    if ((uid < AID_SYSTEM) || (gid < AID_SYSTEM)) {
        ALOGE("invalid uid/gid: %d %d\n", uid, gid);
        return -1;
    }
    //组合应用程序安装目录pkgdir=/data/data/应用程序包名
    if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, 0)) {
        ALOGE("cannot create package path\n");
        return -1;
    }
    //组合应用程序库目录libdir=/data/data/应用程序包名/lib
    if (create_pkg_path(libdir, pkgname, PKG_LIB_POSTFIX, 0)) {
        ALOGE("cannot create package lib path\n");
        return -1;
    }
    //创建目录pkgdir=/data/data/应用程序包名
    if (mkdir(pkgdir, 0751) < 0) {
        ALOGE("cannot create dir '%s': %s\n", pkgdir, strerror(errno));
        return -errno;
    }
    //修改/data/data/应用程序包名目录的权限
    if (chmod(pkgdir, 0751) < 0) {
        ALOGE("cannot chmod dir '%s': %s\n", pkgdir, strerror(errno));
        unlink(pkgdir);
        return -errno;
    }
    //创建目录libdir=/data/data/应用程序包名/lib
    if (mkdir(libdir, 0755) < 0) {
        ALOGE("cannot create dir '%s': %s\n", libdir, strerror(errno));
        unlink(pkgdir);
        return -errno;
    }
    //修改/data/data/应用程序包名/lib目录的权限
    if (chmod(libdir, 0755) < 0) {
        ALOGE("cannot chmod dir '%s': %s\n", libdir, strerror(errno));
        unlink(libdir);
        unlink(pkgdir);
        return -errno;
    }
    //修改/data/data/应用程序包名目录的所有权限
    if (chown(libdir, AID_SYSTEM, AID_SYSTEM) < 0) {
        ALOGE("cannot chown dir '%s': %s\n", libdir, strerror(errno));
        unlink(libdir);
        unlink(pkgdir);
        return -errno;
    }
    //修改/data/data/应用程序包名/lib目录的所有权限
    if (chown(pkgdir, uid, gid) < 0) {
        ALOGE("cannot chown dir '%s': %s\n", pkgdir, strerror(errno));
        unlink(libdir);
        unlink(pkgdir);
        return -errno;
    }
    return 0;
}

4. APK的卸载

从Installd的实现中,我们可以看到unInstall操作其实就是删除相应的数据文件和资源文件。unInstall的具体实现如下:

int uninstall(const char *pkgname, uid_t persona)
{
    char pkgdir[PKG_PATH_MAX];
    if (create_pkg_path(pkgdir, pkgname, PKG_DIR_POSTFIX, persona))
        return -1;
    /* delete contents AND directory, no exceptions */
    return delete_dir_contents(pkgdir, 1, NULL);
}

5. 待完成的任务

  1. PackageInstaller.apk等第三方App如何实现APK安装;
  2. 如何重命名一个APK,以及更换APK的ICON;
  3. 通过adb push命令将APK安装包推送到/system/app能否实时触发APK安装;
  4. APK安装完成之后,是否能立即查询到该APK信息;卸载完成之后,是否可立即判断当前的包已经被卸载;
  5. 制作Demo完成APK安装,使用命令完成APK安装;
  6. 如何通过APK安装/卸载的广播进行相关编程;

6.小结

  1. adb的实现机制
  2. Content Provider命名、自定义Permission命名等需要保证全局唯一;
  3. 安装、卸载完成会发送广播;
  4. Pm、monkey、Am脚本的实现机制;
  5. 可以通过adb push或者直接将APK放入到/system/app、/vendor/app完成预置操作;
  6. 如果遇到安装失败的情况,终极方案直接删除相应的文件,并重启手机;
  7. 一台PC最多同时连接16个device/emulator;
  8. /data/data//该文件的权限为751; /data/data//lib该目录的权限为755,说明每个App的lib是所有用户可读可运行的。

7.参考文献

  1. <深入理解Android卷II>
  2. <Android的设计与实现卷I>
  3. In Depth: Android Package Manager and Package Installer
  4. APK安装过程及原理详解
  5. Android应用程序安装过程源代码分析
  6. Android内核开发:浅析APK的安装过程
  7. android APK应用安装过程以及默认安装路径
  8. Android应用安装过程及原理
  9. ADB原理与adb常用命令
  • 5
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值