Hi镰烧,各位老鐵,歡迎走進(jìn)自動(dòng)化打包系列的最后一篇楞陷,上文已整體分析并構(gòu)建了自動(dòng)化打包項(xiàng)目怔鳖,下面跟大家伙嘮嘮打包的實(shí)戰(zhàn),打包過程遇到的各位問題及處理方式固蛾。
在寫之前结执,大家伙先來看看之前的打包過程圖解:
通過這張圖可以看到度陆,整個(gè)打包的過程準(zhǔn)確地來講就是資源合并的過程,自動(dòng)化打包踩坑記錄篇也就是資源合并的坑點(diǎn)献幔。
合并Assets目錄及apktool.yml實(shí)戰(zhàn)踩坑記
通常在合并游戲和渠道的assets目錄的時(shí)候懂傀,先處理渠道的特殊配置,然后將渠道assets目錄資源拷貝覆蓋到游戲資源目錄就可以了蜡感。但是這里會(huì)遇到兩個(gè)坑點(diǎn):
坑點(diǎn)一:assets目錄的資源怎么缺失了蹬蚁。
以打YSDK渠道包為例,當(dāng)你美滋滋的打出來的包體運(yùn)行的時(shí)候郑兴。缚忧。。bangbangbangbang 杈笔,哈哈闪水,點(diǎn)擊YSDK渠道登錄沒反應(yīng)。問題在哪里呢蒙具?不著急哈球榆,首先你找一個(gè)可以正常運(yùn)行的包體,反編譯后跟你打出來的包體禁筏,你會(huì)發(fā)現(xiàn)assets目錄缺少了一堆文件持钉。
那這些文件都是哪里來的?最后你會(huì)發(fā)現(xiàn)在YSDK的.jar包里面篱昔,當(dāng)打包將.jar轉(zhuǎn)化為smail的時(shí)候只是將源碼進(jìn)行了轉(zhuǎn)化每强,沒有處理資源文件,導(dǎo)致資源的丟失州刽。
所以在合并libs資源的時(shí)候需要將jar里面的非class資源抽出來處理空执,后面代碼有說明
坑點(diǎn)二:unknown目錄的生成
細(xì)心的朋友可能通過上面的截圖就可以看到,jar文件里面除了assets目錄下的資源以外還多了BeaconVersion.txt文件穗椅,而且這個(gè)文件在最開始的時(shí)候也沒有打到游戲里面去辨绊,這個(gè)文件又要怎么處理呢?
其實(shí)匹表,在回編譯生成apk文件的時(shí)候门坷,apktool工具會(huì)將非.apk文件打包放到unknown的目錄下。這里可以參考下apktool源碼分析袍镀。
Apktool源碼解析——第一篇
Apktool源碼解析——第二篇
public void decodeUnknownFiles(ExtFile apkFile, File outDir, ResTable resTable) throws AndrolibException {
LOGGER.info("Copying unknown files...");
File unknownOut = new File(outDir, UNK_DIRNAME);
ZipEntry invZipFile;
// have to use container of ZipFile to help identify compression type
// with regular looping of apkFile for easy copy
try {
Directory unk = apkFile.getDirectory();
ZipExtFile apkZipFile = new ZipExtFile(apkFile.getAbsolutePath());
// loop all items in container recursively, ignoring any that are pre-defined by aapt
Set<String> files = unk.getFiles(true);
for (String file : files) {//取出apk內(nèi)所有文件名
if (!isAPKFileNames(file) && !file.endsWith(".dex")) {//不是常規(guī)文件也不是.dex文件
// copy file out of archive into special "unknown" folder
unk.copyToDir(unknownOut, file);//拷貝至unknown目錄
try {
// ignore encryption
apkZipFile.getEntry(file).getGeneralPurposeBit().useEncryption(false);
invZipFile = apkZipFile.getEntry(file);
// lets record the name of the file, and its compression type
// so that we may re-include it the same way
if (invZipFile != null) {//這里把他們收集起來默蚌,如果需要回編譯還可以原封不動(dòng)的塞回去
mResUnknownFiles.addUnknownFileInfo(invZipFile.getName(), String.valueOf(invZipFile.getMethod()));
}
} catch (NullPointerException ignored) { }
}
}
apkZipFile.close();
} catch (DirectoryException | IOException ex) {
throw new AndrolibException(ex);
}
}
當(dāng)你處理了前面兩個(gè)坑點(diǎn),將資源文件都拷貝到對應(yīng)的目錄的時(shí)候,你以為美滋滋的打包了苇羡, 你會(huì)發(fā)現(xiàn)你處理的unknown目錄的資源在回編譯的時(shí)候沒有打到Apk里面绸吸。為什么?
大家請看這句:
// lets record the name of the file, and its compression type
// so that we may re-include it the same way
if (invZipFile != null) {//這里把他們收集起來,如果需要回編譯還可以原封不動(dòng)的塞回去
mResUnknownFiles.addUnknownFileInfo(invZipFile.getName(), String.valueOf(invZipFile.getMethod()));
}
apktool工具在反編譯apk時(shí)惯裕,會(huì)把編譯過程的資源信息寫到apktool.yml文件中温数,然后在回編譯的時(shí)候讀取apktool.yml的配置信息找對應(yīng)的資源文件
public void writeMetaFile(File mOutDir, Map<String, Object> meta)//鍵值對信息
throws AndrolibException {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
Yaml yaml = new Yaml(options);
try (
Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(
new File(mOutDir, "apktool.yml")), "UTF-8"));//輸出目錄
) {
yaml.dump(meta, writer);
} catch (IOException ex) {
throw new AndrolibException(ex);
}
}
因此,咱們在合并資源到unknown目錄的時(shí)候蜻势,需要修改下apktool.yml信息
坑點(diǎn)三:!!brut.androlib.meta.MetaInfo
這個(gè)坑點(diǎn)撑刺,比較隱秘,是不同版本apktool工具發(fā)現(xiàn)的握玛,是我在排查問題時(shí)遇到的够傍,打包工具用的是2.3.3版本的,但是電腦配置環(huán)境變量apktool版本是2.0.1的版本挠铲,然后手動(dòng)回編譯時(shí)冕屯,老是報(bào)錯(cuò):“could not determine a constructor for the tag 'tag:yaml.org,2002:brut.androlib.meta.MetaInfo'”。才發(fā)現(xiàn)高版本的的apktool工具在生成apktool.yml文件時(shí)拂苹,自動(dòng)在第一行上添加了這個(gè)字段安聘,再用版本回編譯的時(shí)候報(bào)錯(cuò)。如果反編譯和回編譯用的版本都是一樣的就沒有這個(gè)問題瓢棒。
坑點(diǎn)四:brut.androlib.AndrolibException: brut.common.BrutException: could not exec
這個(gè)問題排查了好久浴韭,最終發(fā)現(xiàn)是游戲包體資源放到assets目錄太多,在回編譯成apk包體時(shí)脯宿,無法編譯念颈。在apktool.yml處理下doNotCompress 字段下的字符串?dāng)?shù)字限制導(dǎo)致的(windows 命令行支持的字符串長度有限制,不超過8191個(gè)字符)连霉。
針對這個(gè)問題處理榴芳,還需要兼容apktool這個(gè)工具的版本。
apktool 2.0.2下以下版本跺撼,沒有做這個(gè)不壓縮機(jī)制處理窟感,可以正常回編譯财边。
2.0.2到2.3.2版本新增不壓縮的過濾規(guī)則
“jpg|jpeg|png|gif|wav|mp2|mp3|ogg|aac|mpg|mpeg|mid|midi|smf|jet|rtttl|imy|xmf|mp4|m4a|m4v|3gp|3gpp|3g2|3gpp2|amr|awb|wma|wmv|webm|mkv”
但是在2.3.3以上版本版本又將不過濾規(guī)則去掉了肌括。
為兼容apktool版本,在回編譯時(shí)修改設(shè)置過濾規(guī)則修改apktool.yml文件下的doNotCompress字段酣难。
針對上述的坑點(diǎn)問題,可查看PackageApkTool/MergeLibUtils.py中的modify_jars具體實(shí)現(xiàn)
合并libs目錄實(shí)戰(zhàn)踩坑記
通常在合并游戲和渠道的libs目錄的時(shí)候黑滴,這里需要處理.jar和.so兩個(gè)類型的文件處理憨募。
1、提取.jar文件里面非class資源袁辈,拷貝到對應(yīng)的目錄下
2菜谣、保證渠道提供的.so資源文件在游戲包體對應(yīng)的armeabi/armeabi-v7a/x86等目錄下都保持一致。不然會(huì)導(dǎo)致部分機(jī)型crash
3、需要額外處理微信相關(guān)功能的.java文件轉(zhuǎn)化為對應(yīng)的jar文件尾膊,方便在后續(xù)jar自動(dòng)轉(zhuǎn)化為smali代碼統(tǒng)一處理
針對上述的坑點(diǎn)問題媳危,可查看PackageApkTool/MergeResource.py中的merge_libs_resource具體實(shí)現(xiàn)
合并res目錄實(shí)戰(zhàn)踩坑記
通常在合并游戲和渠道的libs目錄的時(shí)候,會(huì)直接將渠道的資源拷貝覆蓋到游戲目錄里面冈敛。
一般渠道的res目錄資源都是有前綴來區(qū)分的待笑,但是需要考慮的是,游戲資源和渠道資源同名文件的覆蓋問題抓谴。
坑點(diǎn)一:res目錄下-V4目錄問題
渠道提供的資源目錄結(jié)構(gòu)與游戲反編譯后的目錄結(jié)構(gòu)不一致問題暮蹂。通常在新建安卓工程的時(shí)候,res目錄的資源包大多是drawable癌压、drawable-hdpi仰泻、drawable-xhdpi、values滩届、values-hdpi等形式集侯。但是反編譯之后你就發(fā)現(xiàn)游戲的目錄格式不一樣:
如果游戲資源已經(jīng)存在了資源文件,當(dāng)拷貝渠道的資源后帜消,回編譯時(shí)浅悉,會(huì)提示資源重復(fù),為了處理這個(gè)問題券犁,在合并資源文件的時(shí)候术健,就需提前判斷反編譯后的目錄結(jié)構(gòu)≌吵模拷貝到對應(yīng)的資源文件中荞估。
坑點(diǎn)二:res目錄下values目錄下arrays.xml/colors.xml/demens.xml/strings.xml資源丟失問題:
拷貝完渠道res資源之后,渠道的資源同名資源會(huì)覆蓋掉游戲反編譯后原有的資源來保證資源是渠道的最新版本稚新,但是需要注意的是values目錄下arrays.xml/colors.xml/demens.xml/strings.xml的資源丟失問題勘伺,因?yàn)橛螒騛pk包反編譯后的資源信息都打包到這幾個(gè)文件里面了,覆蓋后褂删,找不到對應(yīng)的資源會(huì)導(dǎo)致各種問題飞醉,所以需要針對這幾個(gè)資源文件做合并處理玄括。
坑點(diǎn)三:res目錄下values/values-hdpi等目錄下values.xml摹芙、values-hdpi.xml等資源沖突問題:
個(gè)別渠道的res/values目錄下提供的是values.xml判没、values-hdpi.xml的復(fù)合資源文件旧巾,將資源屬性都定義到里面利虫,values.xml準(zhǔn)確來說就是arrays.xml/colors.xml/demens.xml/strings.xml等的結(jié)合體:
其實(shí)在處理arrays.xml/colors.xml/demens.xml/strings.xml資源的時(shí)候笆怠,并沒有處理掉values.xml資源极祸,后面在生成R文件的時(shí)候就會(huì)提示資源沖突了祠汇。需要先將values.xml轉(zhuǎn)化為arrays.xml/colors.xml/demens.xml/strings.xml等文件盖袭。
坑點(diǎn)四:Error parsing XML: duplicate attribute 資源屬性名稱重復(fù)定義問題:
res目錄下values/values-hdpi等目錄下失暂,渠道的資源屬性和游戲的資源屬性重復(fù)定義彼宠,比如,游戲已定義了app_name這個(gè)字段弟塞,但是渠道資源里面也定義了這個(gè)字段凭峡。這類的處理是將渠道定義的屬性去掉。
針對上述的坑點(diǎn)問題决记,可查看PackageApkTool/MergeResUtils.py中的handle_res_dirs具體實(shí)現(xiàn)
合并Manifest文件實(shí)戰(zhàn)踩坑記
通常在合并游戲和渠道的Manifest文件的時(shí)候摧冀,先修改渠道的差異化參數(shù)配置后,直接將對應(yīng)的節(jié)點(diǎn)屬性拷貝到游戲的Manifest文件上面就可以了霉涨。
1按价、這里需要額外處理游戲的啟動(dòng)入口和SDK的閃屏邏輯處理
2、個(gè)別渠道需要特殊處理游戲的主Activity問題
坑點(diǎn)一:apktool無法識別compileSdkVersion 笙瑟、compileSdkVersionCodename
在實(shí)際操作過程中發(fā)現(xiàn)個(gè)別游戲包體反編譯后楼镐,Manifest文件中有compileSdkVersion 、compileSdkVersionCodename這兩個(gè)字段往枷,即使下載了最新版本的apktool工具也無法識別框产。這個(gè)是因?yàn)橛螒蚓幾g生成apk包時(shí)設(shè)置編譯版本為compileSdkVersion = 28 才會(huì)生成的。這里需要額外處理下將compileSdkVersion 错洁、compileSdkVersionCodename字段去掉秉宿。
但是在個(gè)別包體發(fā)現(xiàn),單單去掉還不行屯碴,需將targetSdkVersion設(shè)置為23以上才不會(huì)異常描睦。
針對上述的坑點(diǎn)問題,可查看PackageApkTool/MergeManifesUtils.py中的merger_manifest_config具體實(shí)現(xiàn)
生成R文件實(shí)戰(zhàn)踩坑記
坑點(diǎn)一:aapt停止運(yùn)行
其實(shí)在生成R文件時(shí)導(dǎo)致aapt停止運(yùn)行的原因有很多导而,但是基本上一般是資源文件或是xml文件中有錯(cuò)誤造成的忱叭。具體錯(cuò)誤的原因需要慢慢找。
這個(gè)問題發(fā)現(xiàn)在反編譯包體后今艺,生成的資源文件有問題韵丑,發(fā)現(xiàn)在布局xml中都自動(dòng)生成了n1這個(gè)字段。
最終定位的原因是模擬游戲母包的apk包是Android Studio3.0 Build Apks 生成包體虚缎,classpath 'com.android.tools.build:gradle:3.0.0' 生成的包體反編譯后都會(huì)這樣撵彻,可以通過將gradle版本設(shè)置:classpath 'com.android.tools.build:gradle:2.3.3' 就沒有該問題出現(xiàn)
坑點(diǎn)二:第三方庫通過R.xxx引用資源導(dǎo)致資源找不到問題。
這個(gè)問題很典型的案例就是個(gè)別第三方渠道引用了v7庫实牡,v7庫里面的res資源是通過R.xxx引用的陌僵。在最終打出來的包體資源只會(huì)跟當(dāng)前包名有關(guān),為解決類似這種需要指定包名的問題铲掐,可以生成多個(gè)R文件的思路解決拾弃。
PS:但是這里有個(gè)雷點(diǎn):生成的R文件除了包名不一致外,資源的ID都是一樣的摆霉。不知道會(huì)不會(huì)有問題。
針對上述的坑點(diǎn)問題,可查看PackageApkTool/MergeRFileUtils.py中的create_r_files具體實(shí)現(xiàn)
渠道差異化處理實(shí)戰(zhàn)踩坑記
坑點(diǎn)一:YSDK渠道ysdkconf.ini文件解析
文件不是標(biāo)準(zhǔn)的.ini的標(biāo)準(zhǔn)格式携栋,需要額外處理搭盾,同時(shí)文件的字符集是utf-8-sig,讀取后第一行老是多一個(gè)空格婉支,用.ini格式讀寫時(shí)鸯隅,老報(bào)錯(cuò),需先處理下字符集向挖。
針對上述的坑點(diǎn)問題蝌以,可查看PackageApkTool/YsdkChannel.py中的modify_assets_resource具體實(shí)現(xiàn)
坑點(diǎn)二:xml.etree import ElementTree as ET 解析xml文件出現(xiàn) ns0:xxx錯(cuò)誤
這個(gè)問題很好解決,在解析文件之前需將在parse前一定要設(shè)置namespace何之,類似Manifest需添加下面代碼跟畅。
ET.register_namespace('android', "http://schemas.android.com/apk/res/android")
結(jié)語:
坑點(diǎn)記錄的詳細(xì)代碼,可到開源項(xiàng)目打包工具下載:PackageApkTool
該工具后續(xù)持續(xù)完善溶推,歡迎大家star
如果覺得我的文章對你有幫助徊件,請隨意贊賞。您的支持將鼓勵(lì)我繼續(xù)創(chuàng)作蒜危!