1.ARouter
ARouter是阿里开源的一款路由框架,是一个用于帮助Android App进行组件化改造的框架,支持模块间的路由、通信、解耦。
在组件化架构中,ARouter帮助无相互依赖的组件间进行跳转和通信。以组件化开发中Activity跳转为例,无论上层框架如何封装,activity的底层跳转总是要通过startActivity()实现的,所以就需要获取到目标Activity的实例或路径。为了实现模块间解耦,又不能直接引用目标Activity,最简单的办法就是给目标Activity设置一个别名,然后通过映射表的方式Map
Activity提前将映射关系注入到Map中,当A Activity发起跳转到B的请求时,基础模块会从映射表中查找对应的B Activity实例,然后进行跳转。如果找不到对应的Activity实例,可以将跳转结果回传避免引起异常。
2.ARouter的使用
①添加依赖和配置
在app的build.gradle中添加依赖:
android {
……
defaultConfig {
...
//ARouter配置
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
// ARouter依赖 替换成最新版本, 需要注意的是api要与compiler匹配使用,均使用最新版可以保证兼容(ARouter网址:https://github.com/alibaba/ARouter)
compile 'com.alibaba:arouter-api:1.5.2'
annotationProcessor 'com.alibaba:arouter-compiler:1.5.2'
...
}
目前api和compiler的最新版本都是1.5.2,使用时可以到github上查找最新版本。
②初始化SDK
官方建议初始化尽可能早,推荐在Application中初始化:
public class MyApplication extends Application {
@override
public void onCreate() {
super.onCreate();
if (isDebug()) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); //初始化
}
}
注: ARouter.getInstance().destroy( ) ; 是释放资源的API。释放代码写在Application生命周期的onTerminate( )里。
③简单的页面跳转
从MainActivity跳转到SecondActivity为例:
@Route(path="/activity/main")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
ARouter.getInstance().build( "/activity/second").navigation();//页面跳转
}
}
首先在Activity/Fragment类上面写上@Route注解。注意:Route path的路径至少要有两级,而且必须以/开始,即/xx/xx。
页面跳转只需要一行代码即可完成,即ARouter.getInstance().build(“目标界面对应的路径”).navigation(); 。
接下来是跳转的目标界面:
@Route(path="/activity/second")
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
……
}
}
目标Activity类上面需要声明@Route注解,而且path要与跳转时使用的路径一致。
注意:不要忘了在清单文件里面配置Activity。
如果路径的标签太多就不好管理,所以最好写个类统一管理和维护路径标签,这样不仅利于维护也方便后期拓展。这里先封装一下路径管理:
public final class Constance {
public static final String ACTIVITY_MAIN = "/activity/main";
public static final String ACTIVITY_SECOND = "/activity/second";
}
封装完毕了路径标识,继续ARouter的基本使用。
④带参数的界面跳转
Android可以通过Bundle实现带参数的界面跳转,而使用ARouter框架传递参数通过以下方式:
ARouter.getInstance().build("/activity/second")
.withString("name", "mff")
.withInt("age", 30)
.withParcelable("test",new MyBean("qwe",20))
.navigation();
通过withXXX将参数传递进入,其中第一个参数是参数的key,第二个参数是要传递的属性值,也就是value。
注:ARouter传递对象的时候,首先该对象需要Parcelable或者Serializable序列化。
目标界面要获取传递过来的值,就需要在目标界面使用Autowired注解:
@Route(path="/activity/second")
public class SecondActivity extends AppCompatActivity {
@Autowired(name="name")
public String name; //用于接收name参数
@Autowired(name="age")
public int age; //用于接收age参数
@Autowired(name="test")
public MyBean bean; //用于接收对象
@Override
protected void onCreate(Bundle savedInstanceState) {
……
ARouter.getInstance().inject(this); //自动完成参数注入(如果没有这句话,name和age都无法接收到传过来的值)
text view.setText(name+age); //必须在inject之后
}
}
注意:要想接收传过来的参数,就必须在Activity/Fragment类里面进行ARouter注入,使用inject自动完成参数注入,即ARouter.getInstance().inject(this);
注意,只有当@Autowired(name = "test"),也就是key标签一致的情况下,才可以获取到对象的值,如果不写标签名,结果会为null。所以为了规避每一个可能会遇到的风险,建议在@Autowired里面都写上与之对应具体的key名。
⑤ARouter实现类似startActivityForResult()
第一步:在MainActivity中重写onActivityResult方法,设置requestCode 为123:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode) {
case 123:
Log.i(TAG, "onActivityResult");
}
}
第二步:在跳转的navigation方法(这是一个方法重载)里面的第二个参数,设置定义的requestCode。
ARouter.getInstance().build( Constance.ACTIVITY_SECOND).navigation( MainActivity.this, 123);
第三步:在SecondActivity中设置setResult方法,写上对应的resultCode:
setResult(1234);
修改完毕,编译运行,跳转到第二个界面然后返回上一个界面,日志成功打印:
------onActivityResult
⑥自定义分组,实现跳转界面
1)ARouter针对所有的路径(/test/1、/test/2)进行分组,只有在分组中的某一个路径第一次被访问的时候,该分组才会被初始化
2)可以通过@Route注解主动指定分组,否则使用路径中第一段字符串(/*/)作为分组
3)一旦主动指定分组之后,应用内路由需要使用 ARouter.getInstance().build(path, group) 进行跳转,手动指定分组@Route(path = "/test/1", group = "app"),否则无法找到。
使用自定义分组来跳转界面,只需要改动三个位置:
(1)类注解新增group,赋值自定义的组名,依旧统一写在一个类里面便于管理
@Route(path=Constance.ACTIVITY_MAIN, group = Constance.GROUP_FIRST)
public class MainActivity extends AppCompatActivity {
}
(2)在build方法里面(这是一个方法重载),添加与之对应的组名
ARouter.getInstance().build( Constance.ACTIVITY_SECOND, Constance.GROUP_FIRST).navigation();
(3)在跳转的目标Activity里的类注释,加上同样的组名
@Route(path=Constance.ACTIVITY_SECOND, group = Constance.GROUP_FIRST)
public class SecondActivity extends AppCompatActivity {
}
这样就实现了自定义分组完成界面跳转的功能。
注意:如果SecondActivity注解中的group与build中group值不一致,也会报错There's no route matched!,最终会走到NavigationCallback的onLost回调方法里。
⑦界面跳转动画
直接调用withTransition,里面传入两个动画即可(R.anim.xxx)
⑧使用URI进行跳转
ARouter框架也可以使用URI进行匹配跳转,只需匹配路径一致即可完成跳转:
⑨Fragment跳转
Fragment的跳转也可以参照Activity跳转,第一步先写上类注释,然后是强转:
3.ARouter的拦截器
拦截器是ARouter框架的亮点。说起拦截器可能印象最深的是OkHttp的拦截器,OkHttp的拦截器主要是用来拦截请求体(比如添加请求Cookie)和拦截响应体(判断Token是否过期),在真正的请求和响应前做一些判断和修改,然后再去进行操作,这就是拦截器的简单概念。
ARouter的拦截器作用是通过拦截住一次跳转请求,然后进行处理,处理后再决定后续怎么走。比如在初始页面点击按钮跳转到首页时,就可以增加一个拦截器,用来判断token是否过期,如果过期就跳转到登录页面,而不再去跳转到首页了。
ARouter的拦截器是通过实现IInterceptor接口,并重写init()和process()方法来完成拦截器内部操作的。init方法会在拦截器初始化时自动调用。process方法是当有路由操作被发起时触发,可以根据需要通过onInterrupt()拦截路由,或者通过onContinue()继续路由操作。注意两个方法必须调用一个,否则路由会丢失不继续执行。
定义一个拦截器:
@Interceptor(priority = 1, name= "MyInterceptor")
public class MyInterceptor implements IInterceptor {
@Override
public void init(Context context) {
Log.i(TAG, "拦截器init");
}
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
if(postcard.getPath().equals( Constance.ACTIVITY_SECOND)) { //拦截器发现此次跳转是要跳转到第二个页面
Log.i(TAG, MyInterceptor.class.getName() + "进行了拦截处理");
callback.onInterrupt(null);
ARouter.getInstance().build( Constance.ACTIVITY_THIRD).navigation();//拦截住此次跳转,不让它跳转到第二个页面了,改成跳转到第三个页面
} else {
Log.i(TAG,"没有拦截,走onContinue方法");
callback.onContinue(); //不进行拦截处理,交还控制权。(一定要交还控制权,否则就算拦截条件不满足,也不会进行后续的路由跳转了)
}
}
}
定义ARouter拦截器必须要使用@Interceptor注解。注解里面的priority声明拦截器的优先级。
注:priority数值越小,优先级越高,越先执行(四大组件中的广播,优先级的取值是 -1000到1000,数值越大优先级越高)
如果两个拦截器的优先级一样,项目编译就会报错。所以,不同拦截器定义的优先级属性值不能相同。
拦截器代码写好了,还需要修改一下MainActivity的代码,增加NavigationCallback回调方法,NavigationCallback是ARouter在路由跳转的过程中用来监听路由具体过程的。
@Route(path="/activity/main")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
//页面跳转
ARouter.getInstance().build( Constance.ACTIVITY_SECOND).navigation( MainActivity.this, new NavigationCallback() {
@Override
public void onFound(Postcard postcard) {
//路由目标被发现时调用
Log.i(TAG,"onFound");
}
@Override
public void onLost(Postcard postcard) {
//路由被丢失时调用
Log.i(TAG,"onLost");
}
@Override
public void onArrival(Postcard postcard) {
//路由到达之后调用
Log.i(TAG,"onArrival");
}
@Override
public void onInterrupt(Postcard postcard) {
//路由被拦截时调用
Log.i(TAG,"onInterrupt");
}
});
}
}
修改完成后,重新运行App,打印日志结果如下:
-----拦截器init
-----onFound
-----com.demo.mydemo.router.MyInterceptor进行了拦截处理
-----onInterrupt
-----没有拦截,走onContinue方法
首先初始化拦截器,然后调用NavigationCallback这个回调函数里面的onFound(),然后执行拦截器里面的process()方法。由于满足了if条件会走onInterrupt方法被拦截。拦截后又去跳转ACTIVITY_THIRD,这次跳转又会进入拦截器,但是由于不满足if条件会走else里的onContinue方法,于是最终从MainActivity跳转到了ThirdActivity里。这就是ARouter中拦截器的工作流程。
注意:只有Activity才会触发拦截器,Fragment和IProvider并不支持拦截。
4.ARouter初始化源码
ARouter初始化是在Application里调用ARouter.init方法:
public static void init(Application application) {
if (!hasInit) {
hasInit = _ARouter.init(application);//初始化
if (hasInit) {
_ARouter.afterInit(); // 初始化完成后,加载拦截器服务,并初始化所有拦截器
}
}
}
ARouter初始化调用的是_ARouter的init方法:
_ARouter.java:
protected static synchronized boolean init( Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor); //重点
hasInit = true;
mHandler = new Handler( Looper.getMainLooper());
return true;
}
在_ARouter的init方法中又调用了LogisticsCenter的init方法来加载所有的路由元信息。LogisticsCenter意为物流中心。
LogisticsCenter.java:
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
try {
loadRouterMap(); //先尝试用插件加载路由表(要先引入插件apply plugin: 'com.alibaba.arouter')
if (registerByPlugin) { //使用插件加载成功
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else { //使用插件加载失败,就扫描所有dex文件,找到所有包名为com.alibaba.android.ARouter.routes的类(即编译时生成的所有路由表),并把类名放到routerMap里,之后会实例化找到的所有类,并通过这些集类加载对应的集映射索引到WareHouse里
Set
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) { //当前app是新安装时,会根据包名扫描包下面包含的所有路由表并缓存到SP文件,这些路由表是在编译时由APT生成的
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
context.getSharedPreferences( AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
PackageUtils.updateVersion(context);
} else { //不是新安装的版本,就从SP文件中读取路由表
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE ).getStringSet(AROUTER_SP_KEY_MAP, new HashSet
}
//遍历路由表,区分是哪种路由表,然后反射创建路由表实例,并调用其loadInto方法填充Warehouse相应的Map
for (String className : routerMap) {
if (className.startsWith( ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) { //类名以com.alibaba.android.arouter.routes.ARouter$$Root开头的会填充到Warehouse.groupsIndex,即所有IRouteGroup实现类的class对象
((IRouteRoot) (Class.forName( className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith( ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) { //类名以com.alibaba.android.arouter.routes.ARouter$$Interceptors开头的会填充到Warehouse.interceptorsIndex,即所有IInterceptor实现类的class对象
((IInterceptorGroup) (Class.forName( className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith( ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) { //类名以com.alibaba.android.arouter.routes.ARouter$$Providers开头的会填充到Warehouse.providersIndex,即所有provider的RouteMeta
((IProviderGroup) (Class.forName( className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}...
}
}
LogisticsCenter初始化,目的就是加载所有的路由元信息。有两种方式:
1)loadRouterMap()方法:直接使用在编译时收集好的路由表信息,然后反射创建帮助类实例后,调用其loadInto方法来填充Warehouse相应的Map。这需要开发者要先引入插件才行。如果加载成功,registerByPlugin就为true,否则false。
2)若第一步没有加载成功,就会通过ClassUtils.getFileNameByPackageName在运行时搜集dex中"com.alibaba.android.arouter.rou tes "包下的所有类(即路由表),然后遍历并区分是哪种路由表,接着反射创建路由表实例后,调用其loadInto方法来填充Warehouse相应的Map。
两种方式对比:
相同点:都是使用路由表信息反射创建路由表实例后,调用其loadInto方法来填充Warehouse相应的Map。
不同点:路由表信息的收集方式不同。前者是在编译时搜集就完毕了,后者是运行时。
一般都是使用第一种,因为运行时遍历dex搜集会比较耗时,也就是init会较为耗时。
5.ARouter跳转源码
要想跳转Activity最终必定要走到startActivity(intent)方法,而intent一般需要目标Activity的Class。所以navigation()中需要有寻找目标Activity的Class这一过程的。
①第一步:ARouter.getInstance()获取ARouter单例
public static ARouter getInstance() {
if (!hasInit) { // 未初始化则报异常
throw new InitException( "ARouter::Init::Invoke init(context) first!");
} else {
if (instance == null) {
synchronized (ARouter.class) {
if (instance == null) {
instance = new ARouter();
}
}
}
return instance;
}
}
ARouter构造方法使用了单例模式,如果没有初始化则报异常。
②第二步:build(string)方法获取Postcard,Postcard承载了一次跳转/路由需要的所有信息
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
调用了_ARouter的同名方法,返回Postcard。ARouter使用了外观模式,其所有方法都是调用了_ARouter的同名方法。
进入_ARouter的build方法:
protected Postcard build(String path) {
……
return build(path, extractGroup(path), true);
}
调用了build的重载方法,其中第二个参数group是通过extractGroup(path)获取的,也就是path的第一级,即"/test/activity"中的"test"。
接着看build重载方法:
protected Postcard build(String path, String group, Boolean afterReplace) {
……
return new Postcard(path, group);
}
最后返回创建的Postcard实例,此时Postcard对象的信息还不完整,之后还需要调用LogisticsCenter中的方法完善Postcard的相关信息。
Postcard译为明信片,它的类注释是:A container that contains the roadmap,即一个包含路线图的容器。它继承自路由元信息RouteMeta,承载了一次跳转/路由需要的所有信息,通过Postcard可以获取到路径的组getGroup以及全路径getPath等很多跳转信息。
public final class Postcard extends RouteMeta {
private Uri uri; //使用Uri方式发起的路由
private Bundle mBundle; //启动activity时传入的Bundle
private int flags = 0; // 启动activity的Flags
private int timeout = 300; //路由超时时间
private IProvider provider; //如果是获取provider,就有值
private boolean greenChannel; //是绿色通道
private String action;
// activity转场动画相关
private Bundle optionsCompat;
private int enterAnim = -1;
private int exitAnim = -1;
...
}
public class RouteMeta {
private RouteType type; // 路由类型,activity、service、fragment、IProvider等,编译时会根据被@Route注解的类的类型来设置
private Element rawType; //路由原始类型,在编译时用来判断
private Class> destination; //目的地:具体的XxxActivity.class等
private String path; //Path
private String group; //Group
private int priority = -1; //优先级,越小优先级越高
private int extra;
private Map
private String name; //路由名字,用于生成javadoc
private Map
}
Postcard:路由的信息。 理解为是生活中的明信片。例如Postcard中的mBundle相当于明信片上寄件人写的 “你好,xxx 节日快乐!” 文字内容。
RouteMeta:路由元信息,即基础信息。 相当于明信片上的收件人地址这种必备的基础信息。 明信片上可以没有文字内容,但想要被邮寄就必须有收件人地址。
③第三步:开始路由过程
通过path构建了PostCard后调用其navigation()方法开始了路由过程:
public Object navigation() {
return navigation(null);
}
public Object navigation(Context context) {
return navigation(context, null);
}
public Object navigation(Context context, NavigationCallback callback) {
return ARouter.getInstance().navigation( context, this, -1, callback);
}
public Object navigation(Context context, Postcard postcard, int requestCode, NavigationCallback callback) {
return _ARouter.getInstance().navigation( mContext, postcard, reque, callback);
}
连续走了两个重载方法,最后走到ARouter的navigation方法,并且把自己(postcard)传了进去。ARouter的navigation方法同样会走到_ARouter的同名方法:
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
……
// 给路由设置context,例如启动activity需要。如果没有传就使用mContext,即初始化ARouter时传入的Application
postcard.setContext(null == context ? mContext : context);
// 1. 完善postcard信息(目前只有path、group,还需要知道具体目的地,例如要跳转到的activity信息)
LogisticsCenter.completion(postcard);
if (null != callback) { //如果设置了回调
callback.onFound(postcard);
}
// 2. 不是绿色通道的话,要先走拦截器
if (!postcard.isGreenChannel()) {
interceptorService.doInterceptions( postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard){ //拦截器处理结果:继续路由
// 3. 获取路由结果
_navigation(postcard, requestCode, callback);
}
@Override
public void onInterrupt(Throwable exception) { //拦截器处理结果:中断路由,回调中断
if (null != callback) {
callback.onInterrupt(postcard);
}
}
});
} else {
//绿色通道,不走拦截器,继续导航去获取路由结果
return _navigation(postcard, requestCode, callback);
}
return null;
}
使用前面构建好的PastCard经过整体3个步骤就完成了路由:
1)完善postcard信息:只有path、group还不行,还需要知道具体目的地,例如要跳转到的Activity信息。
2)拦截器处理:不是绿色通道的话,要先经过路由器的处理,结果是中断或者继续。
3)获取路由结果:例如进行目标Activity的跳转、获取IProvider实现对象、获取Fragment等。
④获取路由结果
也就是_navigation()方法:
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = postcard.getContext();
//根据路由类型来走对应逻辑
switch (postcard.getType()) {
case ACTIVITY:
// Activity,使用postcard.getDestination()来构建intent、传入Extras、设置 flags、action
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
int flags = postcard.getFlags();
if (0 != flags) {
intent.setFlags(flags);
}
// 当前的context不是activity,需要添加flag:FLAG_ACTIVITY_NEW_TASK(启动startActivity需有任务栈的,application/service启动activity没有任务栈,所以必须要new_task_flag新建一个任务栈)
if (!(currentContext instanceof Activity)) {
intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK);
}
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}
// 最后在主线程执行startActivity
runInMainThread(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
break;
case PROVIDER:
//provider,想要获取的服务,即IProvider的实现类。直接从postCard中获取
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
//Broadcast、ContentProvider、Fragment都是使用postcard.getDestination()反射创建实例
Class> fragmentMeta = postcard.getDestination();
try {
Object instance = fragmentMeta.getConstructor().newInstance();
if (instance instanceof Fragment) {
((Fragment) instance).setArguments( postcard.getExtras());
} else if (instance instanceof android.support.v4.app.Fragment) {
((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
}
return instance;
} ...
}
return null;
}
postcard经过完善后,路由类型type、目的地destination等都已经赋了值。destination就是目标类的class对象。
6.拦截器源码
路由中设置了拦截器,拦截器的作用是对路径进行判断决定是否需要中断。
未设置绿色通道的路由需要经过拦截器处理,也就是interceptorService的doInterceptions()方法。
interceptorService对象是在afterInit()里初始化的:
_ARouter.java:
static void afterInit() {
interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}
InterceptorService继承IProvider,所以interceptorService是一个服务,在ARouter初始化后获取,用于处理拦截器的逻辑。具体的实现类是InterceptorServiceImpl:
@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
...
@Override
public void init(final Context context) { //init方法在服务被创建后调用。init的作用是从拦截器路由信息索引里面加载并反射创建所有的拦截器实例
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
if (MapUtils.isNotEmpty( Warehouse.interceptorsIndex)) {
//遍历Warehouse.interceptorsIndex,使用存储在其中的拦截器class对象反射创建拦截器实例,然后存入Warehouse.interceptors。也就是说,ARouter初始化完成后就获取到了所有拦截器实例
for (Map.Entry
Class extends IInterceptor> interceptorClass = entry.getValue();
try {
IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
iInterceptor.init(context);
Warehouse.interceptors.add( iInterceptor); //存入Warehouse.interceptors
}...
}
interceptorHasInit = true;
...
}
}
});
}...
@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
if (MapUtils.isNotEmpty( Warehouse.interceptorsIndex)) { //有拦截器
LogisticsCenter.executor.execute(new Runnable() { //放入线程池异步执行
@Override
public void run() {
CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size()); //interceptorCounter用于保证所有拦截器都走完,并且设置了超时
try {
_execute(0, interceptorCounter, postcard); //执行第一个拦截器,如果没有中断则递归调用继续后面的拦截器
interceptorCounter.await( postcard.getTimeout(), TimeUnit.SECONDS);
if (interceptorCounter.getCount() > 0) { // count>0说明超时了,拦截器还没处理完
callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
} else if (null != postcard.getTag()) { //Tag!=null说明被某个拦截器回调中断了
callback.onInterrupt( (Throwable) postcard.getTag());
} else {
callback.onContinue( postcard); // 所有拦截器处理完、没超时、也没异常,则继续路由
}
}...
}
});
} else { //没有拦截器则继续路由
callback.onContinue(postcard);
}
}
private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
if(index < Warehouse.interceptors.size()) {
//从Warehouse.interceptors中获取第index个拦截器,执行process拦截方法,如果回调到onContinue就继续下一个
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
iInterceptor.process(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
counter.countDown();
_execute(index + 1, counter, postcard); // 继续下一个拦截器
}
@Override
public void onInterrupt(Throwable exception) {
postcard.setTag(null == exception ? new HandlerException("No message.") : exception);
counter.cancel();
...
}
});
}
}
}
doInterceptions()方法中判断如果有拦截器,就放入线程池异步执行第一个拦截器,且使用interceptorCounter保证所有拦截器都走完,同时也设置了超时。 如果第一个拦截器没有回调中断,则递归调用继续后面的拦截器。
拦截器的执行流程很清楚了,就是从Warehouse.interceptors中获取第index个拦截器,执行process拦截方法,如果回调到onContinue就继续下一个;若回调onInterrupt就中断路由。
6.Warehouse
Warehouse译为仓库,用于存放被@Route、@Interceptor注释的路由相关的信息。既然是仓库,那么就是有存有取。
比如:moduleB发起路由跳转到moduleA的activity,moduleB没有依赖moduleA,只是在moduleA的activity上增加了@Route注解。 由于进行activity跳转需要目标Activity的class对象来构建intent,所以必须有一个中间人,把路径"/test/activity"翻译成Activity的class对象,然后moduleB才能实现跳转(因此在ARouter的使用中moduleA、moduleB 都是需要依赖arouter-api的)。这个中间人那就是ARouter了,而这个翻译工所作用到的词典就是Warehouse,它存着所有路由信息。
class Warehouse {
//所有IRouteGroup实现类的class对象,在ARouter初始化中赋值,key是path第一级。(IRouteGroup实现类是编译时生成,代表一个组,即path第一级相同的所有路由,包括Activity和Provider服务)
static Map
//所有路由元信息,在completion中赋值,key是path。首次进行某个路由时就会加载整个group的路由,即IRouteGroup实现类中所有路由信息。包括Activity和Provider服务
static Map
//所有服务provider实例,在completion中赋值,key是IProvider实现类的class
static Map
//所有provider服务的元信息(实现类的class对象),在ARouter初始化中赋值,key是IProvider实现类的全类名。主要用于使用IProvider实现类的class发起的获取服务的路由,例如ARouter.getInstance().navigation(HelloService.class)
static Map
//所有拦截器实现类的class对象,在ARouter初始化时收集到,key是优先级
static Map
//所有拦截器实例,在ARouter初始化完成后立即创建
static List
...
}
其中groupsIndex、providersIndex、interceptorsIndex是ARouter初始化时就准备好的基础信息,为业务中随时发起路由操作(Activity跳转、服务获取、拦截器处理)做好准备。
那么Warehouse的信息是如何收集到的呢?
8.面试题
①请问ARouter是怎么完成组件与组件之间通信的,请简单描述清楚?
第一步:注册子模块信息到路由表里面去,怎么注册,难道是自己去注册,当然不是,采用编译器APT技术,在编译的时候,扫描自定义注解,通过注解获取子模块信息,并注册到路由表里面去。
第二步:寻址操作,寻找到在编译器注册进来的子模块信息,完成交互即可。
②能画一张ARouter 通信的示意图吗?