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

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í)反饋。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,590評(píng)論 19 139
  • 這一章主要針對(duì)項(xiàng)目中可以用到的一些實(shí)用功能來介紹Android Gradle,比如如何隱藏我們的證書文件,降低風(fēng)險(xiǎn)...
    acc8226閱讀 7,971評(píng)論 3 25
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,112評(píng)論 25 709
  • 轉(zhuǎn)載注明出處:http://www.itdecent.cn/p/5255b100930e 0. 前言 完全由個(gè)人翻...
    王三的貓阿德閱讀 2,746評(píng)論 0 4
  • 本周在熊逸老師的帶領(lǐng)下,領(lǐng)略了《周易》的風(fēng)采,更重要的是從不同的維度來看《易經(jīng)》?!吨芤住匪阖赃@種事是不可能被證偽...
    路上的威利閱讀 1,097評(píng)論 0 1

友情鏈接更多精彩內(nèi)容