Android R文件生成

  • Post author:
  • Post category:其他


Android R文件生成

Android在打包时,通过AAPT工具,对主工程和引入的依赖里的所有资源文件进行编译压缩,并会对res/里的资源文件如drawable、layout、values等生成唯一的id,同时生成R.java文件,保存所有的id值,以及生成resource.arsc文件,建立id对应资源的值(如string)或文件路径(如png)的关系表。

在这里插入图片描述

如上图是我们apk中的最终R文件的样子,可以发现里面会按类型分为不同的类,如anim、attr、string等,每个类里有相应类型的所有资源id,id是16进制的int值,变量名就是我们在资源文件中定义的资源名字。

在这里插入图片描述

上图是一个id的组成:

  1. 第一个字节是指资源所属包,7F代表普通应用程序
  2. 第二个字节是指资源的类型,如02指drawable,03指layout
  3. 第三四个字节是指资源的编号,往往从00开始递增

除此之外我们还可以看到R文件里的id,都是

public static final的静态常量

,这会有什么好处呢?

Activity.java


Activity.java

R.layout.activity_second


R.layout.activity_second

Activity.class


Activity.class

可以看到,由于java编译器的优化,在编译时,所有

使用静态常量的地方,会被直接替换为常量值。


这样一来,R文件里的id在编译完java文件后,就没有被引用的地方了,此时如果开启proGuard混淆,就会删除整个R文件,从而会

减少field数和包大小。



AAR

以上是R文件的基本原理,但是这里有一个特殊,就是AAR的R文件规则。

AAR是被主工程引入的SDK,一个工程可能会引入多个AAR,这也就导致了一个问题:

每个AAR的module如果在自己编译生成AAR时,按照正常的流程生成R文件,那么由于资源id的值都是从00递增,会导致集成到主工程时的冲突(大量id重复)。


所以其实AAR在编译时不会进行真正的R.java文件的生成,而是等到在主工程集成编译时统一进行所有资源的id分配。

这里需要说明的是,如果

不同的AAR中有同类型的同名资源,则可能会在运行中有很多莫名其妙的问题,所以我们需要保证资源名的唯一性。

这里AAR其实做了两步工作:

  1. 为了支持我们的调用语法,生成了一个R.java文件,所以我们才可以在AAR的代码中调用AAR包名下的R.xxx

    aar里的Activity调用aar.R.layout.xxx


    aar里的Activity调用aar.R.layout.xxx

    这里生成的R文件里的id,并不是public static final的常量,而是

    public static的变量

    ,这是为什么呢?因为如果是常量,则会在编译打包时,调用的地方被替换为常量值,而这个值是AAR内部生成的临时id,是不对的,这样的话

    主工程编译时将无法修改这个值

    ,就有问题了。

  2. 因为AAR生成的R.java并不是最终正确的,所以这个R.java文件不会被带入AAR中,但是会生成一个R.txt文本文件,这个文件以文本的形式记录了AAR中所有资源的类型、名字等,以便于

    主工程打包时,可以依据这些资源信息统一生成最终的R.java文件。



主工程

主工程在编译时,会将主工程下的所有资源,连同所有AAR依赖里的R.txt文件一起,为所有的资源统一分配id,并生成R.java文件和resource.arsc文件,这时就可以保证每个资源都是唯一的id值。

这里需要注意的是:

  • 主工程编译时,最终

    除了会在主工程包名下,生成一个包含主工程和AAR所有资源的R.java文件之外,还会在每个AAR相应的包名下,生成一个包含AAR资源的R.java文件

    ,当然,相同资源的id是一样的。这就是为什么我们可以在主工程中调用主包名的R文件,和AAR包名的R文件,都可以获取到一些资源id的原因。

    主工程包名下的R文件


主工程包名下的R文件

AAR包名下的R文件


AAR包名下的R文件

  • 上述最终生成的所有R.java文件里的id值,都是

    public static final的静态常量

    ,因为此刻的id值都已经确定了。然后在编译java文件时,常量值会被替换(包括资源文件中的引用也会被替换),从而使R文件的field无引用,可以通过proGuard删除。

上述第2点,在主工程编译完成后,我们会发现一个问题,就是

AAR里面的文件,使用到资源id的地方,并没有被替换为相应的常量值,但是R文件里面的资源id确实是常量。

这是因为AAR的class文件,在主工程编译时,不会再次进行编译,也就是说

AAR的class文件原封不动的打包进apk

。而资源id为常量是在主工程编译时才行程的,但AAR生成class时,使用的是上面说到的变量,所以一直被保留了下来。

这个可能是因为Google怕影响编译打包速度,而将AAR的class文件直接带入apk中,但是却忽略了资源id的引用被保留下来的问题。

这个问题可以被gradle插件解决,大致原理就是:

gradle插件将所有AAR中引用到R文件资源id的地方,全部都替换为相应的id常量值,然后在proGuard混淆时,所有的R文件就会因为没有被引用到而删除了。



版权声明:本文为qq_15827013原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。