我是如何阅读ARouter源码的

以下是我阅读源码的过程中遇到的问题,以及是如何分析和解决的,文章比较长,贴了很多代码,希望能帮到看文章的人。看之前建议先下载源码运行,地址:https://github.com/alibaba/ARouter

问题

要想了解ARouter的原理,说白了就是需要知道,Activity是如何跳转的?

带着问题去找答案;

从程序引用中找答案,核心代码如下:

ARouter.getInstance().build(ARouterConstants.SPLASH_GUIDE).navigation();

这句代码就是跳转的精髓所在,所以先要吃透这行代码。

解析

1. 初始化
ARouter.getInstance()
2. 构建
.build(String path)方法

源码解析:

  1. 创建一个Postcard对象
 /**
     * Build the roadmap, draw a postcard.
     *
     * @param path Where you go.
     */
    public Postcard build(String path) {
        return _ARouter.getInstance().build(path);
    }
  1. 在 _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));
        }
    }
  1. 要走下去,这里有一个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)没看,所以并不知道这里的数据情况)。

  1. 先假设这里是空的,那么回到第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对象。

  1. 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;

  2. 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”,所以这个的目的也就清楚了,就是跳转导航,那么接下来看看,它是如何做到跳转的呢?

  3. 最终的跳转的实现还是通过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。

  4. 跳转到_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),为了了解预加载做了什么事情。

  1. 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();做的事情就是预处理的事情。

  2. 分析_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);来完成

  3. 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>
    
  4. 继续阅读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。

  5. 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)) {}
    
  6. 前面我们拿到了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)
  1. 将地址和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究竟是如何相互跳转的呢?
  1. 继续回到_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) {
      }
    }
    
  2. 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);
    
  3. 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;
                }
            }
        }
    
    1. 首先通过映射的对象找到RouteMeta
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    

    这里先关心存在的情况;代码解读下去,发现没敢啥正经是,completion做的事情,就是将RouteMeta的属性值赋值到Postcard中。

  4. 跳转的关键代码

    实现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.最后一步就是更加映射进行跳转了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值