0%

Android ARouter

前言

目前组件化开发已经很成熟, 今天来说一下组件化开发框架ARouter

Github地址

https://github.com/alibaba/ARouter

项目中的中文文档已经非常的详尽了

基础功能

  1. 添加依赖和配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    android {
    defaultConfig {
    ...
    javaCompileOptions {
    annotationProcessorOptions {
    arguments = [AROUTER_MODULE_NAME: 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. 添加注解

    1
    2
    3
    4
    5
    6
    // 在支持路由的页面上添加注解(必选)
    // 这里的路径需要注意的是至少需要有两级,/xx/xx
    @Route(path = "/test/activity")
    public class YourActivity extend Activity {
    ...
    }
  3. 初始化SDK

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 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)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    -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 插件实现路由表的自动加载 (可选)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    apply plugin: 'com.alibaba.arouter'

    buildscript {
    repositories {
    jcenter()
    }

    dependencies {
    classpath "com.alibaba:arouter-register:?"
    }
    }

    可选使用,通过 ARouter 提供的注册插件进行路由表的自动加载, 默认通过扫描 dex 的方式 进行加载通过 gradle 插件进行自动注册可以缩短初始化时间解决应用加固导致无法直接访问 dex 文件,初始化失败的问题,需要注意的是,该插件必须搭配 api 1.3.0 以上版本使用!

  7. 使用 IDE 插件导航到目标类 (可选)

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

进阶用法

  1. 通过URL跳转

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 新建一个Activity用于监听Scheme事件,之后直接把url传递给ARouter即可
    public class SchemeFilterActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Uri uri = getIntent().getData();
    ARouter.getInstance().build(uri).navigation();
    finish();
    }
    }

    AndroidManifest.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <activity android:name=".activity.SchemeFilterActivity">
    <!-- Scheme -->
    <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. 解析参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    // 为每一个参数声明一个字段,并使用 @Autowired 标注
    // URL中不能传递Parcelable类型数据,通过ARouter api可以传递Parcelable对象
    @Route(path = "/test/activity")
    public class Test1Activity extends Activity {
    @Autowired
    public String name;
    @Autowired
    int age;

    // 通过name来映射URL中的不同参数
    @Autowired(name = "girl")
    boolean boy;

    // 支持解析自定义对象,URL中使用json传递
    @Autowired
    TestObj obj;

    // 使用 withObject 传递 List 和 Map 的实现了
    // Serializable 接口的实现类(ArrayList/HashMap)
    // 的时候,接收该对象的地方不能标注具体的实现类类型
    // 应仅标注为 List 或 Map,否则会影响序列化中类型
    // 的判断, 其他类似情况需要同样处理
    @Autowired
    List<TestObj> list;
    @Autowired
    Map<String, List<TestObj>> map;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ARouter.getInstance().inject(this);

    // ARouter会自动对字段进行赋值,无需主动获取
    Log.d("param", name + age + boy);
    }
    }


    // 如果需要传递自定义对象,新建一个类(并非自定义对象类),然后实现 SerializationService,并使用@Route注解标注(方便用户自行选择序列化方式),例如:
    @Route(path = "/yourservicegroupname/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);
    }
    }
  1. 声明拦截器(拦截跳转过程,面向切面编程)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查
    // 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行
    @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初始化的时候调用该方法,仅会调用一次
    }
    }
  2. 处理跳转结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 使用两个参数的navigation方法,可以获取单次跳转的结果
    ARouter.getInstance().build("/test/1").navigation(this, new NavigationCallback() {
    @Override
    public void onFound(Postcard postcard) {
    ...
    }

    @Override
    public void onLost(Postcard postcard) {
    ...
    }
    });
  3. 自定义全局降级策略

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 实现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) {}}
  4. 为目标页面声明更多信息

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 声明接口,其他组件通过接口来调用服务
    public interface HelloService extends IProvider {
    String sayHello(String name);
    }

    // 实现接口
    @Route(path = "/yourservicegroupname/hello", name = "测试服务")
    public class HelloServiceImpl implements HelloService {

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

    @Override
    public void init(Context context) {

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public class Test {
    @Autowired
    HelloService helloService;

    @Autowired(name = "/yourservicegroupname/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("/yourservicegroupname/hello").navigation();
    helloService3.sayHello("Vergil");
    helloService4.sayHello("Vergil");
    }
    }
  7. 预处理服务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 实现 PretreatmentService 接口,并加上一个Path内容任意的注解即可
    @Route(path = "/xxx/xxx")
    public class PretreatmentServiceImpl implements PretreatmentService {
    @Override
    public boolean onPretreatment(Context context, Postcard postcard) {
    // 跳转前预处理,如果需要自行处理跳转,该方法返回 false 即可
    }

    @Override
    public void init(Context context) {

    }
    }

更多功能

  1. 初始化中的其他设置

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    // 构建标准的路由请求
    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();

    // 使用自己提供的线程池
    ARouter.setExecutor();
  3. 获取原始的URI

    1
    String uriStr = getIntent().getStringExtra(ARouter.RAW_URI);
  4. 重写跳转URL

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 实现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; // 按照一定的规则处理之后返回处理后的结果
    }
    }
  5. 生成路由文档

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 更新 build.gradle, 添加参数 AROUTER_GENERATE_DOC = enable
    // 生成的文档路径 : build/generated/source/apt/(debug or release)/com/alibaba/android/arouter/docs/arouter-map-of-${moduleName}.json
    android {
    defaultConfig {
    ...
    javaCompileOptions {
    annotationProcessorOptions {
    arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: "enable"]
    }
    }
    }
    }

其他

  1. 路由中的分组概念

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    apply plugin: 'com.neenbedankt.android-apt'

    buildscript {
    repositories {
    jcenter()
    }

    dependencies {
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
    }
    }

    apt {
    arguments {
    AROUTER_MODULE_NAME project.getName();
    }
    }

    dependencies {
    compile 'com.alibaba:arouter-api:x.x.x'
    apt 'com.alibaba:arouter-compiler:x.x.x'
    ...
    }
  4. Kotlin项目中的配置方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 可以参考 module-kotlin 模块中的写法
    apply plugin: 'kotlin-kapt'

    kapt {
    arguments {
    arg("AROUTER_MODULE_NAME", project.getName())
    }
    }

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