Gradle自动实现Android组件化模块构建



Material Render Phone

不管全世界所有人怎么说,我都认为自己的感受才是正确的。无论别人怎么看,我绝不打乱自己的节奏。喜欢的事自然可以坚持,不喜欢的怎么也长久不了。

—— 村上春树

背景

随着App的不断迭代,业务会变得越来越复杂,业务模块会越来越多,且每个模块的代码也会变得越来越多。为了应对这一场景,我们需要把不同的业务模块划分成一个个组件,在修改业务代码的时候只需要在对应模块修改就可以了。通过高内聚,低耦合的业务模块来保证工程的健壮性和稳定性。现在问题来了,当组件的数量变得越来多的时候,我们如何管理业务组件呢?

原创声明: 该文章为原创文章,未经博主同意严禁转载。

为什么我们要用Gradle管理组件呢?

先来看看Android组件化需要实现的目标。(什么是组件化构建?)

  1. 按照业务逻辑划分模块
  2. 项目模块能够单独启动测试
  3. 能够根据需求引入或删除某些业务模块
  4. 通过不同模块的组合,组成不同的App

对于第一点:需要根据技术架构和业务架构来划分模块,这里需要根据实际情况来考虑。我们需要优化的是第二、三、四点。

对于第二点:Android是通过应用com.android.application或com.android.library来决定该模块是以App模式还是以Library模式构建。App模式和Library模式的最大区别就是,App能够启动,而Library不可以。所以如果我们的模块能独立启动的话,我们需要每次手动去改动模块的build.gradle文件。好一点的做法定义一个布尔值来判断是否处于debug模式,但是这里有个问题是,不是每个模块都能独立启动的。所以无论采用何种方案,都需要我们手动管理。

对于第三点:当我们开发好业务模块后,可能我们需要频繁的新增或删除某些业务模块。如果是这样的话,我们也是需要频繁手动修改App的build.gradle。

对于第四点:有时候,我们可能会在不同的App中引用相同的组件(例如:滴滴的普通版和企业版,普通版包含企业版的功能),这个时候,我们也不希望要频繁手动管理组件依赖,特别是在组件还可以独立运行的时候。

所以,在我们实践组件化的时候,最大的问题就是,我们需要频繁的手动build.gradle文件来管理组件应用的插件和App的依赖。

使用Gradle来管理组件

先安利下笔者写的Gradle插件:Calces。如果觉得这个插件有用的话,可以star下,如果你有更好的想法的话,可以向我提交pull request。

废话少说,一下是通过Calces快速实现Android组件化构建的流程。

Demo地址:SimpleCalces

项目结构:

simple_calces_dir

  1. 引入依赖库
    在Gradle 2.1及更高版本的插件构建脚本代码:
    在项目的build.gradle中

    1
    2
    3
    4
    5
    6
    buildscript {
    ...
    }
    plugins {
    id "calces.modules" version "1.0.11"
    }

    在较旧版本的Gradle中或需要动态配置的情况下的插件构建脚本代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    buildscript {
    repositories {
    maven {
    url "https://plugins.gradle.org/m2/"
    }
    }
    dependencies {
    classpath "gradle.plugin.com.tangpj.tools:calces:1.0.11"
    }
    }
    apply plugin: "calces.appConfig"
  1. 在项目build.gradle配置AppConfig

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    appConfig {
    debugEnable false
    apps {
    app{
    modules ':library1', ':library2'
    }
    }
    modules{
    library1{
    mainActivity ".Library1Activity"
    applicationId "com.tangpj.library1"
    isRunAlone true
    }
    library2{
    mainActivity ".Library2Activity"
    applicationId "com.tangpj.library2"
    isRunAlone true
    }
    }
    }
  1. 在modules(子模块)引入模块自动化构建插件 (注意:不需要手动配置com.android.library或com.android.application)

    1
    apply plugin: 'calces.modules'

这样我们就完成了组件化的构建了,是的,我们不再需要再手动管理单个组件了与App的构建了,通过Calces,我们能实现快速的组件化构建与多App同时构建。

那么问题来了,我们如何实现组件间的通信呢?在简单的项目中,推荐该Demo一样,通过使用Android隐式Intent来实现组件间通信。在中大型项目中,笔者推荐使用阿里的路由解决方案:ARouter。具体使用方法参考官方文档就可以了,使用方法十分简单。

注意:在使用隐式Intent实现组件件通信的时候需要注意找不到相应组件异常:java.lang.IllegalStateException: Could not execute method of the activity。导致这个异常的原因是找不到目标组件导致的,所以在实际开发的时候,需要捕获这一异常,并且根据项目实际情况来进行实际的处理。使用ARouter框架则能通过设置降级策略来实现异常处理(查看ARouter文档了解更多)。

如果只是实现项目的简单组件化,那么看到这里就可以了,如果希望实现更加灵活的组件化架构的读者可以继续看下去,下面笔者将全面分析组件化的优势与笔者总结的组件化构建思想。

组件化构建简述

组件化构建与其说是一种技术,不如说是一种思想。组件化构建是通过对项目重新划分成一个个高内聚、低耦合的模块来解决项目过于臃肿,代码过于复杂的一种架构思想。

我们通过对Google官方的架构演示Demo todo-mvp进行拆分来对Android组件化进行深入的分析。

Demo地址:TodoCalces

todo系列app是Google android-architecture项目中为了演示Android架构的最佳实现而编写的一系列演示Demo。todo app的特点是,它足够简单,代码量少,易于理解。但是又不会过于简单,因为它是一个包含完成功能的App。它实现了任务列表、任务详情、新建任务、编辑任务、统计任务的功能。

todo-mvp实现的功能:

  • 任务列表
  • 任务详情
  • 新增/编辑任务
  • 统计任务

我们将以todo-mvp的功能来划分为4个业务模块,将底层划分为2个模块,分别是superLib(提供mvp架构支持与其它的一些支持库功能)与dataLib(数据支持模块,Room提供底层数据库支持功能)。对于大型项目还可以加入resLib支持模块,用来存放公共图片资源、字符穿资源、样式等。

架构划分图如下:

Todo Calces Architecture

从架构图可以看出,所有的业务组件都依赖底层库,而APP又依赖于业务组件,APP组件在这里是作为一个独立组件存在的。在一般的组件化实践中,都不包含APP这个组件的,APP组件的存在是有其意义的。

首先,我们的组件化除了实现组件的独立管理和动态配置APP所依赖的组件外,还有一个十分重要的目的就是,通过组合不同的组件,打包多个不同的APP。例如,QQ有分普通版和轻聊版,轻聊版是功能简化版的QQ。如果我们使用组件化来管理工程的话,我们只需要把不需要的模块移除掉就可以了。而APP组件在这里的作用是充当一个包装盒,把需要的组件包装进来。并且我们可以通过控制包装盒的样式来配置不同的APP风格。在这里我们可以通过Application中的Style来实现。

这里我们还是以todo-mvp为例,例如我们需要实现一个不包含统计功能的todo APP,按照我们的原理,我们只需要去掉statistics的依赖就可以了。

架构划分图如下:

Todo No Statistic Architectur

如果nostatsitcs需要不同的配色的方案的话,只需要在AndroidManifest的application标签中配置对应的theme就可以了。

使用Calces实现todo-mvp的组件化

通过上面的分析,我们来试下对todo-mvp项目按照业务功能来划分组件。我们先来看看划分后的目录:

todo_calces_dir

好了,我们已经对todo-mvp项目进行初步的划分了。根据上面分析的理论得知,我们的业务模块是可以单独运行的,并且我们能够快速构建一个不包含statistics模块的APP。

我们只需要使用Calces就能快速实现我们需要的功能。

按照Calces的教程,我们得知,实现Calces只需要三个步骤:

  1. 引入依赖库
  2. 在项目的build.gradle中配置AppConfig
  3. 在业务模块中引入模块自动化构c持续

第一点和第三点在其它所有项目中的配置都是一样的,在这里不作论述,下面我们看看对于TodoCalces项目,我们要如何配置AppConfig 。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
appConfig {
debugEnable false
apps {
app {
mainActivity "com.tangpj.tasks.TasksActivity"
modules ':modules:addtask',
':modules:taskdetail',
':modules:tasks',
':modules:statistics'
}
app2 {
name 'nostatistic'
applicationId 'com.tangpj.nostatistic'
modules ':modules:addtask',
':modules:taskdetail',
':modules:tasks'
}
}
modules {
addtask {
name ":modules:addtask"
applicationId "com.tangpj.addtask"
mainActivity ".AddEditTaskActivity"
isRunAlone false
}
taskdetail {
name ":modules:taskdetail"
applicationId "com.tangpj.taskdetail"
mainActivity ".TaskDetailActivity"
isRunAlone true
}
task {
name ":modules:tasks"
applicationId "com.tangpj.tasks"
mainActivity ".TasksActivity"
isRunAlone true
}
statistics {
name ":modules:statistics"
applicationId "com.tangpj.statistics"
mainActivity ".StatisticsActivity"
isRunAlone true
}
}
}

根据AppConfig可以得出,我们分别配置了2个APP,分别是app1和app2。并且app2中是没有依赖statistics的。现在我们两个APP运行的对比图。

app1(带statistics模块):

todo_calces

app2(不带statistics模块):

nostatistic

从运行图可以看出,app1和app2的配色方案是不一样的,并且app2中不带statistics模块,通过对项目实行合理的划分和引入Calces就能够快速实现组件化构建了。

结论:通过Calces能轻松实现业务组件的管理,多APP的快速构建。当我们的业务组件只有4个的时候,可能无法体现Calces的优势,但是如果我们的业务组件有40个的时候,Calces给我们带来的优势就非常明显了。我们可以通过灵活依赖不同的组件,实现快速构建多个APP的目的。就像Calces的介绍图案一样,把组件当成积木来使用。

如何测试

Android自动化测试展开来说是一个非常大并且不算简单的工程,在这里笔者不打算展开来说。只是简单的介绍组件化构建如何让我们更方便地去测试。

并不是所有的自动化测试都一样,它们通常在使用范围、实现难度和执行时间上存在不同。我们一般把自动化测试划分为三种分别是:

  1. 单元测试:目的是测试代码的最小单元。在基于Java的项目中,这个单元是一个方法。单元测试容易编写,快速执行,并在开发过程中针对代码的正确性提供宝贵的反馈。
  2. 集成测试:用来测试一个完成的组件或子系统,确保多个类之间的交互是否按预期运行。集成测试需要比单元测试需要更长的执行时间,而且更加难以维护,失败的原因难以诊断。
  3. 功能测试:通常用于测试应用程序端到端的功能,包括从用户的角度与所有外部系统的交互。当我们讨论用户角度时,通常是指用户界面。因为用户界面会随着时间的推移发生变动,维护功能测试代码会变得乏味而耗时。

为了优化投资回报率,代码库应该包含大量的单元测试、少量集成测试以及更少的功能测试。

占比如下图所示:

自动化测试金字塔

从上文知道,在我们的组件化分的时候,会划分一个基础依赖库(superLib)。基础依赖库为我们的项目提供了基本的支持,并且该库在项目中是比较稳定、并且不包含业务逻辑的,所以在基础依赖库中,我们应该大量应用单元测试。而集成测试则适用于我们的数据依赖库(dataLib)中,我们可以通过集成测试来验证产品代码与数据模块的交互。而我们的业务模块中包含了大量的业务逻辑,这部分是经常变动的部分,我们可以为我们的业务模块编写一些UI自动化测试代码,但是因为业务(界面)经常变动的原因,所以这部分测试代码是难以维护,并且复用性十分低的。。

最后,我们得出的结论是:应该把主要精力放在单元测试上,所以如果当你的精力不足以编写所有测试代码的时候,你应该把主要的精力放在单元测试上,而不是放在收益最小的功能测试上。

关于自动化测试,笔者给的建议就到这里了,如果需要深入理解测试的话,可以自行查找资料,或者关注笔者的博客。后续的博客中,有可能会写关于自动化测试相关的知识。

小结

通过Calces插件,我们在实现Android组件化时只需要关注如何合理划分组件的架构与如何实现组件间的通信就可以了。对于Android组件化来说,最主要问题有两个:

  1. 大型项目如何合理划分组件模块
  2. 当项目的组件数量非常多的时候如何管理

第二个问题,可以通过Calces快速解决,至于第一个问题,笔者给出的指导就是,业务模块在合理的情况下要尽可能的小,因为越小的模块,越容易达到高内聚低耦合的目的。读者不需要担心项目模块划分得过于细不便于管理的问题,因为Calces能够轻松帮你管理好各个模块。

历史精选

Android开发利器之Data Binding Compiler V2 —— 搭建Android MVVM完全体的基础

关于我

如果这片文章对你有所启发的话,可以关注下笔者的公众号或GitHub

扫一扫关注我:

qr