Arouter初始化过程
Arouter初始化入口是Arouter类中的init方法,但Arouter类只是个代理类,而被代理的是和它同包下的_Arouter类,看下_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类把初始化工作转交给了LogisticsCenter的init方法,该方法主要做了2件事:
1.获取指定包名的类文件(编译期部分自动生成的类,不包括AutowiredProcessor生成的)。
2.将上述获取的类文件按条件缓存起来(缓存到Warehouse类中)
先分析第一步,截取LogisticsCenter的init方法的部分代码:
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
Set routerMap; //存储类文件的集合
// It will rebuild router map every times when debuggable.
//第一次安装isNewVersion返回的是true,之后会缓存当前的versionName和versionCode
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
//ROUTE_ROOT_PAKCAGE的值是com.alibaba.android.arouter.routes
//该方法是获取指定包名下的所有类文件
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 finish.
} else {
//版本没更新,且非Debug模式下,直接读取缓存的类文件集合
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet()));
}
//...省略其他代码
} catch (Exception e) {
//...
}
//...
}
继续看ClassUtils的getFileNameByPackageName方法:
/**
* 通过指定包名,扫描包下面包含的所有的ClassName
*
* @param context U know
* @param packageName 包名
* @return 所有class的集合
*/
public static Set getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException {
final Set classNames = new HashSet<>();
//getSourcePaths方法的主要作用是获取apk所有dex包的路径
List paths = getSourcePaths(context);
//CountDownLatch保证了只有所有的子线程都运行结束后才能继续运行,构造时传入子线程的数量,这里传传的是dex包的数量,即一个dex包起一个子线程去加载里面的类
final CountDownLatch parserCtl = new CountDownLatch(paths.size());
for (final String path : paths) {
//这里获取的线程池的最大线程数量是cpu个数+1
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 dexEntries = dexfile.entries();
while (dexEntries.hasMoreElements()) {
String className = dexEntries.nextElement();
//只有符合条件的类才被加入到集合中
//包名是com.alibaba.android.arouter.routes下的类
//不包含AutowiredProcessor生成的类(不在此包下)
if (className.startsWith(packageName)) {
classNames.add(className);
}
}
} catch (Throwable ignore) {
//...
} finally {
if (null != dexfile) {
try {
dexfile.close();
} catch (Throwable ignore) {
}
}
//标记一个线程结束
parserCtl.countDown();
}
}
});
}
//阻塞,直到所有子线程都跑完
parserCtl.await();
return classNames;
}
public static List getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException {
ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0);
File sourceApk = new File(applicationInfo.sourceDir);
List 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配置的
// 通过vm的版本来判断是否支持MultiDex,vm版本大于等于2.1.0则支持(其中第一位是major版本,2表示ART,1表示Dalvik,第二位表示minor版本,小版本),否则不支持,需要额外去加载。
// MultiDex支持的判断以及加载的方法,Arouter参考了Android原生MultiDex包下的相关代码,有兴趣的同学可以自己研究下
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;
}
拿到符合条件的类集合后,执行第二步,按条件将这些信息缓存到Warehouse中,先看看Warehouse类的结构:
class Warehouse {
//缓存路由分组列表,既作为 ARouter$$Root$$app类中,loadInto的入参
static Map> groupsIndex = new HashMap<>();
//根据path缓存某个具体路由节点
static Map routes = new HashMap<>();
static Map providers = new HashMap<>();
//缓存provider类型的节点,ARouter$$Providers$$app类中,loadInto的入参
static Map providersIndex = new HashMap<>();
//用于缓存拦截器,ARouter$$Interceptors$$app中loadInto方法的入参
static Map> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
static List interceptors = new ArrayList<>();
static void clear() {
routes.clear();
groupsIndex.clear();
providers.clear();
providersIndex.clear();
interceptors.clear();
interceptorsIndex.clear();
}
}
第二步的关键代码如下:
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
// 类名以com.alibaba.android.arouter.routes.ARouter$$Root打头
// 就是加载路由分组列表的类
// 把所有分组缓存在Warehouse的groupsIndex中
// 只缓存了分组,并没有加载所有的路由节点,具体的路由节点是在真正跳转的时候才会加载该节点所在的group里所有的路由节点,也就是会一次性加载group下的所有节点
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// 类名以com.alibaba.android.arouter.routes.ARouter$$Interceptors打头
// 加载所有的拦截器,缓存在Warehouse的interceptorsIndex中
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// 类名以com.alibaba.android.arouter.routes.ARouter$$Providers打头
// 加载Providers类型的节点,缓存在Warehouse的providersIndex中
// value是以RouteMeta的形式,真正调用时,会以RouteMeta的信息转换成IProvider类行的服务接口
// 不止IProvider类型的节点,所有的路由节点都是以RouteMeta为载体的
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
初始化的工作并复杂,最核心的就是加载了路由分组列表,拦截器,以及provider类型的路由节点,每个group里的节点列表是在某个节点被调用时,一次性加载其所在group里的所有节点,然后缓存在Warehouse的routes中。