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

一 背景

为什么要二次打包

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

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

二次打包的基本原理

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

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

二次打包之资源文件ID

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

一个是resources.arsc.里面包含了所有资源文件的索引ID

示例:


<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的基础上进行封装和修改的,打包的过程比较繁琐,总结下来大概有如下几个步骤:

通过aapt工具,生成R.java和.arsc文件
通过aidl工具,把aidl文件打包成java文件
通过javac工具,将Java文件编译成.class文件
通过dx工具,把class文件和第三方jar打包成dex文件
通过apkbuilder工具,把dex和资源文件打包成apk文件
通过JarSinger工具,对apk进行签名
通过zipAlign工具,对apk做对齐处理
我们要改的就是第一步:aapt生成R文件!步骤如下

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

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

使用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文件到对应位置

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

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

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

作者:b923228cc7b5
链接:https://www.jianshu.com/p/440bfa4932ad
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的:(android,r语言,python,经验分享,游戏)