ARouter是一款优秀的组件化路由,项目地址:ARouter
一、配置
1、首先要明确项目是java项目还是kotlin项目,另外需要使用ARouter的每个Module都需要配置。
java项目配置:
android {
...
defaultConfig {
...
//配置ARouter
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
//引入依赖
//配置ARouter
implementation 'com.alibaba:arouter-api:1.5.2'
annotationProcessor 'com.alibaba:arouter-compiler:1.5.2'
kotlin项目配置
plugins {
...
id 'kotlin-kapt'
}
android{
...
//配置ARouter
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
}
//引入依赖
//配置ARouter
implementation 'com.alibaba:arouter-api:1.5.2'
kapt 'com.alibaba:arouter-compiler:1.5.2'
2、在Application中初始化
class App : Application() {
override fun onCreate() {
super.onCreate()
var isDebug = true //伪代码,注意上线前关闭
if (isDebug) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
ARouter.openLog() // 打印日志
ARouter.openDebug() // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(this) // 尽可能早,推荐在Application中初始化
}
}
3、跳转测试
创建了一个新的SecondActivity,目录结构如下:
MainActivity只是设置了点击事件:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.bt).setOnClickListener {
//跳转到SecondActivity
ARouter.getInstance().build("/testarouter/SecondActivity").navigation()
}
}
}
SecondActivity注意要定义路径Path,必须是以“/” 开头,且必须至少包含二个“/” (至少包含二级)
@Route(path = "/testarouter/SecondActivity")
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
}
}
跳转测试成功
配置和跳转写的很详细是因为前面容易踩坑There is no route match the path [/xxx/xxx]找不到路径,如果还是碰到问题,请查看issue上的问题集真正解决"There is no route match the path [/xxx/xxx], in group [xxx][ ]"
4、混淆见Github项目地址ARouter
5、使用 IDE 插件导航到目标类
官方的插件ARouter Helper只支持java,不支持kotlin,但看到目前有其他插件支持kotlin
安装后如下
有多个跳回地址时可选
二、具体使用
1、Path路径封装
一般会将Path路径写一个集中管理类
class ARouterPath {
companion object {
const val SecondActivity = "/testarouter/SecondActivity";
}
}
调用
//跳转到SecondActivity
ARouter.getInstance().build(ARouterPath.SecondActivity).navigation()
@Route(path = ARouterPath.SecondActivity)
class SecondActivity : AppCompatActivity() {...}
2、Fragment获取并加载
创建一个普通Fragment
@Route(path = ARouterPath.MainFragment)
class MainFragment : Fragment() {...}
Activity获取Fragment并加载
//获取Fragment对象
var fragment: MainFragment =
ARouter.getInstance().build(ARouterPath.MainFragment).navigation() as MainFragment
//容器加载Fragment
supportFragmentManager.beginTransaction().replace(R.id.fl_container, fragment).commit()
3、传值
带参数跳转,以下是API
1)传递普通数据类型及Bundle
相对比较简单,注意定义注解@Autowired,注意inject注入。在java中接收值的Activity的对应参数必须是public的。
传值
//跳转到SecondActivity
ARouter.getInstance()
.build(ARouterPath.SecondActivity)
.withString("name","zhangsan")
.withInt("age",22)
.navigation()
接收值
@Route(path = ARouterPath.SecondActivity)
class SecondActivity : AppCompatActivity() {
@JvmField
@Autowired
var name: String? = null
@JvmField
@Autowired(name = "age") //扩展命名空间
var userAge: Int = 0 //不能写null会运行崩溃,因为ARouter有转Integer的操作
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//需要注入,这一步不要忘记
ARouter.getInstance().inject(this@SecondActivity)
setContentView(R.layout.activity_second)
}
}
2)传递对象
自定义了一个User类,代码如下:
class User(var name: String, var age: Int = 0){
override fun toString(): String {
return "User(name='$name', age=$age)"
}
}
传值
//跳转到SecondActivity
ARouter.getInstance()
.build(ARouterPath.SecondActivity)
.withObject("user", User("zhangsan", 22))
.navigation()
发现报错空指针异常,看报错的点:
原来需要引入Fastjson,不然下面红框部位就报空指针异常,因为没有SerializationService的实现类。于是引入Fastjson,发现还是报错,如下:
找不到默认的构造方法,但是Kotlin的Data类是有的,看报错的地点:
看网友的博客才知道这是一个创建工程时没有引入的Kotlin的库,如下:
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
于是终于不蹦了,看接收值的代码:
@Route(path = ARouterPath.SecondActivity)
class SecondActivity : AppCompatActivity() {
@JvmField
@Autowired
var user: User? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//需要注入
ARouter.getInstance().inject(this@SecondActivity)
setContentView(R.layout.activity_second)
}
}
运行成功。
将Fastjson改为Gson:
@Route(path = ARouterPath.CommonJson)
class JsonServiceImpl : SerializationService {
private val gson: Gson by lazy {
Gson()
}
override fun init(context: Context?) {
}
override fun <T : Any?> json2Object(input: String?, clazz: Class<T>?): T {
return gson.fromJson(input, clazz)
}
override fun object2Json(instance: Any?): String {
return gson.toJson(instance)
}
override fun <T : Any?> parseObject(input: String?, clazz: Type?): T {
return gson.fromJson(input, clazz)
}
}
运行传值成功。
ARouter会根据SerializationService的实现类来序列化和反序列化对象,实现SerializationService后,不需要再添加其他代码。
另外一个需要注意的点是如果自定义对象类型是serializable,那么会被当成serializable处理,如果是parcelable那么会被当成parcelable方式处理,只有在不是 parcelable 也不是 serializable 的时候,才会当成自定义对象处理。 所以只需要将自定义的类,不要去实现 Serializable, Parcelable 接口 这两个接口,那么就可以正常传值。
3)List和Map类型
根据官方文档,接收List或Map类型的值,不能标注具体的实现类类型。如下:
传值
var arrayList = ArrayList<User>()
var user = User("zhangsan",100)
arrayList.add(user)
//跳转到SecondActivity
ARouter.getInstance()
.build(ARouterPath.SecondActivity)
.withObject("arrayList",arrayList)
.navigation()
接收值
@Route(path = ARouterPath.SecondActivity)
class SecondActivity : AppCompatActivity() {
@JvmField
@Autowired
var arrayList: List<User>? = null //不能是ArrayList
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//需要注入
ARouter.getInstance().inject(this@SecondActivity)
setContentView(R.layout.activity_second)
}
}
接收值成功。
4、回传值
1)常规处理
Android的页面跳转有时候是需要回传值的,那么ARouter中应该如何回传? ARouter跳转的navigation方法中有带requestCode的方法,这个方法在requstCode大于等于0时就是调用的startActivityForRestult。这里是一个要注意的点。
navigation方法
重写onActivityResult方法,做相应处理即可,跟平时一样。
2)Fragment中回传值
使用ARouter,从Fragment跳转到其他Activity,需要返回值时,回调的值是在Fragment所在Activity的onActivityResult方法中,而不是Fragment的onActivityResult方法中。解决方法:
//获取跳转的Postcard,Postcard中有跳转的所有信息
val postcard: Postcard = ARouter.getInstance()
.build(ARouterPath.SecondActivity) //跳转的Activity
.withInt("num", 123) //携带参数
//Completion the postcard by route metas(完成明信片)
LogisticsCenter.completion(postcard)
//通过Arouter 的Postcard创建 intent
var intent = Intent(activity, postcard.destination)
// 将Postcard中的参数赋值给intent
intent.putExtras(postcard.extras)
//Fragment自己来跳转
startActivityForResult(intent,REQUES_CODE)
//在Fragment的onActivityResult中处理数据
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUES_CODE && resultCode == AppCompatActivity.RESULT_OK) {
if (data != null) {
//处理数据
}
}
}
5、降级策略
1)什么是降级策略
跳转过程中如果出现错误,可以进行处理,一种是坚挺路由操作的回调方法NavigationCallback,另一种是全局处理(实现DegradeService接口)。NavigationCallback优先级比全局处理的高,不会再进入DegradeService。
2)二种降级策略
坚挺路由
//跳转到SecondActivity
ARouter.getInstance()
.build(ARouterPath.SecondActivity)
.navigation(this, object : NavigationCallback {
override fun onFound(postcard: Postcard?) {
//找到跳转目标
}
override fun onLost(postcard: Postcard?) {
//丢失跳转目标
}
override fun onArrival(postcard: Postcard?) {
//跳转完成
}
override fun onInterrupt(postcard: Postcard?) {
//路由被拦截
}
})
全局处理
@Route(path = "/AA/BB/CC") //Route注解采用任意的Path都可以
class DegradServiceImpl : DegradeService {
override fun init(context: Context?) {
}
override fun onLost(context: Context?, postcard: Postcard?) {
//丢失目标时的处理
}
}
6、拦截器
拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行。
@Interceptor(priority = 1, name = "拦截器名称") //多个拦截器会按优先级顺序依次执行
class TestInterceptor : IInterceptor {
override fun init(context: Context?) {
//拦截器的初始化,会在sdk初始化的时候调用该方法,仅会调用一次
}
override fun process(postcard: Postcard?, callback: InterceptorCallback?) {
//Postcard包含跳转的所有信息
//获取跳转的路径
postcard?.path
//获取Bundle
var bundle = postcard?.extras
//通过Bundle获取跳转携带的值
bundle?.getInt("num")
//额外添加值
postcard?.withInt("num", 123)
//以下两种至少需要调用其中一种,否则不会继续路由
//1、继续路由
callback?.onContinue(postcard)
//2、中断路由
callback?.onInterrupt(null)
callback?.onInterrupt(RuntimeException("有异常"))
}
}
7、转场动画
方式一:
// 转场动画(常规方式)
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();
8、跨模块调用服务
1)通过依赖注入解耦:服务管理(一) 暴露服务
ARouter提供了IProvider接口,模块可以继承IProvider接口来暴露服务
// 声明接口,其他组件通过接口来调用服务
public interface HelloService extends IProvider {
String sayHello(String name);
}
// 实现接口
@Route(path = "/yourservicegroupname/hello", name = "测试服务")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello, " + name;
}
@Override
public void init(Context context) {
}
}
2)通过依赖注入解耦:服务管理(二) 发现服务
public class Test {
@Autowired
HelloService helloService;
@Autowired(name = "/yourservicegroupname/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("/yourservicegroupname/hello").navigation();
helloService3.sayHello("Vergil");
helloService4.sayHello("Vergil");
}
}
参考了以下博客,表示感谢:
kotlin fastjson:defalut constructor not found
ARouter 传自定义对象获取值为null的解析 及解决方法
startActivityForResult与ARouter在fragment中使用startActivityForResult