aapt2 生成资源 public flag 标记
前言
之前寫過一篇aapt2適配之資源id固定,該文章介紹了如何使用aapt2固定資源id,其實這篇文章是對該文章的一點補充,主要介紹如何在固定id的同時,將該資源進行導出,打上public標記,供其他資源進行引用。整個問題的解決方案斷斷續(xù)續(xù)差不多思考了一個來月,現(xiàn)將解決方法簡單介紹一下。
從aapt2資源id固定說起
首先來回顧一下aapt2如何將資源id符號表導出,使用–emit-ids參數(shù)指定導出文件即可
| 12345 | android { aaptOptions { additionalParameters "--emit-ids", "${project.file('public.txt')}"}} |
以及符號表導出后,如何使用導出的符號表進行資源id的固定,使用–stable-ids參數(shù),指定導入文件即可
| 12345 | android { aaptOptions { additionalParameters "--stable-ids", "${project.file('public.txt')}"}} |
執(zhí)行./gradlew assembleDebug編譯后,看下產(chǎn)出的resources_debug.ap_文件,看下arsc中對應的資源是否打上了PUBLIC導出標記
| 1 | aapt2 dump ./app/build/intermediates/res/resources-debug.ap_ |
執(zhí)行命令后輸出如下內(nèi)容
| 123456789 | Package name=io.github.lizhangqu.aapt2 id=7f type layout id=1 entryCount=1spec resource 0x7f010090 io.github.lizhangqu.aapt2:layout/activity_main() (file) res/layout/activity_main.xml type style id=2 entryCount=1spec resource 0x7f020080 io.github.lizhangqu.aapt2:style/AppTheme() (style) |
可以看出,arsc中對應的資源并沒有打上PUBLIC標記。問題就出在這,我們明明指定了導入資源符號表,為什么沒有PUBLIC標記呢?
從aapt public.xml說起
回顧一下aapt是如何添加PUBLIC標記的,其實這事和android gradle plugin有關(guān),在android gradle plugin 1.3以下的版本,我們可以直接往src/main/res/values目錄下添加public.xml文件,該文件會自動參與編譯,但是不幸的是在1.3以上版本,所有public類型的資源全都會被android gradle plugin過濾掉,因此正常的途徑我們是無法讓public.xml參與編譯的。
所以在aapt的時候,我們在在mergeResources任務最后,將public.xml文件拷貝到build目錄下merge完畢的res目錄下,即build/intermediates/res/merged/目錄,然后gradle在執(zhí)行processResources任務時會將我們拷貝進去的文件與其他資源文件一同參與編譯,于是就可以為那些資源打上PUBLIC標記,并進行id的固定。
那么在aapt2中,由于aapt2提供了一種固定資源id的方式,最開始,個人以為這種方式和aapt的public.xml作用是一樣的,在經(jīng)過嚴格的測試后發(fā)現(xiàn)其實并不是想象的那么簡單,它只是固定資源id的一種方式,但是并不包括添加資源PUBLIC標記,因此aapt2的public.txt不等于aapt的public.xml,在aapt2中如果要添加PUBLIC標記,其實還是得另尋其他途徑。
通過查看aapt2的源碼發(fā)現(xiàn),資源是否屬于public類型,在資源文件compile為flat文件的時候就已經(jīng)決定了,也就是說必須主動聲明public類型的資源,這個資源才會被打上PUBLIC標記,所以問題就變成了和aapt一樣,只要在mergeResource任務的最后,將public.xml拷貝到mergeResource任務的輸出文件夾即可。但是有一個棘手的問題,就是aapt2的mergeResource任務的輸出文件不是原始資源文件,而是經(jīng)過編譯后的flat文件,所以最終的問題變成了如何將public.xml文件編譯為flat文件。
由于最開始沒有想到這種方式,一直糾結(jié)于怎么修改aapt2的源碼,來達到添加PUBLIC標記的效果,后來不經(jīng)意間想到這種方式,發(fā)現(xiàn)之前想復雜了。這個問題其實非常簡單,只需要獲取aapt2可執(zhí)行文件,自己調(diào)用一下compile命令,傳遞相關(guān)參數(shù)執(zhí)行下資源編譯步驟,然后將編譯后的文件拷貝到mergeResource任務的輸出文件夾,參考這篇文章aapt2資源compile過程可以獲得aapt2資源編譯的命令行參數(shù)及使用方式。那么gradle代碼怎么實現(xiàn)呢?也很簡單
| 12345678910111213141516171819202122232425262728 | project.afterEvaluate { def android = project.getExtensions().findByName('android')android.getApplicationVariants().all { def variant -> def mergeResourceTask = project.tasks.findByName("merge${variant.getName().capitalize()}Resources") if (mergeResourceTask) {mergeResourceTask.doLast { def variantData = variant.getMetaClass().getProperty(variant, 'variantData') def mBuildToolInfo = variantData.getScope().getGlobalScope().getAndroidBuilder().getTargetInfo().getBuildTools() //buildTools下的所有可執(zhí)行文件都在這個map里Map<BuildToolInfo.PathId, String> mPaths = mBuildToolInfo.getMetaClass().getProperty(mBuildToolInfo, "mPaths") as Map<BuildToolInfo.PathId, String>project.exec(new Action<ExecSpec>() {@Overridevoid execute(ExecSpec execSpec) { //拼接aapt2 compile參數(shù)execSpec.executable "${mPaths.get(BuildToolInfo.PathId.AAPT2)}"execSpec.args("compile")execSpec.args("--legacy")execSpec.args("-o")execSpec.args("${mergeResourceTask.outputDir}")execSpec.args("${project.file('src/main/res/values/public.xml')}")}})}}}} |
然后將public.xml放在app/src/main/res/values/目錄下,編譯一下,然后用aapt2 dump命令查看一下產(chǎn)出的arsc文件
| 123456789 | Package name=io.github.lizhangqu.aapt2 id=7f type layout id=1 entryCount=1spec resource 0x7f010090 io.github.lizhangqu.aapt2:layout/activity_main PUBLIC() (file) res/layout/activity_main.xml type style id=2 entryCount=1spec resource 0x7f020080 io.github.lizhangqu.aapt2:style/AppTheme PUBLIC() (style) |
可以看到,資源名后面多了一個PUBLIC字符,也就是說該資源被打上PUBLIC標記了,對于其他項目中的資源,可以在aapt2鏈接的時候直接使用-I參數(shù)引用該arsc,就和android.jar的引用是一樣的,使用@包名:資源類型/資源名進行引用,如
| 1 | @io.github.lizhangqu.aapt2:style/AppTheme |
到這里為止,其實已經(jīng)解決了PUBLIC標記的問題,但是還不夠完美
自動轉(zhuǎn)換public.txt文件為public.xml文件
上面雖然解決了PUBLIC標記的問題,但是還得自己維護app/src/main/res/values/public.xml文件,由于aapt2自己會導出一份public.txt文件,因此可以利用這份文件,在編譯過程中將其自動轉(zhuǎn)為public.xml文件,就不需要我們自己維護public.xml文件。
這個問題其實說簡單也很簡單,只是有一些細節(jié)需要處理
- public.txt中存在styleable類型資源,public.xml中不存在,因此轉(zhuǎn)換過程中如果遇到styleable類型,需要忽略
- vector矢量圖資源如果存在內(nèi)部資源,也需要忽略,在aapt2中,它的名字是以$開頭,然后是主資源名,緊跟著__數(shù)字遞增索引,這些資源外部是無法引用到的,只需要固定id,不需要添加PUBLIC標記,并且$符號在public.xml中是非法的,因此忽略它即可
- 由于aapt2有資源id的固定方式,因此轉(zhuǎn)換過程中可直接丟掉id,簡單聲明即可
- aapt2編譯的public.xml文件的上級目錄必須是values文件夾,否則編譯過程會報非法路徑
所以整個過程就變成了讀取public.txt的每一行,正則匹配資源類型,資源名,資源id,如果是內(nèi)部資源和styleable類型資源,直接無視,否則寫入public.xml文件,寫入只需要寫入資源類型和資源名即可,資源id按需選擇,所以最終的代碼抽象為如下函數(shù)
| 12345678910111213141516171819202122232425262728293031323334353637383940414243 | /** * 轉(zhuǎn)換publicTxt為publicXml */@SuppressWarnings("GrMethodMayBeStatic")void convertPublicTxtToPublicXml(File publicTxtFile, File publicXmlFile, boolean withId) { if (publicTxtFile == null || publicXmlFile == null || !publicTxtFile.exists() || !publicTxtFile.isFile()) { throw new GradleException("publicTxtFile ${publicTxtFile} is not exist or not a file")}GFileUtils.deleteQuietly(publicXmlFile)GFileUtils.mkdirs(publicXmlFile.getParentFile())GFileUtils.touch(publicXmlFile) project.logger.info "convert publicTxtFile ${publicTxtFile} to publicXmlFile ${publicXmlFile}"publicXmlFile.append("<!-- AUTO-GENERATED FILE. DO NOT MODIFY -->")publicXmlFile.append("\n")publicXmlFile.append("<resources>")publicXmlFile.append("\n")Pattern linePattern = Pattern.compile(".*?:(.*?)/(.*?)\\s+=\\s+(.*?)")publicTxtFile.eachLine {def line ->Matcher matcher = linePattern.matcher(line) if (matcher.matches() && matcher.groupCount() == 3) {String resType = matcher.group(1)String resName = matcher.group(2) if (resName.startsWith('$')) { project.logger.info "ignore to public res ${resName} because it's a nested resource"} else if (resType.equalsIgnoreCase("styleable")) { project.logger.info "ignore to public res ${resName} because it's a styleable resource"} else { if (withId) {publicXmlFile.append("\t<public type=\"${resType}\" name=\"${resName}\" id=\"${matcher.group(3)}\" />\n")} else {publicXmlFile.append("\t<public type=\"${resType}\" name=\"${resName}\" />\n")}}}}publicXmlFile.append("</resources>")} |
最開始的那段代碼就可以優(yōu)化為如下代碼
| 12345678910111213141516171819202122232425262728293031 | project.afterEvaluate { def android = project.getExtensions().findByName('android')android.getApplicationVariants().all { def variant -> def mergeResourceTask = project.tasks.findByName("merge${variant.getName().capitalize()}Resources") if (mergeResourceTask) {mergeResourceTask.doLast { //目標轉(zhuǎn)換文件,注意public.xml上級目錄必須帶values目錄,否則aapt2執(zhí)行時會報非法文件路徑 File publicXmlFile = new File(project.buildDir, "intermediates/res/public/${variant.getDirName()}/values/public.xml") //轉(zhuǎn)換public.txt文件為publicXml文件convertPublicTxtToPublicXml(project.file('public.txt'), publicXmlFile, false) def variantData = variant.getMetaClass().getProperty(variant, 'variantData') def mBuildToolInfo = variantData.getScope().getGlobalScope().getAndroidBuilder().getTargetInfo().getBuildTools()Map<BuildToolInfo.PathId, String> mPaths = mBuildToolInfo.getMetaClass().getProperty(mBuildToolInfo, "mPaths") as Map<BuildToolInfo.PathId, String> project.exec(new Action<ExecSpec>() {@Override void execute(ExecSpec execSpec) {execSpec.executable "${mPaths.get(BuildToolInfo.PathId.AAPT2)}"execSpec.args("compile")execSpec.args("--legacy")execSpec.args("-o")execSpec.args("${mergeResourceTask.outputDir}")execSpec.args("${publicXmlFile}")}})}}}} |
總結(jié)
有時候,解決問題一種思路行不通可以換一種方式,說不定就會柳暗花明呢!
http://fucknmb.com/2018/02/02/aapt2-%E7%94%9F%E6%88%90%E8%B5%84%E6%BA%90public-flag%E6%A0%87%E8%AE%B0/總結(jié)
以上是生活随笔為你收集整理的aapt2 生成资源 public flag 标记的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android application
- 下一篇: 再谈 Application Provi