组件化集成

上半年的技术需求,是做白牌组件集成,就是将白牌独立出来的组件,集成到合作项目中.

看起来很简单,就像我们使用okhttp,只要导入一句**compile 'com.squareup.okhttp3:okhttp:3.4.1'**即可.

但是事与愿违.过程充满了艰辛,下面记录一下这次集成中的总结.

项目背景

首先明确一下项目的背景.

在合作项目中,小说使用replugin形式集成,表现为一个插件apk.我们称之为壳app,这个app是一个壳,他的实际入口为壳app中依赖的读书aar.

我们所要做的,就是在这个aar中集成白牌组件.即插件apk-->读书aar-->白牌组件.

以下所有均围绕这个过程准备.在此之前我们需要做一个依赖统一管理,如果不做这个,那么各个版本依赖冲突起来,简直解决到怀疑人生.

依赖统一

在项目根目录新建config.gradle文件,配置如下

ext {
    android = [
            minSdkVersion    : 15,
            targetSdkVersion : 25,
            compileSdkVersion: 25,
            buildToolsVersion: "25.0.3"
    ]

    dependencies = [
            "support-v4"          : 'com.android.support:support-v4:25.3.1',
            "appcompat-v7"        : 'com.android.support:appcompat-v7:25.3.1',
            "okhttp3"             : 'com.squareup.okhttp3:okhttp:3.4.1',
            "xlog"                : 'com.xxxx.cooperate.xlog:xlog-common:1.0.0',
            "mmkv"                : 'com.tencent:mmkv:1.0.10',
            "android-gif-drawable": 'pl.droidsonroids.gif:android-gif-drawable:1.2.6'
    ]
}

然后在项目根目录的build.gradle中引入,

apply from: "config.gradle"

就可以在任意子moudle中使用了

 compileSdkVersion       rootProject.ext.android.compileSdkVersion
 buildToolsVersion       rootProject.ext.android.buildToolsVersion
 
 compile rootProject.ext.dependencies["appcompat-v7"]

回到我们具体项目里面,白牌组件目前需要接入的有两个,一个是common模块,一个是core模块.这两个模块也有依赖关系,表现为common依赖core.core模块依赖三方模块.

那么具体到项目表现为

SDK	    --->commom,core,三方
common  --->core,三方
core    --->三方

针对上面复杂的依赖关系,总结了四种依赖集成方式.

使用源码依赖

对项目而言,我们集成三方依赖是为了使用它的代码,完成某些任务,那么最简单的集成方式,就是将源码拷贝过来,进行项目集成.

我们可以做如下方式集成:

1.将common和core模块下载到本地,并拷贝到三方壳项目中

2.在setting.gradle中引入项目,':CooperateCommonModule',':CooperateCoreModule'

3.调整项目依赖.

core中如下:

dependencies {
    compile fileTree(include: ['*.so', '*.jar'], dir: 'libs')
    compile rootProject.ext.dependencies["appcompat-v7"]
    compile rootProject.ext.dependencies["okhttp3"]
    compile rootProject.ext.dependencies["xlog"]
    compile rootProject.ext.dependencies["mmkv"]
    compile rootProject.ext.dependencies["android-gif-drawable"]
}

common中如下:

dependencies {
    compile  fileTree(dir: 'libs/main', include: ['*.jar'])
    compile  fileTree(include: ['*.jar'], dir: 'libs')
    compile  project( ':CooperateCoreModule')
}

SDK中如下:

dependencies {
    FileTree tree = fileTree(dir: 'libs', includes: ['*.so', '*.jar'])
    compile tree
    provided fileTree(include: ['*.jar'], dir: 'providedlib')
    compile  project( ':CooperateCommonModule')
}

这样就可以正常使用白牌组件了.当然这中间还有很多冲突问题需要解决.我会写在最后的备注中.

使用aar集成

有了上一步源码集成成功的前提,我们下一步改造为aar集成.aar集成有三种方式,我们使用第一种,单aar集成.

因为我们的项目是层层依赖的,那我们先从最底层的core模块处理.

对core模块进行assembleRelease命令,会在build/output文件夹获得一个aar文件.

这种build方式打出的aar,只包含模块本身的代码,不包含三方依赖.

打开内网maven,登陆后使用upload功能,上传此aar.然后配置如下

接着我们就可以使用compile ''com.xxxx.cooperate.core:core-thirdpart:1.0.2"使用此aar了.

我们提一个知识点,就是maven仓库中的某aar的pom.xml文件.这个文件记录了aar的描述,声明,依赖等.

观察这个aar的pom.xml文件,可以看到

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>comxxxx.cooperate.core</groupId>
    <artifactId>core-thirdpart</artifactId>
    <version>1.0.2</version>
    <packaging>aar</packaging>
</project>

只有aar自身的描述,没有包含的三方依赖声明.这就和前面的只包含模块本身的代码,不包含三方依赖一致.

同理,打出common模块的aar,遵循上面的描述,我们需要配置下core模块的引用,及core模块的三方引用.

dependencies {
    compile  fileTree(dir: 'libs/main', include: ['*.jar'])
    compile  fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.xxxx.cooperate.core:core-thirdpart:1.0.2'
    compile rootProject.ext.dependencies["appcompat-v7"]
    compile rootProject.ext.dependencies["okhttp3"]
    compile rootProject.ext.dependencies["xlog"]
    compile rootProject.ext.dependencies["mmkv"]
    compile rootProject.ext.dependencies["android-gif-drawable"]
}

上传并得到使用方式compile 'com.xxxx.cooperate.common:common-thirdpart:1.0.2'

同理在SDK中使用common,core的远程依赖.

dependencies {
    FileTree tree = fileTree(dir: 'libs', includes: ['*.so', '*.jar'])
    compile tree
    provided fileTree(include: ['*.jar'], dir: 'providedlib')
    compile 'com.xxxx.cooperate.common:common-thirdpart:1.0.2'
    compile 'com.xxxx.cooperate.core:core-thirdpart:1.0.2'
    compile rootProject.ext.dependencies["appcompat-v7"]
    compile rootProject.ext.dependencies["okhttp3"]
    compile rootProject.ext.dependencies["xlog"]
    compile rootProject.ext.dependencies["mmkv"]
    compile rootProject.ext.dependencies["android-gif-drawable"]
}

这样就可以正常使用白牌组件了.

使用fat-aar集成

从上面的集成方式可以看到这么搞起来好像很复杂啊,有没有简单一点的方式呢.

我们可以使用fat-aar技术,官网这么解释的.

Gradle script that allows you to merge and embed dependencies in generated aar file.

它是一个gradle脚本,会把各个模块合并到一起,打成一个胖aar.这个aar里面包含了各个模块的所有东西,java文件,res文件等.

实话讲,这种方式我研究了一阵子,因为涉及到脚本的修改以及各种版本库的冲突,我放弃了这种方案.此处提供一些资料,供有兴趣的同学研究一下.

fat-aar实践及原理分享

android-fat-aar github

这项技术对比下面的maven远程依赖技术有优点与缺点.

优点:如果使用方无法访问我们的maven库,那可以把所有模块资源打成fat-aar,提供一个aar供依赖方使用.

缺点:这个库已经不维护了,需要使用特定版本的gradle编译,并需要修改脚本,适应自己的项目.

使用maven远程依赖集成

探索到了现在我一直在思考一个问题,github上有很多组件供大家使用,他们是怎么完成这个依赖的问题呢?

比如我们使用的okhttp,它依赖okio,但是我们使用的时候,只需要compile okhttp就行了.

以前也在网上搜过相应的文章,但是只是简单的提到,需要把三方库变成maven远程依赖.对于我们项目,方式二已经变成了远程依赖,但是为什么做不到呢?

其实,很简单,和库的pom.xml有关.我们方式二打包出来的pom.xml,是在网页生成的,并没有这个类的远程依赖描述.我们观察下别人家的库pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.tencent.news</groupId>
    <artifactId>xxxx-lib</artifactId>
    <version>2.7.20</version>
    <packaging>aar</packaging>
    <dependencies>
        <dependency>
            <groupId>com.android.support</groupId>
            <artifactId>appcompat-v7</artifactId>
            <version>25.2.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.tencent.news</groupId>
            <artifactId>xxxx</artifactId>
            <version>2.7.20</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.tencent.news</groupId>
            <artifactId>xxxx</artifactId>
            <version>2.7.20</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>	

这里面有一个节点dependencies描述了当前库的远程依赖.如何生成这个pom.xml文件就和我们的打包方式有关了.

方式二中我们使用assembleRelease方式打包,这种方式打出来的包,只是简单的压缩了下代码.我们需要引入maven打包方式.

1.在core模块下新建upload.gradle文件

// 指定编码
tasks.withType(JavaCompile) {
    options.encoding = "UTF-8"
}

apply plugin: 'maven'

// 对应的仓库地址
def URL_PUCBLIC = "http://dev.inner.xxxx.local/nexus/repository/maven-releases/"
def USERNAME = "xxxx"
def PASSWORD = "xxxx"
def GROUP_ID = "com.xxxx.cooperate.core"
def ARTIFACT_ID = "core-thirdpart"
def VERSION = "1.0.5"
// 上传到公共仓库
task uploadToPublic(type: Upload) {
    group = 'upload'
    configuration = configurations.archives
    uploadDescriptor = true
    repositories{
        mavenDeployer {
            repository(url: URL_PUCBLIC) {
                authentication(userName: USERNAME, password: PASSWORD)
            }
            pom.version = VERSION
            pom.artifactId = ARTIFACT_ID
            pom.groupId = GROUP_ID
        }
    }
}

2.在core的build.gradle中引入

// 引用上传脚本
apply from: "./upload.gradle"

3.刷新项目,就可以再gradle的任务中看到uploadToPublic,双击执行这个task.就可以在maven库中看到当前aar已上传.我们观察下它的pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.xxxx.cooperate.core</groupId>
    <artifactId>core-thirdpart</artifactId>
    <version>1.0.5</version>
    <packaging>aar</packaging>
    <dependencies>
        <dependency>
            <groupId>com.android.support</groupId>
            <artifactId>appcompat-v7</artifactId>
            <version>25.3.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.4.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.xxxx.cooperate.xlog</groupId>
            <artifactId>xlog-common</artifactId>
            <version>1.0.0</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.tencent</groupId>
            <artifactId>mmkv</artifactId>
            <version>1.0.10</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>pl.droidsonroids.gif</groupId>
            <artifactId>android-gif-drawable</artifactId>
            <version>1.2.6</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>		

可以看到我们已经做到了当前aar的三方远程依赖.

4.在common中也可以按照上述操作.

整理完以后我们可以看下依赖如何:

common中使用

dependencies {
    compile  fileTree(dir: 'libs/main', include: ['*.jar'])
    compile  fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.xxxx.cooperate.core:core-thirdpart:1.0.5'
}

在SDK中使用

dependencies {
    FileTree tree = fileTree(dir: 'libs', includes: ['*.so', '*.jar'])
    compile tree
    provided fileTree(include: ['*.jar'], dir: 'providedlib')
    compile 'com.xxxx.cooperate.common:common-thirdpart:1.0.3'
}

完美!

这才是我心目中的使用方式,其实我们也可以把sdk的aar做下类似的包装.不过就比较麻烦了,还有一堆provided的依赖需要处理,这个还是暴露给业务方处理比较合适.

备注

冲突问题如何解决.

在解决aar打包问题前,我使用的是源码依赖.但是这种最简单的方式依旧冲突重重.

首先提供学习资料

Gradle 依赖&解决依赖冲突

Gradle学习

Gradle3.0新指令api、provided、implementation等对比

只要掌握了exclude,provided的奥义,就能排除问题.我们以wup.jar为例,看下jar类型冲突解决.

子模块Reader,Jce均使用了wup.jar,如果都使用compile的话,会出现类冲突.所以我们可以在各自模块中,使用

provided fileTree(include: ['*.jar'], dir: 'providedlib')

只保证编译期通过,不降代码打入各模块

然后在最顶层的app中,打入wup.jar

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

这样就解决了类冲突.

在集成当中,我发现,冲突最多的还是android自身的support包,各种版本号不一致,会导致各种各样的问题.我们尽量使用依赖的统一管理,来统一各模块的库版本号,尽可能的减少冲突发生.