文章目录

  • 背景
  • 一、尝试复现
  • 二、初步想法
  • 三、继续思考
    • 3.1 源码引入
    • 3.2 是否可以经过二次混淆改名
    • 3.3 Android Transform
    • 3.4 直接修改本地aar文件
      • 3.4.1 找到要修改的本地aar
      • 3.4.2 下载jarjar.jar
      • 3.4.3 jarjar的快速使用
      • 3.4.4 aar重新打包
      • 3.4.5 jarjar的其他用途
    • 3.4 小彩蛋
  • 总结
  • 拓展阅读

背景

周末在某论坛上, 看到一个有意思的问题, 一个Android同行发帖提问: 如果两个三方库都经过了混淆, 导致凑巧包名+类名冲突了, 该如何解决。两个冲突的三方库目录如下图所示:


一、尝试复现

写了个Demo工程, 引入帖子里提到的两个库, 一个是抖音平台的, 一个是易盾的.

//gradle文件中添加:
repositories {//引入相关maven地址mavenCentral()maven { url 'https://artifact.bytedance.com/repository/AwemeOpenSDK' }
}//引入帖子里提到的两个指定版本库
implementation 'com.bytedance.ies.ugc.aweme:opensdk-common:0.1.6.2'
implementation 'io.github.yidun:quicklogin:3.2.1'

尝试运行, 错误信息如下, 可以看到是在dexBuilderDebug过程中报错的, 即dex阶段:

二、初步想法

两个三方库, 如果没有混淆, 基本上是不可能出现类名冲突的问题. 但是基于商业保密等原因, 把自己的三方库源码进行部分混淆的作法还是相对常见的, 通用的做法如下图所示, 由此导致的类名冲突问题似乎是无法避免的.

//模块中的build.gradle修改
//开启模块混淆
minifyEnabled true//模块中的proguard-rules.pro
对某些入口函数进行keep, 否则让用户调用类似于a.a();的代码就太low了

针对类名冲突的问题, 基于自己的Android开发经验, 可以比较轻松的想到几种可行方案:

  • 如果某个库可以获取到源码, 那么尽量使用源码引入;
  • 是否可以经过二次混淆重新命名;
  • Android Transform直接修改字节码;
  • 有没有其他方式可以直接修改本地aar文件.

三、继续思考

3.1 源码引入

这没什么好说的, 可以源码当然选择源码接入, 但是一般情况是拿不到源码的, 此方案排除.

3.2 是否可以经过二次混淆改名

我们知道, debug包一般都不会开混淆, 那么如果我们直接打release包, 是否可以通过二次混淆来“将错就错”的把这个冲突问题解决呢?
这就涉及到了打包流程问题, 到底是混淆在前面, 还是类名冲突检测在前面; 因此在官网上搜一下Android的打包流程, 基本上网上搜到的都是下面这个图, 没有涉及到混淆、和类名冲突检测的内容.

没有现成的资料只能自己测试, 直接打release包:

* What went wrong:
Execution failed for task ':app:checkReleaseDuplicateClasses'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.CheckDuplicatesRunnable> Duplicate class a.a.a.a.a.a found in modules jetified-opensdk-common-0.1.6.2-runtime (com.bytedance.ies.ugc.aweme:opensdk-common:0.1.6.2) and jetified-quicklogin-3.2.1-runtime (io.github.yidun:quicklogin:3.2.1)

果然报错了, 那就说明了一个问题:

  • 冲突检测是在混淆之前进行的, 又因为R8是在dex生成的过程中进行混淆的, 因此打包流程中, 在生成dex阶段, 先进行重复类检测, 再进行release混淆.

明白了这个流程, 就知道想通过打release包进行二次混淆绕过错误的方法, 是不可行的.

3.3 Android Transform

从 1.5.0-beta1 开始,Gradle插件包含了一个Transform API,允许第三方插件在将已编译的 class 类文件转换为 dex 文件之前对其进行操作。Transform 是一个链式结构,每个Transform都是一个Gradle的Task,Android 编译器通过 TaskManager 将每个Transform串联起来。
既然Transform是在生成dex之前进行的, 那么3.2中的打包流程会更精细化为:

aidl处理, 源码编译, 三方库class收集;
Transform链式串联修改字节码;
dex阶段, 进行重复类名检测, 字节码混淆;
对包进行签名.

因此, 使用Transform的方式, 在流程上是一定可行的, 但是在实际工程中的实现难度大且性能低下, 原因有以下几点:

  • 修改包名要连带所有import到的地方全部修改, 因此非常容易出错, 且只有在运行时才会崩溃, 测试难度极大;
  • 每次编译打包都需要进行Transform, 影响编译速度;

(Tips:在AGP7.0中 Transform 已经被标记为废弃了,并且将在AGP8.0中移除。改用了一种编译更快且代码更简洁的API AsmClassVisitorFactory, 感兴趣可以查看这篇文章)

3.4 直接修改本地aar文件

综上所述, 如果可以直接修改本地的aar文件, 并且从远程依赖修改为本地依赖, 就可以避免Transform带来的编译性能问题, 接下来就是如何稳定修改包名以及修改所有import的问题了, 刚好有一个工具可以帮我们解决该问题, 那就是jarjar.jar, 因此整个的修改流程如下:

3.4.1 找到要修改的本地aar

Android Studio项目中通过远程implement添加的依赖,会自动到maven库中下载相应版本的aar。那么这些文件都下载到哪里了呢?其实Android Studio中所有项目都共用同一个本地缓存库,路径是:

\Users\.gradle\caches\modules-2\files-2.1

然后通过:包名\模块名\版本号\哈希值\jar或aar文件, 即可找到本案例中我们要修改quicklogin-3.2.1.aar, 将该aar拖拽到Android Studio中, 可以看到其目录结构:

因为我们要用到的jarjar工具是对.jar文件进行修改, 因此需要先对aar进行如下解压:

//解压aar
unzip quicklogin-3.2.1.aar -d tempFolder

3.4.2 下载jarjar.jar

  • code.google.com/p/jarjar/ 直接官网下载1.4的最新版本
  • 不能架梯子的小伙伴,可以直接下载百度网盘里的备份,密码: 3t53

3.4.3 jarjar的快速使用

  • 新建一个文件夹,将jarjar-1.4.jar和需要修改包名的jar包放在该文件夹下
  • 在该文件夹下新建一个txt文本,取名rule.txt
  • 打开rule.txt,输入如下内容并保存
## 内容格式:  rule <要改变的包名称> <改变的名称>
## a.a.a.a.a下所有的类改名为a.a.a.a.changerule a.a.a.a.a.**  a.a.a.a.change.@1
  • 通过cmd执行如下命令, 便生成了经过修改之后的temp.jar:
java -jar jarjar-1.4.jar  process rule.txt  quicklogin-3.2.1.jar  temp.jar

3.4.4 aar重新打包

对于aar的重新打包, 这里多说一嘴, 网上最常见的【错误】流程是:

  • 将aar尾缀改成zip
  • 解压,修改文件
  • 再打包成zip,再改aar后缀

这种方式得出的aar在AS里依旧会被识别成zip,导致无法导入依赖. 而后续正确流程应该如下所示:

  • 对于3.4.1中解压出来的tempFolder中的旧jar, 即classes.jar删除;
  • 将3.4.3中获取到的temp.jar, 直接拷贝到tempFolder中, 并改名为classes.jar
  • 重新打包aar, 命令如下(注意空格后有一个点)
jar cvf newAAR.aar -C tempFolder/ .

这样就获得了一个最终的新的aar文件, 将该aar拖拽到Android Studio中, 其目录结构如下图所示, 可以看到包名已经修改完成, 具体的自定义修改规则, 可以通过自定义3.4.3中的rule文件来实现.

最终直接将该aar通过本地依赖的方式集成到工程中, 既可以避免Transform方案引入的编译性能问题, 又可以解决三方库类名冲突的问题.

3.4.5 jarjar的其他用途

jarjar替换包名的思路很简单,就是遍历jar包然后基于ASM修改class文件。rule文件中的规则为一行一条,除了rule,还有其他2种形式的指令:

//用来替换类名,所有用到被替换类的类都会跟着被改变
rule <pattern> <result> //用来移除指定的类,在rule之前执行
zap <pattern> //只会保留指定的Package的名称,在rule之后执行
keep <pattern>

其中,pattern是需要匹配的名称。2个星号是替换所有匹配的,1个星号是只替换当前包下的类。result是取代后的名称,可以使用@1、@2这类的符号表示要使用第几个pattern的*或**所代表的字串。

3.4 小彩蛋

在思考本文提到的二次混淆的解法中, 有个同事提到, 如果一个类名足够短, 那么就不会再被混淆了, 为了验证这个观点是否是正确的, 专门写了个demo, 先写一个简单的类, 再在代码中进行调用以保证不会在打包后被优化删除:

然后直接打release包进行混淆, 查看mapping文件, 发现还是会被混淆:

xx.xx.xx.xx.a.a():14:15 -> c

总结

  • 对于遇到三方库中的类名冲突的开发者, Transform方案会稍显笨重, 简单且有效的方案就是使用jarjar工具对aar中的jar进行修改, 并对aar进行重新打包, 并在工程中将远程依赖转为本地aar依赖;
  • 对于三方库作者, 最好定制混淆规则, 避免和其他库冲突, 给使用者带来不必要的困扰;
  • 在Android的打包流程中, 第一步是aidl处理, 源码编译, 三方库class收集; 第二步是Transform链式串联修改字节码; 第三步是dex阶段, 先进行重复类名检测, 再对字节码混淆; 第四步是对包进行签名;
  • 即便是类名、方法名再简单, 该被混淆还是会被混淆.

拓展阅读

  • Android Apk 编译打包流程

Android开发——如何解决三方库中的类名冲突问题相关推荐

  1. Android开发之引用三方库导致SO库冲突的解决办法

    //app build.gradle//IotVideo sdk包含libc++_shared.so,libmarsxlog.so,解决so的冲突如下 //下面意思是只匹配第一个就好了android{ ...

  2. 成功解决keras库中出现AttributeError: ‘str‘ object has no attribute ‘decode‘

    成功解决keras库中出现AttributeError: 'str' object has no attribute 'decode' 目录 解决问题 解决思路 解决方法 解决问题 Attribute ...

  3. Android开发 ---如何操作资源目录中的资源文件2

    Android开发 ---如何操作资源目录中的资源文件2 一.颜色资源管理 效果图: 描述: 1.改变字体的背景颜色 2.改变字体颜色 3.改变按钮颜色 4.图像颜色切换 操作描述: 点击(1)中的颜 ...

  4. 解决maven库中没有Oracle jdbc驱动的问题Cannot resolve com.oracle:ojdbc14:10.2.0.1.0

    解决maven库中没有Oracle jdbc驱动的问题Cannot resolve com.oracle:ojdbc14:10.2.0.1.0 在IDEA Maven项目中添加oracle数据库驱动总 ...

  5. android打包aar包含三方库和三方aar

    前段时间做安卓SDK开发,其中我们的SDK里集成了支付宝的人脸认证SDK,现在说一说怎样解决将三方SDK和三方引入库一起打包进SDK. 1. 修改app下的bulid gradle文件: 把apply ...

  6. 【Android 开发入门】我认识中的Android

    2014年12月从csdn专家福利获得的一本书<Android游戏开发技术实战详解>,尘封了一年多的时间,今天才翻开来看. 我认识中的Android,提到Android最先浮现在我脑海中的 ...

  7. Android开发模式万佛朝中MVX(MVC、MVP、MVVM)

    今天公司的测试服务器开小差了,后台被吐槽的体无完肤,虽然我们都知道跟他没有什么关系,但是生活需要乐趣,总要有人背锅,哈哈!~~~暂时没有环境开发了,那就分享一下之前做的一篇关于Android开发模式的 ...

  8. android开发打开第三方库,Android开发NDK调用三方so库

    概要 在日常开发中,android NDK的作用无外乎有两种:一种是通过调用底层C/C++的算法,提高app的运行效率:另一种则是通过C/C++的特性,或者和驱动交互等,实现一些功能性的需求.接下来将 ...

  9. Android开发实现QQ三方登录 标签: android开发qq三方登录

    本文分为两个部分:一是QQ的授权部分:二是获取用户的基本信息部分 一.授权部分 1.首先,先去腾讯开放平台获取APP ID和APP KEY(未注册腾讯开发者账号的可能需要先注册账号),获取的过程还是还 ...

最新文章

  1. 用Eclipse的snippets功能实现代码重用
  2. React router 4 获取路由参数,跨页面参数
  3. 优化我们的业务之Timecard
  4. C++ Time类重载运算符
  5. 019_Vue子组件向父组件传值
  6. 找到两个字符串的公共字符,并按照其中一个的排序
  7. 判断字典中指定key是否存在
  8. Android加密通信防抓包,[原创]基于Taintdroid思想的android ssl\tsl保密通信抓包研究(未成功,分享一下思路)...
  9. WinPcap笔记(9):保存数据包到堆文件
  10. 服务器摆放需要预留U位么_卧室系列 | 选床+摆放新姿势,提升睡眠品质小技巧...
  11. php实现简单的框架,PHP 实现简单的 MVC 框架
  12. 怎样看出一个人有数学天赋?
  13. PhantomJS其他语言调用
  14. html设置图片高度宽度自适应屏幕,css让图片自适应屏幕大小的方法
  15. C++ Primer 第5版 练习5.14
  16. cobar mysql 性能_Cobar
  17. mac录屏如何把声音录进去?
  18. 钉钉作弊软件开发者,被判 5 年半,为什么提供「虚拟定位」会被判这么久?...
  19. 【内存泄露】LeakCanary常见问题
  20. 技术管理者的管理框架

热门文章

  1. #内存泄露# #valgrind# valgrind简介
  2. 一点点linux系统的学习心得
  3. 告别 Google 网站站长,迎接 Google 搜索中心
  4. 痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU特性那些事(2)- RT1052DVL6性能实测(CoreMark)...
  5. Neural-Motifs 运行环境配置
  6. @Before和@After的区别
  7. 80老翁谈人生(203):748工程支撑王选最终走向成功!
  8. Amazing grace 奇异恩典
  9. SSID、BSSID、ESSID
  10. 中医四大经典著作之一《神农本草经》