大家好, 此文用一个较详细的叙述来介绍 Android
的组件化框架 Component, 我从 17 年开始设计并且研究组件化框架的. 以及和其他框架相比, 为什么更优秀, 更好用。下文且听我细细道来~
其实最简短的介绍就是下面几件事:
Activity
跳转 和 Fragment
获取其实上面的模块交互中, 核心是 跨模块调用
, 但是为什么路由跳转也同样重要呢?而且要单拎出来.
是因为在 Android
环境中, 到处充斥着 Activity
跳转、获取Fragment
. 这种操作特别多. 如果你利用跨模块调用去做, 你会发现虽然能做, 但是十分的不方便, 而且不人性化, 所以路由跳转也同样重要
其实只要解决了最根本的跨模块调用功能的都算组件化框架。下面的分析不带有攻击性~~~
比如 CC, 它的设计就是解决了跨模块调用的. 它的出发点就是基于解决 跨模块调用这点
. 它将这方面做得很好.
基本上除了它之外的其他框架, 都是基于 URI
设计的. 会比较侧重于路由方面的功能, 跨模块调用为辅. 比如
Component
介绍Component
除了实现了基本的路由跳转, 跨服务调用等基本功能外. 还有很多令人欣喜的功能
ActivityResult
我们很多人都可能遇到一个不是经常遇到的问题, 也就是在某一个地方( Adapter
, Dialog
,Service
), 忽然要跳转一个界面去获取这个界面返回的 ActivityResult
, 这个很多人都会说, 可以使用 EventBus
之类的库呀. 那你赢了好吧. 确实可以用, 但是不鼓励
如果不借助任何的库, 你得先拿到 Activity
去调用 startActivityForResult
, 然后重写 Activity
的 onActivityResult
方法去获取到 Intent
进而拿到数据, 传给需要的地方. 流程图如下:
很明显, 代码不仅写得多, 而且完全没有技术含量, 写多了真的会吐的有没有. 而且每次还得和 Activity
建立接口或者方法的交互.
那 Component
是怎么做的呢?
Router
.with(getContext())
.host("component1")
.path("main")
.putString("name", "cxj")
.putString("pass", "123")
.requestCode(456) // requestCode
.forwardForResult(new BiCallback.BiCallbackAdapter<ActivityResult>() {
@Override
public void onSuccess(@NonNull RouterResult result,
@NonNull ActivityResult activityResult) {
super.onSuccess(result, activityResult);
// 你就拿到了 ActivityResult
}
});
Activity
交互的环节,Component
可以让你的代码更加的紧凑, 不会因为你需要 startActivityForResult
而分割你的代码.是不是很爽?会很好的让你写代码心情愉悦. 内部实现原理其实和 Glide
和 RxPermission
一致, 都是通过预埋一个 Fragment
实现的.
当然了这部分代码其实可以抽取出来做成一个工具类啥的, 有兴趣的可以研究下. 并不是只能依赖 Component
才能拥有此功能
什么是页面拦截器
?
我们知道很多框架都有拦截器
的概念. 页面拦截器
说白了. 就是只对某些路由请求有效的拦截器. 画图如下
上图可以很清晰的说明, 当你的路由是 router://order/detail
你目标是订单详情界面, 会经过两个全局拦截器.
当框架发现你跳转的界面有一个 登陆拦截器
( 页面拦截器
), 则框架就会先执行登陆拦截器, 登陆拦截器会完成整个登陆的过程, 在登陆成功之后, 自动完成之前的跳转.
上图解决了一个啥问题呢?
你在平时的写代码中, 你处理这些场景只有两种方式:
在每一次跳转之前先解决好跳转到目标界面的先决条件
在目标界面进行需要的先决条件的处理.
两种方式都有各自的缺点, 都挺恶心的. Component
呢就很巧妙的在跳转前处理了, 但是却不用发起的界面多写任何一句代码.
有些人说 ARouter
也能实现啊, 那么区别和优势在哪里呢?
Arouter
每一个用注解声明的拦截器都是全局拦截器, 也就是说每一个路由都会执行. 而 Component
的页面拦截器只有当最终跳转的目标是某个界面的时候, 某个界面上标记的拦截器才会被执行. 所以页面拦截器不会每一个路由都会执行到.ARouter
当判断到需要登录的时候, 会帮你跳转到登录, 本次跳转就结束了. 当你完成登陆之后, 你需要再次发起跳转.Component
在执行到某个拦截器, 拦截器会负责帮你完成需要的工作, 然后才会继续之前的跳转. 不用用户去再次发起跳转.此处展示一个订单详情的标记范例:
@RouterAnno(
// host 可省略不写
host = "order",
path = "detail",
// 页面拦截器(此处是一个登陆拦截器, 完成自动登陆)
interceptorNames = "user.login",
desc = "订单详情界面"
)
public class OrderDetailAct extends AppCompatActivity{
// ........
}
Intent
标记第三方界面或者系统界面我们用过组件化框架的人都知道, 所有组件化框架都是针对项目中的 Activity
可以通过框架路由. 但是系统和第三方的界面就不能囊括在内. 因为基于 URI
设计的那些组件化框架都没法对系统的界面进行一个路由标记. 所以没法跳转. 但是其实这又是一个很有必要的功能.
所以 Component
支持你自定义 Intent
. 如下就是自定义了一个静态方法, 返回系统的拍照界面的 Intent
, 并且对拍照界面的 Intent
标记了一个 页面拦截器
去处理拍照权限的问题. 这时候你在任何的地方只需要
Router.with(this).hostAndPath("system/takePhone").forward()
即可完成跳转, 并且不需要关心权限问题.
/**
* 拍照界面,需要拍照权限
*/
@Nullable
@RouterAnno(
host = "system",
path = "takePhone",
// 拍照权限申请拦截器
interceptorNames = "help.cameraPermision"
)
public static Intent takePictureIntent(@NonNull RouterRequest request) {
Intent intent = new Intent();
// 指定开启系统相机的Action
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
return intent;
}
自定义 Intent
可以实现让你对任何一个界面进行标记, 系统界面、第三方界面、你自己写的界面. 从而实现任何一个界面都可路由.
而自定义 Intent
和 页面拦截器
配合就可以达到更妙的效果. 如下为示意图.
我们都用过 Retrofit
, 它为何受欢迎?是因为它把对一个网络接口的调用用一个接口方法来描述出来. 方便、易用. 并且让你的所有网络接口都聚集在一块. 好维护
那我们组件化为何会有路由 Api 呢?
有很多朋友反馈呀. 当跳转到一个界面的入口特别多的时候. 如下的代码就会被 copy
很多遍, 当有改动的时候改的地方多, 并且不好找.
Router.with(this)
.hostAndPath("order/detail")
.putString("xxx1","xxx")
.putInt("xxx2", 5)
.putLog("xxx3", 555L)
.forward();
在没有组件化之前, 代码没有分离之前, 很多小伙伴会在目标界面写一个静态的 startAct
方法, 跳转到该界面都会调用此方法. 现在组件化了很多时候都像上述一样 Copy
, 不好维护.
首先这个问题在我看来, 其实算是一个问题也不算一个问题. 在我看来, 组件化之后, 代码和资源的隔离, 肯定会出现这种情况. 其实也是一个必然性. 但是为了广大的朋友, 我这边特别注意到一个开源库的路由就是使用路由 Api
做的
ARetrofit 我觉得很棒的思路. 于是后面我也支持了这个功能. 最简单的一个范例:
@RouterApiAnno()
@HostAnno("user")
public interface UserApi {
@PathAnno("login")
void toLoginView(
Context context,
@ParameterAnno("name") String name,
@ParameterAnno("pass") String pass,
);
}
// 声明一个上述的接口, 然后你就可以下面这样子使用啦
Router.withApi(UserApi.class).toLoginView(this, "xiaojinzi", "123");
这种方式可以很好地让某一些入口特别多的跳转集中调用这个方法, 达到维护方便的作用!
看个人喜好. 我个人会更喜欢直接使用代码跳转.
为什么会聊这个话题. 有些人觉得拦截器
的执行线程有啥好聊的, 肯定在子线程
啊. 但是我也得告诉你. 得分场景.
很多路由框架, 拦截器
的执行线程在子线程
, 是为了能让拦截器
做任何事情. 因为它们的设计上, 路由跳转和服务发现(也可以叫做跨模块功能调用) 是设计在一块的. 拦截器
既能拦截到路由跳转, 也能拦截到功能的调用.
我不评价这种设计的好坏. Component
的服务发现和路由跳转是完全分离的两个过程.
Component
中只有 路由跳转
有拦截器的概念. 那么针对 路由跳转
. 不针对 功能调用
的场景下, 其实 拦截器
的执行线程应该在主线程更合理. 原因如下:
sdk
, 框架
, 网络库
… 换句话说就是纯功能的库或者组件.子线程
你想弹个加载框框, 你切线程?让当前线程先等着?然后各种线程的唤起?我想这个不是你想要的void
, 什么时候往下执行, 需要用户手动调用.比如这个权限申请的拦截器, 你什么时候处理好你要做的事情, 你什么时候调用 chain.proceed(chain.request());
让拦截器继续, 如果处理失败, 你调用 Callback
的 onError
返回错误即可. 这种功能, 其他框架处理的话, 就麻烦喽。。。
/**
* 电话权限申请的拦截器
*/
@InterceptorAnno("help.callPhoePermision")
public class CallPhoePermisionInterceptor implements RouterInterceptor {
@Override
public void intercept(final Chain chain) throws Exception {
PermissionsUtil.with(chain.request().getRawContext())
.request(Manifest.permission.CALL_PHONE)
.execute(new PermissionsCallback() {
@Override
public void onResult(boolean granted) {
if (granted) {
chain.proceed(chain.request());
} else {
chain.callback()
.onError(
new Exception("fail to request call phone permision")
);
}
}
});
}
}
当你用上组件化
框架去跳转, 你会发现整个路由的过程就不是立马跳转了, 而是经过一段时间的.
你可以任何和处理一个任务、一个耗时请求一样. 所以自动取消路由功能看起来没啥用, 其实挺有用:
比如用户点击了一个按钮, 发起跳转. 中间有一个拦截器呢, 发现要处理一个任务. 于是去做任务了.
这时候用户等不及了, 点击了返回键。。。。可想而知, 之前发出去的路由如果不能被取消, 当他路由失败的时候, 回调的时候. 因为你没有判断界面是否销毁, 处理不得当. 奔溃了。。。
所以 Component
中, 如果你的路由发起的时候是 Fragment
或者 Activity
. 当相关的 Fragment
或者 Activity
销毁了, 路由自动取消. 不会回调的.
Component
也完全支持 RxJava
Component
可以让一个跳转返回一个 [Observable]
, 让你可以把跳转和其他 [Observable]
去结合使用.
比如你们用的 Retrofit
支持 RxJava
返回一个 [Observable]
. 同理, Component
也完全支持
比如下面的范例
Completable completable = RxRouter
.with(this)
.host("order")
.path("detail")
.call();
completable.subscribe();
Single<ActivityResult> single = RxRouter
.with(this)
.host("help")
.path("address")
.activityResultCall();
single.subscribe(activityResult -> {
System.out.println("拿到 ActivityResult");
});
Fragment
Androidx
Idea Plugin
H5
module
和其他基于 URI
框架相比, Component
的功能算是最全面了, 而且稳定!
有兴趣的请去我的 github
查看源码. 喜欢我的框架的小伙帮请不要吝啬你的 star
为我加油吧!