• Android聚合SDK母包反编译出包教程


    【前言】

      现在不管是游戏发行,亦或者是游戏买量,都会涉及到1个游戏项目出N个游戏包的情况,在游戏发行场景,1个游戏需要上架多个安卓应用市场,比如:上架oppo、vivo、小米、华为、应用宝、九游之类,那么1个游戏包就需要出包含不同渠道SDK的分包;在游戏买量场景,1个游戏也是需要上架多个推广媒体渠道,比如:上架头条、快手、广点通、百度之类,同时在推广时候往往还需要出各种马甲包,那么1个游戏就需要出好几百个分包,要是每一个游戏包都让研发去对接出包,研发肯定直接给撂摊子不干了。

      1个解决方案是游戏发行或者买量方(后面简称:SP)出一个聚合SDK给研发接,研发接入完成之后,连同游戏代码打包成aar给回SPSP再在IDE中打出不同渠道的分包,这样研发只需要出1个包,可以大大减少了研发的工作量,但是SP自己的工作量并没有减少,依然还是得手动去打N多个包,要是出上百个包的话,估计不吃不喝,一天下来都干不完;当然这个过程也可以通过脚本去实现,不过得熟悉打包涉及的每一个步骤,这样也是可以节省很多时间;

      另1种解决方案也是出一个聚合SDK给研发对接,研发接入完成之后,打一个可以正常运行的apk给回SPSP再反编译把其他渠道的SDK放进来,然后回编译成新的apk,这种方案要是手动实现,难度系数比第1种方案更大,不仅需要熟悉打包涉及的每一个步骤,还需要熟悉apk反编译相关的知识点,同时也得用脚本实现才能达到节省时间的目的;

     鉴于第1种方案比较简单就不在这里展开阐述,下面主要针对第2种解决方案进行讲解

    一、SDK预处理

     一般来说,我们SDK某个功能的实现都会引用到第三方SDK代码,比如:获取OAID SDK、媒体渠道SDK、手机号一键登录SDK、推送SDK等,那么第一步就需要把所有用到的SDK进行合并:

    1、SDK资源合并

    1.1、合并res目录下的资源

    • res目录一般包含以下几个常见的目录:
      layout:存放布局文件
      anim:存放动画定义文件
      drawable/mipmap: 存放图片文件
      color:存放自定义颜色文件
      menu: 存放菜单定义文件
      xml:存放一些配置文件,比如:网络配置、provider配置
      raw:存放不进行压缩的文件
      values:存放字符串、主题样式、颜色、属性、尺寸等的配置文件
    • 合并res目录文件时候,可能会出现文件名冲突的问题,所以这就要求我们在开发自己的SDK时候,需要尽量避免跟第三方SDK的文件名出现冲突,可以在对我们文件命名时候,统一加个前缀,比如:Facebook的资源文件名都会加上com_facebook前缀
    • 同时,需要注意的是,对于values目录下xml文件,比如:
      SDK1 的sdk1_strings.xml中包含 xx1
      SDK2 的sdk2_strings.xml中包含 xx2
       那么,就算两个SDK的资源文件名是不一样的,但是由于xml中包含相同的节点,到回编译时候还是会报错,所以,需要在资源合并时候把相同的节点处理掉,一种解决方案是可以将sdk1、sdk2 values目录下的所有xml文件都写入到一个新的xml文件中,这里暂且命名为values.xml,每写入一个节点可以判断values.xml中是否存在,若是存在则不再写入,这样就能确保不会存在多个相同节点。

    1.2、合并libs目录

    目前libs目录主要是包含以下几个常见目录:

    • x86: 可运行于Intel 32/64位处理器的机器,目前比较少见,一般是比较老旧的机子,性能低
      x86_64:可运行于Intel 64位处理器的机器,目前基本模拟器用得比较多
      armeabiIntel 32/64处理器与ARM 32/64处理的机器都能运行,但是性能低,相当于在绝大多数手机上都是需要辅助ABI或动态转码来兼容
      armeabi-v7a:可运行于ARM 32/64位处理器的机器,性能比armeabi高一点,比arm64-v8a低,适用于早期的手机
      arm64-v8a:可运行于ARM 64位处理器的机器,目前市面上的安卓手机基本都是ARM 64位处理器了

       合并libs目录需要注意的是,需要确保libs目录下对应架构目录的so文件是一致的,比如:SDK1 适配 armeabi-v7aarm64-v8a,但是SDK2 只适配armeabi-v7a, 那么合并之后,就只能保留armeabi-v7a这一个目录,要不在ARM64的手机运行时候,因为会优先加载arm64-v8a目录下的so文件,但是在arm64-v8a目录又没有SDK2的so文件,这会就会闪退!

    1.3、合并assets目录

     合并assets目录,只要文件名没有出现冲突就OK了

    1.4、合并AndroidManifest.xml

     把多个SDK AndroidManifest.xml文件的配置整合到一个AndroidManifest.xml中,需要注意是去掉重复的声明,比如:重复的权限声明、重复的meta-data声明等

    1.5、合并jar

     把所有SDK的jar都复制到到同一个文件夹jars中,若是aar中的jar,由于默认的文件名都是classes.jar,故需要根据aar的文件名,重新命名一下,再复制到jars

    2、jar转smali

    2.1、jar 混淆合并

     混淆主要是用到Android SDK的proguard,一般在SDK的tools/proguard/目录下可以找到
    1)一般手动调用proguard去混淆的话,需要在proguard-rules.pro文件增加java与android类库的依赖:

    # 一般在sdk/platforms/android-31/目录下
    -libraryjars <android.jar的绝对路径>
    # 一般在jdk1.8.0_131/jre/lib目录下
    -libraryjars <rt.jar的绝对路径>
    
    • 1
    • 2
    • 3
    • 4

    2)在proguard-rules.pro文件声明需要进行混淆的jar(-injars)以及混淆之后输出的jar(-outjars)
     在这里基本就是把jars目录下所有的jar都写上去即可

    -injars <jar1的绝对路径>
    -injars <jar2的绝对路径>
    -injars <jarN的绝对路径>
    -outjars <混淆输出jar的绝对路径>
    
    • 1
    • 2
    • 3
    • 4

    3)proguard执行混淆的命令

    java -jar proguard.jar -ignorewarnings  @<proguard-rules.pro的绝对路径>
    
    • 1

    【备注】
     假如你不想混淆,但是你又想把jars目录下所有jar合并成一个jar的话,也可以借助proguard命令去实现,你只需在proguard-rules.pro文件声明keep所有的类即可

    -keep class **{*;}
    -keep interface **{*;}
    
    • 1
    • 2

    2.2、jar转dex

     使用android SDK的dx命令即可,一般在android sdk/build-tools/30.0.3/dx.bat目录,也可以直接使用android sdk/build-tools/30.0.3/lib/dx.jar, 命令如下:

    java -jar dx.jar --dex --no-warning --output=<dex的输出路径> <需要转dex的jar路径>
    
    • 1

     正常情况来说,用上述命令将jar转换成dex即可,但是假如需要转dexjar里面所包含的方法数超过65535,上述命令就会抛出异常,需要改为用以下命令来处理:

    java -jar dx.jar --dex --multi-dex --no-warning --output=<dex存放目录的路径> <需要转dex的jar路径>
    
    • 1

     当然除了用dx, 新版的android sdk还可以选用d8来将jar转为dex

    java -cp d8.jar "com.android.tools.r8.D8" --lib android.jar --output <dex存放目录的路径> <需要转dex的jar路径>
    
    • 1

    2.3、dex转smali

     将dex文件转为smali文件,主要是用到baksmali这个jar, 对每个dex依次执行以下命令即可:

    java -jar baksmali-2.5.2.jar d -o <smali输出的目录> <需要转smali的dex路径>
    
    • 1

    二、母包apk反编译

     apk反编译主要是用到Apktool 这个工具,只需简单执行一下以下命令就能将apk反编译为smali文件:

    java -jar apktool_2.6.1.jar d <母包apk的绝对路径> --only-main-classes -f -o <反编译输出目录的路径> 
    
    • 1

     其中,--only-main-classes 指定只反编译apk根目录下classes[0-9].dex,否则假如assets目录下存在dex文件也会被反编译;
    -f 强制清空输出目录
     接下来,简单聊一下反编译之后的一些常见操作

    1、删除母包模板代码

    1.1、删掉母包SDK相关的代码:

    smali/com/sswl/template
    smali/com/sswl/templatesdk

    1.2、删掉插件化加载官方SDK代码:

    smali/ssbb
    assets/sswl.so

    1.3、删掉AndroidManifest.xml中插件化相关节点(ssbb开头的)

    2、修改包名

    2.1、读取旧包名

    从反编译AndroidManifest.xmlmanifest节点读取package属性的值,这个值就是旧包名,先记录下来,暂且记为oldPackageName

    2.2、修改为新包名

    manifest节点的package属性值设置为新包名,然后用新包名字符串,全局替换掉AndroidManifest.xml文件中旧包名的字符串

    2.3、修改旧包名相关的smali文件

    1)将旧包名下的所有smali文件都复制到新包名下,同时删掉旧包名下的所有文件
    2)遍历所有的smali文件(注意多个smali文件夹的情况),将旧包名字符串替换成新包名, 包括xxx.xxx.xxxxxx/xxx/xxx两种字符串
    3)遍历所有layout文件,将自定义view中跟旧包名字符串替换成新包名,包括xxx.xxx.xxx这种字符串

    3、修改版本号

    manifest节点的versionCode属性值设置为新版本号即可

    4、修改版本名

    manifest节点的versionName属性值设置为新版本名即可

    5、设置目标SDK版本/最小SDK版本号

    查找manifest节点下的uses-sdk节点,若是有找到,把android:targetSdkVersion 的值改为新的值即可,若是没找到新增一个uses-sdk节点在manifest下即可,设置最小SDK版本号同理

    <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="11" android:versionName="1.0.11" package="com.sswl.cloud">
        <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="29"/>
    </mainfest>
    
    • 1
    • 2
    • 3

    6、修改应用名

    1)从AndroidManifest.xml application节点中读取android:label属性的值
    2)若是@string/xxx这种格式,则是引用了strings.xml中的值,需要去strings.xml中修改xxx节点的值
    3)若是硬编码,即直接就是应用名,则直接修改android:label的值即可

    7、修改启动页

    有时需要增加品牌的闪屏或者隐私政策页,则需要修改母包的启动页
    1)在AndroidManifest.xml application节点下遍历所有的activity节点
    2)判断activity节点是否包含属性为android:name="android.intent.action.MAIN"的节点
    3)若是包含,则是找到了母包启动activity,将其android:name的值记录下来,并将其包含android:name="android.intent.action.MAIN"节点的intent-filter节点删掉
    4)将新增的闪屏activity节点添加的application节点下,并在activity节点添加intent-filter声明指定是启动页

    <intent-filter>
         <action android:name="android.intent.action.MAIN" />
         <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    
    • 1
    • 2
    • 3
    • 4

    5)在新的闪屏页显示结束之后,需要设置跳转回原来的启动页

    8、修改应用icon

    1)从AndroidManifest.xml application节点读取android:icon的值
    2)若值是@drawable/xxx,则是引用了drawable目录下的图片, 只需把对应尺寸大小的新icon图片放进去替换掉旧的即可

    • drawable-ldpi: 36 x 36
    • drawable-mdpi: 48 x 48
    • drawable-hdpi: 72 x 72
    • drawable-xhdpi: 96 x 96
    • drawable-xxhdpi:144 x 144
    • drawable-xxxhdpi:192 x 192

    3)若值是@mipmap/xxx,则是mipmap目录下的图片,也是按对应尺寸替换掉旧的即可

    • mipmap-ldpi: 36 x 36
    • mipmap-mdpi: 48 x 48
    • mipmap-hdpi: 72 x 72
    • mipmap-xhdpi: 96 x 96
    • mipmap-xxhdpi:144 x 144
    • mipmap-xxxhdpi:192 x 192

    三、资源合并

    第一步处理完整之后SDK资源,合并到母包反编译后对应资源目录即可,合并处理逻辑同SDK资源合并一致,需要注意的是:

    1、dex方法数超出65535

     要是把SDK的.smali代码全部都放进去smali目录下就有可能在回编译时候,因dex方法数超出65535而报错,最简单的一种解决方案是:直接在smali目录的同级目录下新增smali_classesN目录,把SDK的.smali代码放到新增的目录,假如母包反编译的目录结果是这样子:
    在这里插入图片描述
     那么,可以新增smali_classes3目录,再把SDK的.smali代码复制过去,若是.smali代码本身有多个目录,则可以继续新增smali_classes4smali_classes5以此类推

    四、生成R.java

     假如要是我们SDK引用的第三方SDK的代码中存在R强引用的方式,比如:R.layout.xxxR.id.xxxR.String.xxx等等,那么在回编译之后就需要对合并之后的res资源重新生成R.java文件,然后把最终的R.smali复制到对应的SDK包名下,否则运行的时候会报无法找到的错误。

     当然,假如所用到的SDK代码都没用到R强引用方式,而是用到ResourcesgetIdentifier(String name, String defType, String defPackage)去动态查找的方式,那么生成R.java这一步就可以不做。

     要是实在需要重新生成R.java的话,反编译生成的res/values/public.xml需要留意一下,里面主要是记录了反编译之前母包的资源id,这个文件的存在保证了每次回编译的时候,同一个资源可以固定分配到同一个资源id,要是你删了这个文件再回编译,那么有可能截图的资源id:0x7f010000就不一定是分配给authsdk_anim_loading这个资源了,有可能是分配给你新增的资源文件了;又因为母包在打包时候,源代码中R.xxx.xxx会被替换成0x7fxxxxxx资源id的格式,那么要是一个资源id被赋给了另一个资源,运行时候就会显示错乱,甚至于会报错,所以public.xml的存在对于重新生成R.java文件也是极其关键的。
    在这里插入图片描述

    1、借助aapt来生成R.java

    aapt一般在android sdk/build-tools/30.0.3目录可找到,编译命令如下:

    aapt package -m -J <R.java存放文件夹的路径> -S <res文件夹的路径>  -I <andriod.jar的路径> -M <AndriodManifest.xml路径>
    
    • 1

    正常情况下,要是没有重复的资源,就会在指定的R.java目录下生成 包名层级的R.java文件,类似这样:
    在这里插入图片描述
    上面截图可以看到还顺便帮你把在AndroidManifest.xml自定义的权限生成了Manifest.java文件
    【注意】
    有些小伙伴在使用aapt编译时候,可以会报下面的错误:

    First type is not attr!
    
    • 1

    这个是因为你所使用的aapt版本跟你反编译母包所用到的apktool里面所使用的appt版本不一致导致的,这时候你可以把apktool里面的aapt复制出来使用即可
    在这里插入图片描述

    2、借助aapt2来生成R.java

    aapt2生成R.java文件,相对而言,会麻烦一丢丢,需要分两步来处理:

    2.1、编译

    aapt2 compile -v --dir <res目录的路径> -o <编译生成的zip压缩包路径>
    
    • 1

    生成的压缩包里面就是这样子
    在这里插入图片描述

    2.2、链接

    aapt2 link -I <android.jar的路径> --java <R.java生成文件夹路径> -o <生成res.apk的路径> --manifest <AndroidManifest.xml路径> <上一步编译生成的zip压缩包路径>
    
    • 1

    链接完成之后生成的产物跟aapt是一样的
    在这里插入图片描述
    同时比aapt多生成一个没有dex的apk
    在这里插入图片描述

    【注意】
    有些小伙伴在使用aapt2链接时候,可以会报下面的错误:

    error: attribute android:compileSdkVersion not found.
    error: attribute android:compileSdkVersionCodename not found.
    error: failed processing manifest.
    
    • 1
    • 2
    • 3

    这些提示没找到的属性,直接删掉就可以顺利执行链接了

    五、R.java转R.smali

    1、借助javac将R.java编译成R.class

    javac -encoding UTF-8 -cp D:\android\sdk\platforms\android-30\android.jar -d D:\360MoveData\Users\Administrator\Desktop\12\classes D:\360MoveData\Users\Administrator\Desktop\12\build\com\example\oaidtest2\*.java
    
    • 1

    编译之后输出的文件如下:
    在这里插入图片描述

    1)-encoding UTF-8 : 指定源文件使用的字符编码, 要是java源文件是用UTF-8编码格式写的,不设置这个选项,编译时会报错:错误: 编码GBK的不可映射字符

    2)-cp :指定编译java源文件需要依赖的.class文件的路径,可以是目录,jar文件,zip文件(里面都是class文件)

    3) -sourcepath:指定编译java源文件需要依赖的java文件的路径,可以是目录,jar文件,zip文件(里面都是java文件)

    4)-d :指定编译生成的.class文件存放的目录

    5)最后一个路径是需要编译的java文件路径,*通配符,查找该目录下所有的java文件以及依赖的java文件都会被编译成.class文件,可以空格隔开传入多个java路径

    2、借助d8将R.class文件编译成dex

    d8 --output <dex输出目录的路径> <*.class文件或容器(例如 JAR、APK 或 ZIP 文件)的任意组合>
    
    • 1

    编译完成之后,就会在指定目录下生成一个dex
    在这里插入图片描述

    3、借助baksmali将dex转smali

    java -jar baksmali-2.5.2.jar d -o <smali输出的目录> <需要转smali的dex路径>
    
    • 1

    编译完成之后,在指定目录下输出的文件如下:
    在这里插入图片描述

    4、将生成的R*.smali替换到所有目录下

     遍历母包反编译出来的所有smali目录,要是有找到某个目录下存在R.smali这些文件,就用新生成的R.smali文件替换掉旧的,同时对于在反编译过程中才放进去的第三方SDK代码资源,一般在SDK包名都是没有R.smali的,这时也需要把生成R.smali放进去,不过需要注意的是:把新的R.smali文件复制过去替换掉旧的之后,还需要把R.smali文件里面包名相关的字符串改为对应目录的字符串,比如:复制进这个目录下:smali_classes4\com\sswl\sdk,那么需要把所有R*.smali文件里面所有的com/example/oaidtest2字符串改为com/sswl/sdk

    六、回编译

    借助Apktool进行回编译即可,回编译命令如下:

    java -jar apktool_2.6.1.jar b <母包反编译输出的目录> -f -o <回编译apk的绝对路径>
    
    • 1

    七、apk签名

    由于回编译的apk是未签名的apk,故还需要进行签名操作

    1、v1签名(即jar签名)

    利用jdk的jarsigner进行签名,一般是在jdk1.8.0_131/bin目录下

    jarsigner -verbose -keystore <签名文件的绝对路径> -signedjar <已签名apk的绝对路径> <待签名的apk路径> <签名文件别名> -storepass <储存密码> -keypass <钥匙密码>
    
    • 1

    2、v2签名

    建议选用V2签名,使用apksigner进行签名,一般是在android sdk/build-tools/30.0.3目录下,apk在安装时候,校验签名效率更高,安全性也更高

     apksigner sign --ks <签名文件的绝对路径> --ks-key-alias <签名文件别名> --ks-pass pass:<储存密码> --key-pass pass:<钥匙密码> --out <已签名apk的绝对路径>  <待签名的apk路径>
    
    • 1

    八、apk对齐

    v1签名的apk是签名之后再对齐,v2签名的apk需要先对齐后签名,对齐用到zipalign.exe,一般在android sdk/build-tools/30.0.3目录

    zipalign  -v 4  <待对齐的apk文件路径>  <已对齐的apk文件保存路径>
    
    • 1
  • 相关阅读:
    springboot篮球论坛系统springboot034
    CentOS8.2安装Nginx1.23.2
    shell脚本安装apache服务
    浏览器-js,运行机制
    Windows平台下的Oracle 19c补丁升级
    网络安全--防火墙
    技术分享| 分布式系统中服务注册发现组件的原理及比较
    图书馆防盗的窍门
    晨启,MSP430开发板,51开发板,原理图,PCB图
    Rust 基础(六)
  • 原文地址:https://blog.csdn.net/qq_43278826/article/details/127545672