简单使用
//1 初始化
if (BuildConfig.DEBUG) {
ARouter.openLog();
ARouter.openDebug();
}
ARouter.init(this);
//2 配置需要路由的目标
@Route(path = Constant.MAIN2)
public class Main2Activity extends AppCompatActivity {}
@Route(path = Constant.PROVIDER1)
public class Main1ProviderService implements IProvider {}
//3 开始路由
ARouter.getInstance().build(Constant.MAIN2).navigation();
Main1ProviderService service = ARouter.getInstance().navigation(Main1ProviderService.class);
- 代码 //1 中初始化,相对比较重要,这里会将我们配置生成的,指定目录下的路由文件类名先加载进内存(例如:ARouter&&Group&&main,group代表路由组文件,其中&实为$,csdn markdown转义字符好像无效,用&代替下)。这里涉及到io操作(原理下面会进行解释),所以建议将初始化放在子线程中加载。
- //2 为配置路由,//3 跳转路由。
- IProvider 属于ARouter中的一种范式,用于暴露给模块使用的服务的一种规范。组件间通讯当使用 IProvider 作为接口暴露给对方使用。并且IProvider的接口定义应放在公共组件中,方便各个组件拿到服务。IProvider主要用于组件间通讯,隐藏各个服务的实现细节(IProvider 属于单例创建,使用的时候需要特别注意)。我们获取Provider有两种方式,一个是使用Provider的全类名,一个是通过path。
ARouter.init()解析
public static void init(Application application) {
if (!hasInit) {
logger = _ARouter.logger;
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
hasInit = _ARouter.init(application);//1
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);//2
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
mHandler = new Handler(Looper.getMainLooper());
return true;
}
//3
private volatile static ThreadPoolExecutor executor = DefaultPoolExecutor.getInstance();
- //1 将初始化动作交给_ARouter.init()处理。hasInit 为初始化成功的标志,下面ARouter跳转会根据这个标志相应抛出RuntimeException,所以 ARouter 的 navigation 动作必须放到ARouter::init之后执行。
- //2 LogisticsCenter.init(mContext, executor) 字面意思物流中心初始化。这里将executor线程池传递进去。
- //3 这里我们可以看到这个线程池有一个默认的初始值(如何配置就不继续跟了),当然也可以我们自己配置。在初始化仓库之前配置 ARouter::setExecutor();就可以了
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(); //仅仅将registerByPlugin赋值为false
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set<String> routerMap;
// It will rebuild router map every times when debuggable.
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// These class was generated by arouter-compiler.
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);//1 获取指定包下面的所以ClassName。
if (!routerMap.isEmpty()) {
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
PackageUtils.updateVersion(context); // Save new version name when router map update finishes.
} else {
logger.info(TAG, "Load router map from cache.");
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}
logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
startInit = System.currentTimeMillis();
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);//2
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// Load interceptorMeta
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);//3
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);//4
}
}
}
logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");
...
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
这里主要干了两件事
- 获取 routerMap 从sp中获取信息,如果获取不到则执行 //1 处将信息加载进map中
- 将routerMap中的
- //2 IRouteRoot 配置加载进 groupsIndex 。groupsIndex 掌握一些组的信息 ,路由会根据所有的path生成对应的ARouter&&Group&&xx 文件(xx为模块名)。
- //3 IInterceptorGroup配置加载进 interceptorsIndex
- //4 IProviderGroup 配置加载进 providersIndex 。这里将IProvider接口生成的Provider class文件的全量信息,直接干进providersIndex中。之后navigation(class)方法会从providersIndex 找对应的类信息。
//组信息,用于做懒加载处理
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
//真实的路由信息,懒加载后的RouteMeta都会放在这里(key为path)
static Map<String, RouteMeta> routes = new HashMap<>();
// Cache provider
//懒加载缓存,根据routes或者providersIndex提供的Provider的class信息。创建IProvider放到这个集合中(只创建一次)
static Map<Class, IProvider> providers = new HashMap<>();
//存放IProvider的class信息(key是存放类名)
static Map<String, RouteMeta> providersIndex = new HashMap<>();
上面属于 Warehouse.java中的信息。理解这些静态常量有利于我们分析,之后的整个Router过程。
接下来我们继续进行上面的加载生成的文件 //1处分析
/**
* 通过指定包名,扫描包下面包含的所有的ClassName
* packageName为 com.alibaba.android.arouter.routes
*/
public static Set<String> getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
final Set<String> classNames = new HashSet<>();
List<String> paths = getSourcePaths(context);//获取全部的dex path
final CountDownLatch parserCtl = new CountDownLatch(paths.size());//1
for (final String path : paths) {
DefaultPoolExecutor.getInstance().execute(new Runnable() {
@Override
public void run() {
DexFile dexfile = null;
try {
if (path.endsWith(EXTRACTED_SUFFIX)) {
//NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
dexfile = DexFile.loadDex(path, path + ".tmp", 0);
} else {
dexfile = new DexFile(path);
}
Enumeration<String> dexEntries = dexfile.entries();
while (dexEntries.hasMoreElements()) {
String className = dexEntries.nextElement();
if (className.startsWith(packageName)) {
classNames.add(className);
}
}
} catch (Throwable ignore) {
Log.e("ARouter", "Scan map file in dex files made error.", ignore);
} finally {
if (null != dexfile) {
try {
dexfile.close();
} catch (Throwable ignore) {
}
}
parserCtl.countDown();//2
}
}
});
}
parserCtl.await();//3
Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
return classNames;
}
大概解释这段代码的逻辑:这里通过getSourcePaths()来获取全部的dex path。然后为每一个path分配一个任务(Runnable)丢入线程池中执行。直到整个线程池执行完毕将classNames返回。
细节://1处的 CountDownLatch 为java并发包提供等待多线程完成的解决方案。首先根据path长度创建 count 数以及相同数量的任务。每一个线程执行完成会调用 //2处的方法将 count-- 。//3处为阻塞出口。直到 parserCtl 里面的 count 为 0 时,当前线程才会被唤醒继续执行。
init小结: init主要操作就是将路由需要的所以Class信息全部加载进 Warehouse 仓库的静态变量中。而获取全部 Class 信息,是使用固定包名全量搜索注解处理器生成的文件。并通过反射创建注解处理器生成类的实例,执行其中的方法将路由类信息存放到 groupsIndex 和 providersIndex 中。而全量搜索为了实现同步初始化,用到了线程阻塞技术。但是在android主线程中,最好不要阻塞主线程。所以我建议将ARouter.init()方法放到子线程。如果需要知道ARouter是否初始化完成。可以在init(init方法为同步执行)的方法之后设置一个 boolean 变量或者监听,来获取初始化是否完成的状态。
有了上面对初始化工作的了解,接下来我们继续对ARouter的navigation进行分析。
ARouter.getInstance().build().navigation();
在对路由跳转分析之前我们首先需要搞清楚5个东西:
- Postcard 我理解为相当于弱化的 Intent 对象和Context对象的结合体(内部存储一些路由相关的操作和信息)
- RouteMeta为Postcard 的父类,主要用于存储路由信息,子类 Postcard 暴露给用户,提供路由方法
- _ARouter 为 ARouter的代理类
- LogisticsCenter 物流中心,属于一个工具类,内部实现了路由信息的加载,缓存,初始化等方法。
- Warehouse 仓库 groupsIndex 存放路由组的class信息
- Warehouse 仓库 routes 存放初始化完成后的真实路由信息。
下面开始分析
//1
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;
}
}
//2
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
//3
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));
}
}
上面这三段,相对比较简单,就放在一起说了。
//1 使用DCL获取ARouter唯一实例。
//2 build()方法会交给_ARouter实现,_ARouter.getInstance()依然使用DCL初始化。
//3 又继续将build传递给 build(path, extractGroup(path));执行。
//1
private String extractGroup(String path) {
if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
}
try {
String defaultGroup = path.substring(1, path.indexOf("/", 1));
if (TextUtils.isEmpty(defaultGroup)) {
throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");
} else {
return defaultGroup;
}
} catch (Exception e) {
logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());
return null;
}
}
//2
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);
}
}
//1 将path中的group字符串切出来
//2 根据path和group创建路由所需要的 Postcard 对象。
回到上面接下来会调用Postcard::navigation()方法。
public Object navigation(Context context, NavigationCallback callback) {
return ARouter.getInstance().navigation(context, this, -1, callback);
}
Postcard::navigation()方法最终又会去调用ARouter::navigation()方法,而ARouter会交给_ARouter实现。
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
// Pretreatment failed, navigation canceled.
return null;
}
try {
LogisticsCenter.completion(postcard);//1 完成跳转前的准备工作
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
if (debuggable()) {
// Show friendly tips for user.
runInMainThread(new Runnable() {
@Override
public void run() {
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);//2
}
return null;
}
//1 处主要做postcard路由信息的准备工作,准备的成功与否是判断路由成功与否的标志。
//2 处将准备好的数据执行_navigation()完成真正的路由跳转。
我们先看//1处的代码
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());//1
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<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
Map<String, Integer> paramsType = routeMeta.getParamsType();
if (MapUtils.isNotEmpty(paramsType)) {
// Set value by its type, just for params which annotation by @Param
for (Map.Entry<String, Integer> 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;
}
}
}
解释一下上面这一段的执行逻辑:
首先通过 Warehouse.routes.get(postcard.getPath());去缓存中寻找,当前path对应的路由是否被初始化过,如果没有,会从Warehouse.groupsIndex信息。将当前组的路由全部加载完成后,再把groupsIndex里面对应的group移除掉。然后递归调用当前方法。
此时routers里面,已经用于当前group的所有route信息了。然后postcard需要携带的参数全部复制给他。需要注意的是,这里也对 IProvider 进行了初始化,并将初始化完成的 IProvider 放入到 Warehouse.providers容器中,并赋值给Postcard的provider变量,方便navigation尾声时直接从携带参数中获取。从这里也可以看出 IProvider 实则为单例模式存放于内存中,我们使用的时候直接会从Warehouse.providers去取就行了。
分析完postcard的准备工作我们继续看//2处 _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);
}
// Set Actions
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}
// Navigation in main looper.
runInMainThread(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
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的执行就到了尾声,如果type为
- ACTIVITY就startActivity 返回值为null
- FRAGMENT就创建Fragment 返回值为Fragment
- PROVIDER直接往去拿provider中的唯一实例。 返回值为IProvider
至于type怎么赋值的,它会在注解处理器生成group文件时,会知道自己的type到底是什么,并赋值。
这里缺少对注解处理器的分析,其实我觉得完全没有必要,注解处理器只是一个工具,当我们知道他的运行原理,知道注解处理器生成的文件模板,我们完全可以自己写一套注解处理的实现。