android ARouter源码分析

2023-11-11

背景:

随着项目越做越大,代码量越来越多,项目也随之改造成组件化的开发模式,组件化开发非常适合庞大的项目,将每个业务模块,功能模块解耦,抽离成组件的形式,各个组件遵循严格的依赖关系。因为这层严格的依赖关系,使得组件化比模块化结构更加简洁和清晰,以前的模块化开发,模块间常常相互依赖,形成闭环,改动模块的代码会牵一发而动全身,稳定性和可维护性就显得特别弱小。

组件化严格控制组件间的依赖关系,组件间相互依赖是不允许存在的,所以我们需要一个框架来解决组件间的通信的问题。

ARouter的几个常用功能:

1.组件间的页面跳转。

2.依赖注入

3.拦截器。

关于ARouter如何实现上面三个功能的?

ARouter源码主要由三个模块组成:

ARouter-compiler: 这个模块主要用于解析注解,然后生成路由业务代码的java文件,主要需要阅读的源码文件是

RouterProcessor

InterceptorProcessor

AutowiredProcessor

ARouter-api:这个模块主要暴露一些接口和方法给业务层调用,还封装了一个线程池和一些异常等等,建议需要阅读的源码文件

thread包下的文件,理解为什么需要用到线程池

launcher包下的文件,理解初始化过程中做了哪些工作

Warehouse

LogisticsCenter

InterceptorServiceImpl

ARouter-annotation:定义了一些注解。

先看下页面跳转是如何实现的:

// 页面跳转
ARouter.getInstance().build("/test/activity").navigation();
/**
     * Build postcard by path and group
     */
    protected Postcard build(String path, String group, Boolean afterReplace) {
        if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            if (!afterReplace) {
                PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
                if (null != pService) {
                    path = pService.forString(path);
                }
            }
            return new Postcard(path, group);
        }
    }
/**
     * 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;
        }

        // Set context to postcard.
        postcard.setContext(null == context ? mContext : context);

        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(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(postcard, requestCode, callback);
        }

        return null;
    }

build方法传入path和group(可不传),最后new一个postcard对象,Postcard对象 可以理解为一个封装了页面跳转所需要的内容,它包含页面跳转的参数,动画,context等信息。

在navigation方法里面实现页面跳转的代码是

 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.
            if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
                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()));
                    }

                    addRouteGroupDynamic(postcard.getGroup(), null);

                    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) {
                            logger.error(TAG, "Init provider failed!", e);
                            throw new HandlerException("Init provider failed!");
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }
        }
    }

我们看到代码会从Warehouse类里面获取RouteMeta对象,如果为null,表示根据路由名找不到路由的目标页面,这个时候,做了一次对group的判断处理.

group是干嘛的?可以理解成分组的意思,因为各个组件交给各个人维护,所以很容易出现路由同名的情况,比如个人中心组件定义了相册选择页面的路由地址,购物车组件也有相册选择页面的路由地址,所以group的存在是为了做细分。

除了细分的作用以外,还可以减少内存的消耗,Warehouse.routes是一个static的map对象,如果一次性把所有路由信息全存放里面,有可能造成内存溢出。所以Group的第二个作用就是按组加载路由表。

根据group的特性不难理解为什么这里有个递归方法  

completion(postcard);   // Reload

不知道有没有人跟我一样疑问,这里不会死循环吗?

public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        if (Warehouse.groupsIndex.containsKey(groupName)){
            // If this group is included, but it has not been loaded
            // load this group first, because dynamic route has high priority.
            Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
            Warehouse.groupsIndex.remove(groupName);
        }

        // cover old group.
        if (null != group) {
            group.loadInto(Warehouse.routes);
        }
    }

答案是不会,因为当某个group的路由表信息加载到内存的时候,这个group会从groupsIndex里面删除。

如果RouteMeta不为null,解析uri的相关参数,结合RouteMeta的路由信息设置Postcard的参数。然后调用

_navigation(postcard, requestCode, callback);
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = postcard.getContext();

        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 (0 != flags) {
                    intent.setFlags(flags);
                }

                // Non activity, need FLAG_ACTIVITY_NEW_TASK
                if (!(currentContext instanceof Activity)) {
                    intent.addFlags(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;
    }

最后在上面这个方法里面调用了startActvity方法实现跳转。

整个跳转过程中,离不开路由表,接下来分析下路由表的构建流程,即Warehouse类的初始化工作。拦截器的和provider的相关代码先忽略。

class Warehouse {
    // Cache route and metas
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    static Map<String, RouteMeta> routes = new HashMap<>();
}

路由表的构建在初始化阶段。_ARouter的init方法会调用LogisticsCenter.init(mContext, executor);方法

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
            long startInit = System.currentTimeMillis();
            //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() + "]");
        }
    }

我们看到初始化阶段,如果verson非新版本,那么会遍历所有dex文件,找出dex文件里面的ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes"的class。

如果已经是新版本了,就从shareprefrence里面读取了缓存,然后根据反射去创建了Class对象,然后将创建好的Group表放到内存中。

这时候又出现新的问题com.alibaba.android.arouter.routes是什么?

从上面的分析,我们知道所有group的class文件都在该package下里面,group的class文件都需要实现IRouteGroup的接口,当我们去看哪些class实现了这些接口的时候,发现都在build目录下。如下图所示。

显而易见,这个class是在编译阶段自动生成的,所以ARouter框架使用了AOP的编程模式。

我们接下来去compiler模块里面看下RouterProcessor类。

@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (CollectionUtils.isNotEmpty(annotations)) {
            Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
            try {
                logger.info(">>> Found routes, start... <<<");
                this.parseRoutes(routeElements);

            } catch (Exception e) {
                logger.error(e);
            }
            return true;
        }

        return false;
    }

先找到Route注解的Elements,然后解析。

解析的代码比较多,这里分批贴代码分析

//  Follow a sequence, find out metas of group first, generate java file, then statistics them as root.
            for (Element element : routeElements) {
                TypeMirror tm = element.asType();
                Route route = element.getAnnotation(Route.class);
                RouteMeta routeMeta;

                // Activity or Fragment
                if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
                    // Get all fields annotation by @Autowired
                    Map<String, Integer> paramsType = new HashMap<>();
                    Map<String, Autowired> injectConfig = new HashMap<>();
                    injectParamCollector(element, paramsType, injectConfig);

                    if (types.isSubtype(tm, type_Activity)) {
                        // Activity
                        logger.info(">>> Found activity route: " + tm.toString() + " <<<");
                        routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                    } else {
                        // Fragment
                        logger.info(">>> Found fragment route: " + tm.toString() + " <<<");
                        routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
                    }

                    routeMeta.setInjectConfig(injectConfig);
                } else if (types.isSubtype(tm, iProvider)) {         // IProvider
                    logger.info(">>> Found provider route: " + tm.toString() + " <<<");
                    routeMeta = new RouteMeta(route, element, RouteType.PROVIDER, null);
                } else if (types.isSubtype(tm, type_Service)) {           // Service
                    logger.info(">>> Found service route: " + tm.toString() + " <<<");
                    routeMeta = new RouteMeta(route, element, RouteType.parse(SERVICE), null);
                } else {
                    throw new RuntimeException("The @Route is marked on unsupported class, look at [" + tm.toString() + "].");
                }

                categories(routeMeta);
            }

这里遍历了整个Elements,然后生成对应的RouteMeta。

最后的categories(routeMeta);会对生成的routeMeta做检验,只有合法的routeMeta才会被存入GroupMap里面。path不能为空且path必须是"/"开头才算合法。

然后是通过代码生成java文件,具体生成规则在下面的代码,关键部分补充了注释了。

/** 遍历groupMap **/
            for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
                String groupName = entry.getKey();
                /** 创建void loadInto(Map<String, RouteMeta> atlas);方法对象 **/
                MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                        .addAnnotation(Override.class)     //注解
                        .addModifiers(PUBLIC)              //方法访问属性
                        .addParameter(groupParamSpec);     //方法参数

                List<RouteDoc> routeDocList = new ArrayList<>();

                // Build group method body
                Set<RouteMeta> groupData = entry.getValue();
                /** 遍历每个groupMap下的routeMeta **/
                for (RouteMeta routeMeta : groupData) {
                    RouteDoc routeDoc = extractDocInfo(routeMeta);

                    ClassName className = ClassName.get((TypeElement) routeMeta.getRawType());

                    switch (routeMeta.getType()) {
                        case PROVIDER:  // Need cache provider's super class
                            List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();
                            for (TypeMirror tm : interfaces) {
                                routeDoc.addPrototype(tm.toString());

                                if (types.isSameType(tm, iProvider)) {   // Its implements iProvider interface himself.
                                    // This interface extend the IProvider, so it can be used for mark provider
                                    loadIntoMethodOfProviderBuilder.addStatement(
                                            "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                            (routeMeta.getRawType()).toString(),
                                            routeMetaCn,
                                            routeTypeCn,
                                            className,
                                            routeMeta.getPath(),
                                            routeMeta.getGroup());
                                } else if (types.isSubtype(tm, iProvider)) {
                                    // This interface extend the IProvider, so it can be used for mark provider
                                    loadIntoMethodOfProviderBuilder.addStatement(
                                            "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                            tm.toString(),    // So stupid, will duplicate only save class name.
                                            routeMetaCn,
                                            routeTypeCn,
                                            className,
                                            routeMeta.getPath(),
                                            routeMeta.getGroup());
                                }
                            }
                            break;
                        default:
                            break;
                    }

                    // Make map body for paramsType
                    StringBuilder mapBodyBuilder = new StringBuilder();
                    Map<String, Integer> paramsType = routeMeta.getParamsType();
                    Map<String, Autowired> injectConfigs = routeMeta.getInjectConfig();
                    if (MapUtils.isNotEmpty(paramsType)) {
                        List<RouteDoc.Param> paramList = new ArrayList<>();
                        
                        for (Map.Entry<String, Integer> types : paramsType.entrySet()) {
                            mapBodyBuilder.append("put(\"").append(types.getKey()).append("\", ").append(types.getValue()).append("); ");

                            RouteDoc.Param param = new RouteDoc.Param();
                            Autowired injectConfig = injectConfigs.get(types.getKey());
                            param.setKey(types.getKey());
                            param.setType(TypeKind.values()[types.getValue()].name().toLowerCase());
                            param.setDescription(injectConfig.desc());
                            param.setRequired(injectConfig.required());

                            paramList.add(param);
                        }

                        routeDoc.setParams(paramList);
                    }
                    String mapBody = mapBodyBuilder.toString();

                    /**
                     * loadInto方法里面加入具体的代码实现
                     * 比如atlas.put("/module/2", RouteMeta.build(RouteType.ACTIVITY, TestModule2Activity.class, "/module/2", "m2", null, -1, -2147483648));
                     * **/
                    loadIntoMethodOfGroupBuilder.addStatement(
                            "atlas.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, " + (StringUtils.isEmpty(mapBody) ? null : ("new java.util.HashMap<String, Integer>(){{" + mapBodyBuilder.toString() + "}}")) + ", " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                            routeMeta.getPath(),
                            routeMetaCn,
                            routeTypeCn,
                            className,
                            routeMeta.getPath().toLowerCase(),
                            routeMeta.getGroup().toLowerCase());

                    routeDoc.setClassName(className.toString());
                    routeDocList.add(routeDoc);
                }

                // Generate groups
                /**生成java文件**/
                String groupFileName = NAME_OF_GROUP + groupName;
                JavaFile.builder(PACKAGE_OF_GENERATE_FILE,  //指定java文件的包路径
                        TypeSpec.classBuilder(groupFileName)  //java文件名
                                .addJavadoc(WARNING_TIPS)  //java注解
                                .addSuperinterface(ClassName.get(type_IRouteGroup))  //继承的接口
                                .addModifiers(PUBLIC)   //类属性 公有
                                .addMethod(loadIntoMethodOfGroupBuilder.build())  //加入方法
                                .build()
                ).build().writeTo(mFiler);

                logger.info(">>> Generated group: " + groupName + "<<<");
                rootMap.put(groupName, groupFileName);  //将组名和java文件名做映射关系
                docSource.put(groupName, routeDocList);  
            }

最后在生成root的java文件。

// Write root meta into disk.
            String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(rootFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT)))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfRootBuilder.build())
                            .build()
            ).build().writeTo(mFiler);

AOP阶段路由表的生成规则总结:

1.解析注解得到带有Route注解相关的Elements。

2.遍历所有Elements并解析Route注解的参数,生成对应的RouteMeta数据 ,并插入到groupMap中

3.遍历groupMap,将每个groupName下的所有的RouteMeta生成对应的java代码,然后写入该java文件到内存。此时一个groupName对应一个java文件。

4.并将每个groupName和生成的java文件做映射放在rootMap中。

5.最后再生成对应的root的java文件,该文件主要用于初始化的时候将groupName和java文件的映射关系加载进内存。

到这里,整个页面路由的逻辑都已经理清楚了。

接下来看下依赖注入实现。

AutowiredProcessor类和Autowired注解

@Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (CollectionUtils.isNotEmpty(set)) {
            try {
                logger.info(">>> Found autowired field, start... <<<");
                categories(roundEnvironment.getElementsAnnotatedWith(Autowired.class));
                generateHelper();

            } catch (Exception e) {
                logger.error(e);
            }
            return true;
        }

        return false;
    }
private void categories(Set<? extends Element> elements) throws IllegalAccessException {
        if (CollectionUtils.isNotEmpty(elements)) {
            for (Element element : elements) {
                TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

                if (element.getModifiers().contains(Modifier.PRIVATE)) {
                    throw new IllegalAccessException("The inject fields CAN NOT BE 'private'!!! please check field ["
                            + element.getSimpleName() + "] in class [" + enclosingElement.getQualifiedName() + "]");
                }

                if (parentAndChild.containsKey(enclosingElement)) { // Has categries
                    parentAndChild.get(enclosingElement).add(element);
                } else {
                    List<Element> childs = new ArrayList<>();
                    childs.add(element);
                    parentAndChild.put(enclosingElement, childs);
                }
            }

            logger.info("categories finished.");
        }
    }

编译阶段会去解析Autowired注解,然后获取带有该注解的Elements,然后遍历,如果是私有属性,直接抛异常。然后将正确的elements存到parentAndChild map里面。

接着开始根据parentAndChild的数据里面的内容生成对应的java文件。

 生成autowired的java文件步骤和生成ARouter的步骤相似;

1.创建inject方法对象。inject方法在ISyringe接口里面定义了,所以所有autowired类实现该接口。

2.先判断是否是provder,不是provider,然后去区分是activity还是fragment,activity情况下通过getintent去取参数,fragment情况下通过getArguments方法去取参数。

3.然后根据element属性生成对应的get代码,比如boolean对应getBooleanExtra()方法。

4.判断autowired注解里面的required是否是true,如果是true并且是非基本数据类型,则加入对象是否为null的判断代码。

5.此时autowired注解相关的java文件已经生成完毕。

当我们在某个地方调用如下方法的时候:依赖注入才开始生效。

ARouter.getInstance().inject(this);

然后跟下这个方法,会走到这里

static void inject(Object thiz) {
        AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
        if (null != autowiredService) {
            autowiredService.autowire(thiz);
        }
    }

这里用到一个AutowiredService,这个接口继承了IProvider的接口。然后在AutowiredServiceImpl类里面找到了该接口的具体实现。这个实现类也加了Router注解,那么这里有个新的问题,IProvider是干嘛的?

这里我们要回到RouteProcessor看下Provider的解析代码:

switch (routeMeta.getType()) {
                        case PROVIDER:  // Need cache provider's super class
                            List<? extends TypeMirror> interfaces = ((TypeElement) routeMeta.getRawType()).getInterfaces();
                            for (TypeMirror tm : interfaces) {
                                routeDoc.addPrototype(tm.toString());

                                if (types.isSameType(tm, iProvider)) {   // Its implements iProvider interface himself.
                                    // This interface extend the IProvider, so it can be used for mark provider
                                    loadIntoMethodOfProviderBuilder.addStatement(
                                            "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                            (routeMeta.getRawType()).toString(),
                                            routeMetaCn,
                                            routeTypeCn,
                                            className,
                                            routeMeta.getPath(),
                                            routeMeta.getGroup());
                                } else if (types.isSubtype(tm, iProvider)) {
                                    // This interface extend the IProvider, so it can be used for mark provider
                                    loadIntoMethodOfProviderBuilder.addStatement(
                                            "providers.put($S, $T.build($T." + routeMeta.getType() + ", $T.class, $S, $S, null, " + routeMeta.getPriority() + ", " + routeMeta.getExtra() + "))",
                                            tm.toString(),    // So stupid, will duplicate only save class name.
                                            routeMetaCn,
                                            routeTypeCn,
                                            className,
                                            routeMeta.getPath(),
                                            routeMeta.getGroup());
                                }
                            }
                            break;
                        default:
                            break;
                    }

这里看到如果是provider,会生成一段代码,这段代码会将定义的Interface和该interface的实现类做映射,前提条件是,该interface必须继承IProvider。

那做映射干嘛呢?

我们再去看下Provider的映射表使用的地方。也是在wareHouse类里面,发现和路由跳转一样,也是build()方法里面传入string,然后navigation跳转,其实到这里已经能猜到要干嘛了,为了实现跨组件间的方法调用嘛,比如组件A调用组件B里面的Class C的方法,那么就定义一个interface,然后interface下沉到底层组件,Class C继承该interface并实现该方法,接着按照ARouter的provder配置好后,就能在组件A中调用组件B的Class C的暴露的接口了,组件A也不需要去依赖组件B。

这样做得好处就是减少组件间依赖关系。

Provider已经理解了,我们继续AutowiredService分析。

 /**
     * Recursive injection
     *
     * @param instance who call me.
     * @param parent   parent of me.
     */
    private void doInject(Object instance, Class<?> parent) {
        Class<?> clazz = null == parent ? instance.getClass() : parent;

        ISyringe syringe = getSyringe(clazz);
        if (null != syringe) {
            syringe.inject(instance);
        }

        Class<?> superClazz = clazz.getSuperclass();
        // has parent and its not the class of framework.
        if (null != superClazz && !superClazz.getName().startsWith("android")) {
            doInject(instance, superClazz);
        }
    }

    private ISyringe getSyringe(Class<?> clazz) {
        String className = clazz.getName();

        try {
            if (!blackList.contains(className)) {
                ISyringe syringeHelper = classCache.get(className);
                if (null == syringeHelper) {  // No cache.
                    syringeHelper = (ISyringe) Class.forName(clazz.getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
                }
                classCache.put(className, syringeHelper);
                return syringeHelper;
            }
        } catch (Exception e) {
            blackList.add(className);    // This instance need not autowired.
        }

        return null;
    }

这段代码也好理解,获取传入object对象的class对象,通过类名找到编译阶段生成的实现ISyring接口的相关类,反射创建对象,然后调用该对象的inject方法。

至此:依赖注入也分析完了。

接下来看下拦截器

拦截器是什么,参考OKHttp的实现,拦截器就是在处理一个任务的时候,在该任务之前或者之后加入自己的业务逻辑。拦截器设计模式可以有效减少耦合度。

IntercepterProcessor的注解解析其实和前面两个差不多,拦截器表也在Warehouse类里维护。

区别是拦截器提供了手动注册的方法,不一定要通过Aop的方式去注册。

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(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(postcard, requestCode, callback);
        }

这里我们看到如果是绿色通道,则会调用拦截器的方法。

那什么时候是绿色通道呢?

 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) {
                            logger.error(TAG, "Init provider failed!", e);
                            throw new HandlerException("Init provider failed!");
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                case FRAGMENT:
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }

这里我们看到如果是fragment或者provider的时候,打开了绿色通道,所以这两个不会经过拦截器。

然后我们到InterceptorServiceImpl看下拦截器的执行过程

@Override
    public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
        if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {

            checkInterceptorsInitStatus();

            if (!interceptorHasInit) {
                callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
                return;
            }

            LogisticsCenter.executor.execute(new Runnable() {
                @Override
                public void run() {
                    CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                    try {
                        _execute(0, interceptorCounter, postcard);
                        interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                        if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.
                            callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                        } else if (null != postcard.getTag()) {    // Maybe some exception in the tag.
                            callback.onInterrupt((Throwable) postcard.getTag());
                        } else {
                            callback.onContinue(postcard);
                        }
                    } catch (Exception e) {
                        callback.onInterrupt(e);
                    }
                }
            });
        } else {
            callback.onContinue(postcard);
        }
    }

1.校验拦截器是否初始化。

2.开启线程执行拦截器内部代码。

private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
        if (index < Warehouse.interceptors.size()) {
            IInterceptor iInterceptor = Warehouse.interceptors.get(index);
            iInterceptor.process(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    // Last interceptor excute over with no exception.
                    counter.countDown();
                    _execute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
                }

                @Override
                public void onInterrupt(Throwable exception) {
                    // Last interceptor execute over with fatal exception.

                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception);    // save the exception message for backup.
                    counter.cancel();
                    // Be attention, maybe the thread in callback has been changed,
                    // then the catch block(L207) will be invalid.
                    // The worst is the thread changed to main thread, then the app will be crash, if you throw this exception!
//                    if (!Looper.getMainLooper().equals(Looper.myLooper())) {    // You shouldn't throw the exception if the thread is main thread.
//                        throw new HandlerException(exception.getMessage());
//                    }
                }
            });
        }
    }

按照从0--n的顺序执行拦截器。

这里拦截器的代码也分析完了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android ARouter源码分析 的相关文章

  • 主题以编程方式设置。如何重新加载 Activity 来应用

    如何在不重新启动整个应用程序的情况下应用主题 如果我这样做startActivity getIntent finish 活动退出并且不重新启动 是否可以简单地重新启动 重新创建活动来应用主题 它的顺序不正确 finish intent ne
  • RecyclerView 未显示列表中的所有项目

    我在用RecyclerView在我的应用程序中 每次我打开屏幕时 我只能看到一项 但当我调试时 它每次都会出现onBindViewHolder method 这是我的适配器 Override public ViewHolder onCrea
  • 如何使用gradle这样的格式更改apk名称?

    当我使用 gradle 构建应用程序时 我想将 app release apk 文件名更改为如下所示 format appname of package name V version code yyMMdd R T explain appn
  • 居中复选框视图

    如果除了 或代替 复选框之外 您还对单选按钮感兴趣 请参阅this https stackoverflow com questions 16701806 centering views 2而是提问 尽管存在
  • Android:如何从输入流创建 9patch 图像?

    我使用下面的代码实例化 9patch 图像并将其设置为按钮的背景 下图显示了不理想的结果 InputStream MyClass class getResourceAsStream images btn default normal 9 p
  • 在Android内存中存储gif图像

    我对安卓还很陌生 我想将图像保存到内存中 然后从内存中检索图像并将其加载到图像视图中 我已使用以下代码成功将图像存储在内存中 void saveImage String fileName img cnt jpg File file new
  • NumberPicker 的格式化值在单击时消失

    我的 NumberPicker 在setDescendantFocusability FOCUS BLOCK DESCENDANTS 模式和setWrapSelectorWheel false 已关闭 我用一个简单的格式化程序格式化了我的
  • Android 从键盘读取

    我的登录屏幕根本没有文本字段 当用户使用 RFID 扫描仪扫描他的 id 令牌时 我会得到一个 8 个字符长的字符串 其原理与使用键盘相同 只是更快 我希望我的登录活动在用户扫描其令牌时而不是之前执行 有一个聪明的方法来实现这个吗 我不能有
  • Android-全屏视频视图

    我正在尝试使此 VideoView 以全屏模式显示 public class ViewVideo extends Activity private String filename private static final int INSER
  • 调试 Java InterruptedException,即查找原因

    在调试Android应用程序时 有时中断异常发生并使应用程序崩溃 我已经能够在默认异常处理程序上设置断点 但调用堆栈不提供信息 at java util concurrent locks AbstractQueuedSynchronizer
  • Android 导航回到 Activity;不要重新加载父级

    我有一个场景 我单击 ListFragment 并启动一个新的 Activity 如下所示 public void onListItemClick ListView l View v int position long id super o
  • 无法从 com.android.aaptcompiler.ParsedResource@ef79973 提取资源

    无法从 com android aaptcompiler ParsedResource ef79973 提取资源 无法从 com android aaptcompiler ParsedResource 4c95ce87 提取资源 C Use
  • 如何找到特定路线上两点之间的距离?

    我正在为我的大学开发一个 Android 应用程序 可以帮助学生跟踪大学巴士的当前位置 并为他们提供巴士到达他们的预计时间 截至目前 我获取了公交车的当前位置 通过公交车上的设备 和学生的位置 我陷入了必须找到两个 GPS 坐标之间的距离的
  • 如何在虚拟机 VirtualBox 上运行 Android-x86 4.2 iso?

    我想用Android x86测试和调试我的应用程序 我之前成功尝试过其他版本的Android x86 但是关于android x86 4 2有一个错误 所以我在这里问我的问题 因为它可能会发生在其他人身上 我安装了oracle VM vir
  • 从 Handler.obtainMessage() 获取什么参数

    我正在使用线程来执行一些 BT 任务 我正在尝试向 UI 线程发送消息 以便我可以基于我的 BT 线程执行 UI 工作 为此 我使用处理程序 但我不知道如何检索发送到处理程序的数据 要发送数据 我使用 handler obtainMessa
  • 以编程方式应用样式资源

    我没有找到一种以编程方式做到这一点的方法 所以我在这里发布这个问题 我也没有找到与此相关的任何问题 我有一个资源样式 在 res values styles xml 中定义 我想做的是使用 java 将这种样式应用到我正在操作的 View
  • 在尝试使用 GPS 之前如何检查 GPS 是否已启用

    我有以下代码 但效果不好 因为有时 GPS 需要很长时间 我该如何执行以下操作 检查GPS是否启用 如果启用了 GPS 请使用 GPS 否则请使用网络提供商 如果 GPS 时间超过 30 秒 请使用网络 我可以使用时间或 Thread sl
  • Android UnityPlayerActivity 操作栏

    我正在构建一个 Android 应用程序 其中包含 Unity 3d 交互体验 我已将 Unity 项目导入 Android Studio 但启动时该 Activity 是全屏的 并且不显示 Android 操作栏 我怎样才能做到这一点 整
  • Android BLE 扫描永远找不到设备

    几天以来 我尝试在我的应用程序中实现 BLE 连接 我知道我尝试连接的设备功能齐全 因此问题一定是我的代码 我用BluetoothLeScanner startScan 方法 但回调方法永远不会被调用 public void startSc
  • 画布:尝试使用回收的位图错误

    我是一个相当新的程序员 所以任何建议将不胜感激 我有一个类 每次调用它时都会在循环中运行 AsyncTask AsyncTask 看起来像这样 public class LoadImageTask extends AsyncTask

随机推荐

  • Fisco-bsco 开发联盟链 账户之间的转账

    Fisco bsco 开发联盟链 账户之间的转账 参考 开发第一个区块链应用 FISCO BCOS v2 9 0 文档 fisco bcos documentation readthedocs io 前提 Fisco bcos节点开启 控制
  • 容器性能比无容器服务器,【译】容器 vs 无服务器(Serverless)

    一些历史 不久之前 开发 部署和运维还相当复杂 在一开始 运维不仅需要修补程序代码 还要支持物理机器 保持服务器 硬件与软件处于最新状态也是一项艰巨的任务 在2000年代 一个新的模型 架构即服务 IaaS 很快流行起来 IaaS提供了从第
  • OSG的控制台报错处理

    OSG报错或者出现警告怎么办 最快解决方法是查资料问人 但是都不凑效的情况下 只能分析源码了 报错信息如下 报错调用方定位 触发位置 State cpp bool State checkGLErrors StateAttribute GLM
  • JSP连接数据库

    2019 10 15 JSP连接数据库 一 连接数据库需要用到的包为mysql connector java 5 1 20 bin jar 导入包的方法有两种 1 在Java Build Path中倒导入 2 把我们需要的包拷入WEB IN
  • Java之XML解析-使用dom(org.w3c.dom)解析XML

    转自 Java之XML解析 使用dom org w3c dom 解析XML 下文笔者将讲述使用W3C org w3c dom 提供的接口 解析XML文档的方法分享 W3C解析xml文档的方法 将整个xml文档读入内存 然后构建一个DOM树
  • 如何查看和修改gcc、g++默认include路径

    如何查找gcc g 默认include路径 注意 是Tab上面的那个符号 gcc gcc print prog name cc1plus v g g print prog name cc1plus v 我们都知道在编译的预处理阶段 编译器会
  • Python爬虫的requests(学习于b站尚硅谷)

    目录 一 requests 1 requests的基本使用 1 文档 2 安装 3 响应response的属性以及类型 4 代码演示 2 requests之get请求 3 requests之post请求 1 演示示例 爬取百度翻译 2 ge
  • simulink半桥逆变电路仿真

    逆变是将直流变为脉冲方波信号 电压是100V的 第一幅为原始直流信号 第二幅是逆变电流 第三幅是逆变电压 参数设置 图3 RC1 图4 RC 图5 晶闸管 图6 脉冲信号的参数
  • Java常用类(比较器、System类、Math类、BigInteger和BigDecimal类)

    Java常用类 比较器 System类 Math类 BigInteger和BigDecimal类 一 比较器 一 自然排序 使用Comparable接口 二 定制排序 使用Comparator接口 二 System类 三 Math类 四 B
  • ServletContext

    ServletContext上下文提供对应用程序中所有Servlet所共有的各种资源和功能的访问 Servlet上下文 API用于设置应用程序中所有Servlet共有的信息 Servlet可能需要共享他们之间的共有信息 运行于同 一服务器的
  • 如何使用groovy语言访问url时绕过https ssl认证校验?

    记录一下使用groovy解决https ssl校验问题 import javax net ssl HostnameVerifier import javax net ssl HttpsURLConnection import javax n
  • 生产数据库数据误删、错刷恢复备份实战

    文章目录 故障起因 前提 全备 全备脚本 增备 数据库配置要求 增备脚本 定时备份 故障处理 思路 全备恢复 解析增备 新建binlog解析导出目录 查看整点binlog列表 将每个整点的增量备份文件导出到sql文件 选定结束导入的SQL文
  • react函数式组件(hooks)之useEffect

    文章目录 前言 一 useEffect的作用 二 useEffect的使用 1 class组件 2 函数式组件 总结 前言 React函数式编程没有生命周期 因此需要借助useEffect来实现 一 useEffect的作用 发ajax请求
  • Swift4.0--Photos框架的使用附从相簿中获取图片

    首先发布Demo链接 Photos从相簿中获取图片 效果展示 一 Photos简介 在iOS 8之前 开发者只能用 AssetsLibrary 框架访问的用户的照片库 几年以来 相机应用和照片应用发生了显著的变化 增加了许多新特性 包括按时
  • invalid Key or Package

    使用EasyAR打包apk后出现invalid Key or Packag解决方案 1 Bundle ID IOS 和 PackageName Android 填写的对不对 2 回头看Unity里面Player Setting 里面的名字可
  • Qt 文件读写操作

    转载 http blog csdn net ei nino article details 7301132 文列出Qt读写文件常用方式 还有对文件的一些简单操作 读文件 cpp view plain copy print QString f
  • day28 回溯

    39 组合总和 数字可以被无限制选取 但是无需考虑顺序 组合 因此递归还是需要考虑startIdx 但是每次都从最开始进行回溯 而不是startIdx 1 40 组合总和II 通过标识去除重复值 树层去重 131 分割回文串 每次找到切割点
  • 孩子们的游戏(圆圈中最后剩下的数)

    每年六一儿童节 牛客都会准备一些小礼物去看望孤儿院的小朋友 今年亦是如此 HF作为牛客的资深元老 自然也准备了一些小游戏其中 有个游戏是这样的 首先 让小朋友们围成一个大圈 然后 他随机指定一个数米 让编号为0的小朋友开始报数 每次喊到M
  • firrtl

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 动手 sbt 2 之后 再回头看 chisel第一个实验 根据 https github com freechipsproject firrtl 发现firrtl没有执行s
  • android ARouter源码分析

    背景 随着项目越做越大 代码量越来越多 项目也随之改造成组件化的开发模式 组件化开发非常适合庞大的项目 将每个业务模块 功能模块解耦 抽离成组件的形式 各个组件遵循严格的依赖关系 因为这层严格的依赖关系 使得组件化比模块化结构更加简洁和清晰