写点什么

开枝散叶的组件化技术,90% 的开发者用后都说香

用户头像
Android架构
关注
发布于: 9 小时前

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


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


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

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

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


通过 ARouter 通信。


ARouter 是阿里开源的一个项目。


通过 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 的话,问题就有点严峻。


将会破坏:组件的?**可替


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


代性**、可重用性组件间耦合度


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



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



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



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


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


这就是,中心化的问题。


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


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



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





image-20201218110524940


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


微信的 api 化方案


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



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



mp.weixin.qq.com/s/6Q818XA5F…


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


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



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





image.png



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





image.png





image.png



它的实现原理也相当简单:自动生成一个“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,查找所有应用的地方



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


严重影响开发体验。




  • 支持,自动补全

  • 支持,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

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
开枝散叶的组件化技术,90%的开发者用后都说香