Android Studio 中打 jar 包和 aar 包姿势总结

好久没有写博客了,最近部门低延迟会议项目提测了新版本,这周六可以来公司看看书,写写博客。
前阵子的工作中,有件事情还是耗费了我很长的时间的,就是重新打包对外输出的 SDK,之前只是用过了解过 aar 和 jar ,但并不明白他们之间的关,问题列举:

  • 打的 jar 包中没有包含 libs 目录下引用的 jar 导致目标项目无法直接使用
  • 打的 aar 包引用了 maven 库,目标项目无法直接使用

通过这篇博客,记录如何解决如下几个问题:

  • 在 Android Studio 中如何打 jar 包、使用 jar 包
  • 如何把 libs 目录下依赖的 jar 打进总的 jar 中
  • 如何修改 jar 包中的类
  • 如何在Android Studio 中打 aar 包、使用 aar 包
  • 解决 aar 中依赖的 maven 库在目标工程中无法使用的问题

Android Studio 中打 jar 包

总的来说,有两种方法,这两种方法的前提都是这个 module 的 gradle 文件使用 apply plugin: ‘com.android.library’

直接编译实现

直接 Rebuild Project ,然后在以下目录中可以找到 Jar 包:

图中红色框框中的 jar 包就是 media_upload 这个 moudle 的 jar 包。直接复制到其他 moudle 的 libs 文件夹下,然后进行相应的配置就可以使用。

通过 gradle 脚本实现

首先将下面的 gradle 脚本添加到 media_upload moudle 的 gradle 文件中

1
2
3
4
5
6
task makeJar(type: Jar) {
archiveName = 'upload-sdk.jar'
from('build/intermediates/classes/debug/')
destinationDir = file('build/lib')
}
  • makeJar: Task 的名字
  • type: Jar:告诉 gradle 新建的 task对象从 Jar 基类派生。
  • archiveName:生成 jar 包的名字
  • from(‘build/intermediates/classes/debug/‘):将此路径下所有 class 文件都打包到 jar 包内(这里是 debug 版本)
  • destinationDir:生成 jar 包的目录

然后到 gradle 窗口中,找到 media_upload moudle,然后在它下面的目录找到我们定义名字为 makeJar 的 task:

双击这个 task ,看到以下界面代表 task 执行成功:

这样,生成的 jar 包的名字就是 upload-sdk ,存放于 build/lib 路径处,如下图:

这种打包方法虽然比较麻烦,有一个优点:假设 media_upload 这个 module 还依赖于其他的 jar 包(位于 libs 目录下),那么使用这种方法可以将 media_upload 依赖的其他 jar 包也一并打入总的 upload-sdk.jar 中。

将 libs 下的 jar 打到总的 jar 包下

从上图中可以看到, media_upload 这个 moudle 是依赖于 httpclient 和 okhttputils 这两个 jar 包的,如果直接使用上面的第一种方法,直接编译,这两个 jar 是不会被打进总的 jar 中的;而使用第二种 gradle 脚本的方式,就可以实现,但是 gradle 的脚本需要改变一下:

1
2
3
4
5
6
7
8
9
task makeJar(type: Jar) {
archiveName = 'upload-sdk.jar'
from('build/intermediates/classes/debug/')
from (project.zipTree('libs/okhttputils-2_6_2.jar'))
from (project.zipTree('libs/httpclient-4.2.3.jar'))
destinationDir = file('build/lib')
}

多添加的两行也很好理解:

  • from (project.zipTree(‘libs/okhttputils-2_6_2.jar’)):将 libs/okhttputils-2_6_2.jar 给打包进 upload-sdk.jar 中。
  • from (project.zipTree(‘libs/httpclient-4.2.3.jar’)):将 libs/httpclient-4.2.3.jar 给打包进 upload-sdk.jar 中。

这样最后生成的 upload-sdk.jar 中就已经包含了 httpclient 和 okhttputils 这两个 jar 包了。

在主 module 中使用依赖 jar

至于使用 jar 包,先把 jar 包放置于主 moudle 的 libs 文件夹下,然后再主 module 的 gradle 文件的 dependencies 下添加:

1
2
compile fileTree(dir: 'libs', include: ['*.jar'])

也就是引用 libs 文件夹下所有以 jar 结尾的包。

或者指定引用某一个:

1
2
compile files('libs/xxxx.jar')

在平时编写 SDK 时,一般会存在一个或多个 library 的 moudle,然后会有一个主 moduel 用于针对 library 编写 demo 测试,这时主 moudle 是需要依赖 libray module 的,在主 module 的 gradle 文件中编写如下:

1
2
3
4
compile project(':aaaaa')
compile project(':bbbbb')
compile project(':ccccc')

其中 aaaaa bbbbb ccccc 分别为依赖的 moudle 的名字。不能忘了在 setting.gradle 中添加各个 moudle:

1
2
3
4
include ':aaaaa'
include ':aaaaa'
include ':aaaaa'

这样各个 moudle 才会被编译,Android Studio 中会自动添加。

替换 Jar 包中的类

这个事情的场景是这样:我们上传的 SDK 在计算分段文件的 MD5 值时,计算失败的情况当时由于编码原因,并没有相关的回调到上层,这时候使用我们 SDK 的同事需要联系我们,然后让我们修改代码,重新打 Jar 包,在给他们,然后他们那边再更新验证。

其实添加个回调到上层很简单,但是如果 SDK 的开发部门和使用部门不是同一个,还是比较麻烦,为了避免以后我自己遇到这样的情况,我在想,能不能我自己修改 Jar 包中的类

修改第三方库中类的方法

  • 下载并引入所有第三方类中的源码,然后对应修改。
  • 使用 ASM 等工具直接修改对应类的字节码。

第一种方法的优点是不容易出错,debug 时方便,但是比较麻烦,如果碰上 okhttp 这种用 Maven 组织的项目,对于习惯 Gradle 组织项目的 Android 工程师来说,确实是一个很头疼的事情。

第二种方法的优点是非入侵,但是由于修改了字节码文件,无法和源码对应,同事会导致 debug 时出错几率增大 。

Duplicate Class

相信很多人在打包 Apk 的编译期(Build Time)遇到过「Duplicate Class」的错误。原因是 Dex 构建工具在把所有类打包进同一个 Dex 的时候,如果发现如果有重复的类要打包进 Dex 的话,就会报错。

而在编辑期(Edit Time)的时候,如果我们的所有源码中有重复类的话,IDE 也会提示错误。但是如果我们的源码里有一个和第三方 Jar 包里重复的类,IDE 是不会提示错误的。为什么呢?

因为重复类并不在同一个 Archive 里。Archive 可以看作类的容器,可以是一个 Jar 包,也可以是一个 Dex。当然你某个 Module 中的所有源码,也可以想像成是一个 Archive,所以源码有重复类是会提示错误的。

总结一下,在源码中的类和 Jar 包里的类有重复的情况下,在编辑期是不会提示错误的。只有当到了编译期,构建工具将你源码编译出来的类,和第三方 Jar 里的重复的类,混合到同一个 Archive 里的时候才会报错。

修改 Jar 中类的方法

既然,IDE 允许源码中有和 Jar
里的类重复的类。那么,对于开头提到的问题,我们可以提出一种新的解决方案:

只把 Jar 里想要修改的类的对应源码拷贝到项目中修改,然后在最终编译的时候把 Jar 包里这些类给删除掉。保证在打包进 Dex 的时候只有我们拷贝的源码编译出来的类即可。

这里引入一个库:JarFilterPlugin,它的作用在它主页上写的很明确: Before building the jars into android dex archives, filter files inside them.

使用方法:

  • Intergate this gralde plugin:
1
2
3
4
5
6
7
8
9
buildscript {
repositories {
maven { url "https://jitpack.io" }
}
dependencies {
classpath "com.github.nekocode:JarFilterPlugin:${lastest-verion}"
}
}
  • Apply and configure the plugin:

假设我们想要修改 Android Support V7 包里的 AppCompatActivity 类。我们只需要把 AppCompatActivity 类的源码拷贝到我们的项目中,然后在 App Module 的 budil.gradle 下添加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apply plugin: 'jar-filter'
jarFilters {
"com.android.support:appcompat-v7:(.*)" {
excludes = [
'android/support/v7/app/AppCompatActivity.class',
'android/support/v7/app/AppCompatActivity\\$(.*).class'
]
}
// Local jar
"android.local.jars:xxx.jar:(.*)" {
includes = [
'xxx'
]
}
}

Android Studio 中打 aar 包

aar 中依赖的 maven 库在目标工程中无法直接使用的问题

部门经常对外输出 SDK ,Android 端是以 aar 或者 jar 的形式对外输出,并配套相关 demo ,jar 相关的上面已经记录了,这里记录一下 aar 相关。

假设我有一个 library 引用了 okhttp 的 maven 库,那么如果我在 demo 里使用 build project 从 outputs/aar 路径下拿出来的 aar,那么就会报错,找不到 okhttp 相关的类。 也就是说,直接 build project 这种方式输出的 aar ,在目标项目中直接引用,是没办法使用到 aar 当中的依赖的,当然,在目标项目中将 arr 的依赖再重新依赖一遍,是可以的使用的,但是这种操作确实不太优雅,和我们模块独立的想法也有冲突,那么就要有一种方法解决这个问题。

解决方案1

将 aar 发布到本地或者发布到公司的私服上,私服上会自动让 aar 继续依赖其他的第三方库,这样在目标项目中就可以在 build.gradle 中以依赖的形式来集成aar,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
ext {
PUBLISH_GROUP_ID = 'com.xxxx.yyyy'
PUBLISH_ARTIFACT_ID = 'zzzz-sdk'
PUBLISH_VERSION = '0.0.7'
}
def getRepositoryUsername() {
return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" //账号 放置于 local.properties 不同步至 maven
}
def getRepositoryPassword() {
return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : ""//密码 放置于 local.properties 不同步至 maven
}
uploadArchives {
repositories.mavenDeployer {
// name = 'mavenCentralReleaseDeployer'
repository(url: 'http://maven.nnnn.com/content/repositories/releases/') {//公司私服
// repository(url:'file:///E:/Library/nexus-2.12.0-01-bundle/sonatype-work/nexus/storage/releases'){ //本地仓库
authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
}
pom.project {
groupId project.PUBLISH_GROUP_ID
artifactId project.PUBLISH_ARTIFACT_ID
version project.PUBLISH_VERSION
}
pom.packaging = "aar"
}
}

然后在目标项目的 root build.gradle 中引用本地仓库地址或者公司私服地址:

1
2
3
4
5
6
7
8
9
10
allprojects {
repositories {
jcenter()
maven {
url 'http://maven.nnnn.com/content/repositories/releases'
}
}
}

在目标项目的 build.gradle 中依赖:

1
2
3
4
5
dependencies {
compile("com.xxxx.yyyy:zzzz-sdk:0.0.7")
}

解决方案2

  1. 我们上传 SDK 依赖的 okhttp3 和 gson ,那么首先去下载这两个库对应版本的 jar,放置于 libs 目录下

okhttp 内部有使用 okio ,所以此处也需要引入进来。

  1. 在 liarbay 的 build.gradle 中添加 jar 的依赖

3.此时的 xxxxx-upload-sdk-v0.1.0.aar 结构如下:

已经将 gson、okhttp、okio、classes 都包含进来了。

  1. build project 从 outputs/aar 路径中拿到 aar 放置于目标工程的 libs 目录下并配置使用
1
2
3
4
5
6
7
8
9
10
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
compile(name: 'xxxxx-upload-sdk-v0.1.0', ext:'aar')
}
共82.3k字
0%
.gt-container a{border-bottom: none;}