记 router 选型和组件化思考
ps:未完 ~
之前组件化我写了一篇,基础的问题可以去看那篇
先吐槽下
组件化是从去年开始接触的,因为一是组件化是现在的发展思路,大家或多或少都在做这个,二呢是因为我司项目也是到了需要组件化的时候了
我司业务包结构:
能看到我们的业务分包已经分了很多了,出现的问题比较多,最典型的就是
- 编译打包慢
- 业务沟通难
比如我想启动不是我写的业务模块的页面,我是不知道那个页面的 class 名字的,我就要去问这个页面谁写的,名字叫什么,传什么参数。
或者不同模块间可能有数据传递,那么我还是要问问哪个同学写的这个模块,然后沟通模块间 iterface 通讯接口,并且这个 iterface 通讯接口的获取还是强依赖的,一般我着都是通过 static 来做的
要不就是我的页面需要显示别的模块的列表,那么我需要去看其他同学的 adapter 怎么用,传参,动画,回调设置
上面种种问题很多,一个是强依赖的问题,别人改我就得跟着改,其次是来回文别人很非时间精力,还老是打扰别人,惹人不快。
能看出来我死这是没有文档管理的,所以有啥不知道都得问别人,也是没有好的架构设计,大家都是破罐破摔习惯了,技术上也没有成长,这是不正确的,所以我打算上组件化,只是后来公司变动,裁员我离开了,没有付之行动罢了
组件化我们干什么
组件化就是把我们 app 中种种功能,业务单独抽象拿出来,组织成一个个单独的 module ,当然不是每一个功能都是一个 module ,而是根据相关性把他们或多或少的分成一个活多个 module ,具体的要看你司单位自己的了
借美团的图看一下:
大概就是这个意思
组件化改造过程
任何改造都是由简到难的,组件化也一样
-
我们先不动业务,先把基础功能抽象出来,比如网络,缓存,数据库,放到一个单独的基础 module 中,有人叫 baseLib
-
基础功能实现分离了,那么我们现在可以懂业务库了,每一个打的业务模块抽象成一个单独的 module,比如 orderModule,shopModule
-
进一步抽象业务模块中通用的 UI 或是功能,把他们再抽象出来,形成 commonModule,前文说的哪些通用的 adapter 就可以拿出来放到 commonModule 里
-
module 之间代码是隔离的,那么 module 之间的通信,页面的唤起就需要中间件 router 了,router 说简单他就是一个唤起页面的,说复杂,router 包括业务 module 的初始化,页面的相互调用,外链页面调用,module 间数据交互等,挺多复杂的东西,你司 app 要是还有跨进程通信的部分会更麻烦,这里可以做的很简单,也可以做的很复杂,完全看个人是不是有兴趣在技术上探索一下,更进一步了
-
剩下的就是最麻烦,最难搞的了,module 间依赖管理,小公司就一个 app ,代码虽然分成了一个个 module ,但是 module 还是在 app 工程内的,module 一般提供 file 依赖管理就行了,没啥说的。
但是在大公司就不同了,大公司不单单是只有一个 app 了,是有很多个 app 在同时开发,那么 module 怎么依赖,像 baseLib 这些底层的变化小的可以做 arr 依赖,但是像业务module 用 file 依赖就没法搞了,你把 module file 放到哪个 app 的工程代码里去,若是都放的话,你怎么同步不同 app 工程下对同一个 module 的修改。业务 module 因为修改频繁,提供 arr 依赖分麻烦,不是太适合的
到这里我也不是很清楚了,之前看到一个方案,使用 Git Submodule 命令将子模块全部迁移到独立仓库中!Git Submodule是Git提供的功能,它允许你将一个 Git 仓库作为另一个 Git 仓库的子目录,它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。
我不知道大公司是不是这么做的,但是应该八九不离十了。但是 Git Submodule 有坑,你主工程切分枝了,Submodule 加载进来的 module 可不是跟着改,同样朱工程提交了 Submodule 加载进来的 module 一样也不会跟着提交,关于这块大家看下面文章,作者自己写了一个插件:
大公司永远走的比我们快,大家可以参考微信,美团的相关文章:
上面我们只是完成一个目前大众水平的单工程组件化方案,算是可以接受,但是这不是终点哦,大家跟着大公司的几部继续往下走吧
看过上面2篇文章的同学,可以看到大公司的业务 module 是在太多了,而且还是垂直分组的,这样的话怎么组织 module ,不同的 app 壳对某些业务有不同的要求,比如显示,这些微信,美团采用了下面这个措施:
- 使用 .api 代替 .java 提供对外依赖接口
- 封装优秀的 router 组件
- 使用 flavor 来配置不同的 app 壳对应的 sourceSets
- 使用 p 工程在 module 内部再次实现代码隔离
上边我有逼逼了一次组件化的东西,我最后想说的是:光说没用,大家得亲自去干才行,要不不会有深刻深入的体会的
更多的组件化思路大家看,简书 _ 慕涵盛华 _ 组件化专栏:
router 路由都干什么事
router 路由简单来说就是 module 组件间用来通信的,这个通信包括:
- 页面调起
- 简单的数据间的交互
- 复杂的 interface 交互
1. 页面调起
大家是不是觉得页面调起很简单啊,其实错误了,月面调起很复杂的,从页面调起的来源就能看出来
页面跳转来源:
- 来自 app 内的跳转
- 来自 app 内 web 容器的跳转
- 从 app 外部通过 Uri 唤起的 app 跳转
- 从通知中心 push 唤起的 app 跳转
现在你还觉得 module 间页面跳转还容易吗,不容易的,由是是我们还要在实现代码解耦的基础上实现更难,所以一个 优秀的 router 组件对我们非常重要
2. 对于数据交换
2.1 如果是简单的数据间的交互,我们使用 rxbus ,eventbus,livedatabus 这些就可以,上面几个可以非常简单的实现一对多的效果通知,消息本质不就是数据嘛,所以用来做简单的数据同步通信也是没问题的,推荐 livadatabus 哦
2.2 如果是复杂的 interface 交互,那么一般都是通过 router 注册返回 interface 交互接口实现类的工厂类。或者大家狠狠心,可以把相关的业务代码下沉到基础业务层 commonModule,这样使用静态单例实现通信也行,但是不具有普遍性
另外 interface 交互接口实现类,大家维护一个也行,维护多个也行,在自己,不过遇到跨进程通信的,认为还是维护一个好些
router 选型
目前主流的 router 就是 ARouter 和 WMRouter 了
2者差不多,下面这是对比图:
2者都需要写学习成本,WMRouter 配置比 ARouter 复杂写,但是可以通过配置做的更灵活,我使用的是 WMRouter
WMRouter 学习资料:
- 官方介绍:WMRouter_美团外卖Android开源路由框架
- 官方文档:WMRouter_设计与使用文档
- 美团开源路由框架(WMRouter)学习——使用篇
- WMRouter源码分析(4)-页面路由实例分析
WMRouter 的官方文档写的不是很好,不是从易到难的顺序来写的,大家一定要通篇看下来才能看个大概,然后跑下 demo 才行
Arouter学习资料:
router 自己实现
大家若是想自己实现 router 的话,那么现在不支持 apt 注解的话是没有什么意义了
我可以给大家推荐一写文章,都是自己实现 router 的:
-
Android架构思考(模块化、多进程)
这是 17年初早期的一篇文章,作者的 router 实现了 跨进程通信和 interface 通信,思路还是很好的,但是没有 apt 实现 -
Android组件化之通信(多模块,多进程)
这是对上一篇的改进,添加了 apt 进去 -
Android组件化最佳路由—ARetrofit
这个作者写的像 retrofit 那样用 router -
完美解决Android进程间通信—ABridge
跨进程通信,思路也很不错,大家没事看看
能看到最后的都是有缘人,现在大厂都是 平台化 开发了,已经不单单是用 gradle 插件了,已经上升到了平台画开发容器的程度了,对于此我也是很陌生
下面是我收集的一些文章,大家开拓下思路:
比如阿里的 Quinox 容器框架
关于插件化中的路由实现
-
MyRouter
大家可以通过这个项目去初步熟悉下插件化的代码
添加一下,外部链接打开本地 app
1. 基本思路
不管是哪种开源库,还是自己做,基本都是 通过 Scheme 协议打开 APP 界面,如下面 activity 的设置,这是 Scheme 设置最全的了
<activity
android:name=".ui.activity.ZMCertTestActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="scheme1"
android:host="host1"
android:path="/path1"
android:port="8080" />
</intent-filter>
</activity>
activity 获取外链中的数据
Uri uri = getIntent().getData();
if (uri != null) {
// 完整的url信息
String url = uri.toString();
Log.i(TAG, "url:" + uri);
// scheme部分
String scheme = uri.getScheme();
Log.i(TAG, "scheme:" + scheme);
// host部分
String host = uri.getHost();
Log.i(TAG, "host:" + host);
// port部分
int port = uri.getPort();
Log.i(TAG, "port:" + port);
// 访问路劲
String path = uri.getPath();
Log.i(TAG, "path:" + path);
List<String> pathSegments = uri.getPathSegments();
// Query部分
String query = uri.getQuery();
Log.i(TAG, "query:" + query);
//获取指定参数值
String success = uri.getQueryParameter("success");
Log.i(TAG, "success:" + success);
}
最后外链拼在 intent 中拼接 Scheme 启动我们的 app
Intent intent=new Intent(Intent.ACTION_VIEW,Uri.parse("scheme1://host1:8080/path1?query1=1&query2=true"));
startActivity(intent);
2. 哪些页面应该接受外链启动
WMRouter 美团路由也是这个思路的,区别是,我们是所有的页面都可以由外链启动还是由单一页面接受外链启动?
目前看来主流都是由专门一个页面接受外链启动,解读参数,然后再启动其他页面的,WMRouter 美团路由在官方文档里面也是这样推荐的
另外有一篇关于这个的详细解释,很不错