Android组件化:在Module中使用IOC框架

Android开发中,我们常会使用一些依赖注入的框架(比如xutils)来节约我们初始化View以及View的事件的代码量。但是当我们准备在Module中使用这些东西的时候却发现R文件中的Id并不是常量,而依赖注入中的参数必须是常量值,这该如何是好?

想一想造成这个问题的根本原因是什么?id的非常量问题。如果我们能想办法将id更改为常量,问题岂不就是得到了解决吗?

想要修改id为常量,直接更改R文件是不可能了,那就只能想办法复制出一份与R文件相同的类出来,而区别只是所有的field添加final标记符。有了这个解决方案,那么就开干吧。

第一步,我们来寻找一下R文件的生成时机,也就是生成R文件的Task是哪一个。gradle中每个task都有input和output,我们在build文件中寻找R文件的位置发现在:module/build/generated/source/r/debug/packageName/R.java

为了寻找是哪个Task生成了R文件,我们在build.gradle中加入如下代码:

afterEvaluate {
tasks.all {
    it.outputs.files.each { file->
        if(file.absolutePath.contains('build/generated/source/r'){
           println 'generated->'+it.name
        }
    }
}

运行后结果如下:

generated->processDebugAndroidTestResources
generated->processDebugResources
generated->processReleaseResources

从结果可以看到gradle根据不同的场景(Debug、Release、AndroidTest)有不同的Task与之对应,至此已经找到生成R文件的Task。

第二步,上一步我们找到了Task,我们还需要解决怎么生成R文件的副本文件。

第一种方式:直接复制R文件,添加final关键字,这样的话新的文件包含了所有类型的id值。那有没有办法简单的只取R.id的值呢?

于是我们再去build结果中寻找,经过寻找,我们意外发现在build/intermediates/symbols/debug 以及build/intermediates/bundles/debug中找到了一个R.txt的文件,打开后发现是这样的

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  

从文件内容来看,这个文件应该是用来做所有module的R文件的merge的时候的中间文件,这却刚好方便了我们。

文件中每一行是4段内容,每段内容由空格分开分别是:

[数据类型] [值类型(子类名称)] [字段名称] [字段值]  
int anim abc_slide_out_bottom 0x7f050008
public static final class anim {
    public static final int abc_slide_out_bottom = 0x7f050008;
}

经过这样分析,我们可以将这个文件作为我们自己的Task的input,使用同样的方式生成另一个R文件的副本K.java。不过R.txt中还有一些是int[]类型的,这样的内容我们暂时可以跳过。于是我们有了另一种方式。

第二种方式:解析R.txt文件,摘取其中的ID类型的值,同样的方法也可以筛选其他类型的值。

第三步,自定义Task生成K.java文件。

我们先看第二种方式的实现方式。

1、在/buildSrc/src/main/groovy/packageName/中添加GenerateK.groovy文件。内容如下:

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、执行buildDebugK或者buildReleaseK

现在,我们什么都准备好了,直接执行assembleDebug或者assembleRelease,或者执行buildDebugK或者buildReleaseK就都能生成K.java文件啦。文件位置在:module/build/generated/source/r/debug/packageName/K.java

现在我们再用第一种方式实现:

1、在上一种实现方式的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、修改上一种实现方式的第二步的GenerateK.autoGenerateR(project, taskK)改成GenerateK.autoGenerateK(project, taskK),然后同样的再执行第三步。

打开看看吧,然后把原来注解需要R.id的地方都替换成K.id试试,是不是满足了我们的需求呢?

其实到这里我们已经完工了,但是却不完美,因为每次id增加/删除/修改时都无法实时的在代码提示中收到反馈,需要执行一次buildXXXK这个Task(虽然很快),这个问题...有待研究,或许做一个Android Studio的插件可以达到效果~~

不过,R文件也只支持增加Id而不支持删除Id的实时反馈。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容