作者catRuan ,转载请注明出处
由于作者公司在做组件化开发,有幸使用到大名鼎鼎的Arouter来进行module之间的通讯和跳转解耦,阅读源码之后感觉受益良多,因此写下此文,本文属于作者的个人理解,若有不当之处欢迎指出。
另外,基于Arouter,作者改造了一个更加适用于组件化开发的路由框架:
https://blog.csdn.net/u011791526/article/details/82994750
分析源码之前,先给出Arouter的基本用法,了解的可以跳过
以下代码全部摘自源码,详情可见:https://github.com/alibaba/ARouter
1、模块跳转(可以简化为Activity跳转)
@Route(path = "/test/activity2")
public class Test2Activity extends AppCompatActivity {
……
}
ARouter.getInstance().build("/test/activity2").navigation();
2、模块调用
在这里容我斗胆说一句,这种调用方式对于组件化开发并不是特别好,肉眼可见的耦合,具体原因后面说明
public interface HelloService extends IProvider {
void sayHello(String name);
}
@Route(path = "/service/hello")
public class HelloServiceImpl implements HelloService {……}
((HelloService) ARouter.getInstance().build("/service/hello").navigation()).sayHello("mike");
ARouter.getInstance().navigation(HelloService.class).sayHello("mike");
3、拦截器使用
@Interceptor(priority = 7)
public class Test1Interceptor implements IInterceptor {
……
@Override
public void process(final Postcard postcard, final InterceptorCallback callback) {
if ("/test/activity4".equals(postcard.getPath())) { callback.onContinue(postcard);}
else {callback.onInterrupt(null); }
}
@Override
public void init(Context context) {
mContext = context;
……
}
}
三个必要module和一个可选module:
arouter-annotation:java module,提供了关键的注解、枚举和数据模型
arouter-compiler:java module,一个编译时注解库,用于生成代码,作用于编译时
arouter-api:Android module,Arouter的核心库,解析arouter-compiler生成的代码,作用于运行时
arouter-register:java module,一个插件库,动态的往arouter-api里添加代码,优化router-api的性能,作用于编译时,非必须module(但建议使用)
定义了关键的注解、枚举和数据模型
a.注解
Autowired:变量注解(用于传参)
interceptor:拦截器注解
Route:路由注解
b.枚举
RouteType:路由类型(四大组件+fragment+IProvider)
TypeKind:参数类型(用于页面间传递参数)
c.数据模型
RouteMeta:元路由(保存路由的基本信息)
实体类的入参例如:
RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap(){{put("key1", 8); }}, -1, -2147483648));
arouter-annotation被两个库引用
arouter-compiler:引用其在编译时生成代码
arouter-api:引用其解析编译时生成的代码
核心module之一,在编译时将用户注解的类信息收集起来,生成新的java文件。这个过程叫做编译时注解
本文只聊arouter-compiler完成自己使命后的最终产物,中间过程可以参考下列文章。
(题外话,butterknife也用到了编译时注解,推荐了解)
编译时注解:https://www.jianshu.com/p/07ef8ba80562
编译时注解语法讲解:https://blog.csdn.net/u010405231/article/details/52210401
javapoet:https://www.jianshu.com/p/95f12f72f69a
arouter-compiler生成的java文件路径如下:yourModule/build/generated/source/apt/com.alibaba.android.arouter/
下图为app下的路径,其他模块同理
我把生成的Java文件分为4种类型
1. Root类,类名格式如下
Arouter$$Root$$moduleName
每个module只生成一个,记录了该module下所有group类(这些类也是编译时生成的)
group:若使用@Route(path = "/xxx/xxx")或者@Route(path = "/xxx/xxx/xxx")注解,则其中第一段xxx就是group
若使用 @Route(path = "/xxx/xxx",group = "xxxx")注解,则group= 后面的xxx就是group,且path第一段的值可以不 等于group
例如,下面的代码是名为app的module生成的类,它包含两个group:service和test,他们在编译时又各自生成了对应的类,本类中关键map为routes,它的键值分别为
public class ARouter$$Root$$app implements IRouteRoot {
@Override
public void loadInto(Map> routes) {
routes.put("service", ARouter$$Group$$service.class);
routes.put("test", ARouter$$Group$$test.class);
}
}
2.group类,类名格式如下
Arouter$$Group$$groupName
每个module生成0-n个group类,有多少个group就生成多少个,分别记录了每个group类下所有路由的详细信息
例如,app下共有两个group ,他们各自生成了java文件,而生成的文件中又详细的记录了每个group下面的所有路由,例如service这个group下面共有/service/act,/service/hello, /service/json,/service/single 四个路由,这些路由映射的类信息被储存在RouteMeta中,本类的关键map为atlas,它的键值分别为<路由path,RouteMeta实例>
3.Interceptor类,类名格式如下
Arouter$$Interceptors$$modulename
每个module生成一个Interceptor类,记录了本module下所有的拦截器类
图中的Interceptor类表明app这个module下只有一个拦截器Test1Interceptor,本类的关键map为intercaptors,它的键值分别为
<拦截器优先级,interceptor.class>
public class ARouter$$Interceptors$$app implements IInterceptorGroup {
@Override
public void loadInto(Map> interceptors) {
interceptors.put(7, Test1Interceptor.class);
}
}
4.provider类,类名格式如下
Arouter $$Providers $$modulename
每个module生成一个providers类,记录了本module下所有的provider信息,本类关键map为providers,它的键值分别为<??,RouteMeta实例> ,这里的键??表示的是我们定义的provider所实现的接口,它可能是Iprovider本身,也可能是继承Iprovider的接口,为什么这里不直接放provider实现类作为键呢?下节给答案
public class ARouter$$Providers$$app implements IProviderGroup {
@Override
public void loadInto(Map providers) {
providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648,null));
providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/service/json", "service", null, -1, -2147483648,null));
providers.put("com.alibaba.android.arouter.facade.template.IProvider", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/service/single", "service", null, -1, -2147483648,null));
}
}
Arouter的核心module,上面说的arouter-compiler库给咱们生成了一堆java文件,而arouter-api的职责正是把这些java文件解析出来,为我们业务代码所用。
话不多说,直接甩一张类图,为了方便理解,故只列出关键的类信息。(这里又要说题外话了,读源码还是要自己画画类图,便于梳理逻辑)
顺着这个类图,我们先大概整理一下思路:api库中关键的几个类为Arouter,_Arouter,LogisticCenter,WareHouse和Postcard。
Arouter是咱们使用Arputer时的直接操作类,它呢仅仅是对_Arouter的一个包装(这里作者确实不知道这种设计模式是什么,简化版的装饰器模式?请知道的朋友告知),也就是真正的逻辑都是在_Arouter中实现的
_Arouter:完成了Arouter调用中的各种逻辑
LogisticCenter:真正的解析类就是这个物流中心,就是它负责解析arouter-compiler生成了的java文件
Postcard:路由的映射的实体类,存放了路由的具体信息
WareHouse:存放了有LogisticCenter解析出的各种信息,回忆一下arouter-compiler做了什么呢?它生成了一堆java文件,java文件里呢又用到一堆的map,而是这个WareHouse就是用来存放那一堆的map的。
Arouter初始化
ARouter.init(getApplication());
来看init方法,啥也没干,调了_ARouter的init方法,_ARouter.afterInit()后面讲,先不管
public static void init(Application application) {
if (!hasInit) {
logger = _ARouter.logger;
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
hasInit = _ARouter.init(application);
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}
_ARouter的init 又调了LogisticsCenter的init方法
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
// It's not a good idea.
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
// }
return true;
}
LogisticsCenter的init方法:
通过registerByPlugin这个变量来判断自己有没有被插件初始化,没有的话就开始初始化。
被作者省略掉那一堆代码做了一件事情:解析app的dex文件,找到编译时生成的那一堆java文件,将他们的类名放到routerMap中。
最后遍历这个routerMap通过反射机制拿到root类实例、interceptors类实例,providers类实例,把存在他们里面的数据放到WareHouse中:
root信息->Warehouse.groupsIndex
iterceptors信息->Warehouse.interceptorsIndex
provider信息->Warehouse.providersIndex
上文提到init方法通过判断registerByPlugin变量来决定要不要初始化,而这个变量翻译过来就是“Arouter被插件注册了”,那么插件是怎么工作的呢?这个问题留到arouter-register中去讲,这里不难推测:如果插件生效,那么就由它来进行初始化(找到自动生成的类,解析出其中关键的信息)
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
//billy.qi modified at 2017-12-06
//load by plugin first
loadRouterMap();
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set routerMap;
……
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
// This one of root elements, load root.
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// Load interceptorMeta
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}
……
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
回过头来ARouter初始化方法中的另一个方法afterInit(),它通过Arouter机制拿到拦截器服务,这个服务也是Arouter内部提供的
static void afterInit() {
// Trigger interceptor init, use byName.
interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}
我们找到这个服务,这里会用到java多线程的一些知识
对象锁:https://blog.csdn.net/azhegps/article/details/63031562
闭锁:https://www.cnblogs.com/will-shun/p/7392619.html
这个类做了两件事情:
初始化的时候从WareHose中拿到所有interceptors信息,反射出我们定义的interceptor实例
在恰当的时机(后面会讲)遍历interceptor实例,执行拦截器中我们自己实现的process方法
@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
private static boolean interceptorHasInit;
private static final Object interceptorInitLock = new Object();
@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
……
}
/**
* Excute interceptor
*
* @param index current interceptor index
* @param counter interceptor counter
* @param postcard routeMeta
*/
private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
……
}
@Override
public void init(final Context context) {
……
}
private static void checkInterceptorsInitStatus() {
……
}
}
ARouter.getInstance().build("/test/activity2").navigation();
build方法调用了_Arouter的build方法
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
_Arouter的build方法(除了PathReplaceService相关的一通操作)从path中解析出group,创建一个Postcard对象返回。
PathReplaceService的用法阿里的demo也没给出。
按照作者自己的理解来说,当我们通过实现PathReplaceService自定义一个path的解析器,那么这里将会调用我们自己定义的解析器去解析path,不然的话默认入参即为标准的path
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));
}
}
build方法到这里就完了,它最终创建了一个Postcard对象,这个对象目前只有两条数据,一是路由的path,二group
继续看navigation方法,它是Postcard提供的一个方法
public Object navigation() {
return navigation(null);
}
继续,调了自己同名重载方法
public Object navigation(Context context) {
return navigation(context, null);
}
然后调用Arouter的navigation方法(这种写法真的玄幻,一个bean却要承担处理逻辑的职责,仅仅是为了实现链式调用才这么处理的吗???需要大神拯救)
public Object navigation(Context context, NavigationCallback callback) {
return ARouter.getInstance().navigation(context, this, -1, callback);
}
Arouter里的navigation方法调了_Arouter里的navigation方法
public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}
看来_Arouter里的navigation方法是处理逻辑的地方了
分成两段看
第一段,进入方法首先调用LogisticsCenter里的completion方法,这个待会儿跟进去看,这个方法会抛出异常
先看异常捕获做了什么,catch里判断如果回调接口不等于null ,那直接调用回调函数的onLost方法,说明没找到最终的路由
没有异常的话,说找到路由了,回调接口不为null就调用它的onFound方法,这第一段处理
第二段处理,判断这个PostCard有没有设置绿色通道,设置了就直接调用本类下的_navigation()方法
没有绿色通道的话就要接受拦截器的拷问了,这个拦截器服务,还记得哪里来的么,就是开头说初始化时候,在一个教afterInit的方法里生成的
它会把所有的拦截器遍历一遍(是的,所有,要么不过拦截器要么过所有拦截器)
如果没有一个拦截器中断这次路由,那么onContinue方法将被调用,紧接着就调用_navigation()方法了
如果有一个拦截器中断了这次路由,那么onInterrupt将被调用,紧接着调用回调接口的onInterrupt方法
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
if (debuggable()) { // Show friendly tips for user.
Toast.makeText(mContext, "There's no route matched!\n" +
" Path = [" + postcard.getPath() + "]\n" +
" Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
}
if (null != callback) {
callback.onLost(postcard);
} else { // No callback for this invoke, then we use the global degrade service.
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}
return null;
}
if (null != callback) {
callback.onFound(postcard);
}
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;
}
回过来看LogisticsCenter里的completion方法,这个方法是核心方法之一
回忆初始化时候WareHouse放的数据
root信息->Warehouse.groupsIndex <路由路径,RouteMeta实例> 存放group类信息
iterceptors信息->Warehouse.interceptorsIndex <拦截器优先级,interceptor.class> 存放拦截器类信息
provider信息->Warehouse.providersIndex <接口全路径,RouteMeta实例> 存放provider类信息
方法中用到的 Warehouse.routes这个map 并没有在初始化过程里被处理
可以推理出,它应该用来存放命名格式如下的文件里的数据,存放的键值对为<路由path,RouteMeta实例>
Arouter$$Group$$groupName
是不是呢,果然是诶,Warehouse.routes尝试通过path来获取RouteMeta对象
如果不存在
得通过Warehouse.groupsIndex拿到group对应的编译时生成的Group类
找不到Group类,说明不存在这个组
找到了Group类,反射得到命名如下
Arouter$$Group$$groupName
的实例,调用loadinfo方法,在Warehouse.routes中存放该路由路径对应RouteMeta对象,然后再次调用自己
如果存在的话
先把RouteMeta对象里的数据set到Postcard对象里
判断一下,如果是路由对象是Provider对象的话,从Warehouse.providersIndex中拿到provider的class对象,反射得到实例set到Postcard对象里,顺便设置一下绿色通道,路由对象是Fragment也要设置绿色通道,也就是说跳转才拦截,获取Provider和Fragment不拦截(全程没看到Service的处理)
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}
//todo ruan 初始化时仅加载了Warehouse.groupsIndex:ARouter$$Root$$app
//todo ruan Warehouse.route:ARouter$$Group$$xxx 首次路由时加载
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) { // Maybe its does't exist, or didn't load.
Class extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta.
if (null == groupMeta) {
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
// Load route and cache it into memory, then delete from metas.
try {
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}
} catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
}
completion(postcard); // Reload
}
} else {
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
Uri rawUri = postcard.getUri();
if (null != rawUri) { // Try to set params into bundle.
Map resultMap = TextUtils.splitQueryParameters(rawUri);
Map paramsType = routeMeta.getParamsType();
if (MapUtils.isNotEmpty(paramsType)) {
// Set value by its type, just for params which annotation by @Param
for (Map.Entry params : paramsType.entrySet()) {
setValue(postcard,
params.getValue(),
params.getKey(),
resultMap.get(params.getKey()));
}
// Save params name which need auto inject.
postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
}
// Save raw uri
postcard.withString(ARouter.RAW_URI, rawUri.toString());
}
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) {
throw new HandlerException("Init provider failed! " + e.getMessage());
}
}
postcard.setProvider(instance);
postcard.greenChannel(); // Provider should skip all of interceptors
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment needn't interceptors
default:
break;
}
}
}
最后看看一下_Arouter的_navigation方法,判断type
如果是Activity,看看有没有参数,有的话加上,然后跳转,可以看到,跳转最终也是通过intent来实现的
如果是Provider,返回对应的实体类
如果是Fragment,反射得到Fragment的实体类
其他的不处理(Service全程不处理)
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);
}
// Set Actions
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}
// Navigation in main looper.
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 ((-1 != postcard.getEnterAnim() && -1 != 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;
}
Arouter跳转时可以携带参数,目前支持Activity和Fragment传参数
用法如下:
传参数
ARouter.getInstance()
.build("/test/activity2")
.withString("key1", "value1")
.navigation();
获取参数,方式一,从intent获取
@Autowired
String key1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test2);
String value = getIntent().getStringExtra("key1");
...
}
获取参数,方式二,调用inject方法,自动注入
@Autowired
String key1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test2);
ARouter.getInstance().inject(this);
}
源码-传递参数
先看参数传递是怎么实现的,使用示例中通过withXXX()方法传参,它是Postcard的方法,将用户传入的参数放到bundle里
public Postcard withString(@Nullable String key, @Nullable String value) {
mBundle.putString(key, value);
return this;
}
上文在路由跳转方法_navigation里提到过Activtiy传参的方式,再看一遍_navigation方法,它先创建intent,从Postcard里通过getExtras()方法获取参数,我们猜测这个方法获取的就是刚刚提到的bundle
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.
……
}
return null;
}
果然,getExtras获得bundle对象
public Bundle getExtras() {
return mBundle;
}
源码-获取
这么一来,跳转模块时获取参数的第一种方式就理所当然了,就像普通的Activity跳转一样从intent中获取
第二种方式使用了自动注入的方式获取参数,也就是说我们不需要手动的从intent里获取参数,调用
ARouter.getInstance().inject(this);
自动完成这个步骤。怎么自动的呢?大胆推测:编译时注解+反射?(别问我怎么推测出来的,因为我看了butterknife的源码?,和这个过程是一样的)
其实,前面讲arouter-compiler库的时候只提到它编译生成的路由文件,事实上,在相同根目录下还生成了另一个文件夹
如果不记得相同根路径是哪里:
app/build/generated/source/apt/debug/com/alibaba/android/arouter/模块名
比如
每个使用了Autowired注解的类,都会在编译时期,由compiler库生成一个命名格式如下的文件
类名$$ARouter$$Autowired
查看文件内容,就一个inject方法,入参是activity(fragment同理)对象 ,从intent获取参数然后赋值给变量
public class Test2Activity$$ARouter$$Autowired implements ISyringe {
private SerializationService serializationService;
@Override
public void inject(Object target) {
serializationService = ARouter.getInstance().navigation(SerializationService.class);
Test2Activity substitute = (Test2Activity)target;
substitute.key1 = substitute.getIntent().getStringExtra("key1");
}
}
这里不聊这个文件是怎么生成的。来看看这个方法怎么被调用到的吧
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test1);
ARouter.getInstance().inject(this);
……
}
在onCreate中调用ARouter.getInstance().inject(this),它又调用了 _ARouter.inject(thiz)
/**
* Inject params and services.
*/
public void inject(Object thiz) {
_ARouter.inject(thiz);
}
_ARouter的inject方法,先通过路由机制获取AutowiredService实例,然后调用autowire(thiz)方法
static void inject(Object thiz) {
AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
if (null != autowiredService) {
autowiredService.autowire(thiz);
}
}
AutowiredService类是一个接口,它的实现类为AutowiredServiceImpl,它只有两个方法,其中autowire()方法为核心方法,它通过传入的对象获取类名,然后拼成上文提到的如下格式字符串,再反射得到相应的类,调用inject方法。这样自动注入也就完成了。
类名$$ARouter$$Autowired
Route(path = "/arouter/service/autowired")
public class AutowiredServiceImpl implements AutowiredService {
private LruCache classCache;
private List blackList;
@Override
public void init(Context context) {
classCache = new LruCache<>(66);
blackList = new ArrayList<>();
}
@Override
public void autowire(Object instance) {
String className = instance.getClass().getName();
try {
if (!blackList.contains(className)) {
ISyringe autowiredHelper = classCache.get(className);
if (null == autowiredHelper) { // No cache.
autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
}
autowiredHelper.inject(instance);
classCache.put(className, autowiredHelper);
}
} catch (Exception ex) {
blackList.add(className); // This instance need not autowired.
}
}
}
arouter-register是一个插件库,它在虚拟机编译java文件时动态的修改其内容(和热更新是一个原理),划重点,动态修改而非动态生成,因此它和arouter-complier所承担的角色就有所区别。
gradle插件化https://blog.csdn.net/eclipsexys/article/details/50973205
作者在此不细谈java编译时的相关知识(呃,推荐本书吧《深入了解java虚拟机》),直接来看这个库做了什么吧:
事实上,我们在arouter-api中唯一一次感知到插件的存在就是在Arouter的初始化方法中,来回顾一下初始化的核心方法
LogisticsCenter中的init方法,注意在判断Arouter是否被插件初始化之前,调了一个loadRouterMap()的方法,由此猜测插件就是在该方法中完成了初始化
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
//billy.qi modified at 2017-12-06
//load by plugin first
loadRouterMap();
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set routerMap;
……
}
……
}
是不是呢,点开loadRouterMap()方法一看,啥多余的逻辑都没有,甚至给registerByPlugin变量赋了一个flase。
private static void loadRouterMap() {
registerByPlugin = false;
//auto generate register code by gradle plugin: arouter-auto-register
// looks like below:
// registerRouteRoot(new ARouter..Root..modulejava());
// registerRouteRoot(new ARouter..Root..modulekotlin());
}
是的,上述方法确实什么逻辑都没有,因为它的逻辑是在虚拟机编译java文件时生成的。注释里也提到了,还举了例子,但是,我觉得它的例子不是很对啊,故给出更正如下
编译前生成的代码应该是这样的:
private static void loadRouterMap() {
registerByPlugin = false;
register("com.alibaba.android.arouter.routes.ARouter$$Root$$app");
register("com.alibaba.android.arouter.routes.ARouter$$Providers$$app");
register("com.alibaba.android.arouter.routes.ARouter$$Interceptors$$app");
}
来看看register方法,通过类名反射得到实例,然后判断实例类型,再分别注册每个类型的文件
rivate static void register(String className) {
if (!TextUtils.isEmpty(className)) {
try {
Class> clazz = Class.forName(className);
Object obj = clazz.getConstructor().newInstance();
if (obj instanceof IRouteRoot) {
registerRouteRoot((IRouteRoot) obj);
} else if (obj instanceof IProviderGroup) {
registerProvider((IProviderGroup) obj);
} else if (obj instanceof IInterceptorGroup) {
registerInterceptor((IInterceptorGroup) obj);
} else {
logger.info(TAG, "register failed, class name: " + className
+ " should implements one of IRouteRoot/IProviderGroup/IInterceptorGroup.");
}
} catch (Exception e) {
logger.error(TAG, "register class error:" + className);
}
}
}
以registerRouteRoot方法为例子来看,调用了loadInto方法,往Warehouse.groupsIndex存放数据
private static void registerRouteRoot(IRouteRoot routeRoot) {
markRegisteredByPlugin();
if (routeRoot != null) {
routeRoot.loadInto(Warehouse.groupsIndex);
}
}
最后来看 markRegisteredByPlugin()方法,赋值registerByPlugin变量为true。这样的话自动注册就完成了,api库将不会再自行解析自动生成的Java文件。
private static void markRegisteredByPlugin() {
if (!registerByPlugin) {
registerByPlugin = true;
}
}
ok,分析一下使用arouter-register的优势是什么,那当然是提高效率了,把原本要在运行时完成的事情(遍历apk文件,找到自动生成的类)在编译时期就给做了,那效率还能不提高吗?
这篇文章写了足足一个星期,可谓是耗尽我的心血了(关于Arouter,市面上应该没有比本文讲的更详细的文章了吧,嘿嘿),但是因为详细也导致篇幅特别长,感谢坚持读到这一句话的各位读者。作者认为阅读优秀的源码倒不是仅仅为了感叹一下人家的代码有多么牛逼,而更应该学习从中散射到的知识,比如:编译时注解、java并发编程、gradle插件化、反射等等。
事实上,Arouter-api的内容很少,算是一个比较小的库,阅读起来也相对容易。
人无完人,一份代码也不可能满足所有项目的需求。例如Arouter对于模块的调用,非常简单粗暴,经过一通操作直接得到了模块的实例,这对组件化来说就很不友好了。本来各个module各行其道互不干扰,但因为需要调用彼此的方法,又得强行拿到彼此的实例。因此,作者斗胆对Arouter进行了改造,以便更加适合自己的项目,实现模块间的解耦,相关内容会在下篇博客中讨论,谢谢。