在APK打包过程中,Assets资源漏编译漏打包的本质

背景

作为 Androider,我们平时在 Assets 资源目录下都放点啥呢,字体、预置数据、图片、配置文件…等等,那大家有没有想过,万一哪天我在 Assets 目录下新增了一个子目录放了点自己的资源文件,打包之后再解包发现 Apk 包里没有找到这部分文件,怎么办呢?

原理分析

我们都知道典型的 Android 应用构建流程第一步就是 Android 编译器将应用的源代码转换成 DEX 文件(即 Dalvik 可执行文件),并且把其他所有内容转换成为编译后的资源,然后打包器将 DEX 文件和编译后的资源文件组合成 APK。

这里的资源文件就包括 Assets 目录下的文件在内,还有 res 目录下的所有文件和 AndroidManifest.xml 文件,我们刚刚提到 APK 的资源编译是编译过程中的一项主要工作,AGP3.0.0 之后默认通过 AAPT2 来编译资源。

受到 Android 系统 AAPT 配置的影响,如果走系统默认配置打包,我们的 Assets 目录合并过程中会走一些判断逻辑,如果根据系统规则判定该文件夹是需要被忽略的,那么也就意味着打不进 Apk 里了。

规则是这样的:

# Assets 目录合并忽略模式
!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~

规则判断的源码是这样的:

// 忽略模式核心源码
static bool isHidden(const char *root, const char *path)
{
    if (strcmp(path, ".") == 0 || strcmp(path, "..") == 0) {
        return true;
    }

    const char *delim = ":";
    const char *p = gUserIgnoreAssets;
    if (!p || !p[0]) {
        p = getenv("ANDROID_AAPT_IGNORE");
    }
    if (!p || !p[0]) {
        p = gDefaultIgnoreAssets;
    }
    char *patterns = strdup(p);

    bool ignore = false;
    bool chatty = true;
    char *matchedPattern = NULL;

    String8 fullPath(root);
    fullPath.appendPath(path);
    FileType type = getFileType(fullPath);

    int plen = strlen(path);

    // Note: we don't have strtok_r under mingw.
    for(char *token = strtok(patterns, delim);
        !ignore && token != NULL;
        token = strtok(NULL, delim)) {
            chatty = token[0] != '!';
            if (!chatty) token++; // skip !
            if (strncasecmp(token, "<dir>" , 5) == 0) {
                if (type != kFileTypeDirectory) continue;
                token += 5;
            }
            if (strncasecmp(token, "<file>", 6) == 0) {
                if (type != kFileTypeRegular) continue;
                token += 6;
            }

            matchedPattern = token;
            int n = strlen(token);

            if (token[0] == '*') {
                // Match *suffix
                token++;
                n--;
                if (n <= plen) {
                    ignore = strncasecmp(token, path + plen - n, n) == 0;
                }
            } else if (n > 1 && token[n - 1] == '*') {
                // Match prefix*
                ignore = strncasecmp(token, path, n - 1) == 0;
            } else {
                ignore = strcasecmp(token, path) == 0;
            }
        }

    if (ignore && chatty) {
        fprintf(stderr, "    (skipping %s '%s' due to ANDROID_AAPT_IGNORE pattern '%s')\n",
            type == kFileTypeDirectory ? "dir" : "file",
            path,
            matchedPattern ? matchedPattern : "");
    }

    free(patterns);
    return ignore;
}

源码挺长,我们直接看忽略模式解读图: image.png

举个例子

原理分析比较长,我们看一个详细的例子,更好地理解这个判断规则

随便找一个子 Module,在对应 Assets 目录下增加一个新的子目录,命名为 “__testxxx”,在这个目录下随便放一个测试文件,“test.txt”。

我们尝试打 SNAPSHOP 包然后更新依赖并且打 APK 包,解包以后好像并没有找到这个目录,更没有这个测试文件。

为什么呢?

我们回过头看下规则,其中 “:” 是分隔符,

_* 就是今天的重点了,啥意思呢,当判断到 “__testxxx” 是一个目录时,按照规则会接着判断后一位,恰好后一位是 “_” 下划线,和我们的文件夹名称开头一致,也就被认为需要被忽略合并。

简单来说,就是下划线开头的文件夹目录在打包时将会被认做需要忽略合并的文件夹,不予合并。

解决方案

两种方式,第一种很简单,就是换名字,换个和规则不匹配的名称即可

当然,有些情形下是没办法换组件名字的,不急,还有第二种方案,自定义 AAPT 配置,像这样:

android {
    aaptOptions {
        noCompress ' test1', 'test2'
        ignoreAssetsPattern "!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~"
    }
}

接下来,就看你自己的诉求了,如果是需要打到子 Module 的 SNAPSHOP 中,就把 ignoreAssetsPattern 加到对应 Module 的 android 配置中,如果是需要打到安装包 APK 中,就需要到壳工程的 build.gradle 中去同步一份配置了。

影响面评估

这份配置修改的是打包过程中 Assets 资源合并的规则,那么相对地,就会有一些绕开默认规则且符合自定义规则的资源被合并进 APK 包中了。简单地说:

  • 对于加了配置的 Module,会有一些 _* 规则以外的文件会被合并进去
  • 对于没有加配置的 Module,也就没有影响

大家可以针对 app/build/intermediates/incremental/mergeDebugAssets/merger.xml XML 文件做加配置前后的 DIFF 对比,在合并之前检查是否有不该合并的文件被合并进去。

影响完全可控,并且也达到了我们的初心,让合理资源被正常打包,不合理的资源被忽略,彻底解决漏编译漏打。

作者:闫宇威
本文转自 [https://juejin.cn/post/7192418675939344441]

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

全套视频资料:

一、面试合集在这里插入图片描述
二、源码解析合集
在这里插入图片描述

三、开源框架合集
在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取↓↓↓

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Gradle是一种用于构建和管理Android应用程序项目的工具。它的一个重要功能就是打包编译资源。 在Android Gradle资源是指应用程序使用的各种非代码文件,如图像、布局文件、字符串、样式等。这些资源文件需要经过打包编译过程才能被应用程序使用。 打包资源过程是指将应用程序使用的所有资源文件收集起来,打包成一个或多个二进制资源文件(.arsc),以方便应用程序在运行时访问。这个过程由Gradle的打包任务完成。打包后的资源文件会被放置在应用程序的res目录下的各个res-qualified目录编译资源过程是指将资源文件从其源文件形式编译成二进制格式,以便应用程序可以在运行时使用它们。这个过程由Android资源编译器(AAPT)完成。编译资源过程包括对资源文件进行验证、解析和优化,最终生成资源文件的二进制表示形式。 要在Android Gradle项目进行资源打包编译,我们需要在项目的build.gradle文件配置相应的构建规则和参数。其,可以通过设置资源文件的名称、路径、扩展名等属性来指定要打包编译资源文件。此外,还可以通过配置资源的qualifier(如屏幕密度、语言等)来实现对不同设备和语言环境的资源适配。 通过Android Gradle的打包编译资源功能,我们可以方便地管理和使用应用程序的各种资源文件,使应用程序在不同设备和语言环境下都能正确地加载和显示相应的资源。这对于开发多语言和多平台的应用程序来说,是非常重要的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值