美文网首页
Arouter初始化过程

Arouter初始化过程

作者: 呐喊的码农 | 来源:发表于2018-03-27 14:29 被阅读0次

    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<String> 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<String>()));
            }       
          //...省略其他代码
        } catch (Exception e) {
          //...
        }
        //...
    }
    

    继续看ClassUtils的getFileNameByPackageName方法:

    /**
         * 通过指定包名,扫描包下面包含的所有的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<>();
    
            //getSourcePaths方法的主要作用是获取apk所有dex包的路径
            List<String> 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<String> 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<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配置的
          // 通过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<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
        //根据path缓存某个具体路由节点
        static Map<String, RouteMeta> routes = new HashMap<>();
    
        
        static Map<Class, IProvider> providers = new HashMap<>();
        //缓存provider类型的节点,ARouter$$Providers$$app类中,loadInto的入参
        static Map<String, RouteMeta> providersIndex = new HashMap<>();
    
        //用于缓存拦截器,ARouter$$Interceptors$$app中loadInto方法的入参
        static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
        static List<IInterceptor> 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初始化过程

          本文链接:https://www.haomeiwen.com/subject/buxscftx.html