APEX包管理器简述(一)

介绍

APEX(Android Pony EXpress ) 是Google在Android 10中引进的一种用于管理较低级别系统模块的安装包管理器,用来更新一些不适用APK安装流程的系统组件,比如Bionic 、libnativebridge、libnativehelper、libnativeloader以及一些运行时类库。

使用APEX后,Google就可以通过Google play store对系统组件进行更新,加速Google系统升级,当然也可以通过adb进行本地升级。

adb install apex_file_name
adb reboot

APEX文件格式

APEX包管理器简述(一)_第1张图片

APEX文件是一个zip压缩包,类似于一个APK安装包文件,其内部主要包含四个文件:
1. apex_manifest.json
2. AndroidManifest.xml
3. apex_payload.img
4. apex_pubkey
apex_manifest.json内部包含PackageName和Version;
AndroidManifest.xml与APK的对应文件类似,包含其他一些信息;
apex_payload.img是一个ext4的文件系统镜像;
apex_pubkey是用于为文件系统映像签名的公钥;

apexd进程

apexd的源码位于/system/apex /apexd/目录下,是一个本地守护进程,由init.rc负责启动,下面是apexd.rc中的启动程序,里面包含了两个对应的service,分别是apexd和apexd-bootstrap,对应的进程都是apexd,只是后者接受了bootstrap的参数,这个在main函数中会做不同的处理,后面的部分会讲到;

service apexd /system/bin/apexd
    class core
    critical
    user root
    group system
    shutdown critical

service apexd-bootstrap /system/bin/apexd --bootstrap
    critical
    user root
    group system
    oneshot
    disabled

在对应的Android.bp可以看到apexd.rc会注入到init.rc中通过init启动,并且source文件是apexd_main.cpp,所以可以确定入口函数位于此文件中;

cc_binary {
     
  name: "apexd",
  defaults: [
    "apex_defaults",
    "libapexservice-deps",
  ],
  srcs: [
    "apexd_main.cpp",
  ],
  static_libs: [
    "libapex",
    "libapexd",
    "libapexd_checkpoint_vold",
    "libapexservice",
    "libavb",
    "libdm",
    "libvold_binder",
  ],
  shared_libs: [
    "libselinux",
  ],
  init_rc: ["apexd.rc"],
  // Just like the init, apexd should be able to run without
  // any APEX activated. To do so, it uses the bootstrap linker
  // and the bootstrap bionic libraries.
  bootstrap: true,
  target: {
     
    android: {
     
      ldflags: ["-Wl,--rpath,/system/${LIB}/bootstrap"],
    },
  },
}

进入到main函数,根据rc文件的定义,两个进程通过是否携带参数而做不同的处理,apexd-bootstrap会直接在HandleSubcommand函数中处理结束后被return掉,而apexd则会将service注册到SM中作为守护进程;

int main(int /*argc*/, char** argv) {
     
  // Use CombinedLogger to also log to the kernel log.
  android::base::InitLogging(argv, CombinedLogger());
  if (argv[1] != nullptr) {
     
    return HandleSubcommand(argv);  //处理启动进程携带的参数
  }
  // TODO: add a -v flag or an external setting to change LogSeverity.
  android::base::SetMinimumLogSeverity(android::base::VERBOSE);
  //创建vold对象用于存储方面的需要
  android::apex::StatusOr<android::apex::VoldCheckpointInterface>
      vold_service_st = android::apex::VoldCheckpointInterface::Create();
  android::apex::VoldCheckpointInterface* vold_service = nullptr;
  
  ...
  
  android::apex::onStart(vold_service); //真正执行扫描安装的过程
  android::apex::binder::CreateAndRegisterService();  //向SM注册该service
  android::apex::binder::StartThreadPool(); 
  
  ...
  
  android::apex::binder::JoinThreadPool();
  return 1;
}
int HandleSubcommand(char** argv) {
     
  if (strcmp("--pre-install", argv[1]) == 0) {
     
    LOG(INFO) << "Preinstall subcommand detected";
    return android::apex::RunPreInstall(argv);
  }

  if (strcmp("--post-install", argv[1]) == 0) {
     
    LOG(INFO) << "Postinstall subcommand detected";
    return android::apex::RunPostInstall(argv);
  }

  if (strcmp("--bootstrap", argv[1]) == 0) {
     
    LOG(INFO) << "Bootstrap subcommand detected";
    return android::apex::onBootstrap();
  }

  LOG(ERROR) << "Unknown subcommand: " << argv[1];
  return 1;
}

不管是否携带参数,最终都会走到android::apex命名空间中,对应apexd.cpp文件;两次调用分别对应onStart和onBootStrap成员函数;

int onBootstrap() {
     
  ...
  Status preAllocate = preAllocateLoopDevices();
  ...
  Status status = collectApexKeys();
  ...
  // Activate built-in APEXes for processes launched before /data is mounted.
  status = scanPackagesDirAndActivate(kApexPackageSystemDir);
  ...
  return 0;
}

onBootstrap中主要做了三件事:

  • 根据system和product分区下apex包数量分配环回设备;
  • 获取安装包中的公钥和PackageName,并以键值对的新式存储;
  • 根据注释描述可知,在data分区挂载之前激活内置的apex包,主要是system分区;

开机过程中Apexd service主要的工作还是在onStart完成,包括解析、校验、挂载和绑定等过程,最终挂载到/apex/目录下,系统其他组件使用过程中会直接使用该目录中的可执行文件,而不需要访问system目录;

void onStart(CheckpointInterface* checkpoint_service) {
     
  LOG(INFO) << "Marking APEXd as starting";
  ...
  Status status = collectApexKeys();
  gMountedApexes.PopulateFromMounts();
  // Activate APEXes from /data/apex. If one in the directory is newer than the
  // system one, the new one will eclipse the old one.
  scanStagedSessionsDirAndStage();
  status = resumeRollbackIfNeeded();
  if (!status.Ok()) {
     
    LOG(ERROR) << "Failed to resume rollback : " << status.ErrorMessage();
  }

  status = scanPackagesDirAndActivate(kActiveApexPackagesDataDir);
  if (!status.Ok()) {
     
    LOG(ERROR) << "Failed to activate packages from "
               << kActiveApexPackagesDataDir << " : " << status.ErrorMessage();
    Status rollback_status = rollbackActiveSessionAndReboot();
    if (!rollback_status.Ok()) {
     
      // TODO: should we kill apexd in this case?
      LOG(ERROR) << "Failed to rollback : " << rollback_status.ErrorMessage();
    }
  }

  for (const auto& dir : kApexPackageBuiltinDirs) {
     
    // TODO(b/123622800): if activation failed, rollback and reboot.
    status = scanPackagesDirAndActivate(dir.c_str());
    if (!status.Ok()) {
     
      // This should never happen. Like **really** never.
      // TODO: should we kill apexd in this case?
      LOG(ERROR) << "Failed to activate packages from " << dir << " : "
                 << status.ErrorMessage();
    }
  }
}

onStart()函数中主要完成了以下6件事情:

  1. 写入apexd的状态并且通过vold判断文件系统是否支持并需要校验;
  2. 同上面一样,获取安装包中的公钥和PackageName,并以键值对的新式存储;
  3. 通过/proc/mounts文件获取已经挂载的apex包信息存储到数据库中;
  4. 安装/data/apex/目录下的apexd包,并且/system/apex/对应的apex包做比较,判断是否有升级;
  5. 完成已经处于active状态的apex包挂载动作;
  6. 完成内置的apex包挂载动作;

前三步主要是做一些预处理,后三步完成了所有apex包的安装,包括有升级的话需要重新安装。其中的区别就是对不同状态的apex包进行不同的处理,然后进入到下一个阶段,分别是stage阶段处理session、active阶段处理安装以及确定最后的内置应用是否全部都安装成功,如果没有的话需要继续安装,具体的安装过程下一篇文章详细介绍。

结语

APEX模块主要还是Google为了加速适配新系统,而不必取决于OEM厂商系统更新的速度,这样用户就可以最快使用到Google最新的系统。其类似于APK包管理模块,同样是为了完成模块安装、更新等动作,可以等同理解。

Android Q中Google已经有部分系统组件强制使用APEX包,这样的话OEM厂商就无法再对其进行修改,极大受限与Google的更新,当然现在只是初步实施此模块,所以仅仅是个别组件被强制,其他的只是Google推荐使用,也许在接下来的版本Google会将推荐使用改为强制使用吧?

你可能感兴趣的:(Apex,Android)