ARouter使用及源码简析
阅读原文时间:2021年04月21日阅读:1

ARouter是阿里巴巴开源的Android平台中对页面、服务提供路由功能的中间件,提倡的是简单且够用。

基本用法


1.添加依赖配置
android {
    defaultConfig {
    ...
    //此处每一个Module都需要配置,具体原因见下文
    javaCompileOptions {
        annotationProcessorOptions {
        arguments = [ moduleName : project.getName() ]
        }
    }
    }
}

dependencies {
    // 替换成最新版本, 需要注意的是api
    // 要与compiler匹配使用,均使用最新版可以保证兼容
    compile 'com.alibaba:arouter-api:x.x.x'
    annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
    ...
}
2.添加注解
// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx。具体原因见下文
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}
3.初始化SDK
if (isDebug()) {
    ARouter.openLog();     // 打印日志
    ARouter.openDebug();   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
4.发起路由操作
// 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
ARouter.getInstance().build("/test/activity").navigation();

// 2. 跳转并携带参数
ARouter.getInstance().build("/test/1")
        .withLong("key1", 666L)
        .withString("key3", "888")
        .withObject("key4", new Test("Jack", "Rose"))
        .navigation();
5.添加混淆
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}

进阶用法


1.通过URL跳转
2.解析URL中的参数
3.声明拦截器(拦截跳转过程,面向切面变成)
4.处理跳转结果
5.为目标也声明更多信息
6.自定义全局降级策略

原理简析


1.编译期

首先我们为User模块中的UserDetailActivity和UserSettingActivity分别添加@Route(path = “/home/detail”, group = “user”)和@Route(path = “/setting/user”)两个注解。编译之后会发现工具帮我们生成了这样四个文件

再来看看他们的具体内容。

public class ARouter$$Root$$user implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("setting", ARouter$$Group$$setting.class);
    routes.put("user", ARouter$$Group$$user.class);
  }
}


public class ARouter$$Group$$user implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/home/detail", RouteMeta.build(RouteType.ACTIVITY, UserDetailActivity.class, "/home/detail", "user", null, -1, -2147483648));
  }
}

public class ARouter$$Group$$setting implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/setting/user", RouteMeta.build(RouteType.ACTIVITY, UserSettingsActivity.class, "/setting/user", "setting", null, -1, -2147483648));
  }
}

上面这些文件是如何生成的,生成规则又是怎样的,我们可以查看{@link com.alibaba.android.arouter.compiler.processor.RouteProcessor#parseRoutes(Set)} )}一探究竟。源码比较长,感兴趣的可以自行阅读,简要概括便是:

  1. 以@Route中的group作为类名,生成ARoute.Group.groupName类保存组名相同的path和Activity之间的映射关系。
  2. 以moduleName作为类名生成ARouter.Root.moduleName类,保存同一个module中group和ARoute.Group.groupName.class之间的映射关系。
2.运行期
2.1 初始化

毫无疑问跟踪ARouter.init(this)方法,当然了我们只看核心逻辑{@link com.alibaba.android.arouter.core.LogisticsCenter#init(Context, ThreadPoolExecutor)}。

class Warehouse {
    static Map<String, Class<? extends IRouteGroup>> groupsIndex;
    static Map<String, RouteMeta> routes;
}

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, "com.alibaba.android.arouter.routes");

        for (String className : classFileNames) {
            if (className.startsWith("ARouter$$Root")) {
                ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
            }

            //......

        }
    }

概括来讲就是将group和Arouter.Group.groupName之间的映射关系加载到内存中。

2.2 跳转

接下来继续跟踪ARouter.getInstance().build(“”)方法的调用,核心的处理逻辑在{@link com.alibaba.android.arouter.core.LogisticsCenter#completion(Postcard)}中,看一下具体的实现逻辑。

public synchronized static void completion(Postcard 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 {
                    //如果我们想要跳转的路径所在的组存在,那么加载该组中所有的映射关系到内存中。
                    //这便是作者提到的分组管理按需加载。
                    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() + "]");
                }

                completion(postcard);   // Reload
            }
        } else {
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            //......

        }
    }

最后再看一下完成最终跳转的navigation()方法。该方法最终实现是在_ARouter类中。

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, NavigationCallback callback) {
        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            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() {
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode);
                }

                @Override
                public void onInterrupt(Throwable exception) {

                }
            });
        } else {
            return _navigation(context, postcard, requestCode);
        }
        return null;
    }

    private Object _navigation(final Context context, final Postcard postcard, final int requestCode) {
        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)) {    // Non activity, need less one flag.
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                // Navigation in main looper.
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        if (requestCode > 0) {  // Need start for result
                            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                        } else {
                            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                        }

                        if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                        }
                    }
                });

                break;
        }

        return null;
    }

以上代码省略了太多不相干的东西,看的不爽就请移步Github查看源码实现,当然了后续我也会陆续补充的。

FAQ


1.为什么path一定要声明成两级目录

为了避免一次性将工程中所有的页面和path之间的映射关系加载到内存中,ARouter采用了分组加载的机制。只有当一个分组中的某一个path被加载到内存中时,该分组中所有的映射关系才会被加载到内存中。因此,要求path采用两级目录的原因就是,第一级目录会被截取作为组名。当然了如果你同时声明group的话就可以只使用一级目录,只不过这种方式已经被@Deprecated了。 *(源码参考:{@link com.alibaba.android.arouter.compiler.processor.RouteProcessor#routeVerify(RouteMeta)}
)*

2.为什么一定要在build.gradle中配置javaCompileOptions

其实仔细看,我们真正配置的是annotationProcessorOptions,这个呢其实又是annotationProcessor工具的配置参数,所以我们首先要了解annotationProcessor是什么(Java注解处理器)。它是Google在Android Gradle Plugin 2.2之后提供的一个处理注解的工具,通过它可以将源文件中的注解生成.java文件等。ARouter便是通过这种方式建立path和Activity之间的映射关系。不过具体为什么要配置该参数,主要是因为ARouter将每一个module中的path和Activity之间的映射关系保存在一个单独的类中,为了避免类重名,因此类名最后都是以module名结尾,所以为了获得module名,需要我们手动配置该参数。(源码参考:{@link com.alibaba.android.arouter.compiler.processor.RouteProcessor#init(ProcessingEnvironment)})

3.手动设置分组后,为什么跳转失败

虽然Github上作者说一旦主动指定分组之后,应用内路由需要使用ARouter.getInstance().build(path, group) 手动指定分组进行跳转,否则无法找到。不过在查看了源码实现以及测试之后发现,这么表述其实不太准确。如果你在手动指定分组的同时将path配置成/aa/bb的形式并且aa和组名一致的话,那么使用build(“/aa/bb”)跳转并不会有任何问题。通过刚刚的原理简析相信大家应该已经明白了为什么会出现这种情况。不懂的话可以继续阅读源码或者忽视好了,毕竟该方法已经被@Deprecated,没有特殊需求也应该避免在@Route中手动指明分组。

参考资料
  1. 开源最佳实践:Android平台页面路由框架ARouter
  2. Github源码

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章