万字长文教你Android组件化从入门到精通,学不会你来砍我!

从2017年只有几个大厂在做组件化,到今天已经繁花似锦。

越来越多的团队,越来越多的项目都做了组件化。

大叔相信即使你没有做过组件化项目,但是,对组件化也早就听烂了。

但是,组件化开发多少有些技术门槛。

有很多大神写过相关文章,通俗易懂的不多。深入浅出的更不多。

不才,愿意冒着不要脸的风险一试,通俗易懂、深深浅浅的来聊聊组件化开发,如果对你有一点点启发,请记得回来给大叔点个

一、单工程开发 -> 多module分层开发

这种分层架构,有什么用呢?

分解成多module的项目结构,就是组件化开发了吗?

当然不是,多module分层的项目结构,只是组件化开发的一部分。只是组件化开发的基础。

大叔,搜索了很多资料,发现,对于组件化开发,并没有很严格的定义。

当然,我们没有必要,过于纠结 ”组件化开发的定义“;

我们更关注这种开发思想对项目带来的好处以及在团队中如何运用。

二、组件化的思想&优势

下面是我的理解,如有出入,欢迎提出来一起讨论。

1、将一个大型项目分解成多个module,拆解的过程就是一个化繁为简的过程。

尤其在大团队,大项目上,组件化的优势会更加凸显。

大项目分解成一个个小型项目,相当于将一个复杂的问题拆解成一个个相对简单的问题。

每个成员,可以专注在自己相关业务的module上。

2、分层的module结构,同一层的module间存在代码隔离,这种隔离是编译上的隔离。

同层的代码不能相互调用。底层的代码也不能调用上层。

这种编译隔离,带来了,模块间的高度解耦。

让模块的依赖关系清晰。

3、更高的可重用性

如果构建正确)组件化设计的系统,比传统的整体设计具有更高的可重用性。

什么是组件?什么是模块?

组件强调复用,模块强调职责划分。 他们没有非常严格的划分。

达到可复用要求的模块,那么这个模块就是组件。

base层的module必须是可复用的,如果项目设计的好,business层都能被复用,每个module都能成为组件。

可重用性是组件化思想的核心。

如此架构,是否也适合技术中台的架构?

4、每个组件都具有可替代性如果构建正确

如果我们要为某个已经存在的组件,重新开发一个新组件,将变得非常可行。

组件内的重构也将变得非常可行。

新的组件的设计只要保证对外提供的接口,完全符合,旧组件对外提供的接口

5、组件的热插拔,成为可能(如果构建正确

我们想象下,在APP运行时,business中的组件可以动态加载,也可动态卸载。

那么我们可以轻松实现组件的懒加载:用户用到的组件,那么就加载进来。用完之后便可以卸载。

6、组件的独立编译、测试,成为可能(如果构建正确

大的android工程项目,build一次要到5分钟左右,太浪费时间了。

拆成多个组件之后,如果每个组件都能单独build,单独测试,那么将大大提升开发效率。

上面讨论的这些优势,并不是将简单将 单工程 拆分成 分层的多module工程结构 就能获得这些优势。

想要获得这些优势,还任重道远,我们还需要解决很多问题,才能让我们的项目具备上面的说的优势。

二、组件化后,将面临哪些问题?如何解决?

1、module之间如何优雅的通信

通过ARouter通信。

ARouter是阿里开源的一个项目。github.com/alibaba/ARo…

通过ARouter跨module跳转Activity

@Route(path = "/test/activity")//申明路由
public class YourActivity extend Activity {
    ...
}

//通过路由启动Activity
ARouter.getInstance().build("/test/activity").withLong("key1", 666L).navigation();
复制代码

通过ARouter在module间共享对象,实现module间通信。

比如:我们有一个账号模块 business:account ,提供了登录、登出、用户信息查询等业务。

同级的其他模块,如何跟账号模块通信?获取用户的登录状态以及用户相关信息?

public class AccountBean {
    private String name;
    private int age;
    //....
}

public interface IAccountService extends IProvider {
    void login(Context context);//登录
    void logout(Context context);//登出
    AccountBean getAccountBean();//获取账号信息
}
复制代码

对外的数据结构和接口定义。

@Route(path = BusinessRoutePath.ModuleAccount.ACCOUNT)
public class AccountServiceImpl implements IAccountService {
    //.....
}
复制代码

bussiness:account模块中的实现。

IAccountService accountService = ARouter.getInstance().navigation(IAccountService.class);
accountService.login(activity);
AccountBean bean = accountService.getAccountBean();    
复制代码

但是问题来了:

同层的其他模块,如何,能拿到ARouter的path?

同层的其他模块编译时,如何,共享AccountBean类、IAccountService接口?

这就是模块之间的编译隔离,带来的问题。

我们很自然的想到了framework模块,或者base层的其他模块。

我们只要将这些path定义、AccountBean类、IAccountService接口,下沉到base层,就可以实现编译上的代码共享。

如此一来,就带来了,另一个问题:代码的中心化问题

2、代码的中心化

简单的path字符串定义,放在framework倒是还好。

如果所有business模块对外提供的接口和数据结构,都定义到framework的话,问题就有点严峻。

将会破坏:组件的 可替代性可重用性组件间耦合度

因为framework是基础模块嘛,所有business模块都依赖的模块,如此,不管你的business1模块是否依赖business2模块的对外接口,都会存在这一层依赖。

模块间的代码边界出现一些劣化。缺少了编译上的隔离。许多模块将会变得不够“独立”了。

可替代性可重用性 越来越弱,想要替换或者复用某个business组件将变得越来越难。

将会导致,我们很难知道,哪些business对哪些business 接口有依赖。

同时,framework模块随着功能迭代,会不断膨胀。

这就是,中心化的问题。

于是我们很自然的想到了一个解决方案:

实现了另一种接口暴露的形式——“.api化”。

将 business模块 对外提供的接口单独抽到 business-api 模块中。其他依赖他的模块只需要依赖他的business-api即可。

image-20201218110524940

这个方案如何实践下去呢?

微信的api化方案

微信团队出了一个很巧妙的方案,这个方案对android的组件化开发,产生了非常深远的影响。

后面很多做组件化开发的团队,在解决中心化问题基本都会用到类似的方案。

以下为,微信官方博客的原文引用:

使用方式和思路都很简单。对于java文件,将工程里想要暴露出去的接口类后缀名从“.java”改成“.api”,就可以了。

而且并不只是java文件,其他文件如果也想暴露,在文件名后增加".api”,也一样可以。

当然,要让工程支持这样的方式,gradle文件肯定会有一点改变。

它的实现原理也相当简单:自动生成一个“SDK”工程,拷贝.api后缀文件到工程中就行了,后面其他工程依赖编译的只是这个生成的工程。简单好用。

api方案有点类似于android的AIDL的思路。

微信API方案的变种
gradle 根据src/api文件来,自动生成{moduleName}-api模块。
如果,src/api文件不存在,将不会自动生成 {moduleName}-api 模块。
复制代码

通过API模块来解决代码中心化问题带来的好处:

  1. 让各个business的之间的依赖明确
  2. 让business对外提供的接口明确。

从而加强了模块的:可替代性

只要两个business对外提供的API一致,就可以相互替换。

3、单独编译、测试 business的单个模块

模块变多了,项目变大了,整个项目的编译速度变慢了。

业内有两种常用做法。

  • 方案一:动态配置 build.gradle。

    只要让单个的组建能编译成APP就能单独测试。

  • 方案二:多壳APP

    方案来自,在聚美优品。

    这里需要注意:假如,Demo1是business1的壳APP。那么Demo1还需要依赖哪些businessXXX呢?

    刚好,前面做的api化,能排上用场。

    business1依赖的businessXXX-api模块对应的businessXXX模块,Demo1也需要依赖。

    为甚?因为,business1依赖的businessXXX-api模块,意味着,business1需要依赖 businessXXX提供的功能,比如要跳转到businessXXX的activity?或者,要获取businessXXX的对象。

4、模块变多了,gradle代码同比增长,gradle 代码复用
  • 版本号统一管理,依赖的统一管理
    • 方案一:Extra properties

      developer.android.com/studio/buil…

      docs.gradle.org/current/use…

      在项目跟目录的build.gradle文件中配置Extra属性

      如此可以实现统一管理版本号,和依赖。

      但是,但是,但是,这个方案存在明显的缺陷。

      • 不支持,自动补全

      • 不支持Find Usages,查找所有应用的地方

      • 使用时,不支持点击跳转

      严重影响开发体验。

    • 方案二:buildSrc

      • 支持,自动补全
    • 支持,Find Usages

      • 支持,点击跳转
    • 更完美的语法高亮

    • gradle文件复用

      版本和依赖做到了统一管理,但是每个module都有各自的build.gradle,重复的build.gradle代码依然没有复用。

      我们可以通过apply from:"xxx.gradle"的方式来复用gradle代码。

      如下图

      如上,我们可以在base.gradle中为每一个项目添加配置统一的编译逻辑,如:kotlin的支持,java版本的修改,maven库上场的逻辑等等

5、模块间:string、drawable、value、layout等,资源名冲突问题

如何防止资源名冲突?

比如businessA 和 businessB都在drawable目录下,都有一张同名的图片。

这两张图片只有一张会被打包到apk中,被使用。

这样就容易出现混乱。

这个问题比较好解决。让每个模块的资源名固定一个前缀。只要模块之间的前缀不一样就不会冲突。

gradle的resourcePrefix配置,刚好符合我们的需求。

如下配置,如果module中存在资源不以app_开头,lint走查会报warnning。注意不会编译失败。

android {
    resourcePrefix 'app_'
}
复制代码

如下截图的warning:

6、由于多module分层的项目结构,导致 R.class 冗余

可以通过booster的资源内联工具解决,R类的冗余。

详细可以自己查看booster官网,booster是didi开源的一个插件。booster.johnsonlee.io/feature/shr…

7、模块间,公共资源string、drawable、layout等如何共享?

没有找到很好的解决方案。

每个方案都有缺陷

比如说,business1和business2都要用到同一张图片。

那么这张图片该放到哪里呢?

  • 1、把他放到api模块里来共享

    资源这种,并非功能依赖,放到api模块也不太合适。

    因为这样可能造成business1和business2模块原本没有关联也没有依赖;

    但因为共用同一个资源,却导致存在了依赖。

  • 2、在business1和business2中都放一个图片

    如此会增大包体

  • 3、在business1和business2中都放文件名同名的图片,让编译时资源合并的时候只使用同一张图片。

    如此一来,放开各个模块资源命名,也容易导致开发时发生冲突。

    自定义lint规则,让存在common和{moduleName}两种前缀?

  • 4、将这张图片下沉到base层,如:framework模块,或者,单开一个lib-resource

    如此一来,将会出现资源中心化问题。

上面的方法多少都有些缺陷,大叔还没有找到一个优雅的方式。如果你有什么好想法,一定要留言告诉大叔,大叔在此谢过你了。

8、各个business 模块 之间能不能有直接依赖?

千万不能这么操作。

假如:在 business/setting 中直接在gradle配置中依赖,business:account。

那么编译上的代码隔离就彻底被毁。就跟不要谈组件的可重用性可替代性了。

implementation project(":business:account")
复制代码
9、Application生命周期如何派发

各个组件如何获得Application.attach()、Application.onCreate()、Application.onTerminate()等的回调。

未完待续

10、组件生命周期管理

未完待续,待踩过坑,实现了,再来写。

11、组件实现热插拔

未完待续,待踩过坑,实现了,再来写。

12、等等,未完待续

待继续探索

三、升华

最后我们再回到,组件化本身上来。

组件化开发不仅仅是一种多module分层的项目结构;他不仅仅是一种架构;他更是一种架构思想,一种追求模块复用精神。

有人说小项目没有必要做组件化开发。大叔不这么认为,小项目依然适合做组件化,除非你们团队只有一个项目,并且项目几乎不需要迭代。组件跨项目的复用也是一件让人十分兴奋的优势。

前几年听烂了的技术中台,跟组件化的架构也不谋而合。

最后,十分感谢,前辈们将他们的组件化经验分享到互联网,为我们这些组件化的后来者提供了宝贵的资料。这也是我写这篇文章的原因,也算是反哺吧。最后的话分享一些组件化的学习笔记给大家,有需要的可以在我的github自取!
Android-Architecture-Guide


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 一、概念 组件化:把一个功能完整的App或模块拆分成多个子模块,每个子模块可以独立编译和运行,也可以任意组合成另一...
    TomyZhang阅读 919评论 0 1
  • 一个软件系统的开发可能只需要2到3个月就能完成,而这个系统的迭代和维护时间可能达2到3年之久——《不记得哪本书上说...
    DoneWillianm阅读 1,747评论 4 10
  • 组件通信注解框架实践 目录介绍 01.为何需要组件间通信 02.实现同级组件通信方式 03.先看一个简单的案例 0...
    杨充211阅读 664评论 0 6
  • 原文见:http://blog.zhaiyifan.cn/2016/10/20/android-new-proje...
    MarkZhai阅读 1,790评论 4 35
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,520评论 28 53