从外部与内部直接导航到页面服务的中间件 - ARouter

2016-12-14      3371      Android
项目简介

用于在Android平台,从外部(浏览器等),内部直接导航到页面、服务的中间件。

Gradle依赖

dependencies {
    apt 'com.alibaba:arouter-compiler:x.x.x'
    compile 'com.alibaba:arouter-api:x.x.x'
}

一、功能介绍

  • 支持直接解析URL进行跳转、参数按类型解析,支持Java基本类型(*)
  • 支持应用内的标准页面跳转,API接近Android原生接口
  • 支持多模块工程中使用,允许分别打包,包结构符合Android包规范即可(*)
  • 支持跳转过程中插入自定义拦截逻辑,自定义拦截顺序(*)
  • 支持服务托管,通过ByName,ByType两种方式获取服务实例,方便面向接口开发与跨模块调用解耦(*)
  • 映射关系按组分类、多级管理,按需初始化,减少内存占用提高查询效率(*)
  • 支持用户指定全局降级策略
  • 支持获取单次跳转结果
  • 丰富的API和可定制性
  • 被ARouter管理的页面、拦截器、服务均无需主动注册到ARouter,被动发现
  • 支持Android N推出的Jack编译链

二、不支持的功能

  • 自定义URL解析规则(考虑支持)
  • 不能动态加载代码模块和添加路由规则(考虑支持)
  • 多路径支持(不想支持,貌似是导致各种混乱的起因)
  • 生成映射关系文档(考虑支持)

三、典型应用场景

  • 从外部URL映射到内部页面,以及参数传递与解析
  • 跨模块页面跳转,模块间解耦
  • 拦截跳转过程,处理登陆、埋点等逻辑
  • 跨模块API调用,模块间解耦(注册ARouter服务的形式,通过接口互相调用)

四、基础功能

1添加依赖和配置

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 {
    apt 'com.alibaba:arouter-compiler:x.x.x'
    compile 'com.alibaba:arouter-api:x.x.x'
    ...
}

2添加注解

// 在支持路由的页面、服务上添加注解(必选)
// 这是最小化配置,后面有详细配置
@Route(path = "/test/1")
public class YourActivity extend Activity {
    ...
}

3初始化SDK

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

4发起路由操作

// 1. 应用内简单的跳转(通过URL跳转在'中阶使用'中)
ARouter.getInstance().build("/test/1").navigation();

// 2. 跳转并携带参数
ARouter.getInstance().build("/test/1")
            .withLong("key1", 666L)
            .withString("key3", "888")
            .navigation();

5添加混淆规则(如果使用了Proguard)

-keep public class com.alibaba.android.arouter.routes.**{*;}

五、进阶用法

1通过URL跳转

// 新建一个Activity用于监听Schame事件
// 监听到Schame事件之后直接传递给ARouter即可
// 也可以做一些自定义玩法,比方说改改URL之类的
// http://www.example.com/test/1
public class SchameFilterActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 外面用户点击的URL
        Uri uri = getIntent().getData();
        // 直接传递给ARouter即可
        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>

        <!-- App Links -->
        <intent-filter android:autoVerify="true">
            <action android:name="android.intent.action.VIEW"/>

            <category android:name="android.intent.category.DEFAULT"/>
            <category android:name="android.intent.category.BROWSABLE"/>

            <data
                android:host="m.aliyun.com"
                android:scheme="http"/>
            <data
                android:host="m.aliyun.com"
                android:scheme="https"/>
        </intent-filter>
</activity>

2使用ARouter协助解析参数类型

// URL中的参数会默认以String的形式保存在Bundle中
// 如果希望ARouter协助解析参数(按照不同类型保存进Bundle中)
// 只需要在需要解析的参数上添加 @Param 注解
@Route(path = "/test/1")
public class Test1Activity extends Activity {
    @Param                   // 声明之后,ARouter会从URL中解析对应名字的参数,并按照类型存入Bundle
    public String name;
    @Param
    private int age;
    @Param(name = "girl")   // 可以通过name来映射URL中的不同参数
    private boolean boy;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        name = getIntent().getStringExtra("name");
        age = getIntent().getIntExtra("age", -1);
        boy = getIntent().getBooleanExtra("girl", false);   // 注意:使用映射之后,要从Girl中获取,而不是boy
    }
}

3开启ARouter参数自动注入(实验性功能,不建议使用,正在开发保护策略)

// 首先在Application中重写 attachBaseContext方法,并加入ARouter.attachBaseContext();
@Override
protected void attachBaseContext(Context base) {
   super.attachBaseContext(base);

   ARouter.attachBaseContext();
}

// 设置ARouter的时候,开启自动注入
ARouter.enableAutoInject();

// 至此,Activity中的属性,将会由ARouter自动注入,无需 getIntent().getStringExtra("xxx")等等

4声明拦截器(拦截跳转过程,面向切面搞事情)

// 比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查

// 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行
@Interceptor(priority = 666, name = "测试用拦截器")
public class TestInterceptor implements IInterceptor {
    /**
     * The operation of this interceptor.
     *
     * @param postcard meta
     * @param callback cb
     */
    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        ...

        callback.onContinue(postcard);  // 处理完成,交还控制权
        // callback.onInterrupt(new RuntimeException("我觉得有点异常"));      // 觉得有问题,中断路由流程

        // 以上两种至少需要调用其中一种,否则会超时跳过
    }

    /**
     * Do your init work in this method, it well be call when processor has been load.
     *
     * @param context ctx
     */
    @Override
    public void init(Context context) {

    }
}

5处理跳转结果

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

    @Override
    public void onLost(Postcard postcard) {
        ...
    }
});

6自定义全局降级策略

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

      /**
       * Do your init work in this method, it well be call when processor has been load.
       *
       * @param context ctx
       */
      @Override
      public void init(Context context) {

      }
    }

7为目标页面声明更多信息

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

8使用ARouter管理服务(一) 暴露服务

/**
 * 声明接口
 */
public interface IService extends IProvider {
    String hello(String name);
}

/**
 * 实现接口
 */
@Route(path = "/service/1", name = "测试服务")
public class ServiceImpl implements IService {

    @Override
    public String hello(String name) {
        return "hello, " + name;
    }

    /**
     * Do your init work in this method, it well be call when processor has been load.
     *
     * @param context ctx
     */
    @Override
    public void init(Context context) {

    }
}

9使用ARouter管理服务(二) 发现服务

1. 可以通过两种API来获取Service,分别是ByName、ByType
IService service = ARouter.getInstance().navigation(IService.class);    //  ByType
IService service = (IService) ARouter.getInstance().build("/service/1").navigation(); //  ByName

service.hello("zz");

2. 注意:推荐使用ByName方式获取Service,ByType这种方式写起来比较方便,但如果存在多实现的情况时,SDK不保证能获取到你想要的实现

10使用ARouter管理服务(三) 管理依赖

可以通过ARouter service包装您的业务逻辑或者sdk,在service的init方法中初始化您的sdk,不同的sdk使用ARouter的service进行调用,
每一个service在第一次使用的时候会被初始化,即调用init方法。
    这样就可以告别各种乱七八糟的依赖关系的梳理,只要能调用到这个service,那么这个service中所包含的sdk等就已经被初始化过了,完全不需要
关心各个sdk的初始化顺序。