专栏名称: 鸿洋
你好,欢迎关注鸿洋的公众号,每天为您推送高质量文章,让你每天都能涨知识。点击历史消息,查看所有已推送的文章,喜欢可以置顶本公众号。此外,本公众号支持投稿,如果你有原创的文章,希望通过本公众号发布,欢迎投稿。
目录
相关文章推荐
鸿洋  ·  安卓应用跳转回流的统一和复用 ·  2 天前  
鸿洋  ·  一文搞懂Window、PhoneWindow ... ·  3 天前  
郭霖  ·  Android 跨进程+解耦的数据持久化方案 ·  1 周前  
郭霖  ·  Android ... ·  1 周前  
51好读  ›  专栏  ›  鸿洋

Android系统native进程之我是installd进程

鸿洋  · 公众号  · android  · 2024-11-20 08:35

正文

1
本文摘要


这是Android系统进程系列的第五篇文章,本文同样以自述的方式带您了解installd进程,通过本文您将了解到linstalld进程为啥存在?以及在安装/卸载apk中发挥了哪些作用。(文中的代码是基于android13)

Android系统进程系列的前四篇文章如下:

Android系统native进程之我是init进程

Android系统native进程之属性能力的设计魅力

Android系统native进程之进程杀手--lmkd

Android系统native进程之日志系统--logd、logcat
2
我是谁


installd:“大家好啊,我是你们的新朋友,我的名字叫installd。大家应该还记得lmkd、logd进程吧,我和它们都有共同的"父亲"init进程,因此我也是一个系统native进程。咱们先来互动下有哪位能从我的名字猜出我的工作职责吗?”

一个进程抢先答到:“这还不简单,你的名字是installd,installd应该是installd demon的缩写,也就是说你是一个安装守护进程,也就是和安装有关系的,但是具体是安装什么的这个我就不清楚了。”

installd:“这位仁兄对于我的名字的解释是非常到位的,我的主要职责是和安装apk有关系的。”

这个进程不解的问到:“不好意思,打断一下,我有点懵逼了,你和安装apk有关?据我的了解安装apk是systemserver进程中的PackageManagerService(简称PMS)的工作内容,你怎么和安装apk有关了?”

installd:“这位朋友有这样的不解,我非常理解,更准确的说在android13版本,安装apk这件事情除了和PMS有关还和PackageInstallerService能力有关,关于我为啥与安装apk有关,我先卖个关子,后面会有更详细的解释。”

我来总结下我自己,我的名字叫installd,翻译为中文是安装守护进程 ,主要的工作职责是辅助apk的安装/卸载等工作,在介绍我是如何与apk安装有关系之前,先来介绍下我的出生吧。
3
我的出生


我的父亲是init进程,因为它的子进程是非常非常多的,这么多子进程何时创建、创建之前需要执行哪些命令又更是多上加多,这么多的信息它完全是无招架之力,为了解决这个问题它创建了init脚本语言,哪个子进程需要创建,则配置自己的init脚本语言即可,下面是我的脚本语言:

文件路径:frameworks/native/cmds/installd/installd.rc

//installd代表进程的名字,/system/bin/installd则代表在fork子进程成功后,子进程需要执行的可执行文件
service installd /system/bin/installd
    class main

//在early-boot action发生的时候 执行下面的创建各种目录的命令
on early-boot
    mkdir /config/sdcardfs/extensions/1055
    mkdir /config/sdcardfs/extensions/1056
    mkdir /config/sdcardfs/extensions/1057

    省略其他的创建的目录......

上面的脚本语言看着是不是还是不简单,那就用大白话更轻松的方式来描述下上面脚本语言的内容:init老父亲,在early-boot事件发生的时候帮我创建各种目录,我的进程名字是installd,我所属的进程类别是main,当我被创建成功后执行/system/bin/installd可执行文件。

执行/system/bin/installd可执行文件,会执行下面的方法,执行了下面方法后,我才真正“长大”,可以看到我是使用了binder通信的,也就是谁想使用我的功能可以通过binder通信来调用我提供的能力。

文件路径:frameworks/native/cmds/installd/installd.cpp

int main(const int argc, char *argv[]) 
{
    return android::installd::installd_main(argc, argv);
}

static int installd_main(const int argc ATTRIBUTE_UNUSED, char *argv[]) {
    int ret;
    int selinux_enabled = (is_selinux_enabled() > 0);

    省略代码......

    //initialize_globals方法主要初始化各种目录字符串比如data/  data/app  system/
    if (!initialize_globals()) { 
        SLOGE("Could not initialize globals; exiting.\n");
        exit(1);
    }

    省略代码......

    //InstalldNativeService::start() 方法会启动binder服务,同时把自己放入ServiceManager中
    //这样就可以让使用者使用了,InstalldNativeService是install各种功能的具体实现者
    if ((ret = InstalldNativeService::start()) != android::OK) {
        SLOGE("Unable to start InstalldNativeService: %d", ret);
        exit(1);
    }

    IPCThreadState::self()->joinThreadPool();

    LOG(INFO) << "installd shutting down";

    return 0;
}

4
我为何存在


你们人类经常会思考:人存在的意义是啥?那同样我也有存在的意义,只不过我存在的意义没有那么深奥,我存在的意义就是:为apk的安装提供服务。

我先来简单介绍下apk的安装吧(关于apk的安装更详细的信息在后面文章中会有介绍),介绍完apk的安装我存在的意义自然就清楚了。

apk的安装大概的过程是这样的:

  1. 不管apk是通过adb安装的(apk存储于PC的磁盘)还是应用市场安装的(apk存储于sdcard),首先apk会被拷贝到 /data/app/xxx.tmp目录下面(xxx是一个随机生成的字符串)

  2. 在经过重重的验证、校验(签名、版本号),/data/app/xxx.tmp 目录会重命名为 /data/app/randomstr/pkg+randomstr 目录(randomstr是一个随机的字符串,pkg+randomstr也是个包名+随机字符串),也就是被拷贝的apk最终是存放在 /data/app/randomstr/pkg+randomstr 目录下面。同时会为apk生成一个唯一的id又称appid

上面两个阶段都是由PMS(PackageManagerService的简称)和PackageInstallerService 完成的。但是别以为到此apk的安装就结束了,答案是还没有,还需要创建app data根目录

一个进程挠挠头,疑惑不解的问到:“为什么要创建app data根目录?它和apk的安装又有啥关系呢?”

installd:“好问题,在解释之前,我先提个问题:apk作程序在运行期间是不是会产生大量的数据,而这些数据或多或少的都需要持久化到设备上,那如何保证持久化数据的安全性、不散乱、易清理呢?“

installd:“我在补充下,其中安全性是指持久化的数据只能被所属的apk程序访问、修改、更新,不散乱是指数据集中存放在某处,易清理是指在比如卸载apk的时候也能连同这些数据都清除掉。”

这个进程思考了半宿不好意思的说:“作为一个进程在运行中确实是有数据需要存储的,这点我完全认同,但是对于你的问题,我没有想出一个好办法,请您赐教。”

“其实解决办法是非常的简单的,在apk安装后,系统为apk创建一个根目录,这个根目录具有这样的特性:它的uid(用户id)、gid(组id)都是由userid和appid生成的(userid是当前设备的用户id,appid是apk安装后生成的唯一id),uid具有读写执行权限,而gid只有执行权限,其他用户是没有任何权限的。这样的根目录因为它的uid和apk的appid是一一对应关系,这样就能保证只有持久化数据所属的apk程序才能读写,其他进程是没有任何权限的。并且数据都是持久化在根目录下面的,就可以做到不散乱、易清理了。这个根目录就是app data根目录,app data根目录的格式是:/data/user/userid/packagename。”

用一句大白话讲就是:针对不同的apk,创建一个唯一的根目录,apk在运行过程中只能把数据存储在这个根目录下,这个根目录是绝对安全的。

这个进程立马说:“我懂了,完全懂了,那创建app data根目录的工作肯定是由你来负责了。那能说说为啥PMS不自己来创建呢?”

installd:“我给你点赞,对就是我创建app data根目录。PMS不自己创建的原因是它没有这个能力。因为PMS运行于systemserver进程,而systemserver进程的user是system(appid是1000),user为system是没有为其他apk创建app data根目录的权限的。而我具有这种‘超能力’ 。这也就是我存在的意义,当然我还有别的功能,比如在安装apk的时候我还有对dex进行优化简称dexopt,后面会有介绍。”

我存在的意义是:可以辅助apk的安装,创建app data根目录,同时还有其他功能。
5
我是如何具有“超能力的”


“超能力”这个词用的有点大了,其实也不是啥超能力,我installd进程在运行时只不过是以root用户运行而已,进而具有root权限了,具有root权限那当然可以“为所欲为”了。那就来介绍下我是如何以root用户运行的。

在 我的出生 这节介绍过需要init创建子进程的话需要配置init脚本语言,那再来看下我和lmkd的脚本语言:

//installd的配置信息
文件路径:frameworks/native/cmds/installd/installd.rc
service installd /system/bin/installd
    class main

    省略其他......


//lmkd的配置信息
文件路径:/system/memory/lmkd/lmkd.rc
service lmkd /system/bin/lmkd
    class core
    user lmkd

    省略其他......

init脚本语言有 user 这个关键字,配置了user后代表init创建的子进程的user就是配置的user。

如上,lmkd配置user为"lmkd",则代表lmkd进程的user为lmkd,而installd是没有配置user的,对于fork机制创建的子进程,如果不为子进程设置user的话,子进程会继承父进程的user,因此installd的user和init进程一样也是root。如下图所示

下面是为子进程设置uid的源代码,有兴趣可以看下。

文件路径:service.cpp

//该方法在创建子进程的时候会调用
Result<void> Service::Start() {

    省略代码......

    pid_t pid = -1;
    if (namespaces_.flags) {
        //使用clone方式创建子进程
        pid = clone(nullptrnullptr, namespaces_.flags | SIGCHLD, nullptr);
    } else {
        //使用fork方式创建子进程
        pid = fork();
    }

    if (pid == 0) {
        umask(077);
        //子进程走这,执行[1.1]
        RunService(override_mount_namespace, descriptors, std::move(pipefd));
        _exit(127);
    }

    省略代码......
}

[1.1]
void Service::RunService(const std::optional& override_mount_namespace,
                         const std::vector& descriptors,
                         std::unique_ptr<std::array<int2>, decltype(&ClosePipe)> pipefd) {

    省略代码......
    //设置子进程的属性和能力,执行[1.2]
    SetProcessAttributesAndCaps();

}


[1.2]
//设置子进程的属性和能力
void Service::SetProcessAttributesAndCaps() {

    省略代码......

    //设置子进程的属性,如uid gid等,执行[1.3]
    if (auto result = SetProcessAttributes(proc_attr_); !result.ok()) {
    }

    省略代码......
}

[1.3]
//设置子进程的属性
Result<void> SetProcessAttributes(const ProcessAttributes& attr) {

    省略代码......

    //gid不为0,则调用setgid为子进程设置gid
    if (attr.gid) { 
        if (setgid(attr.gid) != 0) {
            return ErrnoError() << "setgid failed";
        }
    }

    省略代码......

    //uid不为0,则调用 setuid进行设置。在脚本文件不配置user的话 uid为0
    if (attr.uid) {
        if (setuid(attr.uid) != 0) {
            return ErrnoError() << "setuid failed";
        }
    }

    省略代码......

    return {};
}

好了,知道我是如何具有“超能力”后,看看我用“超能力”都提供了哪些能力。
6
我提供的能力

我提供了创建app  data根目录、dexopt、清除app数据、app数据备份、app数据恢复等能力,那就来介绍下这些能力。

创建app  data根目录

创建app data根目录是最重要的功能,我还是结合源码来介绍它吧。

binder::Status InstalldNativeService::createAppDataLocked(
        const std::optional<std::string>& uuid, const std::string& packageName, int32_t userId,
        int32_t flags, int32_t appId, int32_t previousAppId, const std::string& seInfo,
        int32_t targetSdkVersion, int64_t* _aidl_return) {

    省略代码......

    const char* uuid_ = uuid ? uuid->c_str() : nullptr;
    const char* pkgname = packageName.c_str();

    int32_t uid = multiuser_get_uid(userId, appId);

    省略代码......

    int32_t cacheGid = multiuser_get_cache_gid(userId, appId);
    mode_t targetMode = targetSdkVersion >= MIN_RESTRICTED_HOME_SDK_VERSION ? 0700 : 0751;

    省略代码......

    long projectIdApp = get_project_id(uid, PROJECT_ID_APP_START);
    long projectIdCache = get_project_id(uid, PROJECT_ID_APP_CACHE_START);

    //如果flags是FLAG_STORAGE_CE则走下面的逻辑
    if (flags & FLAG_STORAGE_CE) {
        //path=/data/user/userid/pkgname ,它就是 app data的根目录 的路径
        auto path = create_data_user_ce_package_path(uuid_, userId, pkgname);
        //会创建 /data/user/userid/pkgname  /data/user/userid/pkgname/cache 和 /data/user/userid/pkgname/code_cache 目录
        auto status = createAppDataDirs(path, uid, uid, previousUid, cacheGid, seInfo, targetMode,

        省略代码......
    }

    //如果flags是FLAG_STORAGE_DE则走下面的逻辑
    if (flags & FLAG_STORAGE_DE) {
        //path=/data/user_de/userid/pkgname ,它也是 app data的根目录 的路径
        auto path = create_data_user_de_package_path(uuid_, userId, pkgname);

        //会创建 /data/user_de/userid/pkgname /data/user_de/userid/pkgname/cache 和 /data/user_de/userid/pkgname/code_cache 目录
        auto status = createAppDataDirs(path, uid, uid, previousUid, cacheGid, seInfo, targetMode,
                                        projectIdApp, projectIdCache);
        省略代码......
    }

    省略代码......
    return ok();
}


上面的源码的所实现的功能如下:

a.  如果 flags == FLAG_STORAGE_CE,则会创建 /data/user/userid/pkgname  /data/user/userid/pkgname/cache /data/user/userid/pkgname/code_cache 这些目录,并且会设置这些目录的uid、gid(uid、gid都是与当前的userid和apk的appid有关的) 

b.  如果flags == FLAG_STORAGE_DE,则会创建 /data/user_de/userid/pkgname   /data/user_de/userid/pkgname/cache /data/user_de/userid/pkgname/code_cache 这些目录,并且会设置这些目录的uid、gid(uid、gid都是与当前的userid和apk的appid有关的)

对于上面提到的 FLAG_STORAGE_CEFLAG_STORAGE_DE 是不是有些摸不着头脑啊,app data的根目录为啥既可以是 /data/user/userid/pkgname/ 也可以是 /data/user_de/userid/pkgname/呢?这俩目录的作用又是啥呢?

为了解释清楚上面的问题,先来介绍下Android 设备上的两种数据加密类型:凭据加密(Credential Encrypted简称为CE)和设备加密(Device Encrypted简称DE)。

凭据加密:它通过在用户锁屏的情况下加密用户数据来提高设备的安全性。凭证加密使用用户在设备上设置的锁屏密码作为加密密钥,因此只有在用户正确输入锁屏密码之后,系统才能解密用户的数据。

使用凭据加密的存储空间被称为 凭据加密 (CE) 存储空间,这是app data根目录的默认存储位置,只能在用户解锁设备后使用。对于应用中的非常敏感的数据强烈建议存储在CE存储空间。

设备加密:是在设备启动时就会生效的加密方式,它将整个设备的存储空间都加密起来,包括系统分区和用户分区。这意味着即使在设备被盗取或者遗失的情况下,盗取者也无法轻易访问设备上的数据。

使用设备加密的存储空间被称为 设备加密 (DE) 存储空间,该存储位置在直接启动模式下和用户解锁设备后均可使用。对于一般的数据可以存储在DE存储空间。

用更直白的话来描述 凭据加密 (CE) 存储空间和设备加密 (DE) 存储空间 的区别:前者设备第一次启动后如果没有输入正确的锁屏密码,则该存储空间是不能使用的,只有正确输入锁屏密码后才可以使用;而后者 是可以在任何情况下都可以使用的。 

如若app希望在第一次启动后还没输入正确的锁屏密码情况下使用存储空间,则需要使用设备加密 (DE) 存储空间,否则强烈建议使用 凭据加密 (CE) 存储空间(因为它的加密级别更高)。

有了上面的基础知识,就可以分清 FLAG_STORAGE_CEFLAG_STORAGE_DE 了,FLAG_STORAGE_CE代表在凭据加密 (CE) 存储空间 创建对应的目录,而FLAG_STORAGE_DE很明显就是在设备加密 (DE) 存储空间创建对应的目录。在凭据加密 (CE) 存储空间 创建的app data根目录是 /data/user/userid/pkgname/ ,而在设备加密 (DE) 存储空间 创建的app data根目录是 /data/user_de/userid/pkgname/

下面几个图展示了 凭据加密 (CE) 存储空间 和 设备加密 (DE) 存储空间 创建的app data根目录在第一次启动没输入锁屏密码和输入锁屏密码的表现。

设备加密存储空间---各目录都正常显示

凭据加密存储空间输入锁屏密码--各目录都正常显示

创建app data根目录除了存放apk运行过程中产生的数据外,系统运行过程中产生的数据也会存放在该根目录下面。创建app data根目录是apk安装的非常重要的一个环节,如果app data根目录没有创建成功,app是无法运行的。

在apk安装时候,会同时在 凭证加密 (CE) 存储空间 和 设备加密 (DE) 存储空间 都创建 app data根目录(除非在apk的Androidmanifest中使用android:defaultToDeviceProtectedStorage="true"来指定只在设备加密 (DE) 存储空间 创建app data根目录)

如果设备上存在多用户比如它们的userid分别为0、10,若在安装apk的时候针对所有用户都安装这个apk,则app data根目录会创建四个:凭据加密 (CE) 存储空间  /data/user/0/pkgname/  和 /data/user/10/pkgname/,设备加密 (DE) 存储空间  /data/user_de/0/pkgname/ /data/user_de/10/pkgname/

还有很重要一点app data根目录是创建于内部存储空间的。

对dex进行优化

对dex进行优化又叫做 dexopt,dexopt也是apk安装的最后一个步骤,并不是所有的apk在安装的时候都需要执行dexopt,因为dexopt是比较耗时的(比如对于debug版本的apk是不需要dexopt的),可以理解为dexopt对于apk的安装是锦上添花,但是离了它apk安装也是依然可以正常执行的。

谈了这么多的dexopt,来解释下dexopt能带来什么好处吧。dex文件其实就是进化版的class(字节码文件),它在虚拟机上执行的时候也是需要解释器把一行行的指令编译为机器码,再去执行机器码。这样每次执行都需要重复执行上面的步骤,那这性能肯定是慢啊。dexopt就解决了此问题,它会提前把dex进行优化、漏洞检查等,最终产生可以直接执行的机器码,进而提升运行性能。dexopt是art(Android Art Runtime)的一个模块。

dexopt优化后的产物会放在 /data/app/randomstr/pkg+randomstr/cpuabi/目录下面(randomstr是一个随机字符串,pkg+randomstr是包名加上随机字符串,cpuabi是cpu架构如arm等)。

下面是它的源代码,有兴趣可以看下。

binder::Status InstalldNativeService::dexopt(
        const std::string& apkPath, int32_t uid, const std::string& packageName,
        const std::string& instructionSet, int32_t dexoptNeeded,
        const std::optional<std::string>& outputPath, int32_t dexFlags,
        const std::string& compilerFilter, const std::optional<std::string>& uuid,
        const std::optional<std::string>& classLoaderContext,
        const std::optional<std::string>& seInfo, bool downgrade, int32_t targetSdkVersion,
        const std::optional<std::string>& profileName,
        const std::optional<std::string>& dexMetadataPath,
        const std::optional<std::string>& compilationReason, bool* aidl_return) {
    省略代码......

    const char* oat_dir = getCStr(outputPath);
    const char* instruction_set = instructionSet.c_str();
    if (oat_dir != nullptr && !createOatDir(packageName, oat_dir, instruction_set).isOk()) {
        oat_dir = nullptr;
    }

    //解析各种参数
    const char* apk_path = apkPath.c_str();
    const char* pkgname = packageName.c_str();
    const char* compiler_filter = compilerFilter.c_str();
    const char* volume_uuid = getCStr(uuid);
    const char* class_loader_context = getCStr(classLoaderContext);
    const char* se_info = getCStr(seInfo);
    const char* profile_name = getCStr(profileName);
    const char* dm_path = getCStr(dexMetadataPath);
    const char* compilation_reason = getCStr(compilationReason);
    std::string error_msg;
    bool completed = false// not necessary but for compiler
    //调用dexopt开始真正执行优化
    int res = android::installd::dexopt(apk_path, uid, pkgname, instruction_set, dexoptNeeded,
            oat_dir, dexFlags, compiler_filter, volume_uuid, class_loader_context, se_info,
            downgrade, targetSdkVersion, profile_name, dm_path, compilation_reason, &error_msg,
            &completed);
    *aidl_return = completed;

    省略代码......
}

清除app数据

app data根目录及根目录下面的cache和code_cache目录都是由我installd创建的,秉持谁创建谁清除的原则,因此我installd是有职责清除app data根目录的。

清除app数据不单单只是清除app data根目录下面的各种文件(app data根目录是存储于内部存储空间目录),还可以清除apk存储于外部存储空间目录的各种文件(我有root权限,我谁都可以删除)。

下面是官网关于内部存储空间目录和外部存储空间目录的对比。

  • 内部存储空间目录:这些目录既包括用于存储持久性文件的专属位置,也包括用于存储缓存数据的其他位置。系统会阻止其他应用访问这些位置,并且在 Android 10(API 级别 29)及更高版本中,系统会对这些位置进行加密。这些特征使得这些位置非常适合存储只有应用本身才能访问的敏感数据。

  • 外部存储空间目录:这些目录既包括用于存储持久性文件的专属位置,也包括用于存储缓存数据的其他位置。虽然其他应用可以在具有适当权限的情况下访问这些目录,但存储在这些目录中的文件仅供您的应用使用。如果您明确打算创建其他应用能够访问的文件,您的应用应改为将这些文件存储在外部存储空间的共享存储空间部分。

在卸载apk的时候是会同时清掉内部存储空间目录和外部存储空间目录的所有文件的(除非指定了只清除apk而保留数据)。在设置app中清除某app数据也使用的是这个能力。

下面是具体的代码实现,有兴趣同学可以看下:

binder::Status InstalldNativeService::clearAppData(const std::optional<std::string>& uuid,
        const std::string& packageName, int32_t userId, int32_t flags, int64_t ceDataInode) {
    ENFORCE_UID(AID_SYSTEM);
    CHECK_ARGUMENT_UUID(uuid);
    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
    LOCK_PACKAGE_USER();

    const char* uuid_ = uuid ? uuid->c_str() : nullptr;
    const char* pkgname = packageName.c_str();

    binder::Status res = ok();
    //如果是CE,走这
    if (flags & FLAG_STORAGE_CE) {
        auto path = create_data_user_ce_package_path(uuid_, userId, pkgname, ceDataInode);
        //如果只是清除cache,走这
        if (flags & FLAG_CLEAR_CACHE_ONLY) {
            path = read_path_inode(path, "cache", kXattrInodeCache);
        } else if (flags & FLAG_CLEAR_CODE_CACHE_ONLY) {
            //如果只是清除code_cache 走这
            path = read_path_inode(path, "code_cache", kXattrInodeCodeCache);
        }
        //如果flags既不是清除cache和code_cache目录,则是清除app data根目录
        if (access(path.c_str(), F_OK) == 0) {
            if (delete_dir_contents(path) != 0) {
                res = error("Failed to delete contents of " + path);
            } else if ((flags & (FLAG_CLEAR_CACHE_ONLY | FLAG_CLEAR_CODE_CACHE_ONLY)) == 0) {
                remove_path_xattr(path, kXattrInodeCache);
                remove_path_xattr(path, kXattrInodeCodeCache);
            }
        }
    }

    //如果是DE,走这
    if (flags & FLAG_STORAGE_DE) {
        std::string suffix;
        //如果只是清除cache,走这
        if (flags & FLAG_CLEAR_CACHE_ONLY) {
            suffix = CACHE_DIR_POSTFIX;
        } else if (flags & FLAG_CLEAR_CODE_CACHE_ONLY) {
            //如果只是清除code_cache 走这
            suffix = CODE_CACHE_DIR_POSTFIX;
        }

        //如果flags既不是清除cache和code_cache目录,则是清除app data根目录
        auto path = create_data_user_de_package_path(uuid_, userId, pkgname) + suffix;
        if (access(path.c_str(), F_OK) == 0) {
            if (delete_dir_contents(path) != 0) {
                res = error("Failed to delete contents of " + path);
            }
        }
    }

    //清除外部存储空间目录
    if (flags & FLAG_STORAGE_EXTERNAL) {
        std::lock_guard<std::recursive_mutex> lock(mMountsLock);
        for (const auto& n : mStorageMounts) {
            auto extPath = n.second;

            if (android::base::GetBoolProperty(kFuseProp, false)) {
                std::regex re("^\\/mnt\\/pass_through\\/[0-9]+\\/emulated");
                if (std::regex_match(extPath, re)) {
                    extPath += "/" + std::to_string(userId);
                }
            } else {
                if (n.first.compare(014"/mnt/media_rw/") != 0) {
                    extPath += StringPrintf("/%d", userId);
                } else if (userId != 0) {
                    // TODO: support devices mounted under secondary users
                    continue;
                }
            }

            if (flags & FLAG_CLEAR_CACHE_ONLY) {
                // Clear only cached data from shared storage
                auto path = StringPrintf("%s/Android/data/%s/cache", extPath.c_str(), pkgname);
                if (delete_dir_contents(path, true) != 0) {
                    res = error("Failed to delete contents of " + path);
                }
            } else if (flags & FLAG_CLEAR_CODE_CACHE_ONLY) {
                // No code cache on shared storage
            } else {
                // Clear everything on shared storage
                auto path = StringPrintf("%s/Android/data/%s", extPath.c_str(), pkgname);
                if (delete_dir_contents(path, true) != 0) {
                    res = error("Failed to delete contents of " + path);
                }
                path = StringPrintf("%s/Android/media/%s", extPath.c_str(), pkgname);
                if (delete_dir_contents(path, true) != 0) {
                    res = error("Failed to delete contents of " + path);
                }

            }
        }
    }
    省略代码......
}

app数据备份

app数据备份简而言之就是把app data根目录下面的文件进行备份,备份也同样根据凭据加密 (CE) 存储空间和设备加密 (DE) 存储空间分别进行备份,对于 CE 会备份到 /data/misc_ce/userid/rollback/pkgname/ 目录下面,对于 DE 会备份到 /data/misc_de/userid/rollback/pkgname/ 目录下面。

下面是对应的源码,有兴趣可以看下。

binder::Status InstalldNativeService::snapshotAppData(const std::optional<std::string>& volumeUuid,
                                                      const std::string& packageName,
                                                      int32_t userId, int32_t snapshotId,
                                                      int32_t storageFlags, int64_t* _aidl_return) {//niu 该方法作用是创建app data的副本,用于后面回滚
    ENFORCE_UID(AID_SYSTEM);
    CHECK_ARGUMENT_UUID_IS_TEST_OR_NULL(volumeUuid);
    CHECK_ARGUMENT_PACKAGE_NAME(packageName);
    LOCK_PACKAGE_USER();

    const char* volume_uuid = volumeUuid ? volumeUuid->c_str() : nullptr;
    const char* package_name = packageName.c_str();

    省略代码......

    //DE 走这
    if (storageFlags & FLAG_STORAGE_DE) {
        //from代表 /data/user_de/userid/pkgname目录下面的文件
        auto from = create_data_user_de_package_path(volume_uuid, userId, package_name);
        //to代表 /data/misc_de/userid/pkgname目录
        auto to = create_data_misc_de_rollback_path(volume_uuid, userId, snapshotId);

        省略代码......

        // Check if we have data to copy.
        if (access(from.c_str(), F_OK) == 0) {
          //把from的文件拷贝到to目录下面
          rc = copy_directory_recursive(from.c_str(), to.c_str());
        }

        省略代码......
    }

    省略代码......

    //CE 走这
    if (storageFlags & FLAG_STORAGE_CE) {
        //from代表 /data/user/userid/pkgname目录下面的文件
        auto from = create_data_user_ce_package_path(volume_uuid, userId, package_name);
        //to代表 /data/misc_ce/userid/pkgname目录
        auto to = create_data_misc_ce_rollback_path(volume_uuid, userId, snapshotId);//niu to为/data/misc_ce/userid/rooback/snapshotId

        省略代码......

        //把from的文件拷贝到to目录下面
        rc = copy_directory_recursive(from.c_str(), to.c_str()); 

        省略代码......
    }

    省略代码......
}

app数据恢复

app数据恢复刚好和app数据备份相反:根据凭据加密 (CE) 存储空间和设备加密 (DE) 存储空间分别进行恢复,对于 CE 会把 /data/misc_ce/userid/rollback/pkgname/ 目录下面的各种文件 拷贝到 /data/user/userid/pkgname/ 目录下 ,对于 DE 会把 /data/misc_de/userid/rollback/pkgname/ 目录下面的各种文件拷贝到/data/user_de/userid/pkgname/ 目录下。

下面是对应的源码,有兴趣可以看下。

binder::Status InstalldNativeService::restoreAppDataSnapshot(
        const std::optional<std::string>& volumeUuid, const std::string& packageName,
        const int32_t appId, const std::string& seInfo, const int32_t userId,
        const int32_t snapshotId, int32_t storageFlags) {//niu 该方法作用是用来回滚
    省略代码......

    const char* volume_uuid = volumeUuid ? volumeUuid->c_str() : nullptr;
    const char* package_name = packageName.c_str();

    //from_ce为 /data/misc_ce/userid/rollback/pkgname/
    auto from_ce = create_data_misc_ce_rollback_package_path(volume_uuid, userId, snapshotId,
                                                             package_name);
    //from_de为 /data/misc_de/userid/rollback/pkgname/
    auto from_de = create_data_misc_de_rollback_package_path(volume_uuid, userId, snapshotId,
                                                             package_name);

    const bool needs_ce_rollback = (storageFlags & FLAG_STORAGE_CE) &&
        (access(from_ce.c_str(), F_OK) == 0);
    const bool needs_de_rollback = (storageFlags & FLAG_STORAGE_DE) &&
        (access(from_de.c_str(), F_OK) == 0);

    省略代码......

    //CE 走这
    if (needs_ce_rollback) {
        auto to_ce = create_data_user_ce_path(volume_uuid, userId);
        //拷贝到 to_ce
        int rc = copy_directory_recursive(from_ce.c_str(), to_ce.c_str());

        省略代码......
    }

    //DE 走这
    if (needs_de_rollback) {
        auto to_de = create_data_user_de_path(volume_uuid, userId);

        省略代码......
    }

    省略代码......
}

总结

上面提到的这些能力基本都是基于 凭据加密 (CE) 存储空间 和 设备加密 (DE) 存储空间 进行展开的。app data根目录对于apk的安装是非常重要的,如果没有它则app就没办法正常运行;dexopt对于apk的安装不是必须的,不进行dexopt apk也是可以正常安装。当然还有别的能力就不赘述了。
7
我的代理人


我作为一个各种能力的提供方,别的进程想使用我的能力的话可以使用binder的方式来使用,我把自己发布在了ServiceManager ,可以通过"installd"名字从ServiceManager中检索到我的BpBinder,进而可以调用我的能力。由于我是一个系统native进程,为了让java世界的进程也能使用到我,我创建了一个代理人它的名字叫Installer,可以在java世界使用Installer来间接使用我的能力。
8
总结


我是一个系统native进程,我的名字叫installd,我存在的意义不单单为apk的安装/卸载提供辅助功能、还提供了对dex进行优化等功能。



最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!


推荐阅读

安卓应用跳转回流的统一和复用
一文搞懂Window、PhoneWindow、DercorView、WindowManager
一个大型 Android 项目的模块划分哲学



扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~


┏(^0^)┛明天见!