Android 客户端路由框架的整理和思考
前言
提起项目模块化(组件化),相信大家并不陌生。这两年行业内也确实兴起了一股模块化的浪潮,大家都兴致勃勃的投入了其中,感觉要是一个搞研发的没听过模块化,出去跟不好意思跟朋友聊天了。要想实现项目的模块化,就离不开底层路由架构的支持。在这期间,各方大神各显神通,产生了许多优质的开源项目。其中最为出名的自然是阿里出品的 ARouter,大厂出品,必属精品。而公司项目启动模块化大约是在两年前,没能赶上大厂的福利,所以只能自己摸索。当时最大的启发来自于这篇文章:
Android架构思考(模块化、多进程)
文章的作者应该是国内最早一批实践项目模块化的先驱,其提出的:局域网路由解决单进程项目模块化,广域网路由解决多进程项目模块化。至今让人耳目一新,向作者致敬。公司项目模块化的启动就建立在这样的一个基础上,经过两年的业务演变,与当初设计已经大不相同,再加上 kotlin 的引入,是时候整理一下底层的路由框架了。
思考
随着业务需求的发展,项目代码量越来越大,包含的业务模块也越来越多,模块之间耦合的程度也越来越高。随之而来的几个问题,值得我们思考:
- 项目编译速度越来越慢
- 业务模块如何解耦和重用
- 多个小团队之间如何并行开发和测试
这些问题的解决方案其实有两个:插件化和模块化。
插件化虽然功能强大,但是问题也多,当初考虑到公司项目的实际情况,最终选择了利用路由机制来实现模块化解耦,采用的是前文提到的作者设计的路由框架:ModularizationArchitecture。在这两年的实践过程中,慢慢的遇到了一些问题:
- 不支持单独简洁的路由跳转
- 新增一个路由服务的步骤过于繁琐
- 请求一个路由服务的步骤过于繁琐
- 提出的 Provider 和 Action 思想维护成本持续增加
- 设计的路由结果异步回调的方式不是很友好
本想带着这些问题和作者反馈交流,可惜发现作者已经停止维护这个项目了。于是只好自己动手,基于 kotlin 这门语言重新整理设计项目所需要的路由框架。
分析
由于公司项目过于庞大,不利于讲解。为了方便大家理解,所以我们抽取出了一个简洁的工程作为示例:
工程结构如上图所示,并且 module 可以根据项目的业务情况不停的增加,这里只列举了 module-a , module-b 两个 module 作为示例。现在梳理一下,我们的需求是什么:
- 顶层的 app 可以自由组装不同的 module 实现差异化编译。
- 不同的业务模块之间没有相互依赖,都是通过依赖底层的 router 来实现路由通信
这样做的带来的好处,刚好可以解决前面我们思考的一些问题:
- 可以只选择组装自己负责的 module 提升了编译速度
- 可以快速的测试和发布子应用
- 多个团队之间可以并行开发各自的 module
而想要实现这样的架构,我们需要解决两个问题:路由框架和模块拆分。
模块拆分这个问题,我们会在后续的文章中进行整理,这里就不再讨论了,今天主角是路由框架。前面提到过,公司项目其实在两年前就已经引入了一个路由框架,但是在慢慢的实践过程中遇到了许多问题。而我们现在想要做的就是重新整理和设计来解决这些问题。继续梳理一下,我们理想中的路由框架应该是这样的:
- 支持自由组装不同的 module 实现差异化编译
- 提供页面路由和拦截器
- 提供方法路由包括:同步和异步
- 简洁方便
自由组装 module 进行编译可以减少我们编译代码的体量,提升编译速度。页面路由用来打开其他 module 的界面,拦截器用于处理特殊业务在界面跳转之间的切入,比较典型的就是登录状态的检测。方法路由用来访问其他 module 的功能,提供了同步和异步两种方式。最后的简洁方便,是希望通过编译时注解的方式来解放生产力。那么我们现在就来具体看看今天的主角: XRouter 。
配置
1.在 root’s build.gradle 中加入 jcenter 仓库
allprojects {repositories {...jcenter()}
}
2.在 app’s build.gradle 和 module’s build.gradle 中启用 kapt
...
apply plugin: 'kotlin-kapt'
3.在 app’s build.gradle 和 module’s build.gradle 中添加依赖(请使用最新版本)
dependencies {...implementation 'com.xuyefeng:xrouter-core:1.1.3'kapt 'com.xuyefeng:xrouter-compiler:1.0.6'
}
4.在 app’s build.gradle 和 module’s build.gradle 中注册路由 Module
kapt {arguments {arg("XRouterModule", project.getName())}
}
5.在 app’s build.gradle 中注册路由 App
kapt {arguments {arg("XRouterApp", project.getName() + ",modulea,moduleb")}
}
- 路由 App 由三个路由 module 构成,分别是 app、modulea 和 moduleb。对应的是第4步注册的路由 module 的工程名称
- 这里可以根据自由组装的 module 动态设置 XRouterApp
6.初始化 XRouter
@RouterApp
class BaseApplication : Application() {override fun onCreate() {super.onCreate()XRouter.init(this, BuildConfig.DEBUG)}
}
- 推荐在 Application 中初始化,并且添加注解 @RouterApp
- 在 debug 模式下,可以使用 XRouter 作为 tag 过滤日志信息
使用
页面路由
1.注解页面(支持一个页面对应多个路由地址)
@Router("www.baidu.com")
class MainActivity : AppCompatActivity()@Router("www.baidu.com", "www.google.com")
class MainActivity : AppCompatActivity()
2.跳转页面
// 常用跳转
XRouter.with(context).target("www.google.com").jump()// 自定义intentFlags
XRouter.with(context).target("www.google.com").intentFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP).jump()// 自定义跳转动画
XRouter.with(context).target("www.google.com").transition(android.R.anim.fade_in, android.R.anim.fade_out).jump()// 通过url拼接参数
XRouter.with(context).target("www.google.com?name=blue&age=18").jump()// 通过bundle传递参数
XRouter.with(context).target("www.google.com").data("name", "blue").data("age", 18).data(Bundle()).jump()// startActivityForResult
XRouter.with(context).target("www.google.com").requestCode(1001).jump()// 路由结果,只关心成功
XRouter.with(context).target("www.google.com").jump {// jump success to do sth}// 路由结果,只关心失败
XRouter.with(context).target("www.google.com").jump({// jump failure to do sth})// 路由结果,包含失败和成功
XRouter.with(context).target("www.google.com").jump({// jump failure to do sth}, {// jump success to do sth})
3.拦截器
@RouterInterceptor(priority = 8)
public class LoginInterceptor implements XRouterInterceptor {@Overridepublic void onInit(@NotNull Context context) {// do something in application init}@Overridepublic void onProcess(@NotNull XRouterInterceptorCallback callback) {// check login status...// check successcallback.onContinue();// or check failurecallback.onIntercept("check login error");}
}
- 注解参数 priority 决定拦截器的优先级,默认是5,数值越大,优先级越高
- 可以定义多个拦截器,根据优先级依次执行
- onInit 方法在 XRouter 初始化的时候被调用,可以用于做拦截器初始化
- onProcess 方法在页面路由中被调用,可以用于做页面拦截。经典场景是页面路由的过程中需要检测登录状态,如果登录状态失效,终止原路由,改为跳转至登录界面
方法路由
1.注解方法
@Router("toast")
fun toast(context: Context, routerParams: XRouterParams): XRouterResult {Toast.makeText(context, "toast from other module", Toast.LENGTH_SHORT).show()return XRouterResult.Builder().build()
}@Router("getSum", async = true)
fun getSum(context: Context, routerParams: XRouterParams, callback: XRouterCallback?) {// 获取对象数据val fragment = routerParams.obj as Fragment// 获取普通数据val a = routerParams.data.getInt("a")val b = routerParams.data.getInt("b")...val sum = a + bval result = XRouterResult.Builder().data("sum", sum).obj(fragment).build()callback?.onRouterSuccess(result)// orcallback?.onRouterError(result)
}
- 注解的方法需要为全局的静态方法
- 注解的名称可以跟方法名不同,注解的名称用于查找目标方法,方法名用于执行该方法
- 默认为同步路由,可以通过注解参数 async 开启异步路由
- 所有的同步路由接收的参数都是 Context 和 XRouterParams,请保持写法一致
- 所有的异步路由接收的参数都是 Context、XRouterParams 和 XRouterCallback,请保持写法一致
- 路由传递过来的参数可以通过 XRouterParams 获取,路由结果的参数可以通过 XRouterResult 设置,都采用 data 传递普通参数(用法参照 bundle),obj 传递对象数据
2.调用方法
// 无参路由
XRouter.with(context).target("toast").route()// 通过bundle传递参数
XRouter.with(context).target("doSomething").data("name", "blue").data("age", 18).route()// 对象传递
XRouter.with(context).target("doSomething").obj(Fragment()).route()// 同步路由获取结果
val result = XRouter.with(context).target("getSum").data("a", 1).data("b", 2).route()// 异步路由获取结果,只关心成功
XRouter.with(context).target("getSum").data("a", 1).data("b", 2).route {// get paramsval sum = it.getData().getInt("sum")val fragment = it.getObj() as Fragment// route success to do sth}// 异步路由获取结果,只关心失败
XRouter.with(context).target("getSum").data("a", 1).data("b", 2).route ({// route failure to do sth})// 异步路由获取结果,包含失败和成功
XRouter.with(context).target("getSum").data("a", 1).data("b", 2).route ({// route failure to do sth},{// get paramsval sum = it.getData().getInt("sum")val fragment = it.getObj() as Fragment// route success to do sth})
混淆代码
-keep class com.blue.xrouter.** {*;}
-keep interface com.blue.xrouter.** {*;}
总结
关于路由框架,各家各户的实现都大同小异,思路基本上都是相通的,只是设计上的差异罢了。其实路由框架本身并没有太多的技术难点,因此本文并不是讨论什么高深的技术,只是针对实际项目路由框架的演变做了一些整理和思考。并且随着项目的继续发展,这也将是一个持续化的过程。以上就是公司项目在模块化道路上的一些探索,由于本人水平有限,难免有不足之处,望各位不吝指出,不胜感激。
最后再附上:github地址传送门 喜欢就star一下呗
Android 客户端路由框架的整理和思考相关推荐
- WMRouter:美团外卖Android开源路由框架
WMRouter是一款Android路由框架,基于组件化的设计思路,功能灵活,使用也比较简单. WMRouter最初用于解决美团外卖C端App在业务演进过程中的实际问题,之后逐步推广到了美团其他App ...
- 网易考拉Android客户端路由总线设计
1.前言 当前,Android路由框架已经有很多了,如雨后春笋般出现,大概是因为去年提出了Android组件化的概念.当一个产品的业务规模上升到一定程度,或者是跨团队开发时,团队/模块间的合作问题就会 ...
- 网易考拉Android客户端路由总线设计 1
1.前言 当前,Android路由框架已经有很多了,如雨后春笋般出现,大概是因为去年提出了Android组件化的概念.当一个产品的业务规模上升到一定程度,或者是跨团队开发时,团队/模块间的合作问题就会 ...
- java路由总线_网易考拉Android客户端路由总线设计
1.前言 $ e7 | ~% L) i7 @7 B& t3 T5 h* P/ e2 s 当前,Android路由框架已经有很多了,如雨后春笋般出现,大概是因为去年提出了Android组件化的 ...
- Android的路由框架用法
一.什么是android路由? 主要是映射页面跳转关系,根据路由表将页面请求分发到指定页面. 二.android路由使用场景 App接收到一个通知,点击通知打开App的某个页面 浏览器App中点击某个 ...
- Android—简单路由框架实践
简单路由框架实现: 1.目录结构 annotation和factory-compiler是两个java library,分别负责接口还有APT. 2.依赖关系 app作为主工程依赖所有 impleme ...
- android 组件路由框架,XRouter:组件化路由框架
添加jitpack仓库 allprojects { repositories { ... maven { url 'https://jitpack.io' } } } 添加依赖: dependenci ...
- 关于android屏幕尺寸适配的整理以及思考
一直以来android屏幕尺寸相关的东西我都很薄弱,什么dpi, ppi, 英寸我都比较疑惑,本文主要是理清概念,理解头条的屏幕适配原理,以为目前我工作是如何做UI适配的. 一些基础概念 屏幕尺寸 屏 ...
- android路由界面跳转获取携带参数,GitHub - Jude95/Ferryman: Android页面路由跳转框架...
Ferryman Android页面路由框架 主要解决项目初具规模后,页面跳转,传参,页面路由等功能代码十分冗余且难以管理的问题. 主要功能: Android 端页面路由,与 web 页面路由统一,非 ...
最新文章
- linux如何取文件列名,Linux ps 指定列名
- 瓜子二手车发12月二手车价格:汉兰达奥德赛CR-V保值率居首
- Ubuntu下安装JDK1.8并配置开发环境
- 图的BFS和DFS原理及实例分析(java)
- 财务管理与计算机论文,计算机小论文--浅论计算机与财务管理.doc
- StretchDIBits显示8位图问题
- mysql 重建索引,mysql优化之索引重建
- P1_M5_L1 Proportional-Integral-Derivative(PID) Control(比例-积分-微分:PID控制)
- ROS学习笔记9 —— launch文件
- Qt调用外部程序,启动并从中获取信息——QProcess
- JavaMail 使用POP3/SMTP服务发送QQ邮件
- javascript(一)
- 4k分辨率是多少(真4k与假4k区别)
- google map
- Alfira学习篇(Python)
- vmware虚拟机使用详解
- 路径规划——CH算法
- 学习没有动力的解决方法
- 打工人:是什么决定了你的薪资水平?一张图带你揭开涨薪秘诀!
- 最近一百年,全球涌现过哪些最顶尖的、最赚钱的公司?