不用写一句代码,自动实现 Android 组件化模块构建(刚刚发错区了) - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
tangpj
V2EX    Android

不用写一句代码,自动实现 Android 组件化模块构建(刚刚发错区了)

  •  1
     
  •   tangpj 2018-10-10 15:24:55 +08:00 11196 次点击
    这是一个创建于 2560 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Mterial 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 组件化构建的流程。

    你的支持,是我前进的动力,如果觉得有帮助的话,可以点下 star

    Demo 地址:SimpleCalces

    项目结构:

    simple_calces_dir

    1. 引入依赖库

    在 Gradle 2.1 及更高版本的插件构建脚本代码:

    在项目的 build.gradle 中

    buildscript { ... } plugins { id "calces.modules" version "1.0.11" } 

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

     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
     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)
     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。

    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

    2 条回复    2018-10-11 13:59:40 +08:00
    Livid
        1
    Livid  
    MOD
    PRO
       2018-10-10 20:12:38 +08:00
    1. 在主题刚发出来的 10 分钟内,是可以自由移动的。
    2. 把同样的内容在不同的节点发两遍,是违规的。会导致你的账号被降权。
    tangpj
        2
    tangpj  
    OP
       2018-10-11 13:59:40 +08:00
    @Livid 好的知道了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5577 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 08:53 PVG 16:53 LAX 01:53 JFK 04:53
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86