添加依赖
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); } }
传参过程中,可以省略
name
ARouter会自动根据类型匹配参数,但是建议都指定name
,避免一些异常。 -
序列化对象
传递:
ARouter.getInstance().build("路径") .withSerializable("SerializableKey", new TestObj()) .withParcelable("ParcelableKey", new TestObj()) .navigation();
传递对接需要分别实现
Serializable
和Parcelable
接口,调用的方法与基本数据类型大同小异,很好理解。解析:
同样通过注解进行自动参数解析。
// 支持解析自定义对象,URL中使用json传递 @Autowired(name = "ParcelableKey") TestObj obj;
同样建议指定
name
,当然也支持省略name
。 -
自定义对象
传递自定义对象的前提是对象不能实现
Serializable
或Parcelable
接口。传递:
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
的实现类(ArrayList
和HashMap
等),可能有点拗口,简单来说就是接收withObject传参的对象,不能是Serializable
或Parcelable
的实现类。(如果指定了Serializable
或Parcelable
的实现类会影响序列化类型的判断。)
路由管理
上面的跳转处理过程中不可避免会需要指定很多的路由路径(如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) { } }
-