ARouter原理分析

简单使用
	//1 初始化
    if (BuildConfig.DEBUG) {           
        ARouter.openLog();     
        ARouter.openDebug();   
    }
    ARouter.init(this);	

	//2 配置需要路由的目标
	@Route(path = Constant.MAIN2)
	public class Main2Activity extends AppCompatActivity {}
	@Route(path = Constant.PROVIDER1)
	public class Main1ProviderService implements IProvider {}

	//3 开始路由
	 ARouter.getInstance().build(Constant.MAIN2).navigation();
	 Main1ProviderService service = ARouter.getInstance().navigation(Main1ProviderService.class);
  • 代码 //1 中初始化,相对比较重要,这里会将我们配置生成的,指定目录下的路由文件类名先加载进内存(例如:ARouter&&Group&&main,group代表路由组文件,其中&实为$,csdn markdown转义字符好像无效,用&代替下)。这里涉及到io操作(原理下面会进行解释),所以建议将初始化放在子线程中加载。
  • //2 为配置路由,//3 跳转路由。
  • IProvider 属于ARouter中的一种范式,用于暴露给模块使用的服务的一种规范。组件间通讯当使用 IProvider 作为接口暴露给对方使用。并且IProvider的接口定义应放在公共组件中,方便各个组件拿到服务。IProvider主要用于组件间通讯,隐藏各个服务的实现细节(IProvider 属于单例创建,使用的时候需要特别注意)。我们获取Provider有两种方式,一个是使用Provider的全类名,一个是通过path。
ARouter.init()解析
    public static void init(Application application) {
        if (!hasInit) {
            logger = _ARouter.logger;
            _ARouter.logger.info(Consts.TAG, "ARouter init start.");
            hasInit = _ARouter.init(application);//1

            if (hasInit) {
                _ARouter.afterInit();
            }

            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
    }
    
    protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor);//2
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;
        mHandler = new Handler(Looper.getMainLooper());

        return true;
    }
    //3
	 private volatile static ThreadPoolExecutor executor = DefaultPoolExecutor.getInstance();
  • //1 将初始化动作交给_ARouter.init()处理。hasInit 为初始化成功的标志,下面ARouter跳转会根据这个标志相应抛出RuntimeException,所以 ARouter 的 navigation 动作必须放到ARouter::init之后执行。
  • //2 LogisticsCenter.init(mContext, executor) 字面意思物流中心初始化。这里将executor线程池传递进去。
  • //3 这里我们可以看到这个线程池有一个默认的初始值(如何配置就不继续跟了),当然也可以我们自己配置。在初始化仓库之前配置 ARouter::setExecutor();就可以了
    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(); //仅仅将registerByPlugin赋值为false
            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);//1 获取指定包下面的所以ClassName。
                    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);//2
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                        // Load interceptorMeta
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);//3
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                        // Load providerIndex
                        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);//4
                    }
                }
            }
   			logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");
			...
			
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }

这里主要干了两件事

  • 获取 routerMap 从sp中获取信息,如果获取不到则执行 //1 处将信息加载进map中
  • 将routerMap中的
    • //2 IRouteRoot 配置加载进 groupsIndex 。groupsIndex 掌握一些组的信息 ,路由会根据所有的path生成对应的ARouter&&Group&&xx 文件(xx为模块名)。
    • //3 IInterceptorGroup配置加载进 interceptorsIndex
    • //4 IProviderGroup 配置加载进 providersIndex 。这里将IProvider接口生成的Provider class文件的全量信息,直接干进providersIndex中。之后navigation(class)方法会从providersIndex 找对应的类信息。
	//组信息,用于做懒加载处理
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    //真实的路由信息,懒加载后的RouteMeta都会放在这里(key为path)
    static Map<String, RouteMeta> routes = new HashMap<>();

    // Cache provider
    //懒加载缓存,根据routes或者providersIndex提供的Provider的class信息。创建IProvider放到这个集合中(只创建一次)
    static Map<Class, IProvider> providers = new HashMap<>();
    //存放IProvider的class信息(key是存放类名)
    static Map<String, RouteMeta> providersIndex = new HashMap<>();

上面属于 Warehouse.java中的信息。理解这些静态常量有利于我们分析,之后的整个Router过程。
接下来我们继续进行上面的加载生成的文件 //1处分析

    /**
     * 通过指定包名,扫描包下面包含的所有的ClassName
     * packageName为 com.alibaba.android.arouter.routes
     */
    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);//获取全部的dex path
        final CountDownLatch parserCtl = new CountDownLatch(paths.size());//1

        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();//2
                    }
                }
            });
        }

        parserCtl.await();//3

        Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
        return classNames;
    }

大概解释这段代码的逻辑:这里通过getSourcePaths()来获取全部的dex path。然后为每一个path分配一个任务(Runnable)丢入线程池中执行。直到整个线程池执行完毕将classNames返回。

细节://1处的 CountDownLatch 为java并发包提供等待多线程完成的解决方案。首先根据path长度创建 count 数以及相同数量的任务。每一个线程执行完成会调用 //2处的方法将 count-- 。//3处为阻塞出口。直到 parserCtl 里面的 count 为 0 时,当前线程才会被唤醒继续执行。

init小结: init主要操作就是将路由需要的所以Class信息全部加载进 Warehouse 仓库的静态变量中。而获取全部 Class 信息,是使用固定包名全量搜索注解处理器生成的文件。并通过反射创建注解处理器生成类的实例,执行其中的方法将路由类信息存放到 groupsIndexprovidersIndex 中。而全量搜索为了实现同步初始化,用到了线程阻塞技术。但是在android主线程中,最好不要阻塞主线程。所以我建议将ARouter.init()方法放到子线程。如果需要知道ARouter是否初始化完成。可以在init(init方法为同步执行)的方法之后设置一个 boolean 变量或者监听,来获取初始化是否完成的状态。


有了上面对初始化工作的了解,接下来我们继续对ARouter的navigation进行分析。

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

在对路由跳转分析之前我们首先需要搞清楚5个东西:

  • Postcard 我理解为相当于弱化的 Intent 对象和Context对象的结合体(内部存储一些路由相关的操作和信息)
  • RouteMeta为Postcard 的父类,主要用于存储路由信息,子类 Postcard 暴露给用户,提供路由方法
  • _ARouter 为 ARouter的代理类
  • LogisticsCenter 物流中心,属于一个工具类,内部实现了路由信息的加载,缓存,初始化等方法。
  • Warehouse 仓库 groupsIndex 存放路由组的class信息
  • Warehouse 仓库 routes 存放初始化完成后的真实路由信息。

下面开始分析

//1
  public static ARouter getInstance() {
        if (!hasInit) {
            throw new InitException("ARouter::Init::Invoke init(context) first!");
        } else {
            if (instance == null) {
                synchronized (ARouter.class) {
                    if (instance == null) {
                        instance = new ARouter();
                    }
                }
            }
            return instance;
        }
    }
    //2
    public Postcard build(String path) {
        return _ARouter.getInstance().build(path);
    }
    //3
     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 使用DCL获取ARouter唯一实例。
//2 build()方法会交给_ARouter实现,_ARouter.getInstance()依然使用DCL初始化。
//3 又继续将build传递给 build(path, extractGroup(path));执行。

	//1
    private String extractGroup(String path) {
        if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
            throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!");
        }

        try {
            String defaultGroup = path.substring(1, path.indexOf("/", 1));
            if (TextUtils.isEmpty(defaultGroup)) {
                throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");
            } else {
                return defaultGroup;
            }
        } catch (Exception e) {
            logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage());
            return null;
        }
    }
    //2
    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);
        }
    }

//1 将path中的group字符串切出来
//2 根据path和group创建路由所需要的 Postcard 对象。
回到上面接下来会调用Postcard::navigation()方法。

    public Object navigation(Context context, NavigationCallback callback) {
        return ARouter.getInstance().navigation(context, this, -1, callback);
    }

Postcard::navigation()方法最终又会去调用ARouter::navigation()方法,而ARouter会交给_ARouter实现。

    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);//1 完成跳转前的准备工作
        } 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);//2
        }

        return null;
    }

//1 处主要做postcard路由信息的准备工作,准备的成功与否是判断路由成功与否的标志。
//2 处将准备好的数据执行_navigation()完成真正的路由跳转。
我们先看//1处的代码

    public synchronized static void completion(Postcard postcard) {
        if (null == postcard) {
            throw new NoRouteFoundException(TAG + "No postcard!");
        }

        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());//1
        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;
            }
        }
    }

解释一下上面这一段的执行逻辑:
首先通过 Warehouse.routes.get(postcard.getPath());去缓存中寻找,当前path对应的路由是否被初始化过,如果没有,会从Warehouse.groupsIndex信息。将当前组的路由全部加载完成后,再把groupsIndex里面对应的group移除掉。然后递归调用当前方法。
此时routers里面,已经用于当前group的所有route信息了。然后postcard需要携带的参数全部复制给他。需要注意的是,这里也对 IProvider 进行了初始化,并将初始化完成的 IProvider 放入到 Warehouse.providers容器中,并赋值给Postcard的provider变量,方便navigation尾声时直接从携带参数中获取。从这里也可以看出 IProvider 实则为单例模式存放于内存中,我们使用的时候直接会从Warehouse.providers去取就行了。
分析完postcard的准备工作我们继续看//2处 _ARouter::_navigation()方法

  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;
    }

看到这里我们ARouter的执行就到了尾声,如果type为

  • ACTIVITY就startActivity 返回值为null
  • FRAGMENT就创建Fragment 返回值为Fragment
  • PROVIDER直接往去拿provider中的唯一实例。 返回值为IProvider
    至于type怎么赋值的,它会在注解处理器生成group文件时,会知道自己的type到底是什么,并赋值。

这里缺少对注解处理器的分析,其实我觉得完全没有必要,注解处理器只是一个工具,当我们知道他的运行原理,知道注解处理器生成的文件模板,我们完全可以自己写一套注解处理的实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值