欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android跳转-ARouter详细使用教程

程序员文章站 2022-05-14 14:02:25
...

一、简介

Android平台中对页面、服务提供路由功能(跳转)的一个库

1.1 最新版本

模块 arouter-api arouter-compiler arouter-annotation
最新版本 Android跳转-ARouter详细使用教程 Android跳转-ARouter详细使用教程 Android跳转-ARouter详细使用教程

1.2 实例图片

Android跳转-ARouter详细使用教程

1.3 功能介绍

  1. 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
  2. 支持多模块工程使用
  3. 支持添加多个拦截器,自定义拦截顺序
  4. 支持依赖注入,可单独作为依赖注入框架使用
  5. 支持InstantRun
  6. 支持MultiDex(Google方案)
  7. 映射关系按组分类、多级管理,按需初始化
  8. 支持用户指定全局降级与局部降级策略
  9. 页面、拦截器、服务等组件均自动注册到框架
  10. 支持多种方式配置转场动画
  11. 支持获取Fragment
  12. 完全支持Kotlin以及混编(配置见文末 其他#5)

1.4 应用场景

  1. 从外部URL映射到内部页面,以及参数传递与解析
  2. 跨模块页面跳转,模块间解耦
  3. 拦截跳转过程,处理登陆、埋点等逻辑
  4. 跨模块API调用,通过控制反转来做组件解耦

二、使用方法

1.1 导包

导包现在分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'
    ...
}

1.2 初始化

解析在代码中

class BaseApplication :Application(){

    override fun onCreate() {
        super.onCreate()

        if(BuildConfig.DEBUG){ //如果在debug模式下
            // 打印日志,默认关闭
            ARouter.openLog()
            // 开启调试模式,默认关闭(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
            ARouter.openDebug()
            // 打印日志的时候打印线程堆栈
            ARouter.printStackTrace()
        } 

        // 尽可能早,推荐在Application中初始化
        ARouter.init(this) 

    }
}

1.3 普通的Activity跳转

方法又有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

1.4 普通的Activity跳转参数

调用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"))

1.5普通的Activity跳转动画

添加跳转动画有两种方法,一个是兼容,一个是只能大于等于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跳转就是可以根据url来跳转,可以从网页跳到Activity

3.1 网页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 = "当前页面是:" + aaa@qq.comUrlTargetActivity::class.java.name



    }
}

然后在手机打开一个网页,网页内容为:

<a href="arouter://m.aliyun.com/test/activity1">arouter://m.aliyun.com/test/activity1</a></p>

点击了这个网页链接之后,就到根据"arouter://m.aliyun.com(scheme://host)跳转到SchemeFilterActivity这个Activity,然后在这个Activity利用ARouter 根据path = test/activity1跳转到UrlTargetActivity

3.2 Url跳转带常用类型参数

上面的网页链接添加name、age、sex三个参数,例如:

<p><a href="arouter://m.aliyun.com/test/activity1?name=tpnet&age=21&sex=true">arouter://m.aliyun.com/test/activity1</a></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) 进行注入,即可得到参数。

那参数怎么样对应变量呢?

  • 1是根据变量名
  • 2是Autowired注解里面有个属性name,这个属性指定参数名称

    PS: @JvmField这个注解,是因为我用的是Kotlin,ARouter使用的是java,所以为了兼容,需要加这个注解

3.3 Url跳转带自定义类型参数

网页通过传递json参数,然后ARouter帮你自动转换为你的自定义对象。

例如,网页链接为:

<p><a href="arouter://m.aliyun.com/test/activity1?name=tpnet&age=21&sex=true&obj=%7B%22name%22:%22jack%22,%22id%22:666%7D">带json自定义对象</a></p>

然后写一个类,实现SerializationService接口,用来实现json的序列化和反序列化。注意这个类也需要添加path,内容不规定

//这里需要添加path,内容随便
@Route(path = "/service/json")
class JsonObjectImpl : SerializationService {

    override fun init(context: Context?) {

    }


    //json字符串转换为对象
    override fun <T : Any?> json2Object(json: String?, clazz: Class<T>?): 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实例

在需要获取的Fragment添加注解@Route

@Route(path = "/test/fragment")
public class BlankFragment extends Fragment {}

获取的时候,强制转换。

Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();

带RequestCode跳转

在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方法即可获取到这个内容。

重写跳转URL

// 实现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;    // 按照一定的规则处理之后返回处理后的结果
   }
}

跳转到其他Module

方法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) 进行跳转,手动指定分组,否则无法找到

Api总结

// 构建标准的路由请求
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();