Arouter初始化过程

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中。

你可能感兴趣的:(Arouter初始化过程)