关于java:向工程腐化开炮|资源治理

27次阅读

共计 12577 个字符,预计需要花费 32 分钟才能阅读完成。

作者:刘天宇(谦风)

系列文章回顾《向工程腐化开炮 | proguard 治理》《向工程腐化开炮 | manifest 治理》《向工程腐化开炮:Java 代码治理》。本文为系列文章第四篇,聚焦于 Android 资源,这一细分畛域。对工程腐化,间接开炮!

精确的说,本文配角是 Android 资源,而 java 资源归属到 java 代码治理领域,并在《向工程腐化开炮:Java 代码治理》一文中给出了应答计划。

Android 资源从定义和应用形式来看,能够分为 Resource 和 Asset 两个大类。前者提供受控的结构化拜访形式,每个资源均有惟一 id 标识,以及多种配置限定符来反对多语言、多设施、多个性等能力;后者提供原始且绝对自在的目录和文件拜访。Resource 类型是绝大部分资源应用场景下的最佳抉择,本文次要聚焦的即是这种类型资源,对抵触、无用、缺失类援用、硬编码文本,这几种腐化状况,发展工具研发,以及治理实际。

基础知识

本章先简要介绍一些基础知识,不便大家对 Android 资源有一个“框架性”的清晰认知,为了解第二章治理实际内容打下基础。此外,也尝试以独特视角,来解说一些乏味的技术点。

1.1 资源分类

对于 Resource 资源,依照应用场景,官网文档曾经给出了划分和具体阐明。本节以资源编译后,对应 R 外部类的类型,给出一个分类:

以上 24 种资源,均能够通过 R.<type>.<name> 模式在 java 代码中援用,其中一些还能够通过 @<type>/<name> 模式在 manifest 和资源中援用。对上述分类中「是否独立文件」、「是否位于 resources.arsc」两个维度进行一些解读:

  • 是否独立文件。一个资源如果对应一个残缺的独立文件,这种属于 File-Base Resource,在最终 apk 的 res 目录下也会存在一份对应文件;否则,属于 Value-Base Resource,在 apk 中没有独立文件与之对应,其值(如果有)存储在 resources.arsc 中。其中 color 类型比拟非凡,繁多 color 资源是 Value-Base,然而色彩状态列表(ColorStateList)属于 File-Base。此外,是否独立文件,是从资源编译后这一视角来看的,在定义资源时,Android 提供了一种内嵌 xml 资源的模式,能够把多个独立文件类型资源,写在一个 xml 文件中,在此不展开讨论;
  • 是否位于 resources.arsc。绝大部分资源,在 R$<type> 类中 field 的取值,都是 0x7fxxxxxx,并且在 resources.arsc 中都有一条对应记录。对于 File-Base 资源,记录值是 file 的相对路径,对于 Value-Base 资源,记录值就是资源值自身。须要留神的是,styleable 类型资源比拟非凡,仅存在于 R$styleable 类中,其 field 取值也并不是 0x7fxxxxxxx 格局,而是整型或整型数组,并且在 resources.arsc 中并不存在。

通过一个 styleable 定义示例,来帮忙咱们了解上述常识:

# 资源定义于 res/value/attrs.xml
<resources>
    <declare-styleable name="DeclareStyleable1" >
        <attr name="attr_enum" format="enum">
            <enum name="attrEnum1" value="1"/>
            <enum name="attrEnum2" value="2"/>
        </attr>
        <attr name="attr_integer" format="integer"/>
        <attr name="android:padding" format="dimension"/>
    </declare-styleable>
</resources>

在 apk 编译过程中,生成以下 R.java 代码:

# R.java 文件中,生成以下代码
public static final class id {
    public static final int attrEnum1=0x7f060000;
    public static final int attrEnum2=0x7f060001;
}
public static final class attr {
    public static final int attr_enum=0x7f020000;
    public static final int attr_integer=0x7f020001;
}
 
public static final class styleable {public static final int[] DeclareStyleable1 = {0x010100d5, 0x7f020000, 0x7f020001};
    public static final int DeclareStyleable1_android_padding=0;
    public static final int DeclareStyleable1_attr_enum=1;
    public static final int DeclareStyleable1_attr_integer=2;
}

最初,在 resources.arsc 中,生成以下记录:

# resources.arsc 中,生成记录
type | id           | name         | value
id     0x7f060000     attrEnum1      None
id     0x7f060001     attrEnum2      None
attr   0x7f020000     attr_enum      1,2
attr   0x7f020001     attr_integer   0

一个 styleable 定义,最终会生成一连串产物,由此可见,Android 资源的解决逻辑,绝对还是比较复杂的。在这个例子中,还有几个有意思的技术点,值得拿来讲一讲:

  • 一个 attr,name 应用 android:xxxx,在 R.java 和 resources.arsc 中不会生成对应内容,因而在语意可复用时,应用零碎提供的 attr,能够节俭一点包大小空间;
  • 如果多个 styleable 或者 style,定义了同名 attr,理论只会生成一个 attr 资源,相当于进步了复用度;
  • attrEnum1、attrEnum2 这种 id 类型资源,如果其它类型资源(例如 layout)中也有同名定义,那么理论也只会生成一个 id 资源,同样也进步了复用度。

好了,对于资源分类,就到此为止,如果对于资源编译、R.java、resources.arsc 等还不够理解,也没有关系,前面大节或者会给出答案。

1.2 资源援用

资源在定义后,就须要从另外的中央对其进行援用。从援用确定性这个维度来看,能够分为间接和间接(动静)两种;从援用元素为度来看,能够分为 java 代码、manifest、资源三种:

图注:资源援用形式

其中,间接(动静)援用,提供动态化的资源援用形式,能够在运行时,依据上下文条件,决定援用哪个资源,灵便度很高。然而,这种资源援用形式,绝对于间接援用,须要额定进行由资源名称查找资源 id 的解决,因而性能略差,审慎应用。

1.3 资源编译

接下来,看看资源的编译过程:

图注:资源编译过程

首先,资源会进行合并,同名资源仅保留一份,同时,manifest 也会进行合并。接下来,会以上述二者作为外围输出数据,通过 AAPT(2)进行资源编译,具体的编译过程比较复杂,网络上也有比拟全面的解说(能够参考这篇文章:https://www.kancloud.cn/alex_…),这里重点关注资源编译产物,以及与其它解决逻辑的关系:

  • AndroidManifest.xml 文件。其中对资源的援用,会替换为对应资源 id,并编译为二进制格局,最终会被打包到 apk 中。
  • resources.arsc 文件。资源符号(索引)表,记录所有资源 id 与各配置下的资源值,最终也会被打包到 apk 中。
  • 解决(编译)后的资源文件汇合。所有须要编译的独立资源文件(例如 layout),均会编译为二进制格局,和不须要编译的资源文件一起,最终被打包到 apk 中。
  • R.java 文件。记录资源类型 / 名称,与 id 值的对应关系,用于在 java 代码中间接援用。每个模块(subproject、flat aar、external aar)都会生成对应的 package.R.java 文件,最终和其它所有 java 源文件一起,独特进行 javac 编译。
  • 资源对应 keep 规定文件。次要包含 layout 中 view 节点对应 java 类,onClick 属性值对应 java 办法,以及 manifest 中四大组件对应 java 类。这些 keep 规定,会与其它自定义 keep 规定一起,用于后续的 proguard 解决。

从上述整个过程来看,资源编译与其它几个外围处理过程,都有紧密联系,因而,理解资源编译过程,对把握整个 apk 构建,具备重要价值。

1.4 资源裁剪

google 官网的 Android Gradle Plugin,提供了资源裁剪性能。外围原理是,计算资源的间接援用关系,以 manifest 和 java 代码中的援用,作为根援用,所有不被援用到的资源,均属于无用资源。看起来是一个十分无效的性能,然而因为 java 代码中存在间接(动静)援用,为了将这部分援用也笼罩到,采纳了比拟激进的策略:收集 java 代码中的所有字符串常量,如果资源名称以这些常量结尾,则也认为资源有援用。除此之外,还有几个逻辑,用于解决非凡的援用形式。上述解决逻辑,有以下几个问题:

  • 如果通过 Resources.getIdentifier 动静援用资源时,名称参数齐全是一个变量,那么会导致相干资源被误删;
  • 如果 java 代码常量池中,简直蕴含所有单个字符,例如 a -z,1-9,那么所有资源均会被认为有援用,导致不会裁剪任何一个资源(优酷就是如此)。

因而,资源裁剪性能,从技术原理上看,无论如何都是一个非确定性算法,必定会存在误裁、漏裁的可能性。对此,google 提供了白名单机制,来解决误裁问题,还有严格模式,用于勾销对间接(动静)援用的保留逻辑。

对于历史包袱不重的 app,尽早开启这项性能,有利于加重包大小累赘。对于代码复杂度高,历史包袱重的大型 app(优酷就是如此),应该会存在不少间接(动静)援用,不开启严格模式,简直无成果,开启严格模式,存量确认 & 加白名单的老本又极高。对此,优酷的抉择是,通过建设独立的无用资源检测性能,联合包大小治理,促成从源头间接删除资源,这样既能够升高资源解决耗时,又能够实现升高包大小成果。对于新增无用资源,则通过包大小卡口,实现非实时(可提早)清理。

1.5 几个乏味的问题

最初,来讲几个比拟乏味,并且不容易被留神到的技术点。

被忽视的一员 – id 类型资源

id 类型资源,作为惟一标识符,在 Android 资源体系下,承当“穿针引线”作用。例如最罕用的,在 layout 中定义一个 view 节点,赋予其一个 id 名称,这样在 java 代码中,就能够不便的获取这个 view 实例,从而进行后续各种操作。再举个例子,在后面 styleable 示例中,一个 enum 类型 attr 蕴含的每一个枚举值,都会生成一个对应 id 类型资源。

id 类型资源,在编译期的一个重要个性是能够全局复用,这一点在后面 styleable 示例中,曾经讲述过。在 app 运行时,id 类型资源的个性是,部分惟一即可。例如在一个 layout 中,或者在一个 enum 类型的 attr 中,都是如此。讲到这里,有些同学肯定可能想到,咱们是不是能够利用这两个个性,在保障运行时部分唯一性前提下,仅保留一个最小汇合,其它所有定义和援用,均在这个最小汇合内选取即可,而这个最小汇合的数量,取决于所有部分应用场景中,须要 id 数量的最大值。举个例子:

# styleable 类型资源,定义于 res/value/attrs.xml
<resources>
    <declare-styleable name="DeclareStyleable1" >
        <attr name="attr_enum" format="enum">
            <enum name="attrEnum1" value="1"/>
            <enum name="attrEnum2" value="2"/>
        </attr>
        <attr name="attr_integer" format="integer"/>
        <attr name="android:padding" format="dimension"/>
    </declare-styleable>
</resources>

# layout 类型资源,定义于 res/layout/main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/main_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/purple_200"
        android:text="Hello World!"/>
</LinearLayout>

上述一共生成 3 个 id 类型资源:attrEnum1、attrEnum2、main_textview,这两个应用场景中,styleable 须要 2 个 id,layout 须要一个 id,所以最小 id 汇合只须要蕴含 2 个 id。假如咱们能够在资源编译过程中,能够将 ”@+id/main_textview” 批改为 ”@+id/attrEnum1″,就能够缩小 1 个 id 类型资源。在优酷这样复杂度的 app 中,共有 1.3 万多个 id 类型资源,而所有部分应用场景中,须要 id 数量的最大值,置信肯定不会超过两位数。一个 id 类型资源在 apk 中占用的大小(Byte),能够简略认为等于 id 名称长度,激进预计以均匀 20Byte 来计算,1.3 万个 id 资源,可节俭包大小 250KB。因为收益并不显著,并没有理论进行开发,作为一个乏味的思考,留给本文读者。

资源与 java 代码的桥梁 – R 类

通过后面解说,置信读者对 R 类曾经具备肯定理解。在这里,咱们思考几个状况。

第一个状况,每个模块(subproject、flat aar、external aar)都会生成对应 package.R.java 文件,然而这些文件蕴含内容,都是 <app_package>.R 类的子集。那么,咱们是不是能够,移除所有模块的 R 类,对立应用 app 的 R 类,以此来升高包大小呢?答案是必定的,事实上优酷在 apk 构建过程中,会删除所有模块 R 类,并将 java 代码中对这些 R 类的援用,转换为对 appR 类的援用。通过这种形式,升高了 MB 量级的 apk 大小,模块数量越多,收益越显著。

第二个状况,R 类内容非常简单,就是记录了资源类型 / 名称,与资源 id 值的对应关系,manifest 和资源这二者对资源的援用,在编译过程中,曾经转换为对应资源 id 值,那么,如果咱们把所有 java 代码中 R.<type>.<name> 的援用,也全副替换为对应 id 值,是不是 R 类就能够删除了呢?答案是必定的,在曾经实现第一种状况的优化后,这个解决的收益比拟无限,因而并没有理论投入研发和应用。然而咱们的确能够这么做!

资源百晓生 – resources.arsc

resources.arsc 文件,作为资源符号(索引)表,记录所有资源类型、名称、id 值,以及各配置下的值。所有 Resource 类型资源(运行时视角,排除编译期视角的 styleable 资源)均记录在案,app 运行时,无论 java 代码还是资源,都是拿着资源 id 值,到 resources.arsc 中来获取资源值,称之为“资源百晓生”一点都不夸大。这个查找过程十分高效,相当于给定一个 key,获取其在一个 hashmap 中的值。

实际上,通过 Resources.getIdentifier 这种间接(动静)形式获取资源 id 值时,也是以资源类型 + 名称,在 resources.arsc 中进行反向查找,找到后,再持续通过 id 值获取资源值。这个查找过程,相当于给定一个值,获取其在一个 hashmap 中的 key。那么有没有什么形式,能够更高效实现这种运行时灵便的援用资源呢?一个比拟天然的想法,是通过 java 反射获取 R.<type>.<name> 值,那么问题来了,绝对于 Resources.getIdentifier 形式获取,哪种性能更好一些?答案可能并不是简略的二选一,耗时可能与资源数量,以及是否第一次查问同一种类型资源,都有关系,答案就留给读者来思考和验证吧。

治理实际

随着工程模块 & 性能减少,资源腐化逐渐积攒:同名资源的抵触状况愈发频繁,导致屡次构建 apk,资源值无奈保障一致性;资源援用关系简单,代码删除后往往会遗记,或者不敢轻易删除对应资源,导致无用资源继续积攒;layout 中援用自定义 view,然而 view 的 java 实现类被删除,app 运行时 layout 被“加载”时会引发 java 异样;资源中的硬编码文本,带来线上隐衷合规危险,或者国家 / 地区 / 宗教文化争议问题。上述诸多问题,都是过往优酷与资源“腐化”奋斗中,一直遇到的实在问题,通过相干工具建设无效的检测能力,并基于此造成日常研发卡口机制,在确保问题零新增前提下,逐渐消化已有存量问题。

在问题定位、排查过程中,疾速获取资源来自哪个模块,是一个根本诉求。二、三方模块大量引入,以及 app 工程模块化水平进步,都使上述信息获取的老本变得越来越高。为此,首先开发了模块蕴含资源列表性能,能够疾速查看,指标资源位于哪个模块(app 工程、subproject 工程、flat aar、内部依赖模块):

com.youku.android:aln:1.9.49
|-- string/m_mode
|-- layout/pager_last
|-- dimen/h_n_bar_pop_star
|-- asset/config/custom_config.json

com.youku.android:YHP:1.23.511.1
|-- layout/channel_list_footer
|-- layout/f_cover_s_feed_item
|-- drawable-night-xhdpi-v8/ic_ho

接下来,对各个资源“腐化”项的治理实际,逐个解说。

2.1 抵触资源

抵触资源,是指来自不同模块的同名资源,其对应配置下的内容值不统一。在资源编译过程中,同名资源只会保留一份,抉择哪个资源能够认为是“随机的”(理论和模块申明程序无关),这会导致每次构建进去的 apk,对应资源值可能会发生变化。抵触资源,会给运行时带来不确定性危险,轻则文本内容、尺寸大小、UI 色彩产生非预期变动,重则导致异样产生。

在优酷历次迭代中,已经产生屡次抵触资源导致的线上解体,为了解决这个顽疾,首先研发了抵触资源检测工具,示例后果如下:

[conflict] drawable/al_down_arrow
|-- xhdpi-v4
|   |-- md5:cc2ef446bf586b03fd08332a5a75b304 (com.ali.user.sdk:au:4.10.6.18)
|   |-- md5:5f9c59ec3fba027c5783120effa12789 (com.ta.android:lo4android:4.10.6.18)

[conflict] string/str_retry
|-- en
|   |-- not calculated (com.ali.android.phone:bee-build:10.2.3.358)
|-- default
|   |-- 重试 (com.ali.android.phone:photo-build:10.2.3.57)
|   |-- 点击重试 (com.ali.android.phone:bee-build:10.2.3.358)

在上述检测后果中,当同名资源在同一配置下,超过两个模块蕴含此资源值时,才可能发生冲突,因而也才会进行资源特征值计算,否则会显示为 not calculated。不同类型资源的特征值计算形式如下:

与此同时,提供资源名称、模块两种不同颗粒度的疏忽名单配置,以长期排除一些二、三方模块之间的抵触资源。更近一步,提供选项,当检测后果不通过时,终止构建过程,造成卡口机制。

优酷在 2020 年,首先研发了第一版抵触资源检测工具,过后存量抵触资源共计 600 多个,之后联结 QA 同学进行两轮清理专项,升高到 100 个以内,2021 年初卡口上线后,截至以后已降至 40 多个(次要来自二、三方模块之间的抵触):

图注:抵触资源治理状况

抵触资源卡口上线至今,累计拦挡 13 次,无效避免抵触资源,引发的线上非预期状况,甚至是 app 解体的重大故障。

2.2 无用资源

后面「资源援用」一节,曾经对资源的援用关系,进行了基础知识解说。总结下,资源可能在如下三个中央进行间接援用:

  • java 代码。通过 R.resourceType.resourceName 形式援用,例如 R.string.app_name;或者通过资源 id 形式,间接援用,例如 0x7fxxxxxx;
  • 清单文件 AndroidManifest.xml;
  • 其它资源。

以 java 代码和 manifest 作为援用根节点,对资源援用关系进行齐全开展,最终未被蕴含到的资源,即为无用资源。对于通过 Resources.getIdentifier 这种间接(动静)形式援用的资源,不蕴含在此处的资源援用关系计算过程中,因而,无用资源检测后果,须要确认是否存在这种援用形式。基于 google 官网 AndroidGradlePlugin 中的无用资源剖析逻辑,全方位加强对工程构造、AndroidGradlePlugin 版本、各工具链版本等兼容性,补齐更多类型资源间的援用剖析,增加额定模块归属信息,最终积淀为此无用资源检测性能。

图注:无用资源剖析

无用资源检测,剖析后果示例:

project:app:1.0
|-- array/planets_array
|-- color/white
|-- drawable/fake_drawable
|-- layout/layout_miss_view
|-- raw/app_resource_raw_chinese_text
|-- string/string_resource_chinese_name
|-- xml/app_resource_xml_chinese_text

project:library-aar-1:1.0
|-- layout/layout_contain_merge
|-- string/library_aar_1_name

此外,资源的间接援用关系,也能够输入到剖析后果中:

Resource Reference Graph:
array:planets_array:2130771968 is reachable: false. The references =>

attr:attr_enum:2130837504 is reachable: true. The references =>
referenced by code : [com/example/libraryaar1/CustomImageView (project:library-aar-1:1.0)]
referenced by resource : [layout:layout_use_declare_styleable1:2131099652]

attr:attr_integer:2130837505 is reachable: true. The references =>
referenced by resource : [style:CustomTextStyle:2131361792]

无用资源,思考到存在间接(动静)援用导致误检的问题,因而并没有进一步造成卡口,而是作为包大小剖析后果中,一个可瘦身项来出现。2020 年 6 月性能上线时,共有 1.7 万个无用资源,目前曾经降至 0.9 万个,存量清理效果显著。

无用资源治理状况

2.3 缺失类援用

layout 中能够申明自定义 view 节点,如果这个自定义 view 对应类,最终不在 apk 的 dex 文件中,因为资源编译的个性,上述情况并不会引发 apk 构建过程失败,然而在 app 运行时,一旦“加载“此 layout 就会引发异样。上述这种状况,咱们称之为资源的缺失类援用。

资源缺失类援用检测,列出了问题资源,及其所属模块,以及缺失的援用类。示例后果如下:

* [ignored] layout-xxxhdpi/layout_include_layout (project:library-aar-1:1.0)
|-- com.example.libraryaar1.NonExistCustomView

* layout/layout_miss_view (project:app:1.0, project:library-aar-1:1.0)
|-- com.example.myapplication.NonExistView2
|-- com.example.myapplication.NonExistView

与此同时,提供资源名称颗粒度的疏忽名单配置,临时排除一些二、三方模块内的问题资源。更近一步,提供选项,当检测后果不通过时,终止构建过程,造成卡口机制。此项性能,近期刚上线对应卡口,尚未有触发卡口拦挡案例呈现,存量 30 个问题资源,已散发到对应研发团队。

事实上,layout 中的每一个自定义 view 节点,AAPT 在进行解决时,都会生成一条 keep 规定,这会成为一条无用 keep 规定,在「向工程腐化开炮:proguard 治理」一文中,提到了这种状况。在此,把示例再展现下:

# layout 中援用不存在的 class,在 apk 编译过程中,并不会引发构建失败,但仍然会生成绝对应的 keep 规定。# 这个 layout 一旦在运行时被“加载“,那么会引发 Java 类找不到的异样。<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.myapplication.NonExistView
        android:id="@+id/main_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"/>

</LinearLayout>

# 生成的 keep 规定为:-keep class com.example.myapplication.NonExistView {<init> ( ...) ; }

尽管无用 keep 规定卡口,曾经齐全蕴含资源缺失类援用问题,然而二者管控的维度并不统一,因而依然将资源缺失类援用,作为独立能力提供。

2.4 硬编码文本

硬编码文本,是指间接在资源中编写的字符串文本。隐衷合规检测机构,会检测 apk 中的一些敏感文本,做为隐衷合规问题的重点狐疑 & 验证点,例如「发票低头」、「身份证」等,其中一部分就是来自于资源中的硬编码文本(另外可能的起源是 java 代码、so)。硬编码文本,存在以下毛病:

  • 易冗余。多处资源应用同一文本时,会导致存在多份此文本;
  • 不灵便。当线上版本呈现问题时(例如各类经营流动),难以动静批改;
  • 低平安。一些敏感信息,如果以明文硬编码文本模式存在,非常容易被获取,并用于不正当用处。

对于这类问题,开发了对应检测能力,能够自定义正则表达式,对上述资源中硬编码文本进行匹配。检测后果中,依照模块、资源进行逐级聚合。反对以下类型资源中的字符串文本:

以所有中文字符检测为例:

project:app:1.0
|-- array/planets_array
|   |--  string-array 蕴含的中文 item
|-- raw/app_resource_raw_chinese_text
|   |--      <files-path name="我是 raw 类型 xml 资源文件中,蕴含的中文文本" path="game-bundles/" />
|-- string/string_resource_chinese_name
|   |--  我是中文 string 资源
|-- xml/app_resource_xml_chinese_text
|   |--      <files-path name="我是 xml 资源中的中文文本" path="game-bundles/" />
|-- layout/activity_main
|   |--          android:text="你好,世界!" />

project:library-aar-1:1.0
|-- asset/library_aar_1_asset_chinese_text
|   |--  我是蕴含中文文本的 asset 资源文件.

目前在优酷,隐衷合规相干的一些敏感文本,是一个正在进行的摸索方向,因为目前没有明确规定,因而还没有理论落地应用。在日常研发过程,对于须要查找特定硬编码文本的场景,曾经可能起到很好的辅助提效作用。

2.5 治理全景

至此,对于 Android 资源,进行了较全面无效的防腐化能力建设和治理。最初,给出一份全景图:

图注:资源治理全景

还能做些什么

Android 资源,并不会像 java 代码那样多变和简单,后面这些治理项,曾经根本笼罩绝大部分资源腐化场景,然而 Android 资源在日常研发过程中,非常容易被忽视:一个字符串、一个色彩 / 尺寸值、一个属性值,一个布局文件,如同每一个都“微不足道”,即便反复定义、即便忘了清理,看起来也没多大影响。而这,正是资源腐化的可怕之处:单个资源过于“渺小”,开发者的业余意识稍有松散,就成了漏网之鱼。

可能进行批量的清理,诚然值得称赞,然而在日常研发的点滴间,可能时刻坚守工匠精力,升高“腐化”代码产生,更难能可贵。“千丈之堤,以蝼蚁之穴溃;百尺之室,以突隙之烟焚”(韩非子·喻老),与诸君共勉。

【参考文档】

  • 【google】利用资源:https://developer.android.com…
  • 【google】AAPT2:https://developer.android.com…

关注【阿里巴巴挪动技术】微信公众号,每周 3 篇挪动技术实际 & 干货给你思考!

正文完
 0