Android開發(fā)中,我們常會(huì)使用一些依賴注入的框架(比如xutils)來節(jié)約我們初始化View以及View的事件的代碼量。但是當(dāng)我們準(zhǔn)備在Module中使用這些東西的時(shí)候卻發(fā)現(xiàn)R文件中的Id并不是常量,而依賴注入中的參數(shù)必須是常量值,這該如何是好?
想一想造成這個(gè)問題的根本原因是什么?id的非常量問題。如果我們能想辦法將id更改為常量,問題豈不就是得到了解決嗎?
想要修改id為常量,直接更改R文件是不可能了,那就只能想辦法復(fù)制出一份與R文件相同的類出來,而區(qū)別只是所有的field添加final標(biāo)記符。有了這個(gè)解決方案,那么就開干吧。
第一步,我們來尋找一下R文件的生成時(shí)機(jī),也就是生成R文件的Task是哪一個(gè)。gradle中每個(gè)task都有input和output,我們?cè)赽uild文件中尋找R文件的位置發(fā)現(xiàn)在:module/build/generated/source/r/debug/packageName/R.java。
為了尋找是哪個(gè)Task生成了R文件,我們?cè)赽uild.gradle中加入如下代碼:
afterEvaluate {
tasks.all {
it.outputs.files.each { file->
if(file.absolutePath.contains('build/generated/source/r'){
println 'generated->'+it.name
}
}
}
運(yùn)行后結(jié)果如下:
generated->processDebugAndroidTestResources
generated->processDebugResources
generated->processReleaseResources
從結(jié)果可以看到gradle根據(jù)不同的場景(Debug、Release、AndroidTest)有不同的Task與之對(duì)應(yīng),至此已經(jīng)找到生成R文件的Task。
第二步,上一步我們找到了Task,我們還需要解決怎么生成R文件的副本文件。
第一種方式:直接復(fù)制R文件,添加final關(guān)鍵字,這樣的話新的文件包含了所有類型的id值。那有沒有辦法簡單的只取R.id的值呢?
于是我們?cè)偃uild結(jié)果中尋找,經(jīng)過尋找,我們意外發(fā)現(xiàn)在build/intermediates/symbols/debug 以及build/intermediates/bundles/debug中找到了一個(gè)R.txt的文件,打開后發(fā)現(xiàn)是這樣的
int anim abc_fade_in 0x7f050000
int anim abc_fade_out 0x7f050001
int anim abc_grow_fade_in_from_bottom 0x7f050002
int anim abc_popup_enter 0x7f050003
int anim abc_popup_exit 0x7f050004
int anim abc_shrink_fade_out_from_bottom 0x7f050005
int anim abc_slide_in_bottom 0x7f050006
int anim abc_slide_in_top 0x7f050007
int anim abc_slide_out_bottom 0x7f050008
從文件內(nèi)容來看,這個(gè)文件應(yīng)該是用來做所有module的R文件的merge的時(shí)候的中間文件,這卻剛好方便了我們。
文件中每一行是4段內(nèi)容,每段內(nèi)容由空格分開分別是:
[數(shù)據(jù)類型] [值類型(子類名稱)] [字段名稱] [字段值]
int anim abc_slide_out_bottom 0x7f050008
public static final class anim {
public static final int abc_slide_out_bottom = 0x7f050008;
}
經(jīng)過這樣分析,我們可以將這個(gè)文件作為我們自己的Task的input,使用同樣的方式生成另一個(gè)R文件的副本K.java。不過R.txt中還有一些是int[]類型的,這樣的內(nèi)容我們暫時(shí)可以跳過。于是我們有了另一種方式。
第二種方式:解析R.txt文件,摘取其中的ID類型的值,同樣的方法也可以篩選其他類型的值。
第三步,自定義Task生成K.java文件。
我們先看第二種方式的實(shí)現(xiàn)方式。
1、在/buildSrc/src/main/groovy/packageName/中添加GenerateK.groovy文件。內(nèi)容如下:
import org.gradle.api.Project
import org.gradle.api.Task
public static autoGenerateR(Project projcet, Task task) {
File inputR = task.inputs.files.files.toArray()[0]
File outDir = task.outputs.files.files.toArray()[0]
def manifestFile = projcet.android.sourceSets.main.manifest.srcFile
def packageName = new XmlParser().parse(manifestFile).attribute('package')
File file = new File(inputR, 'R.txt')
StringBuffer stringBuffer = new StringBuffer()
HashMap<String, List> fieldHash = new HashMap<>()
file.readLines().each {
String[] fields = it.split(' ')
if (fields.length == 4) {
List tmpList = fieldHash.get(fields[1])
if (tmpList == null) {
tmpList = new ArrayList();
}
if (fields[1].equals('id')) {
tmpList.add('public static final ' + fields[0] + ' ' + fields[2] + ' = ' + fields[3] + ' ;')
fieldHash.put(fields[1], tmpList)
}
}
}
stringBuffer.append('package ' + packageName + ';\n')
stringBuffer.append('public final class K { \n')
fieldHash.each { k, v ->
stringBuffer.append(' public static final class ' + k + ' { \n')
v.each {
stringBuffer.append(' ' + it + '\n')
}
stringBuffer.append(' }\n')
}
stringBuffer.append('}\n')
File destFile = new File(outDir, '/' + packageName.toString().replace('.', '/') + '/K.java')
if (!destFile.parentFile.exists()) {
destFile.parentFile.mkdirs()
}
destFile.write(stringBuffer.toString(), 'utf-8')
}
2、在build.gradle中添加如下代碼:
afterEvaluate {
getTasks().all { tsk ->
if (tsk.name.endsWith("Resources")
&& tsk.name.startsWith("process")
&& !tsk.name.contains('AndroidTest')) {
def buildType = tsk.name.replace("process", "").replace("Resources", "")
def taskK = task("build" + buildType + "K", dependsOn: tsk) {}
tsk.outputs.files.each {
if (it.absolutePath.contains('generated/source/r')) {
taskK.outputs.file(it.absolutePath)
}
if (it.absolutePath.contains('intermediates/symbols')
|| it.absolutePath.contains('intermediates/bundles/')) {
taskK.inputs.file(it.absolutePath)
}
}
taskK.doLast {
GenerateK.autoGenerateR(project, taskK)
}
tsk.doLast {
GenerateK.autoGenerateR(project, taskK)
}
}
}
}
3、執(zhí)行buildDebugK或者buildReleaseK
現(xiàn)在,我們什么都準(zhǔn)備好了,直接執(zhí)行assembleDebug或者assembleRelease,或者執(zhí)行buildDebugK或者buildReleaseK就都能生成K.java文件啦。文件位置在:module/build/generated/source/r/debug/packageName/K.java。
現(xiàn)在我們?cè)儆玫谝环N方式實(shí)現(xiàn):
1、在上一種實(shí)現(xiàn)方式的GenerateK.groovy文件中加入如下代碼:
public static autoGenerateK(Project projcet, Task task) {
File inputR = task.inputs.files.files.toArray()[0]
File outDir = task.outputs.files.files.toArray()[0]
def manifestFile = projcet.android.sourceSets.main.manifest.srcFile
def packageName = new XmlParser().parse(manifestFile).attribute('package')
String packageDir = packageName.toString().replace('.', '/')
File rFile = new File(outDir, packageDir + '/R.java')
StringBuffer rStringBuffer = new StringBuffer();
rFile.readLines().each {
rStringBuffer.append(it + '\n')
}
String kFileContent = rStringBuffer.toString().replace('public static int', 'public static final int')
kFileContent = kFileContent.replace('public final class R', 'public final class K')
File destFile = new File(outDir, '/' + packageName.toString().replace('.', '/') + '/K.java')
if (!destFile.parentFile.exists()) {
destFile.parentFile.mkdirs()
}
destFile.write(kFileContent, 'utf-8')
}
2、修改上一種實(shí)現(xiàn)方式的第二步的GenerateK.autoGenerateR(project, taskK)改成GenerateK.autoGenerateK(project, taskK),然后同樣的再執(zhí)行第三步。
打開看看吧,然后把原來注解需要R.id的地方都替換成K.id試試,是不是滿足了我們的需求呢?
其實(shí)到這里我們已經(jīng)完工了,但是卻不完美,因?yàn)槊看蝘d增加/刪除/修改時(shí)都無法實(shí)時(shí)的在代碼提示中收到反饋,需要執(zhí)行一次buildXXXK這個(gè)Task(雖然很快),這個(gè)問題...有待研究,或許做一個(gè)Android Studio的插件可以達(dá)到效果~~
不過,R文件也只支持增加Id而不支持刪除Id的實(shí)時(shí)反饋。