Android资源管理中的Runtime Resources Overlay-------之PMS、installd和idmap的处理(二)

        前面我们对RRO(Runtime Resources Overlay)的使用做了介绍,并且知道了它大概的原理。下面我们详细介绍其实现过程,先说两个名词:
        target包:就是要被覆盖的包
        overlay包:就是我们的资源包或者主题包等

PMS的处理

        PMS的处理比较简单,它会在开机的时候对所有的包做扫描,提取出包信息,如果有RRO对应的overlay包和target包,则会以local_socket的形式请求installd去做idmap。
        先看PackageManagerService.java中的一些数据结构:

/**
 *这个常量就是系统定义的overlay package存放的路径,也就是前面我们要把demo里的overlay package push
 *到/vendor/overlay的原因。
 */
private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";

//mOverlays用来存储overlay相关的包信息
final ArrayMap<String, ArrayMap<String, PackageParser.Package>> mOverlays =
        new ArrayMap<String, ArrayMap<String, PackageParser.Package>>();

        mOverlays的key是target包的包名。由于一个target包可以被多个overlay包覆盖,所以它的vaule是一个ArrayMap:key表示overlay package的packageName,value则是overlay package。这里就有个问题:如果一个target package被多个overlay package覆盖了,那么到底哪个overlay package 生效呢?这个我们可以通过android:priority="1"来指定overlay package 的优先级,数字越大,优先级越高,取值范围0~9999。
        我们知道在PackageManagerService构造的时候就回去扫描系统一些目录。这当中,第一个扫描的就是/vendor/overlay目录,解析其AndroidManifest.xml文件,拿到它的target package和priority。我们看看frameworks/base/core/java/android/content/pm/PackageParser.java中的相关实现:

private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
            String[] outError) throws XmlPullParserException, IOException {
            
    //......省略无关代码
    
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
        if (tagName.equals("application")) {
             //解析application相关属性
             //.........非我们关注的重点,省略之
        }else if (tagName.equals("overlay")) {
                //解析我们的overlay标签
                pkg.mTrustedOverlay = trustedOverlay;

                sa = res.obtainAttributes(attrs,
                        com.android.internal.R.styleable.AndroidManifestResourceOverlay);
                //获取android:target属性值
                pkg.mOverlayTarget = sa.getString(
                        com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetPackage);
                //获取android:priority属性值
                pkg.mOverlayPriority = sa.getInt(
                        com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority,
                        -1);
                sa.recycle();

                if (pkg.mOverlayTarget == null) {
                    outError[0] = "<overlay> does not specify a target package";
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return null;
                }
                //android:priority属性值必须在0~9999之间
                if (pkg.mOverlayPriority < 0 || pkg.mOverlayPriority > 9999) {
                    outError[0] = "<overlay> priority must be between 0 and 9999";
                    mParseError =
                        PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return null;
                }
                XmlUtils.skipCurrentTag(parser);
        }
    }
}

        在包扫描的过程中会调用到PackageManagerServices的这个方法:

private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
            int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
    //..............省略无关代码
    //pkg就是前面扫描过的overlay package,所以pkg.mOverlayTarget肯定不空
    if (pkg.mOverlayTarget != null) {//true
        //overlay 包的处理逻辑
        // This is an overlay package.
        if (pkg.mOverlayTarget != null && !pkg.mOverlayTarget.equals("android")) {
            //由于是第一次扫描,肯定会走进这个if,如果后面再有这个target包的overlay package则跳过
            if (!mOverlays.containsKey(pkg.mOverlayTarget)) {
                 mOverlays.put(pkg.mOverlayTarget,
                       new ArrayMap<String, PackageParser.Package>());
            }
            
            //将正在扫描的这个overlay package添加进去
            ArrayMap<String, PackageParser.Package> map = mOverlays.get(pkg.mOverlayTarget);
            map.put(pkg.packageName, pkg);
            PackageParser.Package orig = mPackages.get(pkg.mOverlayTarget);
            
            /**
             *orig为空,所以执行不到createIdmapForPackagePairLI方法
             *但是因为我们做了动态idmap或者线程调度原因,如果此时我们的target包已经扫描完,那
             *就得在这里做idmap了,否则后面就没有机会做了。
             */
            if (orig != null && !createIdmapForPackagePairLI(orig, pkg)) {
                        throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                                "scanPackageLI failed to createIdmap");
            }
        }
    } else if (mOverlays.containsKey(pkg.packageName) &&
              !pkg.packageName.equals("android")) {//false 但是这个条件在扫描target package的时候会走到
        //target包的处理逻辑      
        // This is a regular package, with one or more known overlay packages.
        Slog.d(TAG, "idmapidmap, " + pkg.packageName + " is a regular package");
        createIdmapsForPackageLI(pkg);
    }
    //.........省略无关代码
    return pkg;
}

        这里我们看到过滤掉了android系统资源包framework-res.apk,因为系统资源包比较特殊,它的RRO是在AssetManager的native层处理的,这里就不需要处理了。另外,当扫描完/vendor/overlay/目录下的所有overlay包后,会扫描其它包,这个时候就会走到target package的处理逻辑,去做idmap:

    private void createIdmapsForPackageLI(PackageParser.Package pkg) {
        //pkg是target package
        ArrayMap<String, PackageParser.Package> overlays = mOverlays.get(pkg.packageName);
        if (overlays == null) {
            Slog.w(TAG, "Unable to create idmap for " + pkg.packageName + ": no overlay packages");
            return;
        }
        for (PackageParser.Package opkg : overlays.values()) {
            // Not much to do if idmap fails: we already logged the error
            // and we certainly don't want to abort installation of pkg simply
            // because an overlay didn't fit properly. For these reasons,
            // ignore the return value of createIdmapForPackagePairLI.
            createIdmapForPackagePairLI(pkg, opkg);
        }
    }

        找到target package的所有overlay package,然后一个一个去做idmap。这里不太关心做idmap的结果是成功了还是失败了,如果失败,我们就当没有那个overlay package就完了,所以不处理createIdmapForPackagePairLI方法的返回值。

    private boolean createIdmapForPackagePairLI(PackageParser.Package pkg,
            PackageParser.Package opkg) {
        //先做相关检查
        if (!opkg.mTrustedOverlay) {
            Slog.w(TAG, "Skipping target and overlay pair " + pkg.baseCodePath + " and " +
                    opkg.baseCodePath + ": overlay not trusted");
            return false;
        }
        //拿到一个target包的所有overlay包
        ArrayMap<String, PackageParser.Package> overlaySet = mOverlays.get(pkg.packageName);
        if (overlaySet == null) {
            Slog.e(TAG, "was about to create idmap for " + pkg.baseCodePath + " and " +
                    opkg.baseCodePath + " but target package has no known overlays");
            return false;
        }
        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
        // 告诉installer去做idmap
        if (mInstaller.idmap(pkg.baseCodePath, opkg.baseCodePath, sharedGid) != 0) {
            Slog.e(TAG, "Failed to generate idmap for " + pkg.baseCodePath + " and "
                    + opkg.baseCodePath);
            return false;
        }
        PackageParser.Package[] overlayArray =
            overlaySet.values().toArray(new PackageParser.Package[0]);
        //按优先级排序!
        Comparator<PackageParser.Package> cmp = new Comparator<PackageParser.Package>() {
            public int compare(PackageParser.Package p1, PackageParser.Package p2) {
                return p1.mOverlayPriority - p2.mOverlayPriority;
            }
        };
        Arrays.sort(overlayArray, cmp);
        //把所有overlay package的路径写入target package的applicationInfo
        pkg.applicationInfo.resourceDirs = new String[overlayArray.length];
        int i = 0;
        for (PackageParser.Package p : overlayArray) {
            pkg.applicationInfo.resourceDirs[i++] = p.baseCodePath;
        }
        return true;
    }

        这里我们可以看到:PMS调用了mInstaller的idmap方法去做idmap,然后还会对同一个target package的所有overlay package按照优先级排序,最后还会把所有overlay package的路径写入target package的ApplicationInfo的resourceDirs里面去。在Android资源管理中的SharedLibrary和Dynamic Reference-------之资源共享库(一)我们对ApplicationInfo的resourceDirs变量有过说明,不知道大家是否还记得。另外,mInstaller是Installer类的实例,我们看看它的实现:

//frameworks/base/services/core/java/com/android/server/pm/Installer.java
public final class Installer extends SystemService {
    //......省略无关代码
    private final InstallerConnection mInstaller;
    //......省略无关代码
    public Installer(Context context) {
        super(context);
        mInstaller = new InstallerConnection();
    }
    //......省略无关代码
    public int idmap(String targetApkPath, String overlayApkPath, int uid) {
        StringBuilder builder = new StringBuilder("idmap");
        builder.append(' ');
        builder.append(targetApkPath);
        builder.append(' ');
        builder.append(overlayApkPath);
        builder.append(' ');
        builder.append(uid);
        return mInstaller.execute(builder.toString());
    }
}

        Installer的实现非常简单,包装了InstallerConnection的一个实例,让它去执行一条命令:idmap targetApkPath overlayApkPath uid

    public class InstallerConnection {
        private InputStream mIn;
        private OutputStream mOut;
        //这里有一个socket,用于跨进程和installd通信
        private LocalSocket mSocket;

        private final byte buf[] = new byte[1024];
        
        public int execute(String cmd) {
            String res = transact(cmd);
            try {
                return Integer.parseInt(res);
            } catch (NumberFormatException ex) {
                return -1;
            }
        }

        public synchronized String transact(String cmd) {
            //检查连接状态,如果没有连接,则去做连接动作
            if (!connect()) {
                Slog.e(TAG, "connection failed");
                return "-1";
            }
            //给对端也就是installd发命令,如果不成功会做二次尝试
            if (!writeCommand(cmd)) {
                if (!connect() || !writeCommand(cmd)) {
                    return "-1";
                }
            }
            if (LOCAL_DEBUG) {
                Slog.i(TAG, "send: '" + cmd + "'");
            }
            //读取installd执行的结果并返回
            final int replyLength = readReply();
            if (replyLength > 0) {
                String s = new String(buf, 0, replyLength);
                return s;
            }else {
                return "-1";
            }
        }
        private boolean connect() {
            //已经连接
            if (mSocket != null) {
                return true;
            }
            Slog.i(TAG, "connecting...");
            try {
                //连接installd
                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;
        }
        //往installd那里发数据
        private boolean writeCommand(String cmdString) {
            final byte[] cmd = cmdString.getBytes();
            final int len = cmd.length;
            if ((len < 1) || (len > buf.length)) {
                return false;
            }

            buf[0] = (byte) (len & 0xff);
            buf[1] = (byte) ((len >> 8) & 0xff);
            try {
                mOut.write(buf, 0, 2);
                mOut.write(cmd, 0, len);
            } catch (IOException ex) {
                Slog.e(TAG, "write error");
                disconnect();
                return false;
            }
            return true;
        }
    }

        InstallerConnection的工作就是建立socket,向installd发送命令,然后读取installd返回的结果。到这里,对于生成idmap文件而言,PMS和Installer的工作已经完成,剩下的就交给installd这个守护进程了。

installd的处理

        installd进程在init进程解析init.rc的时候就会起来,它主要负责我们安装包的管理,比如install、uninstall、rename、dexopt,当然还有idmap。我们来看看它的具体实现:

//frameworks/native/cmds/installd/installd.c
//没什么好说的,直接看idmap函数了
static int do_idmap(char **arg, char reply[REPLY_MAX])
{
    ALOGD("do_idmap : %s %s %s", arg[0], arg[1], arg[2]);
    return idmap(arg[0], arg[1], atoi(arg[2]));
}


//frameworks/native/cmds/installd/commands.c
int idmap(const char *target_apk, const char *overlay_apk, uid_t uid)
{
    ALOGV("idmap target_apk=%s overlay_apk=%s uid=%d\n", target_apk, overlay_apk, uid);

    int idmap_fd = -1;
    char idmap_path[PATH_MAX];

   //.....路径、uid、权限等的检查,如果不通过直接返回,代码就不贴了

    pid_t pid;
    //fork出子进程
    pid = fork();
    if (pid == 0) {
        //.........子进程,又是一些列的检查,代码不贴

        //在这里做idmap
        run_idmap(target_apk, overlay_apk, idmap_fd);
        exit(1); /* only if exec call to idmap failed */
    } else {
        //父进程,等结果
        int status = wait_child(pid);
        if (status != 0) {
            ALOGE("idmap failed, status=0x%04x\n", status);
            goto fail;
        }
    }

    close(idmap_fd);
    return 0;
fail:
    if (idmap_fd >= 0) {
        close(idmap_fd);
        unlink(idmap_path);
    }
    return -1;
}

static void run_idmap(const char *target_apk, const char *overlay_apk, int idmap_fd)
{
    //idmap的bin文件在/system/bin/idmap
    static const char *IDMAP_BIN = "/system/bin/idmap";
    static const size_t MAX_INT_LEN = 32;
    char idmap_str[MAX_INT_LEN];

    snprintf(idmap_str, sizeof(idmap_str), "%d", idmap_fd);
    //执行bin文件
    execl(IDMAP_BIN, IDMAP_BIN, "--fd", target_apk, overlay_apk, idmap_str, (char*)NULL);
    ALOGE("execl(%s) failed: %s\n", IDMAP_BIN, strerror(errno));
}

        installd的工作也很简单,做一系列的权限检查,然后fork出子进程,加载/system/bin/idmap并执行。

idmap的处理

        idmap也是一个native的bin文件,在/system/bin/下面,主要负责idmap文件的生成和dump。我们可以通过adb shell进去,然后输入idmap --help查看它的用法:
在这里插入图片描述
        --fd 创建idmap,直接输出到对应的fd
        --path 创建idmap,输出到指定的路径
        --inspect dump idmap文件
        --scan 用于一个target package 有多个overlay package的情况:
                dir-to-scan 多个overlay package所在的目录
                target-to-look-for target包的packageName
                target target包的路径
                 dir-to-hold-idmaps 存放生成的idmap文件的目录

        因此,我们可以通过idmap命令手动做idmap,直接把idmap文件生成到/data/resource-cache/目录下,效果跟通过PMS和installd来生成是一样的。另外,我们可以通过idmap --inspect来查看生成的idmap文件的内容,这里贴上一个我们可以先感受下:
在这里插入图片描述
        IDMAP HEADER主要记录了target包和overlay包的路径:/system/priv-app/SystemUI/SystemUI.apk和/vendor/overlay/SystemUIOverlay.apk;
        要覆盖的type的个数:types count=1,因为这里只覆盖了drawable一个type;

        然后是每一个type的idmap信息了,这个例子中只有drawable一个type

        target type和overlay type的值都是0x00000002,说明drawable在target包和overlay包的id都是0x00000002;
        entry offset 0x00000039表示从target包的索引值为0x00000039的那个drawable开始做idmap;
        entry count 0x00000143表示drawable类型的资源,做了0x00000143个;

        这张图我截得不全,因为它太大了。其实这个overlay pacakge仅仅有四张图片,也就是说只需要覆盖4个drawable就可以了,但是为什么entry count 不是0x00000004而是0x00000143呢?

        假设我们的target 包里有10个drawable资源,它们的名称为drawable0~drawable9,id为0x7f020000~0x7f020009;overlay包里有3个资源它们的名称分别是drawable4、drawable5、drawable8,id分别是0x7f030000、0x7f030001、0x7f030002。这样就表示overlay包要覆盖target包里的drawable4、drawable5、drawable8三个资源。那么它生成的idmap的主要内容应该如下:
        target type 0x00000002
        overlay type 0x00000003
        entry offset 0x00000004
        entry count 0x00000006

        entry 0x00000000 drawable/drawable4
        entry 0x00000001 drawable/drawable5
        entry 0xffffffff drawable/drawable6
        entry 0xffffffff drawable/drawable7
        entry 0x00000002 drawable/drawable8
        entry 0xffffffff drawable/drawable9

        为什么会是这个样子呢?我们将在下一篇中介绍,这里我们知道是这个样子就可以了。下面我们看看idmap这个bin文件的部分代码吧,在frameworks/base/cmds/idmap/目录下:

//frameworks/base/cmds/idmap/idmap.cpp
int main(int argc, char **argv)
{
    //.........省略无关代码
    //接installd,这里走--fd
    if (argc == 5 && !strcmp(argv[1], "--fd")) {
        return maybe_create_fd(argv[2], argv[3], argv[4]);
    }
    //.........省略无关代码
}

int maybe_create_path(const char *target_apk_path, const char *overlay_apk_path,
        const char *idmap_path)
{
    //这个也没啥好说的,又是一堆检查。。。
    if (!verify_root_or_system()) {
        fprintf(stderr, "error: permission denied: not user root or user system\n");
        return -1;
    }
    if (!verify_file_readable(target_apk_path)) {
        ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno));
        return -1;
    }
    if (!verify_file_readable(overlay_apk_path)) {
        ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno));
        return -1;
    }
    return idmap_create_path(target_apk_path, overlay_apk_path, idmap_path);
}

//frameworks/base/cmds/idmap/create.cpp
int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path,
        const char *idmap_path)
{
    //已经是新的没必要做了
    if (!is_idmap_stale_path(target_apk_path, overlay_apk_path, idmap_path)) {
        // already up to date -- nothing to do
        return EXIT_SUCCESS;
    }
    //打开文件
    int fd = open_idmap(idmap_path);
    if (fd == -1) {
        return EXIT_FAILURE;
    }
    //做idmap,并写入文件
    int r = create_and_write_idmap(target_apk_path, overlay_apk_path, fd, false);
    close(fd);
    if (r != 0) {
        unlink(idmap_path);
    }
    return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}

   int create_and_write_idmap(const char *target_apk_path, const char *overlay_apk_path,
            int fd, bool check_if_stale)
   {
        //又是检查。。。
        if (check_if_stale) {
            if (!is_idmap_stale_fd(target_apk_path, overlay_apk_path, fd)) {
                // already up to date -- nothing to do
                return 0;
            }
        }
        uint32_t *data = NULL;
        size_t size;
 
        //在这里做idmap
        if (create_idmap(target_apk_path, overlay_apk_path, &data, &size) == -1) {
            return -1;
        }
        //标准的写文件操作,就不说了
        if (write_idmap(fd, data, size) == -1) {
            free(data);
            return -1;
        }
        free(data);
        return 0;
   }
   int create_idmap(const char *target_apk_path, const char *overlay_apk_path,
        uint32_t **data, size_t *size)
   {
        //CRC校验
        uint32_t target_crc, overlay_crc;
        if (get_zip_entry_crc(target_apk_path, AssetManager::RESOURCES_FILENAME,
				&target_crc) == -1) {
            return -1;
        }
        if (get_zip_entry_crc(overlay_apk_path, AssetManager::RESOURCES_FILENAME,
				&overlay_crc) == -1) {
            return -1;
        }
        //绕了半天,这个活儿还是AssetManager来干的^_^
        AssetManager am;
        bool b = am.createIdmap(target_apk_path, overlay_apk_path, target_crc, overlay_crc,
                data, size);
        return b ? 0 : -1;
   }

        其实idmap进程还是围观者,只不过是做权限、访问、CRC等校验,真正的工作是由AssetManager来完成的。带着idmap文件那奇怪的数据形式,我们将在下期介绍idmap文件具体是怎么生成和加载的。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值