Android 组件化 重构 笔记

上篇文章 Android 组件化 之 如何优雅的实现同级组件的通信 讲到

写代码这么多年,一个重要感受是「不要过度封装!」
不仅仅是说业务组件不多,没必要用这么复杂的组件化方案。
我甚至觉得组件化都不是必须的。


组件化的3条好处

  1. 一个工程(project)里面需要选择依赖哪几个组件,然后打成不同的包。
  2. 由第一条引申,如果有需求「每个组件可以单独打个包来测试」。
  3. 加快了增量编译的速度。

第一条「打成不同的包」是很广义的,除了依赖这一个工程打出不同包。还有比如有一些base组件,能自己项目里用,还能打出一个独立的aar供其他项目用,也算作打成不同的包。这些能抽离出来的base代码就可以作为一个组件。



组件化的牺牲

1. 如果分包能解决,为什么要牺牲便利性,抽离出组件来。

很多文章说组件化可以 「解耦」、「业务分层」、「业务隔离」、「代码变的更好维护」,这些其实都是可以通过分包(Package)解决的。
约定好 A小姐 负责 com.xxx.packageA 下的代码,B小姐 负责 com.xxx.packageB 的代码。甚至可以写个脚本,git clone 下载下来工程后,立刻执行 chmod -r 删去某些路径的写权限,避免无意修改别人负责的代码区域。

抽离出组件后并不是一蹴而就的事情,要一直投入精力保持组件成功运行也是需要不少成本的。

比如说我们的资源文件,一般会要求加模块名字的前缀,避免组件化项目运行过程中资源冲突。(谁让resourcePrefix它只管检验不帮忙动手啊)

实战中某个业务module里的UI或是什么代码,需要变成通用的,供多个业务模块使用,要下沉到 lib_base 模块了。这就需要转移各种资源到 lib_base,这就是要逐个文件、逐个资源重命名。还要牵扯到所有用到这个资源的代码,改一个资源名就可能是10+个甚至几十+个文件的变动。IDE是很智能的都帮忙改了,但是自己提交代码前检查 & 别人code review审查平白无故又要多N个文件的工作量,这种痛苦我深有体会。

2. 关于组件独立运行

有一些项目改造成组件化,实现的效果是「每个业务组件 + App壳组件都可以单独打出一个App」,可以方便地单独测试某个业务。设想跟写单元测试代码很相似。
然而效果是测试某个业务内容后,集成到主app里能保证不出问题?不能,这时候又要跑一遍集成后的测试流程。后面甚至发现单独测试业务模块并不能省时间,干脆省略了。久而久之组件独立运行成为了摆设。

3. 关于加快了增量编译的速度

其实现在Android studio的instant run和越来越优化成熟的编译机制,甚至买部好点贵点的电脑,都可以解决这个问题了。而且如果组件化维护不当,到处都是 api 引入组件,那编译速度反而有可能延长。




场景案例

写了这么多组件化的坏话,我也不是说组件化一无是处。
而是我觉得,判断需不需要组件化的唯一一条要素是,当你需要使用这个工程打出不同的app的时候,才需要组件化。
这里我举一个组件化完美适用的场景。
组件化完美场景案例
这是一款小说阅读类App,最下面 lib_base 基础组件,有3个平级的业务组件 lib_user(用户)、lib_read(阅读)、lib_ad(广告),上面有3个可以打成不同 App 的组件。

  1. 国内版本:用户、阅读、广告都用到,所以三者都引用到,打出一个国内App。
  2. 国外版本:不允许有广告啊,会被下架啊。排除掉广告组件,引用用户和阅读组件,打出一个国外版本App。
  3. 为了推广,为了上架谷歌市场单本作品付费阅读,甚至可以没有用户模块。作品资源直接集成到apk里,1美元你直接买断这部作品了,无需联网无需登录离线即可阅读。只引入阅读组件,打出一个单部作品的App。

从这个案例可以看到,核心原则是「是否需要依赖不同的业务层组件,打成不同的包」。
以后再新增的功能,比如说 「社交」、「购物」,应不应该独立成组件,都应该按照这个原则来。是不是有的App需要这个组件,有的App不需要这个组件。

比如说 「登录功能」,需要独立封装成一个 登录组件 吗?

如果没有要打包一个没有登录功能的App,不需要设计单独的 「登录组件」。

如果你的项目 登录 是 必须 的功能,否则后续页面都打不开,就更不应该设计单独的 登录组件了。这样的登录功能,应该放 lib_base 里,属于公共能力的一部分,不应该跟业务组件平级。




现象与问题

然后讲讲最近正在进行的一个组件化重构的方案。App有3个重要组件,「抢单」、「做单」、「我的」。跟小说阅读类的App的「书城」、「书架」、「我的」很像。所以我拿这个小说阅读类App当案例继续说了。
小说阅读类App,重构前
重构前有这几个模块

  1. 书城:用于找书。
  2. 书架:自己收藏的,最近阅读的书。最重要的阅读功能也放到这个组件里。
  3. 我的:个人页面。

这样的项目结构看起来还是很清晰明了的。但是运行时间长了会发现有一个问题。
书城书架,这两个组件很特殊,有很多功能都是重合的。比如说对于作品的介绍页,作品章节列表页面。这些功能因为共用,都慢慢转移到了 lib_base 里面,导致 lib_base 越来越臃肿。
并且这种功能只用于书城书架,并不能用在我的组件里。如果以后要添加任务社区组件,也不会用到这些页面的功能。


解决

lib_base 越来越臃肿了怎么办?
按照我上篇文章 2.3 ARouter的服务管理 (更合适的接口定义) 讲的——定义好每个组件的能力,把「这个组件能提供的能力」封装成一个接口类放到base组件里。这是一种好方法,但是还有一些问题解决不了。
例如一些实体类(BookInfo、AuthorInfo),一些共用的资源文件,图片、UI布局、shape、文案,还是要放到 lib_base里。因为服务管理不能提供实体类和资源文件的共用。
放 lib_base 最不好的一点是,这些资源只有书城书架两个组件用到,我的组件不会用到,它们不能成为 通用资源

对于这种项目,某部分组件依赖和共用很严重的,可以按照 2.4 ARouter的服务管理 (还能优化的空间) 来设计。

小说阅读类App,重构后
作品详细介绍页面作品章节列表作者详细介绍这3个页面Activity放到业务组件里,可以使用ARouter跳转到。
而相关能力接口、实体Bean、resources等这些共用的资源,就可以放到export层里面了。
比如这里我们开发小组讨论协商,先划分一些功能归属。
module_书城_export 负责 作品详细介绍页面作者详细介绍,所以 BookDetailEntity.kt、AuthorDetailEntity.kt 放到这个组件里。
module_书架_export 负责 作品章节列表,所以 BookChapter.kt 放到这个组件里。




代码结构

代码结构

1.Provider

使用ARouter的服务管理能力,在export组件创建一个Provider接口。在这里是 BookshelfProvider.kt,在这个案例里提供了的能力如上图。

一个重点是,这些能力可以是立即return返回回来,也可以通过设立回调Callback,或者是利用rxjava的一个请求。

module_bookshelf_export 定义能力接口,module_bookshelf 写具体实现这些能力的方法。

如果一个接口过于杂乱,还可以分为多个。
举个例子创建一个provider包,有3个Interface文件,对应三个细分能力的分类

  1. BookHistoryProvider.kt 表示 书架Tab 里面的 历史阅读书籍 列表
  2. BookLocalProvider.kt 表示 书架Tab 里面的 手机本地书籍 列表
  3. BookSubscriptionProvider.kt 表示 书架Tab 里面的 用户订阅书籍 列表

2.实体

比如 getBookShelfList 获取书架的所有作品列表 这个接口,不可能返回字符串让其他组件解析吧。所以相关的共用的实体类也应该定义在这里。

3.资源文件

各类资源文件,包括自定义View,这类都是Provider无法提供的能力,也应该定义在这里。




总结

  1. 判断某块功能需不需要独立成组件?判断标准是 「是否需要依赖不同的业务层组件,打成不同的包」
  2. 代码应该放在哪里:
    区分:哪些是所有业务组件都需要的能力,哪些是一部分业务组件需要的能力,哪些是只有自己用的能力。
使用范围放置位置
只有自己使用module_业务
需要给个别业务组件使用module_业务_export
全部业务组件都使用lib_base
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值