安卓經(jīng)常需要打多個(gè)渠道包,當(dāng)二次打包時(shí),資源ID會(huì)重新生成泞坦。如果代碼中有第三方SDK通過直接引用R文件的方式來(lái)獲取資源ID,就會(huì)出現(xiàn)資源ID不匹配的問題砖顷。
本文主要介紹解決此類問題的三種方法贰锁。
一 背景
為什么要二次打包
大家都知道,國(guó)內(nèi)安卓渠道眾多滤蝠,游戲想要上架渠道就要接入他們的sdk豌熄。這對(duì)于游戲開發(fā)商(CP)來(lái)說(shuō)是一個(gè)不小的工作量。
通過接入我們的聚合SDK物咳,CP只需要提供一個(gè)母包锣险,然后使用我們的打包工具就可以打出幾十個(gè)渠道包,非常的高效。
二次打包的基本原理
打包工具的基本原理就是通過反編譯囱持,把SDK的代碼和資源文件打入到游戲母包夯接,然后重新打包簽名,生成對(duì)應(yīng)的渠道包纷妆。
當(dāng)然這其中會(huì)涉及到許多細(xì)節(jié)方面的東西盔几,不是本文重點(diǎn),就不展開了掩幢。
二次打包之資源文件ID
打包時(shí)逊拍,會(huì)生成兩個(gè)文件。
一個(gè)是resources.arsc.里面包含了所有資源文件的索引ID
示例:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<public type="drawable" name="btn_login" id="2130837507" />
<public type="layout" name="paydialog" id="2130903044" />
</resources>
另外一個(gè)是R文件际邻,里面提供了代碼中引用的name和resources.arsc中對(duì)應(yīng)的ID
示例:
public final class R {
public static final class drawable {
public static final int btn_login = 2130837507;
}
public static final class layout {
public static final int paydialog = 2130903049;
}
}
這樣我們通過引用R文件就可以找到對(duì)應(yīng)的資源芯丧。
但是,正如前面所說(shuō)世曾,resources.arsc和R.java是打包的時(shí)候才會(huì)生成缨恒。因此,二次打包會(huì)重新生成資源ID轮听,如果我們通過直接引用R文件的方式來(lái)尋找資源骗露,就會(huì)出現(xiàn)問題。
二 解決方法
1. 常規(guī)操作:動(dòng)態(tài)獲取資源ID
這是最簡(jiǎn)單最通用的解決方法血巍。
比如說(shuō)我們可以通過如下方式獲取一個(gè)布局文件的ID:
public static int getLayoutId(Context context,String paramString) {
mContext = context;
return getResourceId("layout", paramString);
}
protected static int getResourceId(String paramString1, String paramString2) {
return mContext.getResources().getIdentifier(paramString2, paramString1,
mContext.getPackageName());
}
2. 奇技淫巧:替換掉第三方SDK中R文件的引用
可是萧锉,如果第三方SDK就是通過R文件直接引用資源ID怎么辦?
比如說(shuō)述寡,他們引用了com.corpA.sdk.R柿隙,重新打包之后,會(huì)在我們的包名(com.corpB.demo)文件夾下生成新的R文件鲫凶。因此禀崖,我們可以讓SDK直接引用我們包名文件夾下的R文件。
具體咋做呢螟炫?
2.1 SDK代碼反編譯為smali文件
使用apktool將第三方SDK的所有java代碼反編譯為smali文件波附。
2.2 查找替換所有R文件的引用
將smali文件中所有com/corpA/sdk/R替換為com/corp/demo/R。這樣不恭,重新打包后叶雹,第三方SDK在運(yùn)行時(shí)會(huì)去我們包名下最新的R文件中尋找資源ID
這種方法簡(jiǎn)單粗暴财饥,不需要手動(dòng)生成R文件换吧,也不需要更改SDK中原來(lái)的R文件,直接通過打包腳本在反編譯階段改掉第三方SDK的代碼钥星。
3. 曲線救國(guó):直接修改第三方SDK中引用的R文件
凡是總有例外沾瓦,不是所有的SDK都能反編譯的。比如有些SDK會(huì)將Java代碼打成一個(gè)加密的Dex文件,放到asset文件夾下面贯莺。這種加密的Dex文件我們是無(wú)法反編譯的风喇,所以上面的方法2就行不通了。
由于無(wú)法反編譯缕探,這個(gè)Dex里面的內(nèi)容對(duì)于我們就是一個(gè)黑盒魂莫,我們無(wú)法知道它里面是否有直接引用R文件的情況,以及引用了哪里的R文件爹耗。
這個(gè)其實(shí)沒有很好地辦法耙考,只能先按照方法2來(lái)處理掉可以反編譯的Java代碼,然后打出一個(gè)包來(lái)進(jìn)行測(cè)試潭兽。如果加密Dex中有直接引用到R文件倦始,那么就會(huì)出現(xiàn)崩潰,從日志中我們就可以找到引用R文件的位置山卦。然后我們就可以修改指定位置的R文件啦鞋邑。
過程有些繁瑣,下面詳細(xì)說(shuō)明:
3.1 找到引用R文件的位置
通過崩潰日志账蓉,我們可以找到Dex文件中所引用的R文件的位置枚碗,假設(shè)該R文件的路徑是com.corpA.sdk.R
3.2 使用aapt手動(dòng)生成R文件
aapt(Android Asset Packaging Tool)是安卓sdk中負(fù)責(zé)將資源文件和代碼進(jìn)行打包的工具√拊常基本上所有的打包工具都是在aapt的基礎(chǔ)上進(jìn)行封裝和修改的视译,打包的過程比較繁瑣,總結(jié)下來(lái)大概有如下幾個(gè)步驟:
- 通過aapt工具归敬,生成R.java和.arsc文件
- 通過aidl工具酷含,把a(bǔ)idl文件打包成java文件
- 通過javac工具,將Java文件編譯成.class文件
- 通過dx工具汪茧,把class文件和第三方j(luò)ar打包成dex文件
- 通過apkbuilder工具椅亚,把dex和資源文件打包成apk文件
- 通過JarSinger工具,對(duì)apk進(jìn)行簽名
- 通過zipAlign工具舱污,對(duì)apk做對(duì)齊處理
我們要改的就是第一步:aapt生成R文件呀舔!步驟如下
反編譯游戲母包和接入渠道SDK的插件包,將代碼和資源文件合并
修改AndroidManifest.xml中的包名為最終渠道包的包名
-
使用Android sdk的aapt工具手動(dòng)生成R文件
前提是需要首先安裝Andorid sdk相應(yīng)的工具扩灯,并配置好環(huán)境變量媚赖。aapt指令比較復(fù)雜,核心就是下面這個(gè):
os.getenv('ANDROID_BUILD_TOOL') + "/aapt package -f -m -J " + temp_path + " -S " + res_path + " -I " + os.getenv('ANDROID_PLATFORM') + "/android.jar -M " + manifest_path
其中temp_path是生成的R文件輸出路徑珠插;res_path是res文件夾的路徑惧磺;manifest_path是manifest的路徑
3.3 將R文件轉(zhuǎn)換為Smali文件
這個(gè)過程大概4個(gè)步驟: java --> class --> jar --> dex --> smali
示例:
# 1. build_r_class
r_java_path = temp_path + os.sep + package_name.replace('.', os.sep) + os.sep + "R.java"
cmd_build_r_class = "javac -source 1.6 -target 1.6 " + r_java_path
execCmd(cmd_build_r_class)
file_util.deleteFile(r_java_path)
# 2. generate jar
os.chdir("/data/soft/jenkins/workspace/Packaging_Tools-All" + os.sep + temp_path)
cmd_generate_r_jar = "jar cvf " + "r.jar " + "com"
execCmd(cmd_generate_r_jar)
# 3. generate dex
cmd_generate_dex = os.getenv('ANDROID_BUILD_TOOL') + "/dx --dex --output=" + "r.dex " + "r.jar"
execCmd(cmd_generate_dex)
# 4. generate smali
cmd_generate_smali = "java -jar " + "/data/soft/jenkins/workspace/Packaging_Tools-All" + os.sep + "baksmali-2.0.jar " + "r.dex"
execCmd(cmd_generate_smali)
3.4 拷貝smali文件到對(duì)應(yīng)位置
我們要拷貝一份smali文件到包名下對(duì)應(yīng)目錄。
然后同樣拷貝到3.1步驟中R文件的路徑(com.corpA.sdk.R)捻撑。同時(shí)磨隘,我們需要修改smali中R文件的引用為com.corpA.sdk.R缤底。這樣才能保持更Asset下面的Dex文件中的引用一致。
至此番捂,關(guān)于R文件的處理已經(jīng)完成个唧,然后重新打包就可以啦~