Android开发——如何解决三方库中的类名冲突问题
文章目录
- 背景
- 一、尝试复现
- 二、初步想法
- 三、继续思考
- 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开发——如何解决三方库中的类名冲突问题相关推荐
- Android开发之引用三方库导致SO库冲突的解决办法
//app build.gradle//IotVideo sdk包含libc++_shared.so,libmarsxlog.so,解决so的冲突如下 //下面意思是只匹配第一个就好了android{ ...
- 成功解决keras库中出现AttributeError: ‘str‘ object has no attribute ‘decode‘
成功解决keras库中出现AttributeError: 'str' object has no attribute 'decode' 目录 解决问题 解决思路 解决方法 解决问题 Attribute ...
- Android开发 ---如何操作资源目录中的资源文件2
Android开发 ---如何操作资源目录中的资源文件2 一.颜色资源管理 效果图: 描述: 1.改变字体的背景颜色 2.改变字体颜色 3.改变按钮颜色 4.图像颜色切换 操作描述: 点击(1)中的颜 ...
- 解决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数据库驱动总 ...
- android打包aar包含三方库和三方aar
前段时间做安卓SDK开发,其中我们的SDK里集成了支付宝的人脸认证SDK,现在说一说怎样解决将三方SDK和三方引入库一起打包进SDK. 1. 修改app下的bulid gradle文件: 把apply ...
- 【Android 开发入门】我认识中的Android
2014年12月从csdn专家福利获得的一本书<Android游戏开发技术实战详解>,尘封了一年多的时间,今天才翻开来看. 我认识中的Android,提到Android最先浮现在我脑海中的 ...
- Android开发模式万佛朝中MVX(MVC、MVP、MVVM)
今天公司的测试服务器开小差了,后台被吐槽的体无完肤,虽然我们都知道跟他没有什么关系,但是生活需要乐趣,总要有人背锅,哈哈!~~~暂时没有环境开发了,那就分享一下之前做的一篇关于Android开发模式的 ...
- android开发打开第三方库,Android开发NDK调用三方so库
概要 在日常开发中,android NDK的作用无外乎有两种:一种是通过调用底层C/C++的算法,提高app的运行效率:另一种则是通过C/C++的特性,或者和驱动交互等,实现一些功能性的需求.接下来将 ...
- Android开发实现QQ三方登录 标签: android开发qq三方登录
本文分为两个部分:一是QQ的授权部分:二是获取用户的基本信息部分 一.授权部分 1.首先,先去腾讯开放平台获取APP ID和APP KEY(未注册腾讯开发者账号的可能需要先注册账号),获取的过程还是还 ...
最新文章
- 用Eclipse的snippets功能实现代码重用
- React router 4 获取路由参数,跨页面参数
- 优化我们的业务之Timecard
- C++ Time类重载运算符
- 019_Vue子组件向父组件传值
- 找到两个字符串的公共字符,并按照其中一个的排序
- 判断字典中指定key是否存在
- Android加密通信防抓包,[原创]基于Taintdroid思想的android ssl\tsl保密通信抓包研究(未成功,分享一下思路)...
- WinPcap笔记(9):保存数据包到堆文件
- 服务器摆放需要预留U位么_卧室系列 | 选床+摆放新姿势,提升睡眠品质小技巧...
- php实现简单的框架,PHP 实现简单的 MVC 框架
- 怎样看出一个人有数学天赋?
- PhantomJS其他语言调用
- html设置图片高度宽度自适应屏幕,css让图片自适应屏幕大小的方法
- C++ Primer 第5版 练习5.14
- cobar mysql 性能_Cobar
- mac录屏如何把声音录进去?
- 钉钉作弊软件开发者,被判 5 年半,为什么提供「虚拟定位」会被判这么久?...
- 【内存泄露】LeakCanary常见问题
- 技术管理者的管理框架
热门文章
- #内存泄露# #valgrind# valgrind简介
- 一点点linux系统的学习心得
- 告别 Google 网站站长,迎接 Google 搜索中心
- 痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU特性那些事(2)- RT1052DVL6性能实测(CoreMark)...
- Neural-Motifs 运行环境配置
- @Before和@After的区别
- 80老翁谈人生(203):748工程支撑王选最终走向成功!
- Amazing grace 奇异恩典
- SSID、BSSID、ESSID
- 中医四大经典著作之一《神农本草经》