解決資源id衝突 資源的外掛化
--摘自《android外掛化開發指南》
1.一套完整的Android App打包流程(Gradle方案)
第一步:aapt。為res目錄下的資源生成R.java檔案,同時為AndroidManifest.xml生成Manifest.java檔案
第二步:aidl。把專案中自定義的aidl檔案生成相應的Java程式碼檔案
第三步:javac。把專案中所有的Java程式碼編譯成class檔案。包括三部分Java程式碼,自己寫的的業務邏輯程式碼,aapt生成的Java檔案,aidl生成的Java檔案
第四步:proguard。混淆同時生成proguardMapping.txt。這一步是可選的
第五步:dex。把所有的class檔案(包括第三方庫的class檔案)轉換成dex檔案
第六步:aapt。把res目錄下的資源、assets目錄下的檔案,打包成一個.ap_檔案
第七步:apkbuilder。將所有的dex檔案、ap_檔案、AndroidManifest.xml打包為.apk檔案,這是一個未簽名的apk包
第八步:jarsigner。對apk進行簽名
第九步:zipaligin。對要釋出的apk檔案進行對齊操作,以便在執行時節省記憶體
2.res目錄下的所有資源會生成一個R.java檔案,每個資源都對應一個R中的十六進位制整數變數,由三部分組成,即PackageId+TypeId+EntryId(一般是預設0x7f+兩位+四位)
***外掛化中資源id衝突的解決方案***
方案1:
把宿主和外掛的資源都合併到一起
方案1.1:重寫AAPT命令,在外掛apk打包過程中,通過指定資源id的字首,比如0x71,來保證宿主和外掛的資源id永遠不會衝突
1)在AAPT的命令列引數中傳遞apk打包時的字首值
2)把這個值設定給Bundle實體的mApkModule欄位,作為ResourceTable建構函式的引數傳遞進去
3)在ResourceTable的建構函式,讀取Bundle引數中的mApkModule值,也就是字首值,設定給packageId
都是c程式碼,就不貼了
4)把新的AAPT工具命名為aapt_mac,放到專案根目錄下
apply plugin: 'com.android.application' import com.android.sdklib.BuildToolInfo import java.lang.reflect.Method Task modifyAaptPathTask = task('modifyAaptPath') << { android.applicationVariants.all { variant -> BuildToolInfo buildToolInfo = variant.androidBuilder.getTargetInfo().getBuildTools() Method addMethod = BuildToolInfo.class.getDeclaredMethod("add", BuildToolInfo.PathId.class, File.class) addMethod.setAccessible(true) addMethod.invoke(buildToolInfo, BuildToolInfo.PathId.AAPT, new File(rootDir, "aapt_mac")) println "[LOG] new aapt path = " + buildToolInfo.getPath(BuildToolInfo.PathId.AAPT) } } android { compileSdkVersion 25 buildToolsVersion "25.0.3" defaultConfig { applicationId "jianqiang.com.testreflection" minSdkVersion 21 targetSdkVersion 25 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } preBuild.doFirst { modifyAaptPathTask.execute() } aaptOptions { aaptOptions.additionalParameters '--PLUG-resoure-id', '0x71' } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:25.2.0' }
通過反射,把AAPT路徑臨時修改為指向當前app根路徑下的mac_aapt
方案1.2:在外掛apk打包後,修改R,java和resources.arsc中儲存的資源id值,比如預設的0x7f字首,修改為0x71,這樣就保證了宿主和外掛的資源id永遠不會衝突
方案1.3:在public.xml中指定apk中所有資源的id值。但每增加一個資源,都要維護public.xml。所以只能用於固定幾個特定的值
afterEvaluate { for (variant in android.applicationVariants) { def scope = variant.getVariantData().getScope() String mergeTaskName = scope.getMergeResourcesTask().name def mergeTask = tasks.getByName(mergeTaskName) mergeTask.doLast { copy { int i = 0 println android.sourceSets.main.res.srcDirs from(android.sourceSets.main.res.srcDirs) { include 'values/public.xml' rename 'public.xml', (i++ == 0 ? "public.xml" : "public_${i}.xml") } into(mergeTask.outputDir) } } } }
string.xml
<resources> <string name="app_name">ActivityHook1</string> <string name="string1">Test String</string> </resources>
public.xml
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<public type = "string" name="string1" id = "0x7f050024"/>
</resources>
宿主的資源值固定了以後,外掛如果想使用宿主的資源,只要把宿主打包成jar,然後複製到外掛專案的某個位置,使用gradle指令碼provided就可以了,這樣打出的外掛不會包含宿主的程式碼
方案2:如果不事先合併資源,那就為每個外掛建立一個AssetManager,每個AssetManager都是通過反射呼叫addAssetPath方法,把外掛自己的資源新增進去。詳細的程式碼見資源的外掛化
方案1的缺點是資源id的字首是有限的,就256個值,當一個app中有多於256個外掛時,就要考慮方案2了