登录

去注册

登录

注册

去登录

注册

每日一问 关于 R.java 的生成规则,你知道多少?

xiaoyang   2019-09-01   收藏

一般情况,每个 app 会依赖多个 module,一般 module 里面的资源,例如 res中有个 love。

对应的在 module中会有个:

com.module.R.res.love

app 中也可以使用这个资源,但是包名为:

com.app.R.res.love

两者虽然对应一个资源,但是id 值很大概率上是不同的。

那么最终打包是如何处理的呢?

14

最终打包出来的那些资源id,是会重新分配的(同一个资源生成的id,就算不在同一个R.class里,打包出来时也是相同的)。

做了个测试:

  • app依赖了模块test;

  • test模块中的string.xml有个test_value;

  • 在test模块中引用(com.test.R),看到这个test_value的id值是-1900002

  • 而在app中(com.app.R)引用时,id值为-1900023

这时候可以看出,相同的资源,id确实是不同。

打包成apk后,把它拖进AS里,点开classes.dex

  • 首先找到app包名下的R$string,右键查看字节码,发现test_value的id值由原来的-1900023,变成了0x7f10005d

  • 接着找test模块包名下的R$string,查看字节码发现id值一样是0x7f10005d

由此看来,id值在打包成apk的时候,确实是重新分配了,而且同一个资源的id值,也统一了。

源码分析

那它究竟是如何重新分配这些id值的呢?
上一次也分析过https://www.wanandroid.com/wenda/show/8985,很多任务都是在ApplicationTaskManager里的createTasksForVariantScope方法中创建的。
那现在再来看一下这个方法:

    @Override
    public void createTasksForVariantScope(
            @NonNull final VariantScope variantScope,
            @NonNull List<VariantScope> variantScopesForLint) {

        ......

        // Add a task to create the BuildConfig class
        createBuildConfigTask(variantScope);

        // Add a task to process the Android Resources and generate source files
        createApkProcessResTask(variantScope);

        ......
    }

可以看到,创建BuildConfig任务之后,接着调用了一个createApkProcessResTask方法,看上面注释,是用来处理资源和生成源码文件的。一步步点进去看看:

createApkProcessResTask() ->

createProcessResTask() ->

createNonNamespacedResourceTasks() ->

GenerateLibraryRFileTask.doFullTaskAction() ->

GenerateLibRFileRunnable.run() ->

SymbolExportUtils.processLibraryMainSymbolTable()

看看这个processLibraryMainSymbolTable方法:

fun processLibraryMainSymbolTable() {

    ......

    val tablesToWrite = processLibraryMainSymbolTable()

    // Generate R.java files for main and dependencies
    tablesToWrite.forEach { SymbolIo.exportToJava(it, sourceOut, false) }

    ......
}

看中间的注释,可以确定下面一句就是生成R.java的了,它会为tablesToWrite里面的每一个item都生成一个R文件,看一下这个tablesToWrite怎么来的:

internal fun processLibraryMainSymbolTable(): List<SymbolTable> {
    // Merge all the symbols together.
    // We have to rewrite the IDs because some published R.txt inside AARs are using the
    // wrong value for some types, and we need to ensure there is no collision in the
    // file we are creating.
    val allSymbols: SymbolTable = mergeAndRenumberSymbols(
        finalPackageName, librarySymbols, depSymbolTables, platformSymbols
    )

    val mainSymbolTable = if (namespacedRClass) allSymbols.filter(librarySymbols) else allSymbols

    // Generate R.txt file.
    Files.createDirectories(symbolFileOut.parent)
    SymbolIo.writeForAar(mainSymbolTable, symbolFileOut)

    val tablesToWrite =
        RGeneration.generateAllSymbolTablesToWrite(allSymbols, mainSymbolTable, depSymbolTables)
    return tablesToWrite
}

妈耶!看开头的注释:"We have to rewrite the IDs",也就是必须重写这些id的意思了,再看一下它接下来调用的mergeAndRenumberSymbols方法:

fun mergeAndRenumberSymbols(): SymbolTable {
    ......

    // the ID value provider.
    val idProvider = IdProvider.sequential()

    ......
}

可以看到调用了IdProvider的sequential方法,点开看下:

fun sequential(): IdProvider {
        return object : IdProvider {
            private val next = ShortArray(ResourceType.values().size)

            override fun next(resourceType: ResourceType): Int {
                val typeIndex = resourceType.ordinal
                return 0x7f shl 24 or (typeIndex + 1 shl 16) or (++next[typeIndex]).toInt()
            }
        }
    }

emmm,0x7f开头的id值,确实是对应的我们开头打包后的那个test_value的id值(0x7f10005d)。
到这里基本可以确定,最终打包的资源id,就是通过这个IdProvider的匿名子类来重新创建的,而且同一个资源所对应的id,也是一样的。

回复
tustlitao : @NBA乔碧萝 

秀啊

2019-09-09 回复
alguojian : @NBA乔碧萝 

半碟花生米就喝这么高了

2019-09-02 回复
NBA乔碧萝 : @陈小缘 

何时才能像你一样优秀尼? 低头瞅了瞅怀中的妹子,留下了悔恨的泪水。

2019-08-31 回复
陈熔 : @陈小缘 

翻源码的大佬

2019-08-30 回复
酷酷的安浊 : @陈小缘 

秀啊大佬,学习了

2019-08-29 回复
0

评论大佬从Android Gradle Plugin的打包机制出发找出具体实现代码已给出答案。

说下从android appt(2)打包过程得出来的结果,android打包后的产物apk包中resources.arsc文件里面包含应用的静态资源表,包含了anim,color,id,layout,string等资源类型,在同一种类型中资源名称name必须是唯一的,对应的ID也就是同一个,而在AS中打包的时候如果出现主模块和子模块同名称的情况下,会采用主模块的资源文件或属性,这是在AS插件合并多模块资源时做出的处理(包括androidmanifest合并,策略也不同,过程比较复杂),如果用aapt2手动对工程打包,也是要先手动合并多模块的资源,androidmanifest.xml等其他文件,然后在进行apk生成。

以下是aapt手动打包命令:

aapt:
打包
aapt package -f -S res -I D:/Android/sdk/platforms/android-27/android.jar -A assets -M AndroidManifest.xml -F out.apk

生成R.txt,R.java
aapt package -m -J res -S res -I D:/Android/sdk/platforms/android-27/android.jar -A assets -M AndroidManifest.xml


aapt2:
分成complie和link两步
aapt2 compile --dir src/main/res -o resources.zip -v

aapt2 link -o output.apk --manifest src/main/AndroidManifest.xml -I D:/Android/sdk/platforms/android-27/android.jar resources.zip --java ./ -v
回复

删除留言

确认删除留言,会导致相关评论丢失?

取消 确定