组件化 ARouter 使用

围巾🧣 2024年03月12日 672次浏览

添加依赖

  1. groovy复制代码android {
  2. defaultConfig {
  3. ...
  4. javaCompileOptions {
  5. annotationProcessorOptions {
  6. arguments = [AROUTER_MODULE_NAME: project.getName()]
  7. }
  8. }
  9. }
  10. }

  11. dependencies {
  12. // 替换成最新版本, 需要注意的是api
  13. // 要与compiler匹配使用,均使用最新版可以保证兼容
  14. compile 'com.alibaba:arouter-api:x.x.x'
  15. annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
  16. ...
  17. }
  18. // 旧版本gradle插件(< 2.2),可以使用apt插件,配置方法见文末'其他#4'
  19. // Kotlin配置参考文末'其他#5'

目前最新版本(2021年12月7日)是1.5.2版本,以下介绍皆基于此版本说明。

引入,每个模块的 build.gradle 都要写

  1. android {
  2. defaultConfig {
  3. javaCompileOptions {
  4. annotationProcessorOptions {
  5. arguments = [moduleName: project.getName()]
  6. }
  7. }
  8. }
  9. }

  10. dependencies {
  11. api 'com.alibaba:arouter-api:1.5.2'
  12. annotationProcessor 'com.alibaba:arouter-compiler:1.5.2'

  13. }

有 kotlin 的还需配置

  1. apply plugin: 'kotlin-kapt'

  2. kapt {
  3. arguments {
  4. arg("AROUTER_MODULE_NAME", project.getName())
  5. }
  6. }

  7. dependencies {
  8. kapt 'com.alibaba:arouter-compiler:1.5.2'
  9. }

混淆

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

  1. groovy复制代码-keep public class com.alibaba.android.arouter.routes.**{*;}
  2. -keep public class com.alibaba.android.arouter.facade.**{*;}
  3. -keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}

  4. # 如果使用了 byType 的方式获取 Service,需添加下面规则,保护接口
  5. -keep interface * implements com.alibaba.android.arouter.facade.template.IProvider

  6. # 如果使用了 单类注入,即不定义接口实现 IProvider,需添加下面规则,保护实现
  7. # -keep class * implements com.alibaba.android.arouter.facade.template.IProvider

初始化

官方建议尽早初始化,放到Application中

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

在开发调试过程中注意需要调用openDebug()方法,否则可能会出现找不到Activity的情况。

添加注解

在目标Activity/Fragment中用Router添加注解,path为路由路径

  1. @Router(path = "/app/MainActivity")
  2. public class MainActivity extends Activity{
  3. protected void onCreate(){
  4. ...
  5. //注入
  6. ARouter.getInstance().inject(this)
  7. }
  8. }

路径必须用/且至少有两级,同时需要在onCreate中进行注入。

发起路由

  • Activity跳转

    1. ARouter.getInstance().build("/app/MainActivity").navigation();
  • Fragment

    1. aFragment = (AFragment) ARouter.getInstance().build("/fragment/AFragment").navigation();

传参

  • 基本数据类型

    传递参数:

    1. ARouter.getInstance().build(路径)
    2. .withChar("CharKey", 'a')
    3. .withShort("ShortKey", 1)
    4. .withInt("IntKey", 11)
    5. .withLong("LongKey", 12l)
    6. .withFloat("FloatKey", 1.1f)
    7. .withDouble("DoubleKey", 1.1d)
    8. .withBoolean("BooleanKey", true)
    9. .withString("StringKey", "value")
    10. .navigation();

    通过注解解析参数:

    注意不能使用private关键字修饰。

    1. //伪代码如下
    2. public class TestActivity extends Activity{
    3. //用Autowired注解
    4. @Autowired(name = "IntKey")
    5. int i;
    6. @Autowired(name = "StringKey")
    7. String str;
    8. //...省略其他

    9. onCreate(){
    10. ...
    11. Log.d("IntKey = " + i + " StringKey = " + str);
    12. }
    13. }

    传参过程中,可以省略nameARouter会自动根据类型匹配参数,但是建议都指定name,避免一些异常。

  • 序列化对象

    传递:

    1. ARouter.getInstance().build("路径")
    2. .withSerializable("SerializableKey", new TestObj())
    3. .withParcelable("ParcelableKey", new TestObj())
    4. .navigation();

    传递对接需要分别实现SerializableParcelable接口,调用的方法与基本数据类型大同小异,很好理解。

    解析:

    同样通过注解进行自动参数解析。

    1. // 支持解析自定义对象,URL中使用json传递
    2. @Autowired(name = "ParcelableKey")
    3. TestObj obj;

    同样建议指定name,当然也支持省略name

  • 自定义对象

    传递自定义对象的前提是对象不能实现SerializableParcelable接口。

    传递:

    1. ARouter.getInstance().build("路径")
    2. .withObject("ObjectKey", new TestObjectBean())
    3. .navigation();

    解析:

    1. @Autowired(name = "ObjectKey")
    2. TestObjectBean object;

    除了上述两步外,传递自定义对象必须新建一个类,实现 SerializationService,并使用@Route注解标注。

    ARouter这么设计是为了方便用户自己选择Json解析方式。(路径随意指定一个不重复的就可以)

    1. @Route(path = "/app/custom/json")
    2. public class JsonSerializationService implements SerializationService {
    3. Gson gson;
    4. @Override
    5. public <T> T json2Object(String input, Class<T> clazz) {
    6. return gson.fromJson(input,clazz);
    7. }

    8. @Override
    9. public String object2Json(Object instance) {
    10. return gson.toJson(instance);
    11. }

    12. @Override
    13. public <T> T parseObject(String input, Type clazz) {
    14. return gson.fromJson(input,clazz);
    15. }

    16. @Override
    17. public void init(Context context) {
    18. gson = new Gson();
    19. }
    20. }

    除了初始化init方法,其他几个方法都是用来处理json和object对象转换的。

    因为整个自定义对象的传递过程经历了一下几步:

    • withObhect(“”, obj)
    • 调用SerializationService的2json方法将obj转换成string的json对象。
    • 将string的json对象传递到目标页面。
    • 目标页面调用调用SerializationService的2Object方法将json对象转换成obj对象。

    也可以从withObject的源码中看到转换步骤:

    1. /**
    2. * Set object value, the value will be convert to string by 'Fastjson'
    3. *
    4. * @param key a String, or null
    5. * @param value a Object, or null
    6. * @return current
    7. */
    8. public Postcard withObject(@Nullable String key, @Nullable Object value) {
    9. serializationService = ARouter.getInstance().navigation(SerializationService.class);
    10. mBundle.putString(key, serializationService.object2Json(value));
    11. return this;
    12. }

    需要注意的是,因为参数的解析过程是通过类型匹配自动处理的,所以使用withObject()传递List和Map对象时,接收该对象时不能指定List和Map的实现了Serializable的实现类(ArrayListHashMap等),可能有点拗口,简单来说就是接收withObject传参的对象,不能是SerializableParcelable的实现类。(如果指定了SerializableParcelable的实现类会影响序列化类型的判断。)

路由管理

上面的跳转处理过程中不可避免会需要指定很多的路由路径(如Activity路径等),为了方便管理和处理,通常会定义一个常量类去维护和管理所有的路由路径,并且为了各模块可以正常引用需要将常量类下沉到基础模块中(如BaseModule中),虽然这在一定程度上破坏了各模块的独立性(必须依赖常量类模块才能实现路由跳转),增加了业务模块与基础模块的耦合性,但是处于代码维护的角度考虑,这么做还是有必要的。

  1. /**
  2. * 路由管理类
  3. */
  4. public class ARouterPath {
  5. /** push module */
  6. public static final String SERVICE_PUSH_RECEIVER_URL = "/push/PushRegisterActivity";

  7. /** Display Module */
  8. public static final String ACTIVITY_DISPLAY_DISPLAY_URL = "/display/DisplayActivity";
  9. public static final String SERVICE_DISPLAY_UPLOAD_URL = "/display/PushReceiver";
  10. }

拦截器

之前在介绍OkHttp中提到过,OkHttp的拦截器设计是整个框架实现中特别经典,特别有亮点的部分。在ARouter中同样添加了拦截器的实现,拦截器可以对路由的过程进行拦截和处理。

  1. @Interceptor(priority = 8, name = "测试拦截器")
  2. public class TestInterceptor implements IInterceptor {
  3. private static final String TAG = "Interceptor";

  4. @Override
  5. public void process(Postcard postcard, InterceptorCallback callback) {
  6. String threadName = Thread.currentThread().getName();
  7. Log.i(TAG, "拦截器开始执行,线程名称: " + threadName +
  8. "\n postcard = " + postcard.toString());

  9. //拦截路由操作
  10. callback.onInterrupt(new RuntimeException("有异常,禁止路由"));
  11. //继续路由操作
  12. callback.onContinue(postcard);
  13. }

  14. @Override
  15. public void init(Context context) {
  16. Log.i(TAG, "TestInterceptor拦截器初始化");
  17. }
  18. }

拦截器支持添加多个,通过注解@Interceptor()进行注解来实现拦截器的注册,priority用于定义拦截器的优先级,数字越小优先级越高,多个拦截器按优先级顺序执行。

  • 多个拦截器不能拥有相同的优先级。
  • name属性为拦截器指定名称(可省略)。
  • init()方法会在拦截器被初始化时自动调用。
  • process()当有路由操作被发起时会触发,可以根据需要通过onInterrupt()拦截路由,或者通过onContinue()继续路由操作,注意两个方法必须调用一个,否则路由会丢失不会继续执行。

拦截器比较经典的应用时用来判断登录事件,app中某些页面必须用户登录之后才能跳转,这样的话就可以通过拦截器做登录检查,避免在目标页面重复检查。

跳转结果监听处理:

  1. ARouter.getInstance().build("路径")
  2. .navigation(this, new NavigationCallback() {
  3. @Override
  4. public void onFound(Postcard postcard) {
  5. //路由发现
  6. }

  7. @Override
  8. public void onLost(Postcard postcard) {
  9. //路由丢失
  10. }

  11. @Override
  12. public void onArrival(Postcard postcard) {
  13. //达到
  14. }

  15. @Override
  16. public void onInterrupt(Postcard postcard) {
  17. //拦截
  18. }
  19. });

在跳转时通过指定NavigationCallback进行跳转监听。如果只想监听到达事件也可以通过指定抽象类NavCallback来进行简化。

需要注意的是只有Activity才会触发拦截器,Fragment和IProvider并不支持拦截。

通过查看源码,可以发现拦截处理是在_ARouter的navigation()方法中处理的。

  1. if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
  2. //处理拦截器
  3. interceptorService.doInterceptions(postcard, new InterceptorCallback() {
  4. /**
  5. * Continue process
  6. *
  7. * @param postcard route meta
  8. */
  9. @Override
  10. public void onContinue(Postcard postcard) {
  11. _navigation(postcard, requestCode, callback);
  12. }
  13. //省略
  14. }

是否被拦截取决于postcard.isGreenChannel()值,而赋值是在LogisticsCenter的completion()方法中:

  1. switch (routeMeta.getType()) {
  2. case PROVIDER: // if the route is provider, should find its instance
  3. // Its provider, so it must implement IProvider
  4. Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
  5. IProvider instance = Warehouse.providers.get(providerMeta);
  6. if (null == instance) { // There's no instance of this provider
  7. IProvider provider;
  8. try {
  9. provider = providerMeta.getConstructor().newInstance();
  10. provider.init(mContext);
  11. Warehouse.providers.put(providerMeta, provider);
  12. instance = provider;
  13. } catch (Exception e) {
  14. logger.error(TAG, "Init provider failed!", e);
  15. throw new HandlerException("Init provider failed!");
  16. }
  17. }
  18. postcard.setProvider(instance);
  19. //Provider不需要拦截
  20. postcard.greenChannel(); // Provider should skip all of interceptors
  21. break;
  22. case FRAGMENT:
  23. //Fragment不需要拦截
  24. postcard.greenChannel(); // Fragment needn't interceptors
  25. default:
  26. break;
  27. }

通过上面的源码可以看到类型判断是通过routeMeta.getType()很明显就是路由的类型,而ARouter虽然定义了很多的类型:

  1. /**
  2. * Type of route enum.
  3. *
  4. * @author Alex <a href="mailto:zhilong.liu@aliyun.com">Contact me.</a>
  5. * @version 1.0
  6. * @since 16/8/23 22:33
  7. */
  8. public enum RouteType {
  9. ACTIVITY(0, "android.app.Activity"),
  10. SERVICE(1, "android.app.Service"),
  11. PROVIDER(2, "com.alibaba.android.arouter.facade.template.IProvider"),
  12. CONTENT_PROVIDER(-1, "android.app.ContentProvider"),
  13. BOARDCAST(-1, ""),
  14. METHOD(-1, ""),
  15. FRAGMENT(-1, "android.app.Fragment"),
  16. UNKNOWN(-1, "Unknown route type");
  17. //省略
  18. }

比如在RouteType中定义的如:Service、ContentProvider甚至Boardcast等类型,但是ARouter的路由跳转其实只支持:Activity、Fragment和IProvider三种类型,其他类型不支持(基于1.5.2版本),IProvider是ARouter的服务组件功能后面会提到。

startActivityForResult

ARouter也支持startActivityForResult()的方式获取目标页面的回传值。

  1. //路由跳转时需要指定RequestCode,123
  2. ARouter.getInstance().build("/AModule/AModuleActivity").navigation(this, 123);

目标页面在退出时需要通过setResult方法回传ResultCode和数据

  1. //目标页面实现,省略其他代码
  2. @Override
  3. public void onBackPressed() {
  4. Intent data = new Intent();
  5. data.putExtra("name", "我是AModuleActivity");
  6. setResult(321, data);
  7. super.onBackPressed();
  8. }

同时需要在跳转页面,重写onActivityResult()方法,整个使用过程也原生startActivityForResult()基本一致。

  1. //跳转页面实现,其他代码省略
  2. @Override
  3. protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
  4. super.onActivityResult(requestCode, resultCode, data);
  5. if(requestCode == 123 && resultCode == 321){
  6. String name = data.hasExtra("name")? data.getStringExtra("name") : "";
  7. Log.d(TAG, "onActivityResult: name = " + name);
  8. }
  9. }

动态增加路由

上面提到的所有页面的路由添加都是通过注解关键字@Route和注入方法inject()在代码编译时ARouter框架自动添加的。ARouter同样支持动态路由的增加。

  1. ARouter.getInstance().addRouteGroup(new IRouteGroup() {
  2. @Override
  3. public void loadInto(Map<String, RouteMeta> atlas) {
  4. atlas.put("/dynamic/activity", // path
  5. RouteMeta.build(
  6. RouteType.ACTIVITY, // 路由信息
  7. TestDynamicActivity.class, // 目标的 Class
  8. "/dynamic/activity", // Path
  9. "dynamic", // Group, 尽量保持和 path 的第一段相同
  10. 0, // 优先级,暂未使用
  11. 0 // Extra,用于给页面打标
  12. )
  13. );
  14. }
  15. });

这种方式适用于部分插件化开发场景中,添加路由之后的跳转过程与之前一致。

服务

前面也提到了IProvider服务组件功能,是通过实现IProvider接口来定义组件内的开放接口,来满足其他组件的调用需求。通俗点就是AModule中通过服务开放功能,让其他Module可以调用。

  • 实现IProvider接口

    通过IProvider接口定义ARouter服务。

    1. // 声明接口,其他组件通过接口来调用服务
    2. public interface HelloService extends IProvider {
    3. String sayHello(String name);
    4. }

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

    8. @Override
    9. public String sayHello(String name) {
    10. return "hello, " + name;
    11. }

    12. @Override
    13. public void init(Context context) {

    14. }
    15. }

    在组件化开发中,通常会在底层模块中定义一个接口类HelloService(比如在base模块中),再具体模块中实现接口功能。

    注意:服务只有在被使用时才会触发init方法被初始化。

  • 发现服务

    • 依赖注入

      1. public class Test {
      2. @Autowired
      3. HelloService helloService;

      4. @Autowired(name = "/yourservicegroupname/hello")
      5. HelloService helloService2;

      6. public void fun(){
      7. //可以直接使用服务
      8. // 1. (推荐)使用依赖注入的方式发现服务,通过注解标注字段,即可使用,无需主动获取
      9. // Autowired注解中标注name之后,将会使用byName的方式注入对应的字段,不设置name属性,会默认使用byType的方式发现 //服务(当同一接口有多个实现的时候,必须使用byName的方式发现服务)
      10. helloService.sayHello("1");
      11. helloService2.sayHello("2");
      12. }
      13. }
    • 依赖查找

      1. public class Test {
      2. @Autowired
      3. HelloService helloService;

      4. @Autowired(name = "/yourservicegroupname/hello")
      5. HelloService helloService2;

      6. public void fun(){
      7. // 2. 使用依赖查找的方式发现服务,主动去发现服务并使用,下面两种方式分别是byName和byType
      8. helloService3 = ARouter.getInstance().navigation(HelloService.class);
      9. helloService4 = (HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation();
      10. helloService3.sayHello("Vergil");
      11. helloService4.sayHello("Vergil");
      12. }
      13. }
    • 预处理服务

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

      8. @Override
      9. public void init(Context context) {

      10. }
      11. }