以下是我阅读源码的过程中遇到的问题,以及是如何分析和解决的,文章比较长,贴了很多代码,希望能帮到看文章的人。看之前建议先下载源码运行,地址:https://github.com/alibaba/ARouter
问题
要想了解ARouter的原理,说白了就是需要知道,Activity是如何跳转的?
带着问题去找答案;
从程序引用中找答案,核心代码如下:
ARouter.getInstance().build(ARouterConstants.SPLASH_GUIDE).navigation();
这句代码就是跳转的精髓所在,所以先要吃透这行代码。
解析
1. 初始化
ARouter.getInstance()
2. 构建
.build(String path)方法
源码解析:
- 创建一个Postcard对象
/**
* Build the roadmap, draw a postcard.
*
* @param path Where you go.
*/
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
- 在 _ARouter.java中
/**
* Build postcard by path and default group
*/
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));
}
}
- 要走下去,这里有一个service需要了解清楚(PathReplaceService.java代码如下:)
/**
* Preprocess your path
*
* @author Alex <a href="mailto:zhilong.liu@aliyun.com">Contact me.</a>
* @version 1.0
* @since 2016/12/9 16:48
*/
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.java是继承了IProvider.java的接口类。它包含两个成员方法。
而的代码如下:
/**
* Provider interface, base of other interface.
*
* @author Alex <a href="mailto:zhilong.liu@aliyun.com">Contact me.</a>
* @version 1.0
* @since 16/8/23 23:08
*/
public interface IProvider {
/**
* Do your init work in this method, it well be call when processor has been load.
*
* @param context ctx
*/
void init(Context context);
}
光从这两个接口类中,我很难知道他们具体干了什么,所以,具体的还是要回到
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
我需要知道,最终得到的到底是一个怎样的Service。当然,这块是否是重要的,我现在不好说,或者可以默认先跳过去,但我决定,先看看情况。
实现的具体源码:
protected <T> T navigation(Class<? extends T> service) {
try {
Postcard postcard = LogisticsCenter.buildProvider(service.getName());
// Compatible 1.0.5 compiler sdk.
// Earlier versions did not use the fully qualified name to get the service
if (null == postcard) {
// No service, or this service in old version.
postcard = LogisticsCenter.buildProvider(service.getSimpleName());
}
if (null == postcard) {
return null;
}
LogisticsCenter.completion(postcard);
return (T) postcard.getProvider();
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
return null;
}
}
从首行代码中可以知道,这里先会获得一个Postcard:
Postcard postcard = LogisticsCenter.buildProvider(service.getName());
-----------------------分割线---------------------------
/**
* Build postcard by serviceName
* 通过服务名构建一个Postcard对象
* @param serviceName interfaceName
* @return postcard
*/
public static Postcard buildProvider(String serviceName) {
RouteMeta meta = Warehouse.providersIndex.get(serviceName);
if (null == meta) {
return null;
} else {
return new Postcard(meta.getPath(), meta.getGroup());
}
}
RouteMeta meta = Warehouse.providersIndex.get(serviceName);
// 这里其实就是去一个Map对象中根据serviceName取出RouteMeta对象
static Map<String, RouteMeta> providersIndex = new HashMap<>();
// 这里有取的过程,其实应该要知道,添加的过程在哪?
***注意:***在整个过程中,其实我并没有看到有地方在往Map<String, RouteMeta> providersIndex中加数据,所以我可以推测,这个可能是空的(Application中的ARouter.init(this)没看,所以并不知道这里的数据情况)。
- 先假设这里是空的,那么回到第2步。
return build(path, extractGroup(path));
/**
* Build postcard by path and group
*/
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);
}
}
那这里就变成了,直接new Postcard(path, group);这里直接创建一个Postcard对象。
-
Postcard中做了哪些事情?
public Postcard(String path, String group) { this(path, group, null, null); } public Postcard(String path, String group, Uri uri, Bundle bundle) { setPath(path); setGroup(group); setUri(uri); this.mBundle = (null == bundle ? new Bundle() : bundle); }
可以看到这里做的四件事情,setPath, setGroup, setUri 和创建Bundle对象。
Postcard继承了extends RouteMeta
public RouteMeta setPath(String path) { this.path = path; return this; } public RouteMeta setGroup(String group) { this.group = group; return this; } public Postcard setUri(Uri uri) { this.uri = uri; return this; }
到这里,初步的ARouter.getInstance().build()算是分析完了,总结起来,其实很简单,就是创建了一个Postcard对象,这个对象继承的是RouteMeta;
-
Postcard中的navigation方法做了什么事情呢?
ARouter.getInstance().build(ARouterConstants.SPLASH_GUIDE).navigation(); // ------------------------------------------------------ /** * Navigation to the route with path in postcard. * No param, will be use application context. */ public Object navigation() { return navigation(null); } /** * Navigation to the route with path in postcard. * * @param context Activity and so on. */ public Object navigation(Context context) { return navigation(context, null); } /** * Navigation to the route with path in postcard. * * @param context Activity and so on. */ public Object navigation(Context context, NavigationCallback callback) { return ARouter.getInstance().navigation(context, this, -1, callback); }
注释里解释了,“导航到带有path路径的postcard中,没参数的时候,将使用application”,所以这个的目的也就清楚了,就是跳转导航,那么接下来看看,它是如何做到跳转的呢?
-
最终的跳转的实现还是通过ARouter的navigation方法
/** * Launch the navigation. * * @param mContext . * @param postcard . * @param requestCode Set for startActivityForResult * @param callback cb */ public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) { return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback); }
上面传递的context, callback都是null。
-
跳转到_ARouter中实现具体的跳转内容
/** * Use router navigation. * * @param context Activity or null. * @param postcard Route metas * @param requestCode RequestCode * @param callback cb */ 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); } 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); } return null; }
上面代码比较多,我们一点点来,第一句:
8.1. 获取PretreatmentService对象,如果
PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class); if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) { // Pretreatment failed, navigation canceled. return null; }
这里是判断是否做了预处理,如果没有,则返回null。
在前面,我是假设了Warehouse.java中的Map<String, RouteMeta> providersIndex中是没有数据的,但这里,为了了解它们预处理到底做了什么事情,我想先分析Application中的ARouter.init(this)。
3. ARouter.init(this)
去解析Application中的ARouter.init(this),为了了解预加载做了什么事情。
-
init()初始化
private volatile static boolean hasInit = false; public static ILogger logger; /** * 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."); } }
默认是没有初始化的,先给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.");
前后各打印了一个start和over,显而易见,中间的 _ARouter.init(application);或者_ARouter.afterInit();做的事情就是预处理的事情。
-
分析_ARouter.init(application)
private volatile static ThreadPoolExecutor executor = DefaultPoolExecutor.getInstance(); 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 init success!”也能看出,预加载是通过LogisticsCenter.init(mContext, executor);来完成
-
LogisticsCenter.init(mContext, executor);
先看这两个入参,一个是Application,一个是executor,看上面的定义,这是一个线程池对象,接下来,看看预加载到底做了什么事情,LogisticsCenter中的init()方法,代码如下:(注释中解释,这个方法做的事情是load all metas in memory,加载所有的metas进入内存,那么接下来看看它是如何做到将所有的metas加入到内存的?)
/** * LogisticsCenter init, load all metas in memory. Demand initialization */ 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. 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); 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); } 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); } } } logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms."); if (Warehouse.groupsIndex.size() == 0) { logger.error(TAG, "No mapping files were found, check your configuration please!"); } if (ARouter.debuggable()) { logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size())); } } catch (Exception e) { throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]"); } }
3.1. if (ARouter.debuggable() || PackageUtils.isNewVersion(context))
这里是如果是debug模式或者
在ARouter中有
--------------ARouter.java------------------ public static synchronized void openDebug() { _ARouter.openDebug(); } --------------_ARouter.java------------------ static synchronized void openDebug() { debuggable = true; logger.info(Consts.TAG, "ARouter openDebug"); }
也就是说,如果程序中调用了ARouter.openDebug()方法的话,就会设置ARouter.debuggable() =true;
或者PackageUtils.isNewVersion(context)
public static boolean isNewVersion(Context context) { PackageInfo packageInfo = getPackageInfo(context); if (null != packageInfo) { String versionName = packageInfo.versionName; int versionCode = packageInfo.versionCode; SharedPreferences sp = context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE); if (!versionName.equals(sp.getString(LAST_VERSION_NAME, null)) || versionCode != sp.getInt(LAST_VERSION_CODE, -1)) { // new version NEW_VERSION_NAME = versionName; NEW_VERSION_CODE = versionCode; return true; } else { return false; } } else { return true; } }
上面代码可以看到,ARouter会把安装包的versionName和versionCode保存在缓存中,如果他们其中一个发生改变,就返回true;
所以上面两种方式,任何一种改变都会进行下面代码
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); if (!routerMap.isEmpty()) { context.getSharedPreferences(AROUTER_SP_CACHE_KEY,Context.MODE_PRIVATE).edit(). putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply(); } PackageUtils.updateVersion(context);
否则:
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
3.2. 先分析debug模式的情况,或者第一次安装,或者更新versionName或versionCode的情况
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
释文中解释得很清楚,“通过指定包名,扫描包下面包含的所有的ClassName”,感兴趣的可以自行看它是如何做到扫描所有的classname的。
/** * 通过指定包名,扫描包下面包含的所有的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(); 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; }
这个getFileNameByPackageName()方法的入参是:
public static final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes";
3.3. getSourcePaths(context);
List<String> paths = getSourcePaths(context);
/** * get all the dex path * * @param context the application context * @return all the dex path * @throws PackageManager.NameNotFoundException * @throws IOException */ public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException { ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0); File sourceApk = new File(applicationInfo.sourceDir); List<String> sourcePaths = new ArrayList<>(); sourcePaths.add(applicationInfo.sourceDir); //add the default apk path //the prefix of extracted file, ie: test.classes String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; // 如果VM已经支持了MultiDex,就不要去Secondary Folder加载 Classesx.zip了,那里已经么有了 // 通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的 if (!isVMMultidexCapable()) { //the total dex numbers int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1); File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME); for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) { //for each dex file, ie: test.classes2.zip, test.classes3.zip... String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; File extractedFile = new File(dexDir, fileName); if (extractedFile.isFile()) { sourcePaths.add(extractedFile.getAbsolutePath()); //we ignore the verify zip part } else { throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'"); } } } if (ARouter.debuggable()) { // Search instant run support only debuggable sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo)); } return sourcePaths; }
3.4. 其中isVMMultidexCapable()方法,注释也解释了,就是判断vm中是否支持MultiDex
/** * Identifies if the current VM has a native support for multidex, meaning there is no need for * additional installation by this library. * * @return true if the VM handles multidex */ private static boolean isVMMultidexCapable() { boolean isMultidexCapable = false; String vmName = null; try { if (isYunOS()) { // YunOS需要特殊判断 vmName = "'YunOS'"; isMultidexCapable = Integer.valueOf(System.getProperty("ro.build.version.sdk")) >= 21; } else { // 非YunOS原生Android vmName = "'Android'"; String versionString = System.getProperty("java.vm.version"); if (versionString != null) { Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString); if (matcher.matches()) { try { int major = Integer.parseInt(matcher.group(1)); int minor = Integer.parseInt(matcher.group(2)); isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR) || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR) && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR)); } catch (NumberFormatException ignore) { // let isMultidexCapable be false } } } } } catch (Exception ignore) { } Log.i(Consts.TAG, "VM with name " + vmName + (isMultidexCapable ? " has multidex support" : " does not have multidex support")); return isMultidexCapable; } /** * 判断系统是否为YunOS系统 */ private static boolean isYunOS() { try { String version = System.getProperty("ro.yunos.version"); String vmName = System.getProperty("java.vm.name"); return (vmName != null && vmName.toLowerCase().contains("lemur")) || (version != null && version.trim().length() > 0); } catch (Exception ignore) { return false; } }
可以看到,框架对YunOS做了兼容,而判断是否是YunOS系统的办法,两种形式,一:java虚拟机的名字中是否包含“lemur”;二:或者"ro.yunos.version"的版本存在且大于0;
如果是YunOS,那么判断"ro.build.version.sdk"的版本大于等于21则返回true;
但如果是非YunOS系统,是Android系统,那么就查找"java.vm.version"的版本是否大于2的大版本,或者大于等于2.1版本
总结下3.4做的事情,就是如果是YunOS系统,那么要大于等于21版本,如果是Android版本,它的虚拟机要大于等于2.1版本,所以,我理解,这里应该是指,最低是这类的版本的虚拟机vm才支持了MultiDex。
3.5. 如果系统虚拟机支持了MultiDex,那么直接返回
sourcePaths.add(applicationInfo.sourceDir); //add the default apk path return sourcePaths;
即:直接返回包含安装包地址的路径集合。
感兴趣的可以看下,如果不支持MultiDex的话,它做了事情是扫描.zip包的,具体做法可自行了解。
3.6. 继续回到3.2中的getSourcePaths(),知道这里获得了系统的资源路径集合
3.7. 扫描以"com.alibaba.android.arouter.routes"开头的文件,代码内容如下:
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(); } } });
开启线程,拦截了className.startsWith(packageName);即"com.alibaba.android.arouter.routes"开头的包文件。
感兴趣的可以通过日志:
Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
来查看包名和classNames。
我查看到的是:
2021-03-09 15:49:48.746 5058-5058/ai.arouter.demo D/ARouter::: Filter 44 classes by packageName <com.alibaba.android.arouter.routes>
-
继续阅读init()
if (!routerMap.isEmpty()) { context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply(); }
只要扫描到的包是存在的,就将数据存入到缓存中,标识是AROUTER_SP_KEY_MAP。
-
PackageUtils.updateVersion(context);
public static void updateVersion(Context context) { if (!android.text.TextUtils.isEmpty(NEW_VERSION_NAME) && NEW_VERSION_CODE != 0) { SharedPreferences sp = context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE); sp.edit().putString(LAST_VERSION_NAME, NEW_VERSION_NAME).putInt(LAST_VERSION_CODE, NEW_VERSION_CODE).apply(); } }
这步比较简单,就是保存了版本名和版本号。目的就是前面isNewVersion的判断。
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {}
-
前面我们拿到了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); } }
public static final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes"; public static final String DOT = "."; public static final String SDK_NAME = "ARouter"; public static final String SEPARATOR = "$$"; public static final String SUFFIX_ROOT = "Root"; public static final String SUFFIX_INTERCEPTORS = "Interceptors"; public static final String SUFFIX_PROVIDERS = "Providers";
所以知道,上面筛选的三个className分别是:
com.alibaba.android.arouter.routes.ARouter$$Root com.alibaba.android.arouter.routes.ARouter$$Interceptors com.alibaba.android.arouter.routes.ARouter$$Providers
到这步开始有点犯迷糊了。
疑问:1. 这些ARouter$$是什么?2. 他们的含义是什么?3. 为什么要扫描他们?
看到这里,我才发现,这里是有问题需要解决。
为了找到问题,我把ARouter的库文件找了一遍,没发现com.alibaba.android.arouter.routes包名的文件,那么问题来了,这些从哪里来的。
但在build文件夹中发现了它们
找到了它们,又有问题,它们是什么时候生成的,又有什么用途呢?
带着疑问,查看了下ARouter$$Root中的代码,如下
public class ARouter$$Root$$app implements IRouteRoot { @Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("account", ARouter$$Group$$account.class); routes.put("configuration", ARouter$$Group$$configuration.class); routes.put("device", ARouter$$Group$$device.class); routes.put("devices", ARouter$$Group$$devices.class); routes.put("family", ARouter$$Group$$family.class); routes.put("hybrid", ARouter$$Group$$hybrid.class); routes.put("message", ARouter$$Group$$message.class); routes.put("register", ARouter$$Group$$register.class); routes.put("rn", ARouter$$Group$$rn.class); routes.put("room", ARouter$$Group$$room.class); routes.put("scan", ARouter$$Group$$scan.class); routes.put("splash", ARouter$$Group$$splash.class); routes.put("timer", ARouter$$Group$$timer.class); routes.put("weather", ARouter$$Group$$weather.class); } }
继续检查下去,发现,原来,这些是通过@Route生成的
@Route(path = ARouterConstants.DEVICE_ABOUT)
那这样就能解释的通了。
4. @Route
上面的代码先告一段落,这里需要先把@Route做的事情先搞清楚。代码如下:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
/**
* Path of route
*/
String path();
/**
* Used to merger routes, the group name MUST BE USE THE COMMON WORDS !!!
*/
String group() default "";
/**
* Name of route, used to generate javadoc.
*/
String name() default "";
/**
* Extra data, can be set by user.
* Ps. U should use the integer num sign the switch, by bits. 10001010101010
*/
int extras() default Integer.MIN_VALUE;
/**
* The priority of route.
*/
int priority() default -1;
}
到这里卡住了。因为我没有下载源码,是通过jar包直接查看的,其中一个com.alibaba:arouter-compiler包没下载到,这里做的事情主要是程序在编译时,通过apt工具,将@Route注解的文件翻译成.java文件。具体的实现和规则,可以查看apt的使用和规则。
有空的话,可以分析下,apt是如何将@Route注解的.java文件生成编译的文件过程。
继续回到第3点的代码;
5. 继续解析ARouter.init(this)
-
将地址和Activity直接的映射关键保存
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
它的实现是:
@Override public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) { routes.put("account", ARouter$$Group$$account.class); routes.put("configuration", ARouter$$Group$$configuration.class); }
也就是说,所有的映射关系最终会被存储到Warehouse.groupsIndex的集合中。
同理:Warehouse.interceptorsIndex,Warehouse.providersIndex分别保存了它们各自的集合对象。
所以这里就已经很清楚了,总结起来,ARouter.init(this);做的事情,就一件,将注解的class,分别塞到Warehouse.groupsIndex,Warehouse.interceptorsIndex,Warehouse.providersIndex三个集合中。
6. ARouter究竟是如何相互跳转的呢?
-
继续回到_ARouter中的navigation()
PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class); if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) { // Pretreatment failed, navigation canceled. return null; }
/** * Build postcard by serviceName * * @param serviceName interfaceName * @return postcard */ public static Postcard buildProvider(String serviceName) { RouteMeta meta = Warehouse.providersIndex.get(serviceName); if (null == meta) { return null; } else { return new Postcard(meta.getPath(), meta.getGroup()); } }
通过上面的源码,我们知道,程序会在Warehouse.providersIndex里去查找是否有PretreatmentService这个类,上面Application的ARouter.init(this);中的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); } }
通过这里知道,扫描的是com.alibaba.android.arouter.routes.ARouter$$Providers中的文件内容,这个里面的内容不一定会有,我看了下我程序,这里的内容是空的
/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */ public class ARouter$$Providers$$app implements IProviderGroup { @Override public void loadInto(Map<String, RouteMeta> providers) { } }
-
navigation
protected <T> T navigation(Class<? extends T> service) { try { Postcard postcard = LogisticsCenter.buildProvider(service.getName()); // Compatible 1.0.5 compiler sdk. // Earlier versions did not use the fully qualified name to get the service if (null == postcard) { // No service, or this service in old version. postcard = LogisticsCenter.buildProvider(service.getSimpleName()); } if (null == postcard) { return null; } LogisticsCenter.completion(postcard); return (T) postcard.getProvider(); } catch (NoRouteFoundException ex) { logger.warning(Consts.TAG, ex.getMessage()); return null; } }
也就是说:Postcard postcard = LogisticsCenter.buildProvider(service.getName());得到的内是null
而通过不带路径的name,下面代码,得到的postcard也是null
if (null == postcard) { // No service, or this service in old version. postcard = LogisticsCenter.buildProvider(service.getSimpleName()); }
最终获得的pretreatmentService为null,虽然还不太清楚它是干嘛用的,但程序可以继续往下走,
LogisticsCenter.completion(postcard);
-
LogisticsCenter.completion(postcard);
/** * Completion the postcard by route metas * * @param postcard Incomplete postcard, should complete by this method. */ public synchronized static void completion(Postcard postcard) { if (null == postcard) { throw new NoRouteFoundException(TAG + "No postcard!"); } 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<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; } } }
- 首先通过映射的对象找到RouteMeta
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
这里先关心存在的情况;代码解读下去,发现没敢啥正经是,completion做的事情,就是将RouteMeta的属性值赋值到Postcard中。
-
跳转的关键代码
实现Activity跳转的关键代码如下:
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; }
postcard.getType()为ACTIVITY表示的是activity间的跳转,从com.alibaba.android.arouter.routes包下的java文件可以知道这个。
atlas.put("/weather/detail", RouteMeta.build(RouteType.ACTIVITY, WeatherDetailActivity.class, "/weather/detail", "weather", new java.util.HashMap<String, Integer>(){{put("lng", 7); put("weatherCode", 8); put("lat", 7); }}, -1, -2147483648)); -------------------------------------分割线----------------------------------- final Intent intent = new Intent(currentContext, postcard.getDestination());
postcard.getDestination()中保存的就是WeatherDetailActivity.class。
7. 结论
经过上面的步骤,基本是清楚了ARouter的整个流程。
1.在编译的时候,通过apt工具,将需要Autowired,Interceptor,Route等注解的类进行编译生成新文件。
以@Route为例,这了会生成ARouter需要的RouteMeta,RouteMeta中就会包含了跳转需要的路径,注解的type类型(包含Activity,Service,Fragment,ContentProvider等),以及路径对应映射的class类;
2.Application中会将ARouter进行初始化,而初始化做的事情就是扫描编译生成的java文件,并保存到Set集合中;
3.最后一步就是更加映射进行跳转了。