阅读 1344

Arouter全面讲解一

arouter是android实现组件化的路由框架,涉及到的功能有activity、fragment的跳转、跳转带参数、自定义服务、自定义拦截器、拦截下沉、重定向url都是Arouter里面定义的功能,可能用过Arouter的小伙伴们只用过Arouter的跳转以及跳转功能带参数的功能,像它的自定义服务、拦截器、全局降级策略、重定向功能都是很不错的功能,下面我会一一介绍这些功能该怎么使用。

目录

  • 基础依赖
  • 初始化
  • 添加注解
  • 发起路由
  • 添加混淆
  • 使用Gradle实现路由表自动加载
  • 使用IDE插件通过导航的形式到目标类

使用介绍

1.基础依赖

1.1.java版本的依赖

在需要使用Arouter的module中添加如下代码:

android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                //arouter编译的时候需要的module名字
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}

dependencies { 
    ...
    implementation 'com.alibaba:arouter-api:1.5.1'
    annotationProcessor 'com.alibaba:arouter-compiler:1.5.1'
}
复制代码

这里一般习惯的做法是把arouter-api的依赖放在基础服务的module里面,因为既然用到了组件化,那么肯定是所有的module都需要依赖arouter-api库的,而arouter-compiler的依赖需要放到每一个module里面。

1.2.kotlin版本的依赖
plugins {
    ...
    id 'kotlin-kapt'
}

dependencies {
    ...
    implementation 'com.alibaba:arouter-api:1.5.1'
    kapt 'com.alibaba:arouter-compiler:1.5.1'
}
复制代码

注意上面定义plugin的写法是新的androidStudio的写法了,其实kotlin的写法与java的写法就是在编译时注解的依赖形式不一样,其余的都是一样的。

2.初始化

这个很简单,在Application中初始化就可以了:

if (isDebug()) {           // 这两行必须写在init之前,否则这些配置在init过程中将无效
    ARouter.openLog();     // 打印日志
    ARouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
复制代码

这里简单提一句,当时我们项目为了做Arouter启动优化,Arouter的初始化是花了4秒多,结果把Arouter的初始化放到了欢迎页,结果在线上版本发现了有坑,在进程被kill的情况下,回到当前Activity的时候,会发现Arouter的初始化标记为false,所以思来想去,Arouter的初始化工作还是得放到Application。因为即使进程被kill掉了,Application还是会初始化Arouter的,所以初始化工作还是得放在Application中,不过官方实现了Gradle插件路由的自动加载功能,后面会说到。

3.添加注解

3.1@Route注解

Route注解是作用在类上面,里面会携带path路径,这里列举Route注解使用的几种情况:

  • Route注解添加Activity的路由
@Route(path = "/login/loginActivity")
class LoginActivity : AppCompatActivity() {
    ...
}
复制代码
  • Route注解添加全局序列化方式
@Route(path = "/yourservicegroupname/json")
class JsonServiceImpl : SerializationService {
    lateinit var gson: Gson
    override fun <T : Any?> json2Object(input: String?, clazz: Class<T>?): T {
        return gson.fromJson(input, clazz)
    }

    override fun init(context: Context?) {
        gson = Gson()
    }

    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)
    }

}
复制代码

这里用了Route注解定义了SerializationService的序列化的方式,在使用withObject的时候会使用该SerializationService,后面会讲到该情况。

  • Route注解定义了全局降级策略
@Route(path = "/yourservicegroupname/DegradeServiceImpl")
class DegradeServiceImpl : DegradeService {
    override fun onLost(context: Context, postcard: Postcard) {
        Log.d("DegradeServiceImpl""没有找到该路由地址:${postcard.path}")
    }

    override fun init(context: Context?) {
    }

}
复制代码

上面也是用了Route注解定义了全局降级策略,也就是在找不到的路由表的时候,做相应的处理。

  • Route注解实现提服务
interface HelloService:IProvider{
    fun sayHello(name:String):String
}

// 实现接口
@Route(path = "/common/hello", name = "测试服务")
class HelloServiceImpl : HelloService {
    override fun sayHello(name: String): String {
        Log.d("HelloServiceImpl""hello, $name")
        return "hello, $name"
    }

    override fun init(context: Context) {}
}
复制代码

这个例子是官网的写法,意思是通过Route注解实现提供服务,那怎么实现接收服务呢,下面会在另外一种注解的时候讲到。

3.2@Interceptor注解

这个注解可以说非常强大,它能拦截你的路由,什么时候让路由通过什么时候让路由不通过,完全靠该Interceptor注解可以控制。比如我有一个需求,在跳分享的时候,我想看有没有登录,如果没有登录做登录的操作,如果登了了才让分享。如果之前是不是在每一个路由的地方都得判断有没有登录,很繁琐,有了路由拦截器不用在跳转的地方判断。

@Interceptor(priority = 8, name = "登录拦截")
class LoginInterceptor : IInterceptor {
    override fun process(postcard: Postcard, callback: InterceptorCallback) {
        val path = postcard.path
        if (path == "/share/shareActivity") {
            val userInfo = DataSource.getInstance(ArouterApplication.application).getUserInfo()
            if (TextUtils.isEmpty(userInfo)) {
                callback.onInterrupt(Throwable("还没有登录,去登陆"))
            } else {
                callback.onContinue(postcard)
            }
        }else{
            callback.onContinue(postcard)
        }
    }

    override fun init(context: Context?) {
        // 拦截器的初始化,会在sdk初始化的时候调用该方法,仅会调用一次
        Log.d("LoginInterceptor""LoginInterceptor初始化了")
    }

}
复制代码

拦截器可以定义优先级,如果有多个拦截器,会依次执行拦截器。

3.3@Autowired注解

Autowired注解是定义在目标页的属性上,通常用来定义目标页接收的值,还可以定义上面说到的接收服务方:

3.3.1Autowired注解接收值
@Autowired(name = "username")
lateinit var username: String
@Autowired
lateinit var testBean: TestBean
@Autowired(name ="listBean" )
lateinit var listBean: List<TestBean>
复制代码

上面定义基本类型的值接收,还有自定义bean和集合的接收

3.3.2Autowired注解接收服务
@Autowired
lateinit var helloService: HelloService
@Autowired(name = "/common/hello")
lateinit var helloService1: HelloService
lateinit var helloService2: HelloService
lateinit var helloService3: HelloService
复制代码

可以看到,上面定义了前面提供的服务,helloService可以直接指向HelloServiceImpl,如果HelloService有多个服务,那Autowired注解需要指定name路由属性,指明是哪一个服务的实例。 有人好奇helloService2和helloService3没有用@Autowired注解定义服务的来源,别急,下面会提供服务的来源的:

helloService2 =
    ARouter.getInstance().build("/common/hello").navigation() as HelloService
helloService3 = ARouter.getInstance().navigation(HelloService::class.java)

//使用服务
helloService.sayHello("helloService")
helloService1.sayHello("helloService1")
helloService2.sayHello("helloService2")
helloService3.sayHello("helloService3")
复制代码

helloService2是通过build指定路由地址的形式,helloService3是通过navigation指定HelloService的class类也能拿到HelloServiceImpl的服务。

上面的@Autowired注解使用都得在类的初始化中使用ARouter.getInstance().inject(this),否则@Autowired注解不会被执行到

3.3预处理服务

预处理服务意思是在路由navigation之前进行干扰路由,通过实现PretreatmentService接口,比如我想干扰在分享之前判断有没有登录,如果没有登录,自行判断逻辑:

@Route(path = "/yourservicegroupname/pretreatmentService")
class PretreatmentServiceImpl : PretreatmentService {
    override fun onPretreatment(context: Context, postcard: Postcard): Boolean {
        if (postcard.path == "/share/ShareActivity") {
            val userInfo = DataSource.getInstance(ArouterApplication.application).getUserInfo()
            if (TextUtils.isEmpty(userInfo)) {
                Toast.makeText(ArouterApplication.application, "你还没有登录", Toast.LENGTH_SHORT).show()
                return false// 跳转前预处理,如果需要自行处理跳转,该方法返回 false 即可
            }
        }
        return true
    }

    override fun init(context: Context) {}
}
复制代码

其实在这个例子中我演示的拦截器功能和预处理服务功能是一样的,只不过预处理服务是早于拦截器的,等到分析源码的时候我们分析他们的具体区别。

3.4重定义URL跳转

重新定以URL的跳转

// 实现PathReplaceService接口,并加上一个Path内容任意的注解即可
@Route(path = "/yourservicegroupname/pathReplaceService") // 必须标明注解
class PathReplaceServiceImpl : PathReplaceService {
    /**
     * For normal path.
     *
     * @param path raw path
     */
    override fun forString(path: String): String {
        if (path == "/login/loginActivity") {
            return "/share/shareActivity"
        }
        return path // 按照一定的规则处理之后返回处理后的结果
    }

    /**
     * For uri type.
     *
     * @param uri raw uri
     */
    override fun forUri(uri: Uri?): Uri? {
        return null // 按照一定的规则处理之后返回处理后的结果
    }

    override fun init(context: Context?) {
    }
}
复制代码

上面我把登录的路由改成分享的路由,在实际项目中大家看看有什么适用的场景?

4.发起路由

  • 我们先来个最简单的方式:
ARouter.getInstance().build("/test/activity").navigation()
复制代码

主要是通build方法生成postCard对象,最后调用postCardnavigation方法。

  • 传值写法:
ARouter.getInstance().build("/test/1")
            .withLong("key1", 666L)
            .withString("key3""888")
            .withObject("key4", new Test("Jack""Rose"))
            .navigation()
复制代码

上面能用withObject方法传object是因为在上面定义了JsonServiceImpl序列化方式的路由类。withObejct还可以传集合、map等:

ARouter.getInstance().build("/share/shareActivity").withString("username""zhangsan")
    .withObject("testBean", TestBean("lisi", 20))
    .withObject(
        "listBean",
        listOf<TestBean>(TestBean("wanger", 20), TestBean("xiaoming", 20))
    )
    .navigation()
复制代码

这里注意了在路由目标类里面定义接收list、map的时候,接收对象的地方不能标注具体的实现类类型,应仅标注为list或map,否则会影响序列化中类型的判断,其他类似情况需要同样处理 其他几种序列化的方式也带了,大家自行查看postCard的with** 相关方法:

  • 跳转写法: 跳转方法主要指navigation方法,其实说是跳转方法不太准确,因为它不仅仅是跳转用的,比如生成一个interceptor、service等都是通过navigation方法实现的,下一节介绍源码的时候会说到navigation有哪些具体作用

navigation主要有下面几个方法,我们说下NavigationCallback对象,一看就是个回调:

ARouter.getInstance().build("/share/shareActivity").withString("username""zhangsan")
    .withObject("testBean", TestBean("lisi", 20))
    .withObject(
        "listBean",
        listOf<TestBean>(TestBean("wanger", 20), TestBean("xiaoming", 20))
    )
    .navigation(this, object : NavigationCallback {
        override fun onLost(postcard: Postcard?) {
        }
        override fun onFound(postcard: Postcard?) {
        }
        override fun onInterrupt(postcard: Postcard?) {
            Log.d("LoginActivity""还没有登录")
        }
        override fun onArrival(postcard: Postcard?) {
        }
    })
复制代码

实现了四个方法,onLost是找不到路由,onFound是找到路由,onInterrupt表示路由挂了,默认路由设置的超时时间是300s,onArrival表示路由跳转成功的回调,目前只在startActivity的回调,这个后面源码部分会讲到。

5.混淆

混淆部分就没什么好说的了,因为Arouter是通过反射创建arouter的注解类,所以大部分需要加混淆:

-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep public class com.alibaba.android.arouter.facade.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}

# 如果使用了 byType 的方式获取 Service,需添加下面规则,保护接口
-keep interface * implements com.alibaba.android.arouter.facade.template.IProvider

# 如果使用了 单类注入,即不定义接口实现 IProvider,需添加下面规则,保护实现
# -keep class * implements com.alibaba.android.arouter.facade.template.IProvider
复制代码

6.使用Gradle实现路由表自动加载

可以说这个功能虽然是选项配置,但是对于arouter启动优化有很大的作用,我们项目在没使用这个gradle自动加载路由插件的时候初始化sdk需要4秒多,用了这个插件之后基本没消耗时间。它主要是在编译期通过gradle插装把需要依赖arouter注解的类自动扫描到arouter的map管理器里面,在下一章我们通过反编译工具查看它是怎么插装代码的,而传统的是通过扫描dex文件来过滤arouter注解类来添加到map中。

  • 具体使用
//app的module的build.gradle
apply plugin: 'com.alibaba.arouter'
//工程的build.gradle
buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath "com.alibaba:arouter-register:1.0.2"
    }
}
复制代码

7.使用IDE插件通过导航的形式到目标类

在 Android Studio 插件市场中搜索 ARouter Helper, 或者直接下载文档上方 最新版本 中列出的 arouter-idea-plugin zip 安装包手动安装,安装后 插件无任何设置,可以在跳转代码的行首找到一个图标 (navigation) 点击该图标,即可跳转到标识了代码中路径的目标类 目前不支持kotlin的图标样式,大家自己尝试下java跳转

示例代码

文章分类
Android
文章标签