简介
Android平台中对页面、服务提供路由功能的中间件,我的目标是 —— 简单且够用。具体的使用可以参考:https://github.com/alibaba/ARouter。 如果对内部的详细原理感兴趣可以参考:http://www.jianshu.com/p/3c4f4e3e621f。 下文分析的代码来源于ARouter官方Demo,阅读下文之前建议先下载一份源码运行并对照源码进行比对,因为下文中我会用官方demo的源码截图。
APT
APT技术可以让我们通过自定义注解动态生成编译后的class代码,具体的使用我在这里就不详细说了,感兴趣的可以参考我以前写的:编写最基本的APT Demo。 我这里直接来看下ARouter说涉及到的几个注解,以及编译之后动态生成的代码。
annotation
Route:用于标记我们需要跳转的四大组件(可以通过Intent跳转的,因为其实ARouter 内部最后也是通过Intent来进行跳转)、service(此处的sevice是类似于后台的服务,需要继承IProvider)。 Interceptor:主要的作用是通过AOP技术(面向切面编程)在我们进行页面跳转之前可以进行一系列的拦截操作 Autowired:主要的作用是通过IOC技术(依赖注入)获取页面跳转的参数。
注解解析
**ARouter$$Root$$app:**因为Arouter采取的是懒加载技术,所以我们需要对router进行分组,这里的Root内部就是通过Map以组名为key存储了每组router的信息信息。 **ARouter$$Group$$xxx:**我们按不同的router类型进行分组,内部通过Map以router path存储了具体router的信息 **ARouter$$Interceptors$$app:**其中app是我们的module名(通过查看源码可知),内部 以priority优先级为key存储了具体的Interceptors的class信息。 **ARouter$$Providers$$app:**其中app是我们的module名(通过查看源码可知),内部以类的完整限定名为key保存了service的信息,结构同ARouter$$Group$$xxx一致,只是用于不同的功能。
关于ARouter的APT分支就到这了,下面来看下ARouter的初始化。
init
这正式分析初始化之前我们先了解几个类
ARouter:_ARouter的代理类,这里采用了代理模式,其实ARouter对外只开放了这一个api,所有的操作基本上都是通过ARouter来完成了。 **_ARouter:**ARouter所代理得到类,功能的具体执行者。 **LogisticsCenter:**物流调度中心,对路由信息进行解析和加工。 **Warehouse:**仓库,存储了所有具体的router、interceptors、service等信息,内部是一系列的Map。
ARouter的初始化流程:ARouter#init ──》_ARouter#init ──》LogisticsCenter#init ──》Warehouse#Map#put
// 获得指定包名下的所有类名List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);for (String className : classFileNames) {if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {// This one of root elements, load root. 通过反射找到Arouter$$Root$$xx 加载根路由集合((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {// Load interceptorMeta 通过反射找到Arouter$$Interceptors$$xx 加载拦截器集合((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {// Load providerIndex 通过反射找到Arouter$$Providers$$xx 加载服务集合 此处的service对应后台的概念 不是四大组件的service((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);}}
复制代码
其中第三步和第四步的代码会根据指定报名查找下面所有的类信息,然后根据类得到全限定名进行功能分组,并把信息保存在Warehouse的Map中。到此Arouter的初始化过程就完成。
Router的跳转和参数注入
ARouter.getInstance().build("/test/activity1").withString("name", "老王").withInt("age", 18).withBoolean("boy", true).withLong("high", 180).withString("url", "https://a.b.c").withParcelable("pac", testParcelable).withObject("obj", testObj).navigation();public class Test1Activity extends AppCompatActivity {@AutowiredString name;@Autowiredint age;@Autowired(name = "boy")boolean girl;@AutowiredTestParcelable pac;@AutowiredTestObj obj;private long high;@AutowiredString url;@AutowiredHelloService helloService;}
复制代码
其中Test1Activity 通过APT动态生成的代码如下:
atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("pac", 9); put("obj", 10); put("name", 8); put("boy", 0); put("age", 3); put("url", 8); }}, -1, -2147483648));
复制代码
所有的跳转参数都保存在map中,其中key是一一对应,而value是参数类型,对题对照如下:
public enum TypeKind {// Base typeBOOLEAN,BYTE,SHORT,INT,LONG,CHAR,FLOAT,DOUBLE,// Other typeSTRING,PARCELABLE,OBJECT;
}
复制代码
下面我们来看具体的跳转流程。
#build
ARouter.getInstance().build("/test/activity1")升温我们说过ARouter是_ARouter的代理的类,所有的api最终都会进入到真正的执行类_ARouter。 _ARouter#build
protected Postcard build(String path) {if (TextUtils.isEmpty(path)) {throw new HandlerException(Consts.TAG + "Parameter is invalid!");} else {//查找是否存在重定向服务PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);if (null != pService) {path = pService.forString(path);}return build(path, extractGroup(path));}}
复制代码
这里我们先来看下PathReplaceService这个类
public interface PathReplaceService extends IProvider {/*** For normal path.** @param path raw path*/String forString(String path);/*** For uri type.** @param uri raw uri*/Uri forUri(Uri uri);
}
复制代码
PathReplaceService我称它为重定向服务(具体怎么使用请参考官网文档),它继承IProvider,那IProvider是什么,其实他是用来实现service的,所以PathReplaceService就相当于我们自己的自定义服务,唯一的区别是,自定义服务需要我们显示去调用,跟调用router一样,但是PathReplaceService不需要显示调用,他是作用于所有服务和路由的,而且不管你实现了几个PathReplaceService,最终全局都只会保存在APT时扫描到的最后一个服务。为什么这么说,请看下面的代码:
public class ARouter$$Providers$$app implements IProviderGroup {@Overridepublic void loadInto(Map<String, RouteMeta> providers) {//第一个重定向服务providers.put("com.alibaba.android.arouter.facade.service.PathReplaceService", RouteMeta.build(RouteType.PROVIDER, PathReplaceServiceImpl.class, "/redirect/r1", "redirect", null, -1, -2147483648));//第二个重定向服务providers.put("com.alibaba.android.arouter.facade.service.PathReplaceService", RouteMeta.build(RouteType.PROVIDER, PathReplaceServiceImpl2.class, "/redirect/r2", "redirect", null, -1, -2147483648));providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648));}
}
复制代码
因为第一个和第二个重定向服务的key是一样都是PathReplaceService的类全限定名,所以第一个服务会被覆盖掉。好了关于PathReplaceService我们就说到这,他是我们一个重定向服务,作用域所有的跳转,而且全局只有一个。 我们继续往下分析,如果单例没有实现自定义重定向服务的时候,PathReplaceService pService == null,所以会直接调用两个参数的重载的build方法。
protected Postcard build(String path, String group) {if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {throw new HandlerException(Consts.TAG + "Parameter is invalid!");} else {PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);if (null != pService) {path = pService.forString(path);}return new Postcard(path, group);}}
复制代码
这里有调用了一遍ARouter.getInstance().navigation(PathReplaceService.class),感觉没必要啊,但是不管肯定还是一样的返回空。所以最终ARouter.getInstance().build()会返回一个Postcard(个包含跳转信息的容器,包含跳转参数和目标类的信息)。下面进入真正的跳转。其实真正的跳转位于_ARouter#navigation。 ###_ARouter#navigation
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {try {//完善跳转信息LogisticsCenter.completion(postcard);} catch (NoRouteFoundException ex) {……………………return null;}if (null != callback) {callback.onFound(postcard);}//不是绿色通道所以会进入默认interceptorService.doInterceptionsif (!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*/@Overridepublic void onContinue(Postcard postcard) {_navigation(context, postcard, requestCode, callback);}/*** Interrupt process, pipeline will be destory when this method called.** @param exception Reson of interrupt.*/@Overridepublic void onInterrupt(Throwable exception) {if (null != callback) {callback.onInterrupt(postcard);}logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());}});} else {return _navigation(context, postcard, requestCode, callback);}return null;}
复制代码
InterceptorService是我们在初始化完成以后ARouter为我们自动注册的拦截器服务,因为我们并没有为我们得到路由匹配相应的拦截器,所以应该会进入onContinue方法,经过断点调试确实和我们想的一样,可是onContinue是个回调函数,它又具体是在哪被调用的呢?我们经过查找发现是在InterceptorServiceImpl中
InterceptorServiceImpl#doInterceptions
LogisticsCenter.executor.execute(new Runnable() {@Overridepublic void run() {CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());try {_excute(0, interceptorCounter, postcard);interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);if (interceptorCounter.getCount() > 0) { // Cancel the navigation this time, if it hasn't return anythings.callback.onInterrupt(new HandlerException("The interceptor processing timed out."));} else if (null != postcard.getTag()) { // Maybe some exception in the tag.callback.onInterrupt(new HandlerException(postcard.getTag().toString()));} else {callback.onContinue(postcard);}} catch (Exception e) {callback.onInterrupt(e);}}});
复制代码
这里开了一个线程用来执行拦截器或者普通的跳转所以调用了callback.onContinue,接下来就进入到我们真正的跳转执行的地方了。
_ARouter#_navigation
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {final Context currentContext = null == context ? mContext : context;switch (postcard.getType()) {case ACTIVITY:// Build intentfinal Intent intent = new Intent(currentContext, postcard.getDestination());intent.putExtras(postcard.getExtras());// Set flags.int flags = postcard.getFlags();if (-1 != flags) {intent.setFlags(flags);} else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);}// 因为上文我们是在子线程中检查是否有匹配的拦截器,所以我们要在这里切换到UI线程执行具体的跳转new Handler(Looper.getMainLooper()).post(new Runnable() {@Overridepublic void run() {if (requestCode > 0) { // Need start for resultActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());} else {ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());}if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());}if (null != callback) { // Navigation over.callback.onArrival(postcard);}}});break;case PROVIDER:return postcard.getProvider();case BOARDCAST:case CONTENT_PROVIDER:case FRAGMENT: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;} catch (Exception ex) {logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));}case METHOD:case SERVICE:default:return null;}return null;}
复制代码
这里的代码比较简单,就是调用了Android原生的Intent进行跳转,然后根据不同的状态,调用一些回调函数。到此关于ARouter的跳转到这里就结束了,下面我们来看下目标对象的参数是如何获取的。
Autowired
这里在分析参数获取之前我们先废话2句,在看到Autowired注解的时候,是不是感觉似曾相识,没错这里的原理跟ButterKnife是一毛一样的,我强烈怀疑Arouter作者是参考ButterKnife代码写的,所以当我们分析完Autowired的时候,其实就相当于把ButterKnife也给分析了,哈哈,正式一举两得啊。还有,这种开发思想其实在后台开发中非常普遍,比如大名鼎鼎的Spring就是这种IOC(控制反转)思想的最佳代表。好了,下面进入正题。
Autowired注解处理器
当我们在编译过程中,系统会是扫描有Autowired注解的成员变量类,然后生成自动生成以下代码:
public class Test1Activity$$ARouter$$Autowired implements ISyringe {private SerializationService serializationService;@Overridepublic void inject(Object target) {serializationService = ARouter.getInstance().navigation(SerializationService.class);;Test1Activity substitute = (Test1Activity)target;substitute.name = substitute.getIntent().getStringExtra("name");substitute.age = substitute.getIntent().getIntExtra("age", 0);substitute.girl = substitute.getIntent().getBooleanExtra("boy", false);substitute.pac = substitute.getIntent().getParcelableExtra("pac");if (null != serializationService) {substitute.obj = serializationService.json2Object(substitute.getIntent().getStringExtra("obj"), TestObj.class);} else {Log.e("ARouter::", "You want automatic inject the field 'obj' in class 'Test1Activity' , then you should implement 'SerializationService' to support object auto inject!");}substitute.url = substitute.getIntent().getStringExtra("url");substitute.helloService = ARouter.getInstance().navigation(HelloService.class);}
}
复制代码
这里的代码很简单,应该能直接看懂,我们先来看他的父类ISyringe,他其实相当于一个模板类,为了便于编程ARouter内核提供了许多的模板类,存储在如下路径中:
总结
好了,到这我们已经把页面跳转和参数绑定都分析完了,剩下的重定向,拦截器,降级等很多其他功能,其实都是在跳转的过程中插入的一些拦截操作而已,我相信只要大家只要耐下心来看代码都是可以看明白的。
请参考ARouter 源码浅析第二篇