ARouter是由阿里推出的一款页面路由框架,主要用于组件化开发过程中,组件之间页面的跳转和通信,确保组件之间没有相互依赖,也能进行跳转
Android原始的页面跳转最常用到的是显示Intent和隐式Intent跳转方式
显示Intent跳转:
Intent intent = new Intent(this,XXXActivity.class);
startActivity(intent);
缺点:由于需要直接持有对应class,从而导致了强依赖关系,提高了耦合度
隐式Intent跳转:
Intent intent = new Intent();
intent.setAction(“com.android.activity.MY_ACTION”);
startActivity(intent);
缺点:action等属性的定义在Manifest,导致了扩展性较差,规则集中式管理,导致协作变得非常困难。
使用原生Intent跳转时,跳转的过程开发者无法干预,一些埋点,登录判断等通用的逻辑操作比较难实现,不可能再每一个页面去单独判断,我们的目标是在页面跳转的过程统一去判断,而原生的跳转方式无法实现,所以才有了ARouter页面路由。
1.可以实现多个组件之间的跳转,确保各个组件之间相互独立,互不依赖
2.可以实现自动注册(只要添加了相关注解,就可以实现自动注册到ARouter框架)
3.可以知晓页面跳转的过程,对跳转的过程进行监听(降级策略)
4.可以对页面跳转进行拦截(登录判断,埋点)
5.支持InstantRun
6.可以直接解析标准URL进行跳转,并自动注入参数到目标页面中
7.可以通过控制反转来做组件解耦(IProvider)
1.从外部URL映射到内部页面,以及参数传递与解析
2.跨模块页面跳转,模块间解耦
3.拦截跳转过程,处理登陆、埋点等逻辑
4.跨模块API调用,通过控制反转来做组件解耦
ARouter的原理:
ARouter框架主要是采用注解的形式,为目标Activity添加@Route注解,我们称之为路由。路由框架会在编译时期通过apt生成一些存储path和activity.class映射关系的类文件,当app进程启动的时候,加载目标Activity类文件时,把保存这些映射关系的数据读到内存(保存在map中,path=A.class),然后在进行路由跳转时,通过build()方法传入的目标页面的path路劲,ARouter框架会用这个path路劲从路由表(map容器)里面找到对应的Activity.class类文件,然后new Intent(context, activity.Class),当调用ARouter的withString()方法它的内部会调用intent.putExtra(String name, String value),调用navigation()方法,它的内部会调用startActivity(intent)进行跳转,这样便可以实现两个相互没有依赖的module顺利的启动对方的Activity了。
1.框架依赖
在项目的build.gradle中defaultConfig闭包中添加
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
在dependencies中添加
// 替换成最新版本, 需要注意的是api
// 要与compiler匹配使用,均使用最新版可以保证兼容
implementation 'com.alibaba:arouter-api:1.3.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
2.在Application中初始化ARouter
@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(this); // 尽可能早,推荐在Application中初始化
}
目标Activity:添加@Router注解,定义唯一识别的路劲path,path路劲必须两级目录以上("/xx/xx")
跳转的页面:通过ARouter进行跳转
ARouter.getInstance().build("/ac/second").navigation();
目标Activity:通过@Router注解,接收参数的方式有两种
1.通过Intent获取
2.通过注解方式获取,使用@Autowired注解变量,并在onCreat()中添加注入(注入可以放在BaseActivity中)
注意:通过注解方式获取参数,定义参数变量时,不能用private私有属性
执行跳转的页面:通过调用withString(key,value)等api进行传参
ARouter.getInstance().build("/ac/second")
.withString("name","我是传递的参数")
.navigation();
目标Activity:跟上面的都一样
执行跳转的Activity:在navigation()方法中传入请求码即可
注意:如果请求码为0时,是没有startActivityForResult功能
ARouter.getInstance().build("/ac/second").navigation(MainActivity.this,10);
这里所谓的自定义对象指的是对象的自动转换,比如说,AActivity跳转到BActivity时,AActivity携带了一个json字符串,但是我希望在BActivity接收参数的时候,是接收json解析后的对象如User对象,相反也是如此(AActivity携带User对象,BActivity接收参数时,是接收User转成json后的字符串),也就是说转化的过程是在跳转的过程中进行的。
首先定义一个类实现SerializationService,重写里面的方法,定义Gson解析对象
其次在目标Activity中接收参数,接收的是json字符串
最后在跳转的地方传递参数
即,传递的是User对象,但接收的是User对象对应的json字符串,这个转换的过程是在页面跳转时,由ARouter框架自行调用
通过uri跳转的方式,类似采用Scheme协议跳转,在目标Activity的清单文件中定义指定的scheme,host,path等属性,然后拼接成一个uri对象,就可以跳转到指定的Activity,但是采用之前的方法,我们必须在每一个目标Activity清单中都定义scheme相关参数,要是需要跳转的Activity数量比较多的话,那整个清单文件就会显得比较臃肿,维护起来比较麻烦。但是ARouter框架可以直接通过uri进行跳转,这给了我们一个统一处理的方式。我们可以在项目中创建一个统一处理Scheme跳转的SchemeActivity作为中转,该Activity不需要布局文件,然后在清单中为该Activity定义scheme和host,如果外部需要跳转时,就采用统一的scheme和host,然后再拼接目标Activity的路劲,这样就可以在新建的SchemeActivity中统一通过ARouter传入接收的uri,进行跳转到指定的Activity。以下时具体的步骤
首先,创建一个统一管理Scheme协议跳转的页面SchemeActivity作为中转,将收到的uri通过ARouter进行跳转
清单中指定公共的scheme和host
其次,在目标Activity中添加@Router注解,添加页面路劲
最后,在调用的地方,采用Scheme协议进行跳转,要采用公共的scheme和host,确保每一个uri的跳转都会先到达中转Activity中。
String url = "common://com.my.app/ac/login";
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
原理同3.5,只是在执行跳转的时候,在url末尾添加要传递的参数,在目标Activity定义要接收的参数
在目标Activity:
在跳转url后面拼接参数
String url = "common://com.my.app/ac/login?name=我是Scheme跳转&age=20";
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
所谓的拦截器,是指在页面跳转前进行拦截,因此,可以在拦截器里面做一些通用的逻辑判断,比如,目标页面是否需要登录的判断,之前的做法是,在每一个目标页面启动的时候各自进行是否登录的判断,这样做工作量多不说,也不好统一去管理,采用ARouter的自定义拦截器,我们就可以方便的在拦截器中获取目标界面是否需要登录,如果需要登录,则拦截此次的跳转,直接跳转到登录页面,如果目标页面不需要登录,则不拦截,进行正常流程的页面跳转。也可以在拦截器中对目标界面的一些参数进行修改,重新赋值(postCard)
首先定义一个类,实现IInteceptor,在process()方法中获取目标Activity中添加的属性extras属性值,用于判断目标Activity是否需要登录才可以使用
//继续跳转
callback!!.onContinue(postcard)
//终止跳转
//callback.onInterrupt(null)
//抛出异常
// callback.onInterrupt(RuntimeException("我觉得有点异常")),会在降级策略中的回调返回
说明:
1.需要用@Inteceptor添加注解,添加属性priopity,拦截器的优先级,当同时添加多个拦截器时,拦截器的执行顺序时按照该值指定的优先级触发
2.回调方法callback.onContinue(postcard)和callback.onInterrupt(null)必须调用其中一个,否则路由无法继续
3.如果需要在拦截器中添加Toast提示,需要切换到主线程(拦截器是运行在ARouter开启的子线程中)
Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(mContext,"制行拦截器",Toast.LENGTH_SHORT).show();
//已在主线程中,更新UI
}
});
其次,在每一个目标Activity中都添加extras属性,用来标识当前页面是否需要登录
说明:extras属性是一个int类型的值,表明该值有32位,32个二进制位,每一位二进制都可以代表一个开关,也就是说可以同时为一个目标Activity添加32个开关标识位
所谓的降级策略,其实指的就是在界面跳转的过程中,我们可以知道其具体的状态,方便我们进行一些操作,比如说跳转完成,跳转失败,传入的路劲时错误的(页面找不到)和页面找到等四种状态,我们就可以方便的在页面跳转失败的时候,给出具体的提示或者操作。降级策略又分为单一策略和全局策略。
单一策略:针对单个页面跳转有效,采用回调的方式获取状态,navigation()第二个参数可以传入一个回调,可以在回调中去处理自己想要的逻辑
全局策略:针对所有的页面跳转都起作用(类似修改统一的页面找不到提示语)
单一策略和全局策略可以同时使用,但是两种同时使用时,单一策略的优先级比较高,全局策略的会被覆盖
所谓跨模块Api调用,比如,现在有两个模块ModuleA和ModuleB,ModuleB中有一个方法show(),假如ModuleA中想要调用ModuleB中的show()方法,那该怎么实现呢?
因为ModuleA和ModuleB两者没有相互依赖,按照原生的方法很难实现直接跨模块调用api,而ARouter框架里面就可以方便实现,通过给ModuleB暴露一个服务(此服务并不是Android中的服务,而是一个接口),将需要被调用的方法定义在该服务中,这样ModuleA中就可以通过该接口,调用里面定义的方法。
首先,在ModuleB中创建一个服务(暴露一个接口),将需要被调用的方法定义在该接口中,添加@Router注解
其次,在ModuleA中需要调用的地方去获取该服务对象,即可调用里面定义的方法,获取服务对象的方法有三种
1.通过调用ARouter.getInstance().build("/ac/service").navigation();然后强制转成指定的服务对象
CustomService customService = (CustomService) ARouter.getInstance().build("/ac/service").navigation();
customService.showToast(MainActivity.this,"测试而已");
2.通过调用ARouter.getInstance().navigation(CustomService.class);navigation中传入指定服务对象
CustomService customService = ARouter.getInstance().navigation(CustomService.class);
customService.showToast(MainActivity.this,"我的hi");
3.采用注解的方式获取服务对象,跟接收普通参数一样用@Autowired注解,传去服务的路劲path
@Autowired(name = "/ac/service")
protected CustomService customService;
添加跳转动画有两种方法,一个是兼容,一个是只能大于等于sdk16才能用。
兼容跳转:
ARouter.getInstance()
.build("/path/bactivity")
//参数1为打开的Activity的进入动画,参数2为当前的Activity的退出动画
.withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
.navigation(this);
SDK>16的跳转:利用withOptionsCompat添加ActivityOptionsCompat对象
if (Build.VERSION.SDK_INT >= 16) {
ActivityOptionsCompat compat = ActivityOptionsCompat.
makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0);
ARouter.getInstance()
.build("/path/bactivity")
.withOptionsCompat(compat)
.navigation();
} else {
Toast.makeText(this, "API < 16,不支持新版本动画", Toast.LENGTH_SHORT).show();
}
首次创建一个片段,并为片段添加@Router注解,添加路劲path
其次,在需要获取片段对象的地方调用以下代码
Fragment fragment = (Fragment) ARouter.getInstance().build("/fragment/one").navigation();
说明:如果build(path)中的path对应的是一个Activity,则调用navigation()后,是进行跳转的作用,如果path对应的是一个普通的类或者Fragment时,则调用navigation()后,是获取该类的对象,可以通过强制转成指定的类对象即可。
// 构建标准的路由请求
ARouter.getInstance().build("/home/main").navigation();
// 构建标准的路由请求,并指定分组
ARouter.getInstance().build("/home/main", "ap").navigation();
// 构建标准的路由请求,通过Uri直接解析
Uri uri;
ARouter.getInstance().build(uri).navigation();
// 构建标准的路由请求,startActivityForResult
// navigation的第一个参数必须是Activity,第二个参数则是RequestCode
ARouter.getInstance().build("/home/main", "ap").navigation(this, 5);
// 直接传递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();
// 转场动画(常规方式)
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);
// ps. makeSceneTransitionAnimation 使用共享元素的时候,需要在navigation方法中传入当前Activity
ARouter.getInstance()
.build("/test/activity2")
.withOptionsCompat(compat)
.navigation();
// 使用绿色通道(跳过所有的拦截器)
ARouter.getInstance().build("/home/main").greenChannel().navigation();
// 使用自己的日志工具打印日志
ARouter.setLogger();
// 使用自己提供的线程池
ARouter.setExecutor();
有了ARouter页面路由的基础,我们就可以进行组件化开发了,下一篇文章专门来讲解如何使用ARouter进行组件化开发