Android平台中对页面、服务提供路由功能(跳转)的一个库
1.1 最新版本
模块 | arouter-api | arouter-compiler | arouter-annotation |
---|---|---|---|
最新版本 |
1.2 实例图片
1.3 功能介绍
1.4 应用场景
导包现在分java和kotlin, java的导包方法如下,在根moduel的build.gradle添加如下内容:
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
}
}
dependencies {
// 替换成最新版本, 需要注意的是api,最新版本看文章开头
// 要与compiler匹配使用,均使用最新版可以保证兼容
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
...
}
如果java使用的是apt,导入方法则是如下:
apply plugin: 'com.neenbedankt.android-apt'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apt {
arguments {
moduleName project.getName();
}
}
dependencies {
//这里填写最新的版本看文章开始
compile 'com.alibaba:arouter-api:x.x.x'
apt 'com.alibaba:arouter-compiler:x.x.x'
...
}
kotlin的导包方法如下,在根moduel的build.gradle添加如下内容:
apply plugin: 'kotlin-kapt'
kapt {
arguments {
arg("moduleName", project.getName())
}
}
dependencies {
//这里填写最新的版本,看文章开始
compile 'com.alibaba:arouter-api:x.x.x'
kapt 'com.alibaba:arouter-compiler:x.x.x'
...
}
解析在代码中
class BaseApplication :Application(){
override fun onCreate() {
super.onCreate()
if(BuildConfig.DEBUG){ //如果在debug模式下
// 打印日志,默认关闭
ARouter.openLog()
// 开启调试模式,默认关闭(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
ARouter.openDebug()
// 打印日志的时候打印线程堆栈
ARouter.printStackTrace()
}
// 尽可能早,推荐在Application中初始化
ARouter.init(this)
}
}
方法又有Uri和path两种
利用ARouter的path方法:
例如我要在 AActivity跳转到BActivity,这时候AActivity的代码是这样子的(记得编写manifest):
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.textView) as TextView
textView!!.setOnClickListener {
ARouter.getInstance()
.build("/path/bactivity")
.navigation()
}
}
然后BActivity的代码是这样子的(记得编写manifest):
@Route(path = "/path/bactivity")
class BActivity :AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
可以看到,跳转是利用build方法里面的一个字符串进行标识,然后调用navigation进行跳转到 注解@Route标记的字符串路径。
利用Uri方法:
例如上面的AActivity里面的跳转方法改为:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.textView) as TextView
textView!!.setOnClickListener {
val uri = Uri.parse("/path/bactivity")
ARouter.getInstance()
.build(uri)
.navigation()
}
}
然后BActivity保持不变,效果是一样的。
PS: Uri.parse的时候,改为完整路径也是支持的,例如改为val uri = Uri.parse(“tpnet://m.aliyun.com/path/bactivity”),也可以成功跳转到BActivity
调用with对应的类型即可,
ARouter.getInstance().build(RouterPath.PAGE_TEST1)
//第一个参数为key,第二个参数为值
.withLong("longKey", 0x555555L)
.withString("stringKey", "66666")
.navigation()
在接收的activity解析的时候,获取到extras,get对应的类型即可:
val extrsa = intent.extras
println(extrsa.getLong("longKey"))
println(extrsa.getString("stringKey"))
添加跳转动画有两种方法,一个是兼容,一个是只能大于等于sdk16才能用。
兼容方法:
使用withTransition方法,添加 进入动画,退出动画即可
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();
}
PS: akeSceneTransitionAnimation 使用共享元素的时候,需要在navigation方法中传入当前Activity
Url跳转就是可以根据url来跳转,可以从网页跳到Activity
首先定义一个Activity作为中转,网页的链接都跳到这个Activity,然后再从这个Activity打开网页需要打开的Activity
class SchemeFilterActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ARouter.getInstance().build(intent.data).navigation(this,object: NavCallback() {
override fun onArrival(postcard: Postcard?) {
finish()
}
})
}
}
该Activity的manifest为:
<activity android:name=".Url.SchemeFilterActivity">
<intent-filter>
<data
android:host="m.aliyun.com"
android:scheme="arouter"
/>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
intent-filter>
activity>
然后定义你要跳转到的Activity,这里定义一个UrlTargetActivity (记得在Manifest说明):
@Route(path = "/test/activity1")
class UrlTargetActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_url_target)
val tvContent = findViewById(R.id.content) as TextView
tvContent.text = "当前页面是:" + this@UrlTargetActivity::class.java.name
}
}
然后在手机打开一个网页,网页内容为:
<a href="arouter://m.aliyun.com/test/activity1">arouter://m.aliyun.com/test/activity1a>p>
点击了这个网页链接之后,就到根据"arouter://m.aliyun.com
(scheme://host)跳转到SchemeFilterActivity
这个Activity,然后在这个Activity利用ARouter 根据path = test/activity1
跳转到UrlTargetActivity
上面的网页链接添加name、age、sex三个参数,例如:
<p><a href="arouter://m.aliyun.com/test/activity1?name=tpnet&age=21&sex=true">arouter://m.aliyun.com/test/activity1a>p>
然后,在目标Activity: UrlTargetActivity修改为:
@Route(path = "/test/activity1")
class UrlTargetActivity : AppCompatActivity() {
@JvmField
@Autowired
var name: String = ""
@JvmField
@Autowired
var age: Int = 0
@JvmField
@Autowired(name = "sex")
var gender: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_url_target)
val tvContent = findViewById(R.id.content) as TextView
tvContent.text = "当前页面是:" + this@UrlTargetActivity::class.java.name
ARouter.getInstance().inject(this)
tvContent.text = tvContent.text as String + "\n$name - $age - $gender"
}
}
可以看到,使用@Autowired
这个注解变量,然后在onCreate中使用ARouter.getInstance().inject(this)
进行注入,即可得到参数。
那参数怎么样对应变量呢?
2是Autowired注解里面有个属性name,这个属性指定参数名称
PS: @JvmField这个注解,是因为我用的是Kotlin,ARouter使用的是java,所以为了兼容,需要加这个注解
网页通过传递json参数,然后ARouter帮你自动转换为你的自定义对象。
例如,网页链接为:
>
然后写一个类,实现SerializationService接口,用来实现json的序列化和反序列化。注意这个类也需要添加path,内容不规定
//这里需要添加path,内容随便
@Route(path = "/service/json")
class JsonObjectImpl : SerializationService {
override fun init(context: Context?) {
}
//json字符串转换为对象
override fun json2Object(json: String?, clazz: Class?): T {
Log.e("@@","json:"+json)
return JSON.parseObject(json, clazz)
}
//自定义对象转换为json字符串
override fun object2Json(instance: Any?): String = JSON.toJSONString(instance)
}
然后就是自定义的对象:
class TestObj() {
var name: String = "" //这里变量名称对应Url的json的key
var id: Int = 0
override fun toString(): String = "TestObj(name='$name', id=$id)"
}
然后要打开的目标Activtiy接收自定义对象,如常一样,加一个变量即可,记得变量名字要和url的key一致,或者在Autowired注解添加name属性
@JvmField
@Autowired
var obj: TestObj? = null
PS: 也支持Parcelable
拦截器的意思是,例如你想在 AActivity跳到BActivity,如果有拦截器,就可以把这个过程拦截下来,做一些处理(禁止跳转、修改参数)。
添加拦截器的方法是利用Interceptor
注解,实现IInterceptor
接口。例如我添加一个拦截器:
//priority 为拦截器的优先级,多个拦截器时候有用
@Interceptor(priority = 8,name = "测试用拦截器")
class TestInterceptor : IInterceptor {
override fun init(context: Context?) {
// 拦截器的初始化,会在sdk初始化的时候调用该方法,仅会调用一次
Log.e("@@","拦截器初始化")
}
/**
* 拦截器的操作
* @param postcard 数据
* @param callback 回调
*/
override fun process(postcard: Postcard?, callback: InterceptorCallback?) {
if(postcard?.path == "/path/bactivity"){
//如果是跳到BActivity,就加个数据
postcard.withString("extra", "我是在拦截器中附加的参数")
}
//继续跳转
callback!!.onContinue(postcard)
//终止跳转
//callback.onInterrupt(null)
//抛出异常
// callback.onInterrupt(RuntimeException("我觉得有点异常"))
// onContinue和onInterrupt至少需要调用其中一种,否则不会继续路由
}
}
这样子,在BActivity接收的时候,就可以获取到extra,这个key的字符串了
什么鬼是降级策略? 因为打开activity是系统级别的操作,我们提交之后了跳转之后,很难插手,所以降级策略其实就是 在跳转过程中,如果出现错误的话,可以进行处理跳转。 方法有两种,一个是处理单词跳转,一个是全局的跳转处理。
方式1: 单个处理-回调方法:
在navigation的时候,添加NavCallback进行回调,回调方法有四个,出现错误的时候,在onLost方法下面处理即可。
ARouter.getInstance().build(intent.data).navigation(this,object: NavCallback() {
override fun onArrival(postcard: Postcard?) {
//已经打开了目标activity
Log.e("@@","目标activity打开完成")
finish()
}
override fun onFound(postcard: Postcard?) {
//找到了要打开的activity
Log.e("@@","找到了目标activity")
}
override fun onLost(postcard: Postcard?) {
//找不到要打开的activity
Log.e("@@","找不到目标activity")
}
override fun onInterrupt(postcard: Postcard?) {
super.onInterrupt(postcard)
Log.e("@@","被拦截了")
}
})
方法2:全局处理-实现接口
如果找不到目标Activity,ARouter默认会提示一个Toast,找不到对象。例如:
ARouter.getInstance().build("/xxxxx/xxxxx").navigation(this)
这时候如果你要自己全局处理,只需要实现DegradeService接口,并加上一个Path内容任意的注解即可
//path里面的内容可以任意,注意两个斜杠就行
@Route(path = "/cccc/ddd")
class DegradeServiceImpl : DegradeService {
//失败的时候处理,注意:如果在navigation时候没有传递context,这个方法的context会是空的
override fun onLost(context: Context?, postcard: Postcard?) {
Toast.makeText( context,"找不到路径" +postcard?.path ,Toast.LENGTH_SHORT).show()
}
override fun init(context: Context?) {}
}
PS: 不能两种同时使用,单个处理级的方式优先于全局处理,也就是如果同时使用两种方式,只有单独降级能执行。
这里说的服务不是Android四大组件里面的服务,其实是根据path去获取对象。
例如:首先写一个接口,实现IProvider接口,定义一个方法sayHello
// 声明接口
interface HelloService : IProvider {
fun sayHello(name: String)
}
然后写一个实现类,添加注解@Route
,path内容不固定:
//实现接口
@Route(path = "/service/hello",name = "测试服务")
class HelloServiceImpl : HelloService {
override fun sayHello(name: String){
println(name)
}
override fun init(context: Context?) {}
}
然后就可以利用ARouter来调用实现类HelloServiceImpl
//方法1 用类的形式,在navigation方法添加参数
ARouter.getInstance().navigation(HelloService::class.java).sayHello("mike")
// 方法2 用path方式,在build添加实现类的Router路径
(ARouter.getInstance().build("/service/hello").navigation() as HelloService).sayHello("tpnet")
还可以利用注入的方式:
class ServiceObj {
@JvmField
@Autowired
var helloService: HelloService? = null
@JvmField
@Autowired(name = "/service/hello")
var helloService2: HelloService? = null
init {
ARouter.getInstance().inject(this)
}
fun testService(){
// 1. (推荐)使用依赖注入的方式发现服务,通过注解标注字段,即可使用,无需主动获取
helloService?.sayHello("Vergil")
// Autowired注解中标注name之后,将会使用byName的方式注入对应的字段,不设置name属性,会默认使用byType的方式发现服务
// (当同一接口有多个实现的时候,必须使用byName的方式发现服务)
helloService2?.sayHello("Vergil")
}
}
当然一个库的功能是很多的,还有其他的使用方面。
在需要获取的Fragment添加注解@Route
@Route(path = "/test/fragment")
public class BlankFragment extends Fragment {}
获取的时候,强制转换。
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
在navigation方法的第二个参数添加即可:
ARouter.getInstance().build("/test/activity2").navigation(this, 666);
// 我们经常需要在目标页面中配置一些属性,比方说”是否需要登陆”之类的
// 可以通过 Route 注解中的 extras 属性进行扩展,这个属性是一个 int值,换句话说,单个int有4字节,也就是32位,可以配置32个开关
// 剩下的可以自行发挥,通过字节操作可以标识32个开关,通过开关标记目标页面的一些属性,在拦截器中可以拿到这个标记进行业务逻辑判断
@Route(path = "/test/activity", extras = Consts.XXXX)
在拦截器中的process方法,利用getExtra方法即可获取到这个内容。
// 实现PathReplaceService接口,并加上一个Path内容任意的注解即可
@Route(path = "/xxx/xxx") // 必须标明注解
public class PathReplaceServiceImpl implements PathReplaceService {
/**
* For normal path.
*
* @param path raw path
*/
String forString(String path) {
return path; // 按照一定的规则处理之后返回处理后的结果
}
/**
* For uri type.
*
* @param uri raw uri
*/
Uri forUri(Uri uri) {
return url; // 按照一定的规则处理之后返回处理后的结果
}
}
方法1: 和正常跳转一样,设置path,然后navigation过去
方法2: 指定group分组,例如:
@Route(path = "/path/module",group = "m2")
class ModuleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_module)
}
}
调用则:
ARouter.getInstance().build("/path/module", "m2").navigation()
PS: 一旦主动指定分组之后,应用内路由需要使用 ARouter.getInstance().build(path, group) 进行跳转,手动指定分组,否则无法找到
// 构建标准的路由请求
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();
//获取原始的URI
String uriStr = getIntent().getStringExtra(ARouter.RAW_URI);
//关闭ARouter
ARouter.getInstance().destroy();