ARouter 路由框架

来源:互联网 发布:新手可以做淘宝代销吗 编辑:程序博客网 时间:2024/06/05 05:12

随着业务模块的增加,组件化开发势在必行。那么页面的跳转与通信则需要一个强大的轮子来支撑。
一、功能介绍

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

二、典型应用

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

三、基础功能

1.添加依赖和配置
 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'    ...}// 旧版本gradle插件(< 2.2),可以使用apt插件,配置方法见文末'其他#4'// Kotlin配置参考文末'其他#5'   
2.添加注解
// 在支持路由的页面上添加注解(必选)// 这里的路径需要注意的是至少需要有两级,/xx/xx@Route(path = "/test/activity")public class YourActivity extend Activity {    ...}
 3.初始化SDK
if (isDebug()) {           // 这两行必须写在init之前,否则这些配置在init过程中将无效    ARouter.openLog();     // 打印日志    ARouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)}ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
4. 发起路由操作
// 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)ARouter.getInstance().build("/test/activity").navigation();// 2. 跳转并携带参数ARouter.getInstance().build("/test/1")            .withLong("key1", 666L)            .withString("key3", "888")            .withObject("key4", new Test("Jack", "Rose"))            .navigation();
 5.添加混淆规则(如果使用了Proguard)
-keep public class com.alibaba.android.arouter.routes.**{*;}-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}

四、进阶用法

1.通过URL跳转

// 新建一个Activity用于监听Schame事件,之后直接把url传递给ARouter即可public class SchameFilterActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    Uri uri = getIntent().getData();    ARouter.getInstance().build(uri).navigation();    finish();    }}

AndroidManifest.xml

<activity android:name=".activity.SchameFilterActivity">    <!-- Schame -->    <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>

2.解析URL中的参数

// 为每一个参数声明一个字段,并使用 @Autowired 标注// URL中不能传递Parcelable类型数据,通过ARouter api可以传递Parcelable对象@Route(path = "/test/activity")public class Test1Activity extends Activity {    @Autowired    public String name;    @Autowired    int age;    @Autowired(name = "girl") // 通过name来映射URL中的不同参数    boolean boy;    @Autowired    TestObj obj;    // 支持解析自定义对象,URL中使用json传递    @Override    protected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    ARouter.getInstance().inject(this);    // ARouter会自动对字段进行赋值,无需主动获取    Log.d("param", name + age + boy);    }}// 如果需要传递自定义对象,需要实现 SerializationService,并使用@Route注解标注(方便用户自行选择序列化方式),例如:@Route(path = "/service/json")public class JsonServiceImpl implements SerializationService {    @Override    public void init(Context context) {    }    @Override    public <T> T json2Object(String text, Class<T> clazz) {        return JSON.parseObject(text, clazz);    }    @Override    public String object2Json(Object instance) {        return JSON.toJSONString(instance);    }}

3.声明拦截器(拦截跳转过程,面向切面编程)

// 比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查// 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行@Interceptor(priority = 8, name = "测试用拦截器")public class TestInterceptor implements IInterceptor {    @Override    public void process(Postcard postcard, InterceptorCallback callback) {    ...    callback.onContinue(postcard);  // 处理完成,交还控制权    // callback.onInterrupt(new RuntimeException("我觉得有点异常"));      // 觉得有问题,中断路由流程    // 以上两种至少需要调用其中一种,否则不会继续路由    }    @Override    public void init(Context context) {    // 拦截器的初始化,会在sdk初始化的时候调用该方法,仅会调用一次    }}

4.处理跳转结果

// 使用两个参数的navigation方法,可以获取单次跳转的结果ARouter.getInstance().build("/test/1").navigation(this, new NavigationCallback() {    @Override    public void onFound(Postcard postcard) {      ...    }    @Override    public void onLost(Postcard postcard) {    ...    }});

5.自定义全局降级策略

// 实现DegradeService接口,并加上一个Path内容任意的注解即可@Route(path = "/xxx/xxx")public class DegradeServiceImpl implements DegradeService {  @Override  public void onLost(Context context, Postcard postcard) {    // do something.  }  @Override  public void init(Context context) {  }}

6.为目标页面声明更多信息

// 我们经常需要在目标页面中配置一些属性,比方说"是否需要登陆"之类的// 可以通过 Route 注解中的 extras 属性进行扩展,这个属性是一个 int值,换句话说,单个int有4字节,也就是32位,可以配置32个开关// 剩下的可以自行发挥,通过字节操作可以标识32个开关,通过开关标记目标页面的一些属性,在拦截器中可以拿到这个标记进行业务逻辑判断@Route(path = "/test/activity", extras = Consts.XXXX)

7.通过依赖注入解耦:服务管理(一) 暴露服务

// 声明接口,其他组件通过接口来调用服务public interface HelloService extends IProvider {    String sayHello(String name);}// 实现接口@Route(path = "/service/hello", name = "测试服务")public class HelloServiceImpl implements HelloService {    @Override    public String sayHello(String name) {    return "hello, " + name;    }    @Override    public void init(Context context) {    }}

8.通过依赖注入解耦:服务管理(二) 发现服务

public class Test {    @Autowired    HelloService helloService;    @Autowired(name = "/service/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("/service/hello").navigation();    helloService3.sayHello("Vergil");    helloService4.sayHello("Vergil");    }}

五、更多功能

1.初始化中的其他设置

ARouter.openLog(); // 开启日志ARouter.openDebug(); // 使用InstantRun的时候,需要打开该开关,上线之后关闭,否则有安全风险ARouter.printStackTrace(); // 打印日志的时候打印线程堆栈

2.详细的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,第二个参数则是RequestCodeARouter.getInstance().build("/home/main", "ap").navigation(this, 5);// 直接传递BundleBundle params = new Bundle();ARouter.getInstance()    .build("/home/main")    .with(params)    .navigation();// 指定FlagARouter.getInstance()    .build("/home/main")    .withFlags();    .navigation();// 获取FragmentFragment 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方法中传入当前ActivityARouter.getInstance()    .build("/test/activity2")    .withOptionsCompat(compat)    .navigation();// 使用绿色通道(跳过所有的拦截器)ARouter.getInstance().build("/home/main").greenChannel().navigation();// 使用自己的日志工具打印日志ARouter.setLogger();

3.获取原始的URI

String uriStr = getIntent().getStringExtra(ARouter.RAW_URI);

4.重写跳转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;    // 按照一定的规则处理之后返回处理后的结果   }}

六、其他

1.路由中的分组概念

    SDK中针对所有的路径(/test/1 /test/2)进行分组,分组只有在分组中的某一个路径第一次被访问的时候,该分组才会被初始化    可以通过 @Route 注解主动指定分组,否则使用路径中第一段字符串(/*/)作为分组    注意:一旦主动指定分组之后,应用内路由需要使用 ARouter.getInstance().build(path, group) 进行跳转,手动指定分组,否则无法找到

2.拦截器和服务的异同

拦截器和服务所需要实现的接口不同,但是结构类似,都存在 init(Context context) 方法,但是两者的调用时机不同拦截器因为其特殊性,会被任何一次路由所触发,拦截器会在ARouter初始化的时候异步初始化,如果第一次路由的时候拦截器还没有初始化结束,路由会等待,直到初始化完成。服务没有该限制,某一服务可能在App整个生命周期中都不会用到,所以服务只有被调用的时候才会触发初始化操作

3.Jack 编译链的支持

因为不想让用户主动设置一堆乱七八糟的参数,在获取模块名的时候使用javac的api,使用了Jack之后没有了javac,只能让用户稍稍动动手了
现在任何情况下都需要在build.gradle中配置moduleName了。。。。

4.旧版本gradle插件的配置方式

apply plugin: 'com.neenbedankt.android-apt'buildscript {    repositories {    jcenter()    }    dependencies {    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'    }}apt {    arguments {    moduleName project.getName();    }}dependencies {    compile 'com.alibaba:arouter-api:x.x.x'    apt 'com.alibaba:arouter-compiler:x.x.x'    ...}

5.Kotlin项目中的配置方式

// 可以参考 module-kotlin 模块中的写法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'    ...}

七、Q&A

1.”W/ARouter::: ARouter::No postcard![ ]”

这个Log正常的情况下也会打印出来,如果您的代码中没有实现DegradeService和PathReplaceService的话,因为ARouter本身的一些功能也依赖 自己提供的Service管理功能,ARouter在跳转的时候会尝试寻找用户实现的PathReplaceService,用于对路径进行重写(可选功能),所以如果您没有 实现这个服务的话,也会抛出这个日志

推荐在app中实现DegradeService、PathReplaceService

2.”W/ARouter::: ARouter::There is no route match the path [/xxx/xxx], in group [xxx][ ]”

通常来说这种情况是没有找到目标页面,目标不存在

如果这个页面是存在的,那么您可以按照下面的步骤进行排查

检查目标页面的注解是否配置正确,正确的注解形式应该是 (@Route(path=”/test/test”), 如没有特殊需求,请勿指定group字段,废弃功能)

检查目标页面所在的模块的gradle脚本中是否依赖了 arouter-compiler sdk (需要注意的是,要使用apt依赖,而不是compile关键字依赖)

检查编译打包日志,是否出现了形如 ARouter::�Compiler >>> xxxxx 的日志,日志中会打印出发现的路由目标

启动App的时候,开启debug、log(openDebug/openLog), 查看映射表是否已经被扫描出来,形如 D/ARouter::: LogisticsCenter has already been loaded, GroupIndex[4],GroupIndex > 0

3.开启InstantRun之后无法跳转(高版本Gradle插件下无法跳转)?

因为开启InstantRun之后,很多类文件不会放在原本的dex中,需要单独去加载,ARouter默认不会去加载这些文件,因为安全原因,只有在开启了openDebug之后 ARouter才回去加载InstantRun产生的文件,所以在以上的情况下,需要在init之前调用openDebug

4.TransformException:java.util.zip.ZipException: duplicate entry ….

ARouter有按组加载的机制,关于分组可以参考 6-1 部分,ARouter允许一个module中存在多个分组,但是不允许多个module中存在相同的分组,会导致映射文件冲突

5.Kotlin类中的字段无法注入如何解决?

首先,Kotlin中的字段是可以自动注入的,但是注入代码为了减少反射,使用的字段赋值的方式来注入的,Kotlin默认会生成set/get方法,并把属性设置为private 所以只要保证Kotlin中字段可见性不是private即可,简单解决可以在字段上添加 @JvmField

6.通过URL跳转之后,在intent中拿不到参数如何解决?

需要注意的是,如果不使用自动注入,那么可以不写 ARouter.getInstance().inject(this),但是需要取值的字段仍然需要标上 @Autowired 注解,因为 只有标上注解之后,ARouter才能知道以哪一种数据类型提取URL中的参数并放入Intent中,这样您才能在intent中获取到对应的参数

7.新增页面之后,无法跳转?

ARouter加载Dex中的映射文件会有一定耗时,所以ARouter会缓存映射文件,直到新版本升级(版本号或者versionCode变化),而如果是开发版本(ARouter.openDebug()), ARouter 每次启动都会重新加载映射文件,开发阶段一定要打开 Debug 功能;并且每个module 都要编译依赖

GitHub链接

原创粉丝点击