转载于: [有关 Android Studio 重复引入包的问题和解决方案 | YiFeng’s Zone http://yifeng.studio/2017/05/20/android-studio-duplicate-introduce-jar/](http://yifeng.studio/2017/05/20/android-studio-duplicate-introduce-jar/)

有关 Android Studio 重复引入包的问题和解决方案

随着产品功能需求的增加,我们开发的安卓项目不得不入引入越来越多的第三方库。这些三方库可能以 Jar 包的形式放置在 libs 目录下,可能以 Gradle 远程依赖的形式下载引入,也可能是以 Library Module 的形式放置在工程目录下,等等。

随之而来的问题是,复杂的依赖关系很可能导致重复引入包的问题。比较常见的使用场景就是 support-v4 包的重复引入。这样就会导致,执行 Run 操作打包生成 Apk 文件时出现类似这样的 DexException 错误提示,导致编译失败:

                                                                              1                                                                                                                   2                                     
                                                                              Error:Execution failed                                          for task                                          ':app:transformClassesWithDexForDebug'.                                                                                                                   > com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: java.util.concurrent.ExecutionException: com.android.dex.DexException: Multiple dex files define Landroid/support/v4/app/ActivityCompatHoneycomb;                                     

有多种使用场景会出现这种问题。根据解决方案的不同,大体上可以分为两种:本地 Jar 包重复嵌入和 Gradle 远程重复依赖。

第一种,比较好理解。比如 app module 与 library module 各自 libs 目录中嵌入了相同的 Jar 包。这种情况也比较好解决,只需要将 app module 下的重复 jar 包删除即可。

第二种,稍微复杂一点。比如对于 Gradle 远程依赖的两个第三方库,他们内部同时依赖相同的另一个辅助第三方库。这个时候我们就没办法像第一种情况那样手动删除本地文件。好在 Gradle 插件提供了相应的解决方案,即使用 exclude 语法,如:

                                                                              1                                                                                                                   2                                                                                                                   3                                                                                                                   4                                     
                                                                              compile 'com.yifeng.example:example-1:1.0'                                                                                                                   compile ('com.yifeng.example:example-2:1.0') {                                                                                                                       exclude group: 'com.android.support', module: 'support-v4'                                                                                                                   }                                     

如例子中所示,远程依赖的第三方库 example-1 与 example-2 内部同时引入 support-v4 包,那么只需要在其中一个的引入地方添加 exclude 语句,根据 group 和 module 过滤规则,将相同引入的 v4 包剔除在外即可。

当需要在一个依赖中去除多个递归依赖项时,可以使用多条 exclude 语句,比如:

                                                                              1                                                                                                                   2                                                                                                                   3                                                                                                                   4                                     
                                                                              compile ('com.wdullaer:materialdatetimepicker:3.2.2') {                                                                                                                       exclude group: 'com.android.support', module: 'support-v4'                                                                                                                       exclude group: 'com.android.support', module: 'design'                                                                                                                   }                                     

还有一种更简单的写法,使用 Groove 语言的循环语句,模板如下(同样适用于 module 规则):

                                                                              1                                                                                                                   2                                                                                                                   3                                     
                                                                              compile() { dep ->                                                                                                                       [group1, group2].each{ group -> dep.exclude group: group }                                                                                                                   }                                     

对于上面的例子,便可以改造成:

                                                                              1                                                                                                                   2                                                                                                                   3                                     
                                                                              compile() { dep ->                                                                                                                       ['support-v4', 'support-v13', 'design'].each{ module -> dep.exclude module: module }                                                                                                                   }                                     

更多细节,参考:ModuleDependency)。

以上两种场景算是比较好处理的。还有一种特殊情况,就是不同第三方库内部出现相同包名相同文件名的 java 类。这种情况出现的概率很低,但是不幸的是我在工作中就遇见过。

当时项目中引入的 友盟统计 和 移动统一认证 的 SDK 出现重复引入问题,执行 Run 操作编译打包时出现 duplicate entry 错误,如图:

通过错误提示,很容易就找到错误出处,我们看下两个 SDK 中的 jar 包源码:

虽然相同包名相同类名的文件在不同 SDK 中出现的概率极低,但是一旦出现,处理起来就比较棘手。最好的解决方案就是联系提供 SDK 的技术人员反映问题,让其通过修改源码重新打包一个新的 Jar 包。

还有一个解决办法就是,重新命名 Jar 包里的包名或者文件名。网上也有一个工具:jarjar.jar,可以帮助我们重命名包名和文件名,以及 Jar 包中的相关代码引用路径。参考地址如下:

  • GitHub 源码:https://github.com/shevek/jarjar

  • Jar 文件下载:https://code.google.com/archive/p/jarjar/downloads

该工具提供有多种使用方式,最简单实用的就是通过命令行使用。举个例子,打开命令行工具,执行:

                                                                              1                                     
                                                                              java -jar jarjar-1.4.jar process rule.txt example.jar example_output.jar                                     

其中,rule.txt 是包含重命名规则的文件,内容如下:

                                                                              1                                     
                                                                              rule p.rn.asm.** [email protected]                                         1                                     

我们看一下重命名前后 Jar 包内容的代码对比图:


可以看到,不仅包名改变,Jar 包中的相关类引用路径也自动改变。这种 Jar 包重命名的方式虽然能解决重复引入包的问题,但不是长久之计,需要后续持续关注 SDK 的升级替换。

不同 Jar 包包含相同文件(路径也相同)的情况还有一种,就是 duplicate files 错误。多个 Jar 包包含重复的文件。这种情况在网上看到过,出自 StackOverFlow,错误提示类似:

                                                                              1                                                                                                                   2                                                                                                                   3                                                                                                                   4                                                                                                                   5                                                                                                                   6                                                                                                                   7                                                                                                                   8                                                                                                                   9                                                                                                                   10                                     
                                                                              Error:duplicate files during packaging of APK E:\Code\iDoc\app\build\outputs\apk\app-debug-unaligned.apk                                                                                                                       Path in archive: META-INF/license.txt                                                                                                                       Origin                                          1: E:\Code\iDoc\app\libs\spring-core-                                         3.1.0.RELEASE.jar                                                                                                                       Origin                                          2: E:\Code\iDoc\app\libs\spring-web-                                         3.1.0.RELEASE.jar                                                                                                                   You can ignore those files in your build.gradle:                                                                                                                       android {                                                                                                                         packagingOptions {                                                                                                                           exclude                                          'META-INF/license.txt'                                                                                                                         }                                                                                                                       }                                     

可以看到,解决方案已经在错误提示中有给出,使用 packagingOptions 配置的 exclude 语句删除重复文件即可,比如:

                                                                              1                                                                                                                   2                                                                                                                   3                                                                                                                   4                                                                                                                   5                                                                                                                   6                                                                                                                   7                                     
                                                                              packagingOptions {                                                                                                                           exclude 'META-INF/DEPENDENCIES'                                                                                                                           exclude 'META-INF/NOTICE'                                                                                                                           exclude 'META-INF/LICENSE'                                                                                                                           exclude 'META-INF/LICENSE.txt'                                                                                                                           exclude 'META-INF/NOTICE.txt'                                                                                                                       }                                     

最后再补充一点,关于 Gradle 依赖的 Scope 问题。通常依赖某个第三方库我们使用的都是 compile 关键字,实际上,还有一个 Provided 关键字偶尔也会用到。

打开 Project Structure,查看 Modules 的 Dependencies 内容时,可以看到每一个依赖项的右边有个 Scope 选项:

compile 表示依赖的第三方库在工程编译阶段、测试阶段和 Apk 运行阶段都需要使用到;而 provided 表示该第三方库仅仅是在编译阶段和测试阶段使用到,不会出现在运行阶段,即表示不会打包到最终的 apk 文件中去,目标运行环境已经包含有该第三方库。(貌似 Dagger2 的使用可能会涉及到这个问题。)

关于 Scope 的详细介绍,可以参考文章:PROVIDED SCOPE IN GRADLE。

相关拓展阅读:

  • ResolutionStrategy
  • Dependency Management
  • Gradle依赖项学习总结,dependencies、transitive、force、exclude的使用与依赖冲突解决

更多相关文章

  1. android中的数据存取-方式二:file(文件)
  2. Sqlite在Android上的一个Bug - 临时文件写策略
  3. 从源代码编译Android(CyanogenMod For Hero GSM)【转】
  4. Android多国语言文件夹
  5. android 随手记 遍历文件夹下的文件有多少
  6. android 自定义 radiobutton 文字颜色随选中状态而改变
  7. 关于android studio 出现Error:Execution failed for task ':app
  8. Android文件操作的模式
  9. Logger详解(二)

随机推荐

  1. Android在屏幕任意位置显示对话框
  2. android悬浮窗--获取内存
  3. android中限制EditText中输入指定字符
  4. Android之三种实现自定义ProgressBar的方
  5. The Android ION memory allocator
  6. 如何去掉Android kitkat中的红框
  7. 编译NotificationManagerService.java文
  8. 【Android Studio开发】Android Studio如
  9. 2011.09.28——— android 视频缩略图之
  10. Android架构组件(1)Lifecycles体系