目录
_Arouter.getInstance().build(String path, String group)
Arouter设计思想
假如有一个需求:
有两个模块,烹饪模块Cook和食材模块Food。Cook需要使用Food中的一个Fish类。正常来说,我们只需要建立模块间的依赖关系,Cook依赖Food之后,就可以使用Food中的Fish类了。but, 当项目足够复杂时,很容易出现大量的模块间的相互依赖,导致工程无法编译,难以维护的问题。
Arouter提出了一种模块间的解耦方案。核心思想就是:通过注解处理器,编译时动态生成代码的方式解耦代码(JavaPoet),最终实现模块间的解耦。
流程为:
- Arouter编译时动态生成类,这些类均提供类方法——将Class(其他模块用的类)插入Map中。
- Arouter初始化的时候,会反射所有动态生成的类,调用反射类的方法,将Class(其他模块用的类)缓存到Map。
- 其他模块在全局Map中,通过key找Class,并通过Class的接口调用Class的实现方法,最终达到模块解耦的目的。
说起来可能还是有点抽象,我们来举个简单的例子吧
- 我们对Food的Fish注解。编译期间,生成了类FishCompile,这个类提供了一个方法loadInto
FishCompile.class
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("fish", Fish.class);
}
- 另外我们提供了一个初始化方法Arouter.init。init会根据FishCompile的包名找到FishCompile的类名,通过反射创建FishCompile对象f,通过f.loadInto方法,将“fish”和Fish.class插入到全局Map中。这样Cook模块就可以通过“fish”找到Fish.class并使用了。
当然,Arouter的具体实现是没有这么简单的。具体解耦的流程如下
Arouter初始化过程
在Application中调用Arouter的init方法,如下:
Arouter.init(Context context)
Arouter.init方法调动了_Arouter.init方法。
_Arouter.java
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
mHandler = new Handler(Looper.getMainLooper());
return true;
}
_Arouter初始化都是在LogisticsCenter.init方法中实现,跟进去...
LogisticsCenter.java
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
//loadRouterMap返回false
loadRouterMap();
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)) {
// 标记1
// 获取Apk中所有Arouter框架在编译时生成的class文件的类名(全路径名+包名)
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
// sp缓存
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 {
// 如果已经执行过上面的解析,后续重启app直接读取sp缓存
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}
startInit = System.currentTimeMillis();
// 反射创建对象,并缓存到Warehouse(路由相关的缓存、拦截缓存、服务的缓存),className对标图1中的Arouter$$Group$$app、Arouter$$Provider$$app、Arouter$$Root$$app
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() + "]");
}
}

LogitisticCenter的init方法中,我们留了一个悬念——标记1。我们来分析下getFileNameByPackagename方法。
/**
* 通过指定包名,扫描包下面包含的所有的ClassName
*
* @param context U know
* @param packageName 包名
* @return 所有class的集合
*/
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);
// 同步
final CountDownLatch parserCtl = new CountDownLatch(paths.size());
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();
// 编译class文件,检查是否为:com.alibaba.android.arouter.routes路径下的类,如果是,将其类名加到classNames中。
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();
}
}
});
}
parserCtl.await();
Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
return classNames;
}
Arouter路由过程
跳转代码
ARouter.getInstance().build("/base/LoginActivity").navigation();
流程分析
Arouter.getInstance()
Arouter.java
返回Arouter对象。
Arouter.getInstance().build()
Arouter.java
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
返回了_Arouter实体以及调用了_Arouter的build方法——Postcard对象
Arouter.getIntance.build(path)
_Arouter.java
protected Postcard build(String path) {
...
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return build(path, extractGroup(path));
}
}
按照demo的中调用,应该是走到Line9
_Arouter.getInstance().build(String path, String group)
_Arouter.java
/**
* Build postcard by path and group
*/
protected Postcard build(String path, String group) {
...
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return new Postcard(path, group);
}
}
依然会走到Line10,返回一个PostCard对象。
PostCard是什么?
继承自RouteMeta,包含了路由的属性。后面详细介绍
小总结:
ARouter.getInstance().build("/base/LoginActivity")执行到这里时,返回的是一个Postcard对象,包括了当前路由的group信息和path信息。
ARouter.getInstance().build(String).navigation()
ARouter.getInstance().build(String).navigation()等效于PostCard.navigation()方法。
PostCard.java
/**
* Navigation to the route with path in postcard.
* No param, will be use application context.
*/
public Object navigation() {
return navigation(null);
}
navigation是路由的具体实现入口,共有4个路由方式,如下。
PostCard.java
// 通过Application的Context来路由,
public Object navigation()
// 如果能拿到Activity推荐使用这种方式跳转
public Object navigation(Context context)
// 返回路由的结果
public Object navigation(Context context, NavigationCallback callback)
//对标Activity的startActivityForResult
public void navigation(Activity mContext, int requestCode)
// 结合了上述3,4
public void navigation(Activity mContext, int requestCode, NavigationCallback callback)
无论使用哪种路由方式,其结果都是调用了Arouter.navigation方法,区别就是Arouter.navigaiton的入参不一样罢了。有些是null,有些有其他参数。
Arouter.java
public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}
}
Arouter的navigation方法调用了_Arouter的navigation方法。这里采用了门面设计模式,_Arouter是Arouter的门面。
_Arouter.getInstance().navigation(.....)
_Arouter.java
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
// 注明的没有找到路由的报错
Toast.makeText(mContext, "There's no route matched!\n" + " Path = [" + postcard.getPath() + "]\n" + " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
...
// 还记得上面Arouter.getInstance.build.navigation(callback)的callback方法吗? 如果callback不为null,就调用onLost方法返回给调用者
if (null != callback) {
callback.onLost(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() {
@Override
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);
}
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;
postcard.isGreenChannel()方法无论是true还是false,最后都是调用_Arouter._navigation方法。
_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 n
当postCard.getType方法返回的是Activity时,调用startActivity方法执行跳转。
_Arouter.startActivity(int , Context , Intent intent, Postcard , NavigationCallback )
这个方法很简单,如果Context不是Activity,会通过这种方式跳转。intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
/**
* Start activity
* @see ActivityCompat
*/
private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
if (requestCode >= 0) { // Need start for result
/**
* requestCode >=0 要返回result给当前Activity
* 所以,currentContext必须是Activity,否则报错
*/
if (currentContext instanceof Activity) {
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
logger.warning(Consts.TAG, "Must use [navigation(activity, ...)] to support [startActivityForResult]");
}
} else {
/**
* 不需要返回result给当前Activity,所以currentContext是什么就无所谓了
*/
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);
}
}