ARouter原理及用法

路由框架在组件化中占很大的作用,目前主流中挑选了ARouter 1.5.0版本进行源码分析。看源码最香的方式就是断点结合文章一起食用。。

简单使用

我们先来看下 ARouter 能干什么:

一、功能介绍

  1. 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
  2. 支持多模块工程使用
  3. 支持添加多个拦截器,自定义拦截顺序
  4. 支持依赖注入,可单独作为依赖注入框架使用
  5. 支持InstantRun
  6. 支持MultiDex(Google方案)
  7. 映射关系按组分类、多级管理,按需初始化
  8. 支持用户指定全局降级与局部降级策略
  9. 页面、拦截器、服务等组件均自动注册到框架
  10. 支持多种方式配置转场动画
  11. 支持获取Fragment
  12. 完全支持Kotlin以及混编(配置见文末 其他)
  13. 支持第三方 App 加固(使用 arouter-register 实现自动注册)
  14. 支持生成路由文档
  15. 提供 IDE 插件便捷的关联路径和目标类
  16. 支持增量编译(开启文档生成后无法增量编译)
  17. 支持动态注册路由信息

二、典型应用

  1. 从外部URL映射到内部页面,以及参数传递与解析
  2. 跨模块页面跳转,模块间解耦
  3. 拦截跳转过程,处理登陆、埋点等逻辑
  4. 跨模块API调用,通过控制反转来做组件解耦

\

以上是直接从 官方文档 复制过来的,根据 ARouter 支持的能力和使用场景,我们就能知道什么时候怎么用,官网已经很详细介绍了使用,不再赘述。

源码分析

Activity 跳转

activity的跳转绝对是最常见的场景,我们直接从跳转代码入手,以下是代码示例:

// Activity
@Route(path = "/test/router")
public class RouterTestActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ARouter.getInstance().inject(this);
        getIntent().getExtras().getInt("testStr");
        getIntent().getExtras().getInt("testInt");
    }
}

// 跳转
ARouter.getInstance().build("/test/router")
    .withString("testStr", "testtesttest")
    .withInt("testInt", 1)
    .navigation()
复制代码

直接点进 build 方法看一眼:

protected Postcard build(String path) {
    if (TextUtils.isEmpty(path)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        // 这里不展开讲了,可以看到ARouter是通过实现PathReplaceService支持路由协议替换的
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            path = pService.forString(path);
        }
        return build(path, extractGroup(path));
    }
}
复制代码

这里有个路由协议替换的小技巧已用注释说明。extractGroup 方法也不用展开讲,作用就是对路由协议分组,就上述的例子 /test/router的 group 就是 test 如果后续还有 /test/router2这样的路径,都会被分配到 test 组下。继续进 build 方法查看:

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);
    }
}
复制代码

貌似这 build 有点重复的嫌疑啊,好像就多了个 group 是否为空的判断?不管了,最后会创建出一个 Postcard 对象,这个对象相当重要,我们之后在继续看看。好了,现在 build 方法构建出了 postcard 对象,然后调用了 navigation 方法,然后 navigation 会有一连串的重载方法,最后调用:

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    // 省略...
    try {
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        // ...
    }

    // 拦截逻辑
    if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
       
            @Override
            public void onContinue(Postcard postcard) {
                _navigation(context, postcard, requestCode, callback);
            }

            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;
}
复制代码

拦截逻辑暂时不详细描述,拦截器通过后会调用 _navigation,先跟进看LogisticsCenter.completion(postcard),这是一个关键方法:

public synchronized static void completion(Postcard postcard) {
    // 第一次用path获取routeMeta会是空的
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
        // group对应的groupMeta已经生成,在本例中就是test组。
        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 {
            try {
                // 实例化groupMeta并将其所有的routeMeta添加到Warehouse.routes表内
                IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                iGroupInstance.loadInto(Warehouse.routes);
                Warehouse.groupsIndex.remove(postcard.getGroup());
            } catch (Exception e) {
                throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
            }
            // 递归之后会走到下方的else逻辑
            completion(postcard);   // Reload
        }
    } else {
        // 把routerMeta里的属性都添加到postcard
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());

        // uri跳转逻辑这里就不分析,只分析普通的path跳转。
        Uri rawUri = postcard.getUri();
        // ......
    }
}
复制代码

注释已经解释了这块代码的逻辑,但是在这里最好断点,才能理解这个递归的逻辑。结束了对postcard的丰富,继续进入 _navigation:

private Object _navigation(Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    final Context currentContext = null == context ? mContext : context;
    switch(postcard.getType()) {
    case ACTIVITY:
        final Intent intent = new Intent(currentContext, postcard.getDestination());
        intent.putExtras(postcard.getExtras());
        int flags = postcard.getFlags();
        if (-1 != flags) {
            intent.setFlags(flags);
        } else if (!(currentContext instanceof Activity)) {
            intent.setFlags(268435456);
        }

        String action = postcard.getAction();
        if (!TextUtils.isEmpty(action)) {
            intent.setAction(action);
        }

        this.runInMainThread(new Runnable() {
            public void run() {
                _ARouter.this.startActivity(requestCode, currentContext, intent, postcard, callback);
            }
        });
        return null;
       // ......
    }
}
复制代码

终于看到最后 intent 跳转了,postcard.getExtras()里带的就是 withString, withInt这类方法所传进来的参数塞到 Bundle 里。

前面通过断点我们看到了如何去拿到 routerMeta 并把其属性赋给 postcard,从而才有了跳转的 className。这里我们就需要看下编译期如何生成代码的。

代码生成

如果对编译期代码生成完全不了解的,可以先去看一些文章,我这边也有简单写过一篇和编译期生成代码有关的文章 EventBus 3.0 从编译时注解分析源码

源码可直接在 官方文档 arouter-compiler 模块看或者下过来看。先给出本例中生成的代码:

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/router", RouteMeta.build(RouteType.ACTIVITY, RouterTestActivity.class, "/test/router", "test", null, -1, -2147483648));
  }
}
复制代码

看到 loadInto就很熟悉了,就是这个方法把 routeMeta 添加到表中。

@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor {
    private Map<String, Set<RouteMeta>> groupMap = new HashMap<>(); // ModuleName and routeMeta.
    private Map<String, String> rootMap = new TreeMap<>();  // Map of root metas, used for generate class file in order.
    @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;
    }
}
复制代码

BaseProcessor抽象类实际上是继承了 AbstractProcessor,而编译期会扫描所有继承 AbstractProcessor的类执行 process 方法。看到 process 方法突然豁然开朗,第一行代码就是拿到所有被 @Route 注解的节点,看看具体在 parseRoutes里是如何解析的,这边只留了对 Activity 处理代码,否则代码量太多了。

private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
    if (CollectionUtils.isNotEmpty(routeElements)) {

        TypeMirror type_Activity = elementUtils.getTypeElement(ACTIVITY).asType();
        
        // root是当前模块的所有的group,可以先看 group 生成的逻辑
        MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                .addAnnotation(Override.class)
                .addModifiers(PUBLIC)
                .addParameter(rootParamSpec);
                
        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)) {
                if (types.isSubtype(tm, type_Activity)) {
                    // 实例化 RouteMeta 对象
                    routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
                } else {
                    routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
                }

            }
            // 将 routeMeta 分组保存到 groupMap 中,后续遍历有用到
            categories(routeMeta);
        }

        Map<String, List<RouteDoc>> docSource = new HashMap<>();

        // 开始遍历 groupMap
        for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
            String groupName = entry.getKey();
            // 构建loadInfo方法
            MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
                    .addAnnotation(Override.class)
                    .addModifiers(PUBLIC)
                    .addParameter(groupParamSpec);

            Set<RouteMeta> groupData = entry.getValue();
            // 所有该组下 routeMeta 都会被添加到表中
            for (RouteMeta routeMeta : groupData) {
                ClassName className = ClassName.get((TypeElement) routeMeta.getRawType());
                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());
            }

            // 这一步是生成group的java文件,写入之前的代码。kotlin会有单独的compiler处理
            String groupFileName = NAME_OF_GROUP + groupName;
            JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                    TypeSpec.classBuilder(groupFileName)
                            .addJavadoc(WARNING_TIPS)
                            .addSuperinterface(ClassName.get(type_IRouteGroup))
                            .addModifiers(PUBLIC)
                            .addMethod(loadIntoMethodOfGroupBuilder.build())
                            .build()
            ).build().writeTo(mFiler);
            rootMap.put(groupName, groupFileName);
        }
        
        if (MapUtils.isNotEmpty(rootMap)) {
            // 写入代码,routes表添加group对应的className
            for (Map.Entry<String, String> entry : rootMap.entrySet()) {
                loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry.getKey(), ClassName.get(PACKAGE_OF_GENERATE_FILE, entry.getValue()));
            }
        }    
        // 这一步是生成root的java文件
        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);        

    }
}
复制代码

好了,我们把核心的生成代码提取了出来,根据注释先看完这一块代码,然后会发现生成了两个文件,第一个是和 group 有关的文件,这代码我们已经在前面贴出来了,会递归中找到 group 然后把其添加到 routeMeta 表中用来提供 className, 携带参数等。还有一个文件是 root 相关的文件,它是用来存放该模块下所有的group对象的,在前面的递归找到 group 我们并不清楚为什么 Warehouse.groupsIndex 会有数据,其实这是 root 文件的功劳:

public class ARouter$$Root$$demoApp implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("test", ARouter$$Group$$test.class);
  }
}
复制代码

root 文件内添加了所有 group 的 class对象,我们看下 ARouter.init初始化方法干了什么:

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
       // routerMap是从本地文件内读取的,文件内保存的是所有group的className
       for (String className : routerMap) {
            if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                // 这里就是实例化group对象并添加到 Warehouse.groupsIndex
                ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
            }
        }
}
复制代码

这样整个脉络就理清楚了 group 对象表和 route 对象表是如何添加的,弄清楚了这个,intent跳转就很简单了。

高级用法

相信很多人用 ARouter 基本就是跳转Activity或获取Fragment等,这里介绍几个其他稍微高级点的用法。看源码按照上节那样断点看就完事了。

Autowired

@Route(path = "/test/fragment")
public class RouterTestFragment extends Fragment {
    @Autowired
    String testStr;
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        ARouter.getInstance().inject(this);
        Log.d("RouterTestFragment", "testStr -> " + testStr);
        return super.onCreateView(inflater, container, savedInstanceState);
    }
}

// 调用
ARouter.getInstance().build("/test/fragment")
     .withString("testStr", "testtest")
     .navigation();
复制代码

@Autowired 这个注解能比较方便的获取上一个页面传递过来的参数,原理上也是通过注解在编译时生成代码文件,这里就不详细解析生成文件的源码了,实际上本案例生成的代码如下:

public class RouterTestFragment$$ARouter$$Autowired implements ISyringe {
  private SerializationService serializationService;

  @Override
  public void inject(Object target) {
    serializationService = ARouter.getInstance().navigation(SerializationService.class);
    RouterTestFragment substitute = (RouterTestFragment)target;
    substitute.testStr = substitute.getArguments().getString("testStr");
  }
}
复制代码

然后在调用 ARouter.getInstance().inject(this); 时就会通过反射调用到上述的 inject 方法,最后拿到 testStr 参数值。

Provider

provider 也是很常用的一个能力,除了路由跳转之外,很多时候我们需要获取数据、调用api。那这个时候 provider 就派上用场了。

// 实现IProvider的接口放到接口层
public interface IProviderService extends IProvider {
    void test();
}
复制代码
// 实现类放到实现层
@Route(path = "/test/provider")
public class TestProviderService implements IProviderService {
    @Override
    public void test() {
        Log.d("TestProviderService", "test");
    }
    @Override
    public void init(Context context) {
        Log.d("TestProviderService", "init");
    }
}
复制代码
// 使用方式一
@Autowired(name = "/test/provider")
IProviderService providerService;
providerService.test();

// 使用方式二
IProviderService providerService = (IProviderService)ARouter.getInstance().build("/test/provider").navigation();
providerService.test();
复制代码

provider 也可以和 @Autowired 注解配合使用。这里需要注意的 IProvider 的 init 方法在应用生命周期内只会调用一次,第二次 provider 实力从缓存中获取就不会再调用 init 方法了。至于它的原理也不用多赘述,还是一样在编译时生成代码,并通过反射拿到实例。

Interceptor

@Interceptor(priority = 1)
public class TestInterceptor implements IInterceptor {

    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
        if("/test/router".equals(postcard.getPath())) {
            Log.d("TestInterceptor", "/test/router  is intercepted");
        }
        callback.onContinue(postcard);
    }

    @Override
    public void init(Context context) {
        Log.d("TestInterceptor", "init");
    }
}
复制代码

拦截器的注解 @Interceptor 通过也是在编译时扫描生成代码,并且拦截器在 ARouter.init 的时候就会调用其 init方法,每个拦截器都会走 process 方法,我们做想做的事就可以了。源码也不再解析,拦截器也是很好用的一个能力。

总结

ARouter对于大多数组件化项目来说是核心的解耦模块,更多的高级使用可以参考 官方文档 。在使用过程中肯定也会遇到很多的坑和不足,需要慢慢总结。

Tips

  • 建议大项目的ARouter需单独设置线程池,ARouter.getInstance().setExecutor(),默认的线程池带有核心线程,创建线程后不会被回收。
  • 支持传递requestCode并重写onActivityResult方法,但这个处理只能在Activity内做,如果需要在任何类支持回调函数,需要我们自己对ARouter封装一层。
  • ARouter是编译期生成代码的,跳转不了页面或者获取不到provider等情况,大部分可能是生成代码部分的问题,要学会看是否有生成代码了、生成代码里是否注册了路由。如果是这个问题,那么看下配置ARouter模块时是否有问题。

猜你喜欢

转载自juejin.im/post/7095672176132489223