Android二次打包之重新生成R文件

安卓经常需要打多个渠道包,当二次打包时,资源ID会重新生成。如果代码中有第三方SDK通过直接引用R文件的方式来获取资源ID,就会出现资源ID不匹配的问题。
本文主要介绍解决此类问题的三种方法。

一 背景

为什么要二次打包

大家都知道,国内安卓渠道众多,游戏想要上架渠道就要接入他们的sdk。这对于游戏开发商(CP)来说是一个不小的工作量。

通过接入我们的聚合SDK,CP只需要提供一个母包,然后使用我们的打包工具就可以打出几十个渠道包,非常的高效。

二次打包的基本原理

打包工具的基本原理就是通过反编译,把SDK的代码和资源文件打入到游戏母包,然后重新打包签名,生成对应的渠道包。

当然这其中会涉及到许多细节方面的东西,不是本文重点,就不展开了。

二次打包之资源文件ID

打包时,会生成两个文件。

一个是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>

另外一个是R文件,里面提供了代码中引用的name和resources.arsc中对应的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文件就可以找到对应的资源。

但是,正如前面所说,resources.arsc和R.java是打包的时候才会生成。因此,二次打包会重新生成资源ID,如果我们通过直接引用R文件的方式来寻找资源,就会出现问题。

二 解决方法

1. 常规操作:动态获取资源ID

这是最简单最通用的解决方法。

比如说我们可以通过如下方式获取一个布局文件的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怎么办?

比如说,他们引用了com.corpA.sdk.R,重新打包之后,会在我们的包名(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在运行时会去我们包名下最新的R文件中寻找资源ID

这种方法简单粗暴,不需要手动生成R文件,也不需要更改SDK中原来的R文件,直接通过打包脚本在反编译阶段改掉第三方SDK的代码。

3. 曲线救国:直接修改第三方SDK中引用的R文件

凡是总有例外,不是所有的SDK都能反编译的。比如有些SDK会将Java代码打成一个加密的Dex文件,放到asset文件夹下面。这种加密的Dex文件我们是无法反编译的,所以上面的方法2就行不通了。

由于无法反编译,这个Dex里面的内容对于我们就是一个黑盒,我们无法知道它里面是否有直接引用R文件的情况,以及引用了哪里的R文件。

这个其实没有很好地办法,只能先按照方法2来处理掉可以反编译的Java代码,然后打出一个包来进行测试。如果加密Dex中有直接引用到R文件,那么就会出现崩溃,从日志中我们就可以找到引用R文件的位置。然后我们就可以修改指定位置的R文件啦。

过程有些繁琐,下面详细说明:

3.1 找到引用R文件的位置

通过崩溃日志,我们可以找到Dex文件中所引用的R文件的位置,假设该R文件的路径是com.corpA.sdk.R

3.2 使用aapt手动生成R文件

aapt(Android Asset Packaging Tool)是安卓sdk中负责将资源文件和代码进行打包的工具。基本上所有的打包工具都是在aapt的基础上进行封装和修改的,打包的过程比较繁琐,总结下来大概有如下几个步骤:

  1. 通过aapt工具,生成R.java和.arsc文件
  2. 通过aidl工具,把aidl文件打包成java文件
  3. 通过javac工具,将Java文件编译成.class文件
  4. 通过dx工具,把class文件和第三方jar打包成dex文件
  5. 通过apkbuilder工具,把dex和资源文件打包成apk文件
  6. 通过JarSinger工具,对apk进行签名
  7. 通过zipAlign工具,对apk做对齐处理

我们要改的就是第一步:aapt生成R文件!步骤如下

  1. 反编译游戏母包和接入渠道SDK的插件包,将代码和资源文件合并

  2. 修改AndroidManifest.xml中的包名为最终渠道包的包名

  3. 使用Android sdk的aapt工具手动生成R文件

    前提是需要首先安装Andorid sdk相应的工具,并配置好环境变量。aapt指令比较复杂,核心就是下面这个:

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文件转换为Smali文件

这个过程大概4个步骤: 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文件到对应位置

  1. 我们要拷贝一份smali文件到包名下对应目录。

  2. 然后同样拷贝到3.1步骤中R文件的路径(com.corpA.sdk.R)。同时,我们需要修改smali中R文件的引用为com.corpA.sdk.R。这样才能保持更Asset下面的Dex文件中的引用一致。

至此,关于R文件的处理已经完成,然后重新打包就可以啦~

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