组件化 ARouter 使用

幻昼 2024年03月12日 45次浏览

添加依赖

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

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

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

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [moduleName: project.getName()]
            }
        }
    }
}

dependencies {
    api 'com.alibaba:arouter-api:1.5.2'
    annotationProcessor 'com.alibaba:arouter-compiler:1.5.2'
    
}  

有 kotlin 的还需配置

apply plugin: 'kotlin-kapt'

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

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

混淆

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

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

初始化

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

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

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

添加注解

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

@Router(path = "/app/MainActivity")
public class MainActivity extends Activity{
  protected void onCreate(){
    ...
      //注入
      ARouter.getInstance().inject(this)
  }
}

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

发起路由

  • Activity跳转

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

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

传参

  • 基本数据类型

    传递参数:

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

    通过注解解析参数:

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

    //伪代码如下
    public class TestActivity extends Activity{
      //用Autowired注解
      @Autowired(name = "IntKey")
      int i;
      @Autowired(name = "StringKey")
      String str;
      //...省略其他
      
      onCreate(){
        ...
        Log.d("IntKey = " + i + " StringKey = " + str);
      }
    }
    

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

  • 序列化对象

    传递:

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

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

    解析:

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

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

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

  • 自定义对象

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

    传递:

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

    解析:

        @Autowired(name = "ObjectKey")
        TestObjectBean object;
    

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

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

    @Route(path = "/app/custom/json")
    public class JsonSerializationService implements SerializationService {
        Gson gson;
        @Override
        public <T> T json2Object(String input, Class<T> clazz) {
            return gson.fromJson(input,clazz);
        }
    
        @Override
        public String object2Json(Object instance) {
            return gson.toJson(instance);
        }
    
        @Override
        public <T> T parseObject(String input, Type clazz) {
            return gson.fromJson(input,clazz);
        }
    
        @Override
        public void init(Context context) {
            gson = new Gson();
        }
    }
    

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

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

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

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

        /**
         * Set object value, the value will be convert to string by 'Fastjson'
         *
         * @param key   a String, or null
         * @param value a Object, or null
         * @return current
         */
        public Postcard withObject(@Nullable String key, @Nullable Object value) {
            serializationService = ARouter.getInstance().navigation(SerializationService.class);
            mBundle.putString(key, serializationService.object2Json(value));
            return this;
        }
    

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

路由管理

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

/**
* 路由管理类
*/
public class ARouterPath {
    /** push module */
    public static final String SERVICE_PUSH_RECEIVER_URL = "/push/PushRegisterActivity";

    /** Display Module */
    public static final String ACTIVITY_DISPLAY_DISPLAY_URL = "/display/DisplayActivity";
    public static final String SERVICE_DISPLAY_UPLOAD_URL = "/display/PushReceiver";
}

拦截器

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

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

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

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

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

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

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

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

跳转结果监听处理:

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

        @Override
        public void onLost(Postcard postcard) {
						//路由丢失
        }

        @Override
        public void onArrival(Postcard postcard) {
        		//达到    
        }

        @Override
        public void onInterrupt(Postcard postcard) {
						//拦截
        }
    });

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

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

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

if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
  //处理拦截器
	interceptorService.doInterceptions(postcard, new InterceptorCallback() {
		/**
		* Continue process
		*
		* @param postcard route meta
		*/
		@Override
		public void onContinue(Postcard postcard) {
			_navigation(postcard, requestCode, callback);
		}
		//省略
	}

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

            switch (routeMeta.getType()) {
                case PROVIDER:  // if the route is provider, should find its instance
                    // Its provider, so it must implement IProvider
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    if (null == instance) { // There's no instance of this provider
                        IProvider provider;
                        try {
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            logger.error(TAG, "Init provider failed!", e);
                            throw new HandlerException("Init provider failed!");
                        }
                    }
                    postcard.setProvider(instance);
                		//Provider不需要拦截
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                		//Fragment不需要拦截
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }

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

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

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

startActivityForResult

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

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

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

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

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

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

动态增加路由

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

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

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

服务

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

  • 实现IProvider接口

    通过IProvider接口定义ARouter服务。

    // 声明接口,其他组件通过接口来调用服务
    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) {
    
        }
    }
    

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

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

  • 发现服务

    • 依赖注入

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

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

      // 实现 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) {
      
          }
      }