ARouter的使用
官网github:
https://github.com/alibaba/ARouter/blob/master/README_CN.md
这里对其做了分类整理,以及一些想法,便于更方便理解。
跳转
跳转Activity
1、path跳转
path的规则:/group/child…至少两个“/”;和Activity的@Route注解值匹配
// 构建标准的路由请求
ARouter.getInstance().build("/home/main").navigation();
// 构建标准的路由请求,并指定分组
ARouter.getInstance().build("/home/main", "ap").navigation();
2、uri跳转
Uri uri;
ARouter.getInstance().build(uri).navigation();
3、startActivityForResult
ARouter.getInstance().build("/home/main", "ap").navigation(this, 5);
navigation的第一个参数***必须是Activity***,第二个参数则是RequestCode,因为不指定activity,无法得知应该从哪个任务栈启动,导致收不到result。
总结:
跳转activity的时候,但凡涉及activity的任务栈,必须使用navigation(context)并且context只能是activity。建议,如果当前可以获取activity,最好传了。
跳转Fragment
使用方式同Activity,navigation()方法会返回要跳到的对象实例,跳转Fragment可以拿到Fragment实例操作。
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
传值
ARouter提供了丰富的传值方式,基本满足使用。
// 直接传递Bundle
Bundle params = new Bundle();
ARouter.getInstance()
.build("/home/main")
.with(params)
.navigation();
// 指定Flag
ARouter.getInstance()
.build("/home/main")
.withFlags();
.navigation();
// 获取Fragment
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
// 对象传递
ARouter.getInstance()
.withObject("key", new TestObj("Jack", "Rose"))
.navigation();
// 觉得接口不够多,可以直接拿出Bundle赋值
ARouter.getInstance()
.build("/home/main")
.getExtra();
还提供了int、long、short等基本变量和String、Object、Serializable、Parcelable和对应的数组、List。种类相当丰富。
带动画的跳转
// 转场动画(常规方式)
ARouter.getInstance()
.build("/test/activity2")
.withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
.navigation(this);
// 转场动画(API16+)
ActivityOptionsCompat compat = ActivityOptionsCompat.
makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0);
ARouter.getInstance()
.build("/test/activity2")
.withOptionsCompat(compat)
.navigation();
注意 ,makeSceneTransitionAnimation 使用共享元素的时候,需要在navigation方法中传入当前Activity。
more
我们经常需要在目标页面中配置一些属性,比方说"是否需要登陆"之类的
可以通过 Route 注解中的 extras 属性进行扩展,这个属性是一个 int值,换句话说,单个int有4字节,也就是32位,可以配置32个开关 剩下的可以自行发挥,通过字节操作可以标识32个开关,通过开关标记目标页面的一些属性,在拦截器中可以拿到这个标记进行业务逻辑判断
@Route(path = “/test/activity”, extras = Consts.XXXX)
服务
服务是ARouter中很重要的一个概念,ARouter的所有功能都是服务,本质上是接口IProvider的实现类,它也是组件化之间通信的桥梁:
public interface IProvider {
/**
* Do your init work in this method, it well be call when processor has been load.
*
* @param context ctx
*/
void init(Context context);
}
后面的拦截器、重定向、降级、di注入都是服务。只需要实现对应接口,并添加注解,就能自动将服务注册到框架。
拦截器
拦截器可以控制是不是可以继续跳转,以及跳转到哪里。
public interface IInterceptor extends IProvider {
/**
* The operation of this interceptor.
*
* @param postcard meta
* @param callback cb
*/
void process(Postcard postcard, InterceptorCallback callback);
}
如果可以继续,调用callback.continue(postcard), postcard是跳转的信息类,包含了uri、path、传递的数据等,可以在这里对其做处理;如果不继续,可以调用callback.interrept()停止跳转。
注意:
1、使用绿色通道可以跳过所有拦截器:
ARouter.getInstance().build("/home/main").greenChannel().navigation();
2、服务本身不会被拦截;
3、fragment 的navigation()不会被拦截
重定向
重定向是在跳转开始前发生的“准备工作”
public interface PathReplaceService extends IProvider {
/**
* For normal path.
*
* @param path raw path
*/
String forString(String path);
/**
* For uri type.
*
* @param uri raw uri
*/
Uri forUri(Uri uri);
}
提供了对path和Uri两种重定向。入参为当前路径,出参为重定向后的路径。
降级
降级是一个成熟框架应有的优秀设计。
当跳转失败(路由不正确、版本未升级等),会回掉给onLost方法,可以在这时给出提示或者转到备用方案。
public interface DegradeService extends IProvider {
/**
* Router has lost.
*
* @param postcard meta
*/
void onLost(Context context, Postcard postcard);
}
di自动注入
前面说到ARouter提供了强大丰富的跳转传参,与其匹配的,就是怎么接参数。
public interface AutowiredService extends IProvider {
/**
* Autowired core.
* @param instance the instance who need autowired.
*/
void autowire(Object instance);
}
这个服务框架帮我们实现好了,只需要在跳转的对象里使用Autowired注解,并且调用了
ARouter.getInstance().inject(this);
就可以获取到对应的值。示例:
/**
* 身份证名字
*/
@Autowired(name = Extras.ID_CARD_NAME,required = true)
String name ;
required = true 表示必传。
自定义服务
除了框架定义的服务,我们可以自己定义服务,这是组件化中最重要的一环,通过定义服务,注册到ARouter,实现Client-Router-Client隔离。
可以对着上述的几个服务依样画葫芦:
服务提供方Client1:
// 声明接口,其他组件通过接口来调用服务
public interface HelloService extends IProvider {
String sayHello(String name);
}
// 实现接口
@Route(path = "/service/hello", name = "测试服务")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello, " + name;
}
@Override
public void init(Context context) {
}
}
然后服务使用方Client2:
public class Test {
@Autowired
HelloService helloService;
@Autowired(name = "/service/hello")
HelloService helloService2;
HelloService helloService3;
HelloService helloService4;
public Test() {
ARouter.getInstance().inject(this);
}
public void testService() {
// 1. (推荐)使用依赖注入的方式发现服务,通过注解标注字段,即可使用,无需主动获取
// Autowired注解中标注name之后,将会使用byName的方式注入对应的字段,不设置name属性,会默认使用byType的方式发现服务(当同一接口有多个实现的时候,必须使用byName的方式发现服务)
helloService.sayHello("Vergil");
helloService2.sayHello("Vergil");
// 2. 使用依赖查找的方式发现服务,主动去发现服务并使用,下面两种方式分别是byName和byType
helloService3 = ARouter.getInstance().navigation(HelloService.class);
helloService4 = (HelloService) ARouter.getInstance().build("/service/hello").navigation();
helloService3.sayHello("Vergil");
helloService4.sayHello("Vergil");
}
}
这样就可以完成调用。从上面的示例可以看出,服务本身就支持AOP。
注意:这种调用方式,决定了自定义的服务必须要在基础架构的包里,Client1和Client2都依赖这个基础架构包,不然Client2找不到这个类;
applink
从外部唤起app有两种场景,applink和推送,这时候没有办法直接使用ARouter,依然需要一个中间Activity来做中转,在中转Activity中再使用ARouter跳转:
<activity
android:name=".RouterActivity"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<!-- App Links -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="yourhost"
android:scheme="yourscheme" />
</intent-filter>
</activity>
点击带有scheme/host 的url,会唤起RouterActivity:
public class RouterManagerActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri uri = getIntent().getData();
ARouter.getInstance().build(uri).navigation();
finish();
}
}
踩坑
1、startActivityForResult 跳转,一定要带上当前的activity;
2、withFlag 时,如果是clearTop的flag,也要带当前activity;
3、使用Arouter接受参数,要用autowired,也要inject(this),不然getIntent拿不到数据;
4、path必须至少有两个"/";
5、拦截器、重定向器、降级服务的执行顺序是:起点-》重定向器-》降级服务-》拦截器-》终点;如果降级onLost了,拦截器不会生效。
6、1.2.4版本build的时候,强制要求uri有path,就是说,像https://www.baidu.com 这样的uri会直接报错
7、降级发生后,debug下ARouter会默认Toast,但是这个我们可能并不想要。可以在初始化时不开启debugable。
8、如果在navigation 方法中给了callback,降级service会被中断(个人觉得实在不合理。。。一个service级别的东西被一个callback 给中断了)。
思考
ARouter应该怎么用?从上面的分析看,ARouter功能两个大板块,一个是跳转,一个是服务。那么使用也该分两个方向:
1、跳转
使用路由跳转屏蔽了具体要跳到的XXXActivity.class,用字符串(path)做映射,有两个好处,第一个是多目录包下不需要依赖XXXActivity.class,方便拆成多个aar或者apk;第二个是可以动态切换跳转的目的地,只要build的path由服务端下发。
但是是不是每个Activity都要注册在ARouter呢?其实不需要,对于每个组件来说,前一个页面和后一个页面基本是确定的,并且耦合了参数,其实只要控制好组件的入口activity就行了,组件内部怎么跳转并不重要,因为他们的关系是固定的。
2、服务
除了框架提供的,自己自定义的服务可以用来做组件间通信。
假设Client1、Client2、ARouter是三个独立的包,那么Provider放在ARouter所在的包里,Client1和Client2依赖ARouter,那么这种可以直接拿到服务实例,向上文自定义服务写的例子。但是这种例子的问题是,需要把这个服务的类,放到ARouter的包里,否则Client2拿不到实例。
使用ARouter实现全路由分发
背景:许多跳转路由都是服务下发的,客户端透转给路由,交由路由跳转,但是服务端下发的,可能是本地在ARouter中注册的path,也可能是一个h5链接,希望用h5打开。原来的方案,是Arouter处理本地路由,对于h5,再单独处理,这样,如果一个接口后端原来给的是本地path,后来希望指向另一个h5页面,就没办法动态切换;反之亦然。
解决方案:
1、升级ARouter(我本地用的1.3.1),升级后的版本,可以用重定向,在url后面追加固定的path如“/DEAULT/PATH”,绕开build时必须要有path,这样就可以处理类似“https://www.baidu.com” 这样的链接;示例:
@Route(path = "/router/MyPathReplaceService") // 必须标明注解
public class MyPathReplaceService implements PathReplaceService {
public static final String DEFAULT_PATH = "/DEFAULT/PATH";
@Override
public String forString(String path) {
}
@Override
public Uri forUri(Uri uri) {
// 空path的uri 添加默认path
if (TextUtils.isEmpty(uri.getPath())) {
uri = uri.buildUpon().appendEncodedPath(DEFAULT_PATH).build();
}
return uri;
}
@Override
public void init(Context context) {
}
}
2、利用降级服务,示例如下:
@Route(path = “/router/MyDegradeService”) // 必须标明注解
public class MyDegradeService implements IProvider{
public void onLost(Context context, Postcard postcard) {
//判断是否是想跳转本地,如果是,走降级逻辑;不是,认为是跳转到h5.
if (isJumpToNative(postcard)) {
Toasts.shortToast(“升级版本!”);
} else {
Uri targetUri = postcard.getUri();
//如果有默认path,恢复
if (MyPathReplaceService.DEFAULT_PATH.equals(postcard.getUri().getPath())) {
targetUri = targetUri.buildUpon().path("").build();
}
jumpToWebView(targetUri);
}
}
}
说明:
1、为什么不用拦截器?因为拦截器的执行顺序是在降级之后,并且很多情况拦截器不起效(上文有说,个人认为拦截器的定位没有设计好)
2、跳转h5会有toast弹出来,这个是ARouter走到降级后自己在debug环境下弹出的(如果想用降级搞点事情,这个Toast有些多余)
3、判断是否是想跳本地,可以通过path判断,因为本地路由,都是/group/child或者nativeshceme/group/child这种格式。
4、由于ARouter自己降级服务会被callback中断,所以可以不实现ARouter自己的降级服务,改为设置默认callback代理:
...
degradeService = ARouter.getInstance().navigation(MyDegradeService::class.java)
...
private var defaultCallBack: NavigationCallback = object : NavigationCallback {
var callback : NavigationCallback?=null
override fun onLost(postcard: Postcard?) {
degradeService?.onLost(postcard)
callback?.onLost(postcard)
}
override fun onFound(postcard: Postcard?) {
callback?.onLost(postcard)
}
override fun onInterrupt(postcard: Postcard?) {
callback?.onInterrupt(postcard)
}
override fun onArrival(postcard: Postcard?) {
callback?.onArrival(postcard)
}
}
....
fun navigation(context: Context?): Any? {
return ARouter.getInstance().navigation(context, postcard, -1, defaultCallBack)
}
...
fun navigation(context: Context?,callback :NavigationCallback?): Any? {
defaultCallBack.callback = callbak
return ARouter.getInstance().navigation(context, postcard, -1, defaultCallBack)
}