ActivityRouter :https://github.com/mzule/ActivityRouter
Header
在如今的 Android 组件化开发中,一款好的路由框架是不可或缺的。比如目前阿里的 ARouter 、美团的 WMRouter 等。路由框架可以降低 Activity 之间的耦合,从而在不需要关心目标 Activity 的具体实现类, 利用协议完成跳转。
ActivityRouter使用方法
在AndroidManifest.xml配置
<activity
android:name="com.github.mzule.activityrouter.router.RouterActivity"
android:theme="@android:style/Theme.NoDisplay">
<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="mzule" /><!--改成自己的scheme-->
</intent-filter>
</activity>
在需要配置的Activity上添加注解
@Router("main")
public class MainActivity extends Activity {
...
}
想要跳转到 MainActivity ,只要调用以下代码即可
Routers.open(context, "mzule://main")
如果想用 @Router 来调用方法
@Router("logout")
public static void logout(Context context, Bundle bundle) {
Toast.makeText(context, "logout", Toast.LENGTH_SHORT).show();
}
源码解析
ActivityRouter 工程的结构如下
- activityrouter: 路由跳转的具体实现代码
- annotaition: 路由注解
- app: 路由 demo
- app_module: 路由 demo module
- compiler: 注解处理
- stub: 壳 module
annotation
先来看看 Router 的注解
1 | @Target({ElementType.TYPE, ElementType.METHOD}) |
@Router 定义了该 Activity 路由的名字以及一些参数,这里可以注意到 @Retention 是 CLASS ,所以后面肯定在编译期间利用 Processor 来解析 @Router 生成路由表的。
另外,看到 @Target 是 ElementType.TYPE 和 ElementType.METHOD ,其实 @Router 除了跳转 Activity 之外,还有一个功能就是可以执行方法,只要在方法加上 @Router 即可。
路由表的生成源码我们到后面再讲,先来看看有了协议之后,Routers 是如何实现跳转 Activity 的。
activityrouter
1 | public class Routers { |
可以看到不同的 open openForResult 方法重载,最后都是调用了 open(Context context, Uri uri, int requestCode, RouterCallback callback)
。那么接着跟踪:
1 | private static boolean open(Context context, Uri uri, int requestCode, RouterCallback callback) { |
open 方法中有很多都是不同状态下 callback 的回调,真正跳转的逻辑放在了 doOpen 方法中。
1 | private static boolean doOpen(Context context, Uri uri, int requestCode) { |
我们一步步来分析 doOpen 中的具体步骤。先从 Path path = Path.create(uri);
开始看。
1 | public static Path create(Uri uri) { |
上面的代码看完可能会让有些同学感觉很绕,简单地解释下。上面这段代码主要做的事情就是把传入的 uri 解析,生成了一个 Path 对象。该 Path 对象主要包含了 uri 中的 scheme 、host 、path 这三部分,利用单链表的特点把这三部分串连起来。这个 Path 也就是后面用来匹配路由表用的。
可能还有一些同学对 uri 的 scheme 、 host 等不了解,在这里就简单地普及下。
比如现在有一个 uri
mzule://main/home/login?username=tom
这个 uri 就可以分解为
scheme :mzule ,就是 “://” 前面的字符串
host :main ,“://” 后面的字符串
path :home 和 login 都属于 path,就是 “/” 与 “/” 之间的字符串
query :参数,可以理解成键值对,多个之间用 & 连接。获取 username 这个参数,对应的值就是 tom
生成好了 Path 之后,就是遍历路由表进行匹配了。
所谓的路由表其实就是一个 List
private static List<Mapping> mappings = new ArrayList<>();
在调用 RouterInit.init 时候会把路由数据添加到 List 中。准确的说, RouterInit.init 中调用了 Router.map 方法来实现添加的。
1 | static void map(String format, Class<? extends Activity> activity, MethodInvoker method, ExtraTypes extraTypes) { |
那么,我们来看下 Mapping 的结构
1 | public class Mapping { |
- format 就是我们传入的 uri
- activity 就是路由对应的 activity
- method 表示是否是执行方法
- extraTypes 是所携带的参数类型
- formatPath 就是 uri 对应的 Path
具体的 Mapping 初始化是在 Processor 生成的代码中完成的,我们到后面再讲。
在回过头来看 doOpen 方法,在 mapping.match(path) 方法中用来判断该 path 有没有匹配路由表中的路由
1 | public boolean match(Path fullLink) { |
Mapping 的 match 方法就是把自身的 formatPath 和 fullLink 进行比较,最终调用的还是 Path.match 方法,本质就是把 Path 链表中的每一项进行比较,来判断两个 Path 是否相等。这里就不展示具体源码了,有兴趣的同学可以自己回去看。
再后面的就是判断 activity ,如果是空的,就认为是执行方法,否则就构造 Intent 来实现跳转,再利用 requestCode 来判断是 startActivity 还是 startActivityForResult 。其中执行方法主要调用了 MethodInvoker.invoke 方法
1 | public interface MethodInvoker { |
再重点关注下 mapping.parseExtras(uri) 这句代码。这里主要做的事情就是构造 Bundle 传入 uri 的参数。
1 | public Bundle parseExtras(Uri uri) { |
这代码很简单,基本上都加了注释,相信大家都看得懂,就不讲咯。
到这里,整个 ActivityRouter 的流程就讲完啦。
剩下的,就是 Processor 解析注解生成代码了。
compiler
先告诉处理器支持的注解
1 | @Override |
剩下主要看 RouterProcessor 的 process 方法。
方法的代码如下:
1 | @Override |
process 方法中的逻辑可以分为三部分:
- 判断是否有 @module 和 @modules ,即是否是组件化开发的
- 生成 RouterInit
- 生成 RouterMapping
那我们慢慢分析,先来看第一部分
1 | // module |
接下来就是生成 RouterInit 类
1 | if (hasModules) { |
如果是多 module 组件化开发,最终会调用 generateModulesRouterInit ,否则调用的就是默认的 generateDefaultRouterInit 。
这里我们就看 generateModulesRouterInit 的代码吧。
1 | private void generateModulesRouterInit(String[] moduleNames) { |
可以看到,利用了 javapoet 来生成 java 代码,这代码很简单,就不用多讲啦,直接来看下最后生成 RouterInit 类的代码吧
1 | package com.github.mzule.activityrouter.router; |
RouterInit 生成好之后,最后的工作就是生成对应的 RouterMapping_app 和 RouterMapping_sdk 这两个类了。
生成的入口就是 handleRouter(moduleName, roundEnv) 方法。
1 | private boolean handleRouter(String genClassName, RoundEnvironment roundEnv) { |
来看一下最后生成的 RouterMapping_xxx 的代码:
1 | public final class RouterMapping_app { |
至此,ActivityRouter 所有的流程都已经讲完啦!!!
RouterActivity
对啦,还有一点,ActivityRouter 支持从外部唤起 Activity 。
在 AndroidManifest.xml 中声明 RouterActivity ,填写对应 scheme 和 host 。
<activity
android:name="com.github.mzule.activityrouter.router.RouterActivity"
android:theme="@android:style/Theme.NoDisplay">
...
<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="http" android:host="mzule.com" />
</intent-filter>
</activity>
其实先唤起的是 RouterActivity ,然后在 RouterActivity 中根据 uri 再跳转到对应的 Activity ,这点可以从 RouterActivity 的代码中印证。
1 | public class RouterActivity extends Activity { |
这下真的是讲完啦
讲完啦
完啦
啦
Footer
其实现在市面的路由框架基本上都是这种套路,了解其中的奥义可以更好地使用它。
感兴趣的同学可以再去看下 ARouter 之类的源码,相信收获会更大!
再见👋