简介
Android平台中对页面、服务提供路由功能的中间件,我的目标是 —— 简单且够用。具体的使用可以参考:https://github.com/alibaba/ARouter。如果对内部的详细原理感兴趣可以参考:http://www.jianshu.com/p/3c4f4e3e621f。下文分析的代码来源于ARouter官方Demo,阅读下文之前建议先下载一份源码运行并对照源码进行比对,因为下文中我会用官方demo的源码截图。
APT
APT技术可以让我们通过自定义注解动态生成编译后的class代码,具体的使用我在这里就不详细说了,感兴趣的可以参考我以前写的:编写最基本的APT Demo。
我这里直接来看下ARouter说涉及到的几个注解,以及编译之后动态生成的代码。
annotation
其中Param已被Autowired代替
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 {
@Autowired
String name;
@Autowired
int age;
@Autowired(name = "boy")
boolean girl;
@Autowired
TestParcelable pac;
@Autowired
TestObj obj;
private long high;
@Autowired
String url;
@Autowired
HelloService 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 type
BOOLEAN,
BYTE,
SHORT,
INT,
LONG,
CHAR,
FLOAT,
DOUBLE,
// Other type
STRING,
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 {
@Override
public 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.doInterceptions
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(context, postcard, requestCode, callback);
}
/**
* Interrupt process, pipeline will be destory when this method called.
*
* @param exception Reson of interrupt.
*/
@Override
public 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() {
@Override
public 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 intent
final 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() {
@Override
public void run() {
if (requestCode > 0) { // Need start for result
ActivityCompat.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;
@Override
public 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内核提供了许多的模板类,存储在如下路径中:
那么为什么要提供模板类呢?简单了来说,当我们在变成过程中,由于框架作者并不知道哪些具体类被标注了注解,所以要动态获取对象,只能通过反射动态来获取实例,然后调用接口的方法来执行具体的操作,这就是多态的概念,如下代码所示:
这里插一句,反射是会影响性能的,所以一般我们在编程中除非万不得已,否则尽量不要采用反射,但是这里是activity初始化的时候反射,本来就会进行大量耗时的操作,哪怕有一点点的性能损耗也是可以接受的。还记得Arouter的初始化吗?官网上有一句原话是这么说的:
大家有没有想过,为什么要尽可能早的初始化,我想除了要扫描大量的对象并保存到全局的map集合中以外,跟初始化的时候用到反射也有关系吧,毕竟还是有性能损耗的。如下所示
总结
好了,到这我们已经把页面跳转和参数绑定都分析完了,剩下的重定向,拦截器,降级等很多其他功能,其实都是在跳转的过程中插入的一些拦截操作而已,我相信只要大家只要耐下心来看代码都是可以看明白的。
请参考ARouter 源码浅析第二篇