阅读 262

ARouter 不一样的简介

一、前言

ARouter 是阿里巴巴出品的一款优秀的路由以及依赖注入解决方案。其可用于模块化的改造,解除模块之间的强依赖。通过辅以简单的依赖构造脚本,就可以实现完全隔离各个模块之间的依赖。

1. 路由特性

通过 ARouter 来管理路由,可以通过设置路径以及 URI 的方式来路由到目标 Activity。也就免去了我们通过 startActivity 的方式来启动一个 Activity,这有利于模块之间的隔离。

而在路由方面,除了可以携带参数之外,其还有一个更重要的功能便是拦截器。通过拦截器,我们可以很容易也很优雅的实现界面的授权跳转。如用户未登录则可以让其先跳登录,而不用通过写的满天飞的 if/else 的方式判断是否登录,是否可跳转。

2.依赖注入特性

依赖注入特性,是通过实现其接口 IProvider 来实现一个我们的服务类,当然,本质上就是一个普通的 Java 类。然后我们可以通过 ByType,也就是目标类,如 xx.class 来获取该类的实例。也可以通过 ByName ,也就是目标路径,如 "/service/HelloService" 来获取类的实例。

依赖注入的这个特性,天然就是为了模块间的解耦而设计的。

二、简单的路由

1.定义路由

@Route(path = "/test/activity1", name = "测试用 Activity")
public class Test1Activity extends AppCompatActivity {
    @Autowired(desc = "姓名")
    String name;
    @Autowired
    int age;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test1);

        ARouter.getInstance().inject(this);
    }
}
复制代码
  1. 使用注解 @Route 定义路由的路径
  2. 使用 @Autowired 定义要注解的参数
  3. 使用 inject() 方法注解对象,其主要目的是为了给参数赋值

2.路由

ARouter.getInstance()
             .build("/test/activity1")
             .navigation();
复制代码

获取到 ARouter 的实例,然后通过目标路径 build 出一个 PostCard,再然后调用 navigation() 方法就可以路由到目标 Activity 了。当然,如果目标 Activity 有在 AndroidManifest.xml 通过 注册有 scheme、host 的话,也可通过 URI 来进行路由。示例如下: 注册

 <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>
复制代码

路由

Uri testUriMix = Uri.parse("arouter://m.aliyun.com/test/activity1");
                ARouter.getInstance().build(testUriMix)
                        .withString("key1", "value1")
                        .navigation();
复制代码

3.路由的秘密

这个路由看起来好像有点神秘的样子。那我们需要去看 ARouter 的源码吗?当然,如果你有时间和能力也不是不可以,但我觉得在这之前,应该还有更简单的方式来窥探一二。 如下,在 build.gradle 中如果配置了 ARouter 的 annotationProcessorOptions,

javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: "enable"]
            }
        }
复制代码

则会在 build/generated/source/apt/debug/com/alibaba/arouter/ 下面生成相应的 Java 文件。如下图所示。

image.png

在这些生成的 Java 文件中,大概可以分为这么 3 类。

  1. 根路由
public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("test", ARouter$$Group$$test.class);
    routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
  }
}
复制代码

根据实现了 IRouteRoot 类,其以 group 为维度对路由表进行分类。 2) group 路由表

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
    atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", new java.util.HashMap<String, Integer>(){{put("obj", 11); put("name", 8); }}, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
  }
}
复制代码

路由表以 path 为 key ,并以目标 Activity、目标路径、参数等元素构造 RouteMeta 为 Value。对于服务类,也是一样的。下面讲服务类时也会提到。

  1. 参数注入器 Syringe
public class Test1Activity$$ARouter$$Autowired implements ISyringe {
  private SerializationService serializationService;
  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    Test1Activity substitute = (Test1Activity)target;
    substitute.name = substitute.getIntent().getExtras() == null ? substitute.name : substitute.getIntent().getExtras().getString("name", substitute.name);
    substitute.age = substitute.getIntent().getIntExtra("age", substitute.age);
    ......
  }
}
复制代码

其实参数流入就是从 Intent 里面拿参数咯,如果是 Fragment 那就是从 Bundle 里拿,这里就不展开了。

看到这里,如果你的 APT 的技术储备,那即使不看源码,也应该对路由的本质有所了解了吧。

三、拦截器

1. 实现一个自己的拦截器

@Interceptor(priority = 7)
public class Test1Interceptor implements IInterceptor {
    Context mContext;
    @Override
    public void process(final Postcard postcard, final InterceptorCallback callback) {
        if (isLogin) {
            callback.onContinue(postcard);
        } else {
            callback.onInterrupt(null);
        }
    }
    @Override
    public void init(Context context) {
        mContext = context;
        Log.e("testService", Test1Interceptor.class.getName() + " has init.");
    }
}
复制代码

如上,实现拦截器的 3 要素:

  1. 使用注解 @Interceptor,同时还可以定义优先级,优先级越高,就越被先执行。
  2. 实现 IInterceptor 接口
  3. 重载 process() 方法。process() 方法中,callback.onContinue() 和 callback.onInterrupt() 必须调用其中一个。init() 方法可选,其只在初始化执行一次。

2.拦截器的秘密

public class ARouter$$Interceptors$$app implements IInterceptorGroup {
  @Override
  public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
    interceptors.put(7, Test1Interceptor.class);
  }
}
复制代码

拦截器会以优先级做为维度,也就是 key ,建立一个拦截器的表。

四、服务类

1. 实现自己的服务类

public interface HelloService extends IProvider {
    void sayHello(String name);
}
@Route(path = "/yourservicegroupname/hello")
public class HelloServiceImpl implements HelloService {
    Context mContext;

    @Override
    public void sayHello(String name) {
        Toast.makeText(mContext, "Hello " + name, Toast.LENGTH_SHORT).show();
    }
    @Override
    public void init(Context context) {
        mContext = context;
    }
}
复制代码
  1. 定义一个服务接口,继承自 IProvider。
  2. 实现服务接口,并且实现具体的方法。

2. 路由服务类

((HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation()).sayHello("mike");
复制代码

和路由到一个目标 Activity 的使用方法一致。

3. 服务类的秘密

public class ARouter$$Group$$yourservicegroupname implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/yourservicegroupname/hello", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/yourservicegroupname/hello", "yourservicegroupname", null, -1, -2147483648));
    atlas.put("/yourservicegroupname/json", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/yourservicegroupname/json", "yourservicegroupname", null, -1, -2147483648));
    atlas.put("/yourservicegroupname/single", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/yourservicegroupname/single", "yourservicegroupname", null, -1, -2147483648));
  }
}
复制代码

看上去和 Activity 的路由差不多,但是其中有一个很小的区别,那就是 RouteType.PROVIDER。而对于 Activity 则是 RouteType.ACTIVITY。想必其内部应该是通过这个 RouteType 来区分不同的目标对象的。

五、总结

文章大致介绍了 ARouter 的用途,对路由、拦截器以及服务类相对比较详细的介绍。为了避免深入源码的细节,这里从其产生的 Java 文件为视口,从一定程度上揭秘了 ARouter 的大致原理。当然,ARouter 框架,其本身还涉及到其他的技术,这些将在后面的源码分析文章中来分享。