汇总篇章:
简介
组件化被越来越多的Android项目采用,而作为组件化的基础——路由也是重中之重,如果说组件化是肢体,那么路由就是缝合各个组件的筋脉,废话不多说,既然重中之重,那么从源码层次了解ARouter的设计,以及借鉴ARouter的设计来设计自己组件中的ARouter
源码总线
ARouter的源码分析的分析路线,就顺着使用顺序分成:
- 第一步:添加页面注解
- 第二步:初始化ARouter的SDK
- 第三步:发起路由操作
- 第四步:跳转过程监听和参数解析
,来逐步对ARouter分析
依赖这里就附上官网链接:https://github.com/alibaba/ARouter/blob/master/README_CN.md
第一步:添加页面注解
// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
这一步,由于这步处理依赖基础配置等,故我们在第四步的参数解析里面展开处理
第二步:初始化ARouter的SDK
ARouter.init(mApplication);
我们先从ARouter的初始化方法init这个入口开始分析ARouter的分析之旅
/**
* Init, it must be call before used router.
*/
public static void init(Application application) {
/**
* hasInit避免多次初始化
*/
if (!hasInit) {
logger = _ARouter.logger;
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
/**
* 初始化_ARouter实际实现类
*/
hasInit = _ARouter.init(application);
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}
#####实现类_ARouter的init方法
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
return true;
}
在_ARouter的init方法中:
1) 对上下文的处理
2) 提及LogisticsCenter的init的初始化操作
3) 打印log,初始化标记维护
对于LogisticsCenter这个类是个非常重要的类,LogisticsCenter的功能分为3大功能:
1. 单例处理
2. 处理关系映射
3. 解决重复定义组逻辑
我们顺着init方法继续分析,由于init方法过长,这里拆分局部分析:
首先进入先分析第一个方法loadRouterMap,从名字上就能感觉这里是加载router映射的方法,但是进入loadRouterMap方法中
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());
}
这个方法体是空的,但是从注释上我们了解到这个方法是自动注册插件生成代码使用的, 调用这个方法的目的是注册所有路由器、侦听器等,这块我们方法后面的插件代码分析的篇章来详细描述这个方法
接下来我们看到方法维护了一个名为routerMap的Set集合,我们根据条件来逐步分析
-
ARouter的debugger情况下,对routerMap的填充
从方法上对routerMap的填充放在ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
在ClassUtils的getFileNameByPackageName的方法中:
首先通过getSourcePaths获取路径信息,getSourcePaths方法如下: public static List<String> getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException { /** * 首先通过PackageManager获取applicationinfo信息 * 并记录applicationinfo的sourceDir */ ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0); File sourceApk = new File(applicationInfo.sourceDir); List<String> sourcePaths = new ArrayList<>(); /** * 添加默认的apk路径 */ sourcePaths.add(applicationInfo.sourceDir); //提取class文件的前缀信息 String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; // 如果VM已经支持了MultiDex,就不要去Secondary Folder加载 Classesx.zip了,那里已经么有了 // 通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的 if (!isVMMultidexCapable()) { //总dex数量 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()) { /** * 将所有的apk路径 */ sourcePaths.add(extractedFile.getAbsolutePath()); } else { throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'"); } } } if (ARouter.debuggable()) { /** * 如果是ARouter处于debugger情况下,这块是尝试加载InstantRun的文件路径 */ sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo)); } return sourcePaths; } tryLoadInstantRunDexFile方法如下: private static List<String> tryLoadInstantRunDexFile(ApplicationInfo applicationInfo) { List<String> instantRunSourcePaths = new ArrayList<>(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && null != applicationInfo.splitSourceDirs) { /** * 新版本中InstantRun通过applicationInfo.splitSourceDirs直接添加 */ instantRunSourcePaths.addAll(Arrays.asList(applicationInfo.splitSourceDirs)); Log.d(Consts.TAG, "Found InstantRun support"); } else { try { /** * 这个是google的instant run的sdk中获取dex的files路径 */ Class pathsByInstantRun = Class.forName("com.android.tools.fd.runtime.Paths"); Method getDexFileDirectory = pathsByInstantRun.getMethod("getDexFileDirectory", String.class); String instantRunDexPath = (String) getDexFileDirectory.invoke(null, applicationInfo.packageName); File instantRunFilePath = new File(instantRunDexPath); if (instantRunFilePath.exists() && instantRunFilePath.isDirectory()) { File[] dexFile = instantRunFilePath.listFiles(); for (File file : dexFile) { if (null != file && file.exists() && file.isFile() && file.getName().endsWith(".dex")) { instantRunSourcePaths.add(file.getAbsolutePath()); } } } } catch (Exception e) { Log.e(Consts.TAG, "InstantRun support error, " + e.getMessage()); } } return instantRunSourcePaths; }
获取了所有的path之后,在getFileNameByPackageName接着起了一个线程
在这个线程池中,通过DexFile对class加载,然后所有classname放起来,返回给我们routerMap,到这里routerMap中存放的就是我们所有classname
-
接着我们看看非debugger情况下ARouter的处理
首先从sharedPreference名为
SP_AROUTER_CACHE
中读取routerMap的本地缓存并初始化我们容器routerMap接着对routerMap进行分类处理
这里的部分,我们在后面的插件部分详细解释 第一类情况: com.alibaba.android.arouter.routes.ARouter$$Root 启动RouteRoot核心类 第二类情况: com.alibaba.android.arouter.routes.ARouter$$Interceptors 加载相关interceptor的数据 第三类情况: com.alibaba.android.arouter.routes.ARouter$$Providers 加载相关service的数据
由于篇幅缘故,故剩下的两步我们拆成新篇章处理