相關工程githubhttps://github.com/AlexMahao/ARouter
邏輯分析
Arouter
的路由跳轉整體可分爲三個步驟:
- 編譯時期利用
Processor
生成路由清單文件。 - 運行時期加載路由清單文件。
- 跳轉時期根據標識符查詢路由清單,完成路由地址跳轉。
編譯時期 arouter-compiler
RouteProcessor
是處理路由清單生成的類。其初始化方法如下:
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 判斷是否需要生成文檔,以json的形式生成路由文檔
if (generateDoc) {
try {
// ARouter/app/build/generated/source/apt/debug/com/alibaba/android/arouter/docs
docWriter = mFiler.createResource(
StandardLocation.SOURCE_OUTPUT,
PACKAGE_OF_GENERATE_DOCS,
"arouter-map-of-" + moduleName + ".json"
).openWriter();
} catch (IOException e) {
logger.error("Create doc writer failed, because " + e.getMessage());
}
}
iProvider = elementUtils.getTypeElement(Consts.IPROVIDER).asType();
logger.info(">>> RouteProcessor init. <<<");
}
主要是判斷是否生成輔助查看的文檔。
然後在parseRoutes()
中獲取@Route
註解,解析並生成輔助類。該方法的流程不在深入研究。看一下生成的輔助類結構。
ARouter$$Group$$test.java
ARouter$$Group$$yourservicegroupname.java
ARouter$$Root$$app.java
ARouter$$Providers$$app.java
總共生成了以上幾個文件,可以分爲三類:
Root
根節點類: 包含多個分組。Group
: 包含該組下面的詳細的路由地址。Providers
: 暫時忽略。
在Arouter
中有分組的概念。官方文檔解釋如下:
SDK中針對所有的路徑(/test/1 /test/2)進行分組,分組只有在分組中的某一個路徑第一次被訪問的時候,該分組纔會被初始化。可以通過@Route 註解主動指定分組,否則使用路徑中第一段字符串(/*/)作爲分組。注意:一旦主動指定分組之後,應用內路由需要使用 ARouter.getInstance().build(path, group) 進行跳轉,手動指定分組,否則無法找到
@Route(path = "/test/1", group = "app")
根據描述,可以看出ARouter$$Root$$app.java
就是對分組的統計。該類內容如下:
public class ARouter$$Root$$app implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("test", ARouter$$Group$$test.class);
routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
}
}
每一個分組的標識又指向了內部路由清單明細的輔助類。
ARouter$$Group$$test.java
如下:
public class ARouter$$Group$$test implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", null, -1, -2147483648));
atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
}
}
從省城的路由清單裏可以看到,最終的路由清單裏面,包含了路徑已經對應的跳轉信息。
跳轉信息的類爲RouteMeta
, 該類結構如下:
public class RouteMeta {
private RouteType type; // Type of route
private Element rawType; // Raw type of route
private Class<?> destination; // Destination 跳轉的目標類
private String path; // Path of route
private String group; // Group of route
private int priority = -1; // The smaller the number, the higher the priority
private int extra; // Extra data
private Map<String, Integer> paramsType; // Param type
private String name;
}
由此看出,上面的路由清單中聲明瞭當前路由是activity
,跳轉的class
等。
以上就是對編譯時期的分析。
運行時期 arouter-api
應用運行時需要調用Arouter.init()
初始化,該初始化主要用於加載路由清單。
/**
* Init, it must be call before used router.
*/
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()
和_Arouter.afterInit()
,afterInit()
方法主要用於加載攔截器,不在此次分析的範疇。
protected static synchronized boolean init(Application application) {
mContext = application;
// Logistics 後勤中心
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
mHandler = new Handler(Looper.getMainLooper());
return true;
}
繼續深入LogisticsCenter.init(mContext, executor);
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<String> routerMap;
// It will rebuild router map every times when debuggable.
// 如果是debug版本或者是新版本,就會重新加載路由表
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.
// 獲取com.alibaba.android.arouter.routes的類列表,及Processor生成的輔助類
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
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();
// 把上面加載得到的路由映射根據ClassName分爲三種,分別進行註冊
// IRouteRoot 路由的分組輔助類
// IInterceptorGroup 攔截器
// IProviderGroup 服務組件
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);
}
}
}
}
該方法可以分爲兩個部分。第一個部分是獲取編譯時期獲取的類的全路徑列表。第二個部分是分別調用輔助類的loadInto()
將路由表信息保存到Warehouse
中。
該方法內部流程如下:
loadRouterMap()
中將registerByPlugin
置爲false
,所以邏輯直接進入到else
中。- 判斷是否是新版本或者是
debug
,如果是則重新獲取輔助類的全路徑列表,否則就從緩存裏面取。 - 調用輔助類,將路由信息存儲到
Warehouse
中。
跳轉時期 arouter-api
ARouter.getInstance()
.build("/test/activity2")
.navigation();
跳轉的使用方式如上,看一下build
方法,該方法最終調用_Arouter.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);
}
}
group
如果沒有傳入,就是默認組名。
PathReplaceService
是Arouter
提供的一個路徑替換的服務,默認是null
。
最終build()
返回了Postcard
對象。該對象繼承RouteMeta
。結構如下。
public final class Postcard extends RouteMeta {
// Base
private Uri uri;
private Object tag; // A tag prepare for some thing wrong.
private Bundle mBundle; // Data to transform
private int flags = -1; // Flags of route
private int timeout = 300; // Navigation timeout, TimeUnit.Second
private IProvider provider; // It will be set value, if this postcard was provider.
private boolean greenChannel;
private SerializationService serializationService;
// Animation
private Bundle optionsCompat; // The transition animation of activity
private int enterAnim = -1;
private int exitAnim = -1;
}
可見該類包含了一些跳轉所需要的參數。但是當前只是包含了path
等地址標識。然後調用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) {
// 降級處理等 ...
}
if (null != callback) {
callback.onFound(postcard);
}
// 如果不是綠色通多,攔截器做攔截
if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
// 攔截器處理等 ...
} else {
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
該方法首先調用LogisticsCenter.completion(postcard);
從Warehouse
中查詢跳轉所需要的關鍵信息。比如目標class
等。
然後判斷是否是綠色通道,最終都調用了_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:
// 執行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;
}