手游SDK — 第七篇(游戏打包篇(下)- 自动化打包踩坑记录)

Hi,各位老铁,欢迎走进自动化打包系列的最后一篇,上文已整体分析并构建了自动化打包项目,下面跟大家伙唠唠打包的实战,打包过程遇到的各位问题及处理方式。

在写之前,大家伙先来看看之前的打包过程图解:

image.png

通过这张图可以看到,整个打包的过程准确地来讲就是资源合并的过程,自动化打包踩坑记录篇也就是资源合并的坑点。

合并Assets目录及apktool.yml实战踩坑记

通常在合并游戏和渠道的assets目录的时候,先处理渠道的特殊配置,然后将渠道assets目录资源拷贝覆盖到游戏资源目录就可以了。但是这里会遇到两个坑点:

坑点一:assets目录的资源怎么缺失了。

以打YSDK渠道包为例,当你美滋滋的打出来的包体运行的时候。。。bangbangbangbang ,哈哈,点击YSDK渠道登录没反应。问题在哪里呢?不着急哈,首先你找一个可以正常运行的包体,反编译后跟你打出来的包体,你会发现assets目录缺少了一堆文件。

image.png

那这些文件都是哪里来的?最后你会发现在YSDK的.jar包里面,当打包将.jar转化为smail的时候只是将源码进行了转化,没有处理资源文件,导致资源的丢失。

image.png

所以在合并libs资源的时候需要将jar里面的非class资源抽出来处理,后面代码有说明

坑点二:unknown目录的生成

细心的朋友可能通过上面的截图就可以看到,jar文件里面除了assets目录下的资源以外还多了BeaconVersion.txt文件,而且这个文件在最开始的时候也没有打到游戏里面去,这个文件又要怎么处理呢?

其实,在回编译生成apk文件的时候,apktool工具会将非.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内所有文件名
          if (!isAPKFileNames(file) && !file.endsWith(".dex")) {//不是常规文件也不是.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) {//这里把他们收集起来,如果需要回编译还可以原封不动的塞回去
                      mResUnknownFiles.addUnknownFileInfo(invZipFile.getName(), String.valueOf(invZipFile.getMethod()));
                  }
               } catch (NullPointerException ignored) { }
            }
        }
        apkZipFile.close();
   } catch (DirectoryException | IOException ex) {
        throw new AndrolibException(ex);
   }
}

当你处理了前面两个坑点,将资源文件都拷贝到对应的目录的时候,你以为美滋滋的打包了, 你会发现你处理的unknown目录的资源在回编译的时候没有打到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) {//这里把他们收集起来,如果需要回编译还可以原封不动的塞回去
    mResUnknownFiles.addUnknownFileInfo(invZipFile.getName(), String.valueOf(invZipFile.getMethod()));
}

apktool工具在反编译apk时,会把编译过程的资源信息写到apktool.yml文件中,然后在回编译的时候读取apktool.yml的配置信息找对应的资源文件

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目录的时候,需要修改下apktool.yml信息

坑点三:!!brut.androlib.meta.MetaInfo

这个坑点,比较隐秘,是不同版本apktool工具发现的,是我在排查问题时遇到的,打包工具用的是2.3.3版本的,但是电脑配置环境变量apktool版本是2.0.1的版本,然后手动回编译时,老是报错:“could not determine a constructor for the tag 'tag:yaml.org,2002:brut.androlib.meta.MetaInfo'”。才发现高版本的的apktool工具在生成apktool.yml文件时,自动在第一行上添加了这个字段,再用版本回编译的时候报错。如果反编译和回编译用的版本都是一样的就没有这个问题。

坑点四:brut.androlib.AndrolibException: brut.common.BrutException: could not exec

这个问题排查了好久,最终发现是游戏包体资源放到assets目录太多,在回编译成apk包体时,无法编译。在apktool.yml处理下doNotCompress 字段下的字符串数字限制导致的(windows 命令行支持的字符串长度有限制,不超过8191个字符)。

针对这个问题处理,还需要兼容apktool这个工具的版本。
apktool 2.0.2下以下版本,没有做这个不压缩机制处理,可以正常回编译。
2.0.2到2.3.2版本新增不压缩的过滤规则
“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以上版本版本又将不过滤规则去掉了。

为兼容apktool版本,在回编译时修改设置过滤规则修改apktool.yml文件下的doNotCompress字段。

针对上述的坑点问题,可查看PackageApkTool/MergeLibUtils.py中的modify_jars具体实现

合并libs目录实战踩坑记

通常在合并游戏和渠道的libs目录的时候,这里需要处理.jar和.so两个类型的文件处理。
1、提取.jar文件里面非class资源,拷贝到对应的目录下
2、保证渠道提供的.so资源文件在游戏包体对应的armeabi/armeabi-v7a/x86等目录下都保持一致。不然会导致部分机型crash
3、需要额外处理微信相关功能的.java文件转化为对应的jar文件,方便在后续jar自动转化为smali代码统一处理

针对上述的坑点问题,可查看PackageApkTool/MergeResource.py中的merge_libs_resource具体实现

合并res目录实战踩坑记

通常在合并游戏和渠道的libs目录的时候,会直接将渠道的资源拷贝覆盖到游戏目录里面。
一般渠道的res目录资源都是有前缀来区分的,但是需要考虑的是,游戏资源和渠道资源同名文件的覆盖问题。

坑点一:res目录下-V4目录问题

渠道提供的资源目录结构与游戏反编译后的目录结构不一致问题。通常在新建安卓工程的时候,res目录的资源包大多是drawable、drawable-hdpi、drawable-xhdpi、values、values-hdpi等形式。但是反编译之后你就发现游戏的目录格式不一样:

image.png

如果游戏资源已经存在了资源文件,当拷贝渠道的资源后,回编译时,会提示资源重复,为了处理这个问题,在合并资源文件的时候,就需提前判断反编译后的目录结构。拷贝到对应的资源文件中。

坑点二:res目录下values目录下arrays.xml/colors.xml/demens.xml/strings.xml资源丢失问题:

拷贝完渠道res资源之后,渠道的资源同名资源会覆盖掉游戏反编译后原有的资源来保证资源是渠道的最新版本,但是需要注意的是values目录下arrays.xml/colors.xml/demens.xml/strings.xml的资源丢失问题,因为游戏apk包反编译后的资源信息都打包到这几个文件里面了,覆盖后,找不到对应的资源会导致各种问题,所以需要针对这几个资源文件做合并处理。

坑点三:res目录下values/values-hdpi等目录下values.xml、values-hdpi.xml等资源冲突问题:

个别渠道的res/values目录下提供的是values.xml、values-hdpi.xml的复合资源文件,将资源属性都定义到里面,values.xml准确来说就是arrays.xml/colors.xml/demens.xml/strings.xml等的结合体:

image.png

其实在处理arrays.xml/colors.xml/demens.xml/strings.xml资源的时候,并没有处理掉values.xml资源,后面在生成R文件的时候就会提示资源冲突了。需要先将values.xml转化为arrays.xml/colors.xml/demens.xml/strings.xml等文件。

坑点四:Error parsing XML: duplicate attribute 资源属性名称重复定义问题:

res目录下values/values-hdpi等目录下,渠道的资源属性和游戏的资源属性重复定义,比如,游戏已定义了app_name这个字段,但是渠道资源里面也定义了这个字段。这类的处理是将渠道定义的属性去掉。

针对上述的坑点问题,可查看PackageApkTool/MergeResUtils.py中的handle_res_dirs具体实现

合并Manifest文件实战踩坑记

通常在合并游戏和渠道的Manifest文件的时候,先修改渠道的差异化参数配置后,直接将对应的节点属性拷贝到游戏的Manifest文件上面就可以了。
1、这里需要额外处理游戏的启动入口和SDK的闪屏逻辑处理
2、个别渠道需要特殊处理游戏的主Activity问题

坑点一:apktool无法识别compileSdkVersion 、compileSdkVersionCodename

在实际操作过程中发现个别游戏包体反编译后,Manifest文件中有compileSdkVersion 、compileSdkVersionCodename这两个字段,即使下载了最新版本的apktool工具也无法识别。这个是因为游戏编译生成apk包时设置编译版本为compileSdkVersion = 28 才会生成的。这里需要额外处理下将compileSdkVersion 、compileSdkVersionCodename字段去掉。
但是在个别包体发现,单单去掉还不行,需将targetSdkVersion设置为23以上才不会异常。

针对上述的坑点问题,可查看PackageApkTool/MergeManifesUtils.py中的merger_manifest_config具体实现

生成R文件实战踩坑记

坑点一:aapt停止运行

其实在生成R文件时导致aapt停止运行的原因有很多,但是基本上一般是资源文件或是xml文件中有错误造成的。具体错误的原因需要慢慢找。

这个问题发现在反编译包体后,生成的资源文件有问题,发现在布局xml中都自动生成了n1这个字段。


image.png

最终定位的原因是模拟游戏母包的apk包是Android Studio3.0 Build Apks 生成包体,classpath 'com.android.tools.build:gradle:3.0.0' 生成的包体反编译后都会这样,可以通过将gradle版本设置:classpath 'com.android.tools.build:gradle:2.3.3' 就没有该问题出现

坑点二:第三方库通过R.xxx引用资源导致资源找不到问题。

这个问题很典型的案例就是个别第三方渠道引用了v7库,v7库里面的res资源是通过R.xxx引用的。在最终打出来的包体资源只会跟当前包名有关,为解决类似这种需要指定包名的问题,可以生成多个R文件的思路解决。

PS:但是这里有个雷点:生成的R文件除了包名不一致外,资源的ID都是一样的。不知道会不会有问题。

针对上述的坑点问题,可查看PackageApkTool/MergeRFileUtils.py中的create_r_files具体实现

渠道差异化处理实战踩坑记

坑点一:YSDK渠道ysdkconf.ini文件解析

文件不是标准的.ini的标准格式,需要额外处理,同时文件的字符集是utf-8-sig,读取后第一行老是多一个空格,用.ini格式读写时,老报错,需先处理下字符集。

针对上述的坑点问题,可查看PackageApkTool/YsdkChannel.py中的modify_assets_resource具体实现

坑点二:xml.etree import ElementTree as ET 解析xml文件出现 ns0:xxx错误

这个问题很好解决,在解析文件之前需将在parse前一定要设置namespace,类似Manifest需添加下面代码。

ET.register_namespace('android', "http://schemas.android.com/apk/res/android")

结语:

坑点记录的详细代码,可到开源项目打包工具下载:PackageApkTool

该工具后续持续完善,欢迎大家star

如果觉得我的文章对你有帮助,请随意赞赏。您的支持将鼓励我继续创作!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容