ARouter 简单使用及原理

ARouter

帮助 Android App 进行组件化改造的路由框架。
一个最显著的作用就是实现两个互相不依赖的 module 进行页面跳转和数据传递,是项目组件化中衔接各 module 页面跳转通信很好的实现方式。

如何使用

官方 github 上的说明文档还是蛮清晰明了易上手的 在这我更想记录我对它的源码分析和理解。

源码分析

先从初始化说起,

//首先会在 Application 里调用 ARouter 的 init() 方法
public static void init(Application application) {
    //hasInit 是用 volatile 修饰的,
    //volatile 是 Java 虚拟机提供的最轻量级的同步机制,volatile 修饰的变量对所有线程具有可见性
    //也就是说可以认为任一线程获取到的值就是实际值,并非缓存值
    if (!hasInit) {
        //调用 _ARouter 的 init 方法进行实际初始化
        hasInit = _ARouter.init(application);
        if (hasInit) {
            _ARouter.afterInit();
        }
    }
}
//_ARouter
protected static synchronized boolean init(Application application) {
    //入参赋值给了 Context 类型对象,看来是要用 Application 的 Context 类型功能
    mContext = application;
    //executor 是个线程池处理器,默认线程数是设备 cpu数 + 1
    LogisticsCenter.init(mContext, executor);
    //这里也有一个标记位
    hasInit = true;
    //创建一个主线程的 Handler 对象,估计用来做主线程相关的操作,例如 UI 更新等
    mHandler = new Handler(Looper.getMainLooper());
    //返回结果给前面的 hasInit,这样一来就会执行 _ARouter 的 afterInit() 方法
    return true;
}
//再来看看 LogisticsCenter 的 init() 方法
public synchronized static void init(Context context, ThreadPoolExecutor tpe) {
    mContext = context;
    executor = tpe;
    //这里有个通过插件方式的加载,不过看似好像废弃了,我们直接看 else 部分
    Set routerMap;
    if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
        //ROUTE_ROOT_PAKCAGE 指的是 ARoute 包名,~这个package单词是拼错了吗,还是故意这么写~
        //这个工具方法作用就是找出该包下面的所有类名
        //这里有个疑问,如果是 ARoute 自己的类,那每次应该都一样,感觉没用。结合这个变量名 routerMap,
        //我想应该存的是要路由的类名,有可能是通过 @Route,这里先记着。
        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);
    } else {
        //如果 debug 没开或者不是新包或者新版本的话就从缓存里取
        //所以应该说,只要是第一次跑,都会执行上面 if 语句
        routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet()));
    }
    //遍历 roterMap,缓存一些对象,看循环内的逻辑,可见 routerMap 应该是 ARoute 包自身的类名集合
    for (String className : routerMap) {
        //根据类名判断,结合源码其实可以知道,要找的类就是 routes 包下面的
        //ARouter$$Root$$arouterapi
        //ARouter$$Providers$$arouterapi
        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);
        }
    }
}

路径注解,就是在具体 Activity 页面上添加的 @Route 注解标识路径,

@Route(path = "/xxx/xxx/xxx")
//像这样在指定 Activity 上加上路径说明,
//不过 Route 这个注解其实还可以配置其他值,例如 name,group

接下来就是进行页面跳转时的路由操作,

//像这样可实现路由跳转
ARouter.getInstance().build("/xxx/xxx/xxx").navigation();
//先看 ARouter 的 build() 方法
public Postcard build(String path) {
    //调用了 _ARouter 的 build() 方法,返回 Postcard 类型对象
    return _ARouter.getInstance().build(path);
}

Postcard

这个概念说是代表着包含着路由表信息的实体,继承自 RouteMeta,这个父类倒是包含了路由的一些基础信息。总之路由过程中,它应该就是那个载体了。另外还可配置切换动画,用于页面转场效果。

//继续 _ARouter 的 build() 方法
protected Postcard build(String path) {
    //这个 Service 会从刚才初始化时遍历 routeMap 过程的 Warehouse.providersIndex 里去找
    //但根据分析肯定找不到,然后会去重建,但我经过最新版本 1.5.1 的断点也没看到创建成功,
    //我们就按代码实际运行的逻辑来
    PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
    if (null != pService) {
        path = pService.forString(path);
    }
    //这里会先提取 group 字符串,假设路径为 /aaa/bbb/ccc,那么 aaa 就是这里的 group
    return build(path, extractGroup(path), true);
}
protected Postcard build(String path, String group, Boolean afterReplace) {
    if (!afterReplace) {
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            path = pService.forString(path);
        }
    }
    //最终创建 Postcard 对象,赋值 Postcard 对象的 path 和 group 字段
    return new Postcard(path, group);
}

完了之后是 Postcard 对象调用 navigation() 方法,

//Postcard 的三个重载方法
public Object navigation() {
    return navigation(null);
}
public Object navigation(Context context) {
    return navigation(context, null);
}
public Object navigation(Context context, NavigationCallback callback) {
    return ARouter.getInstance().navigation(context, this, -1, callback);
}
//ARouter
public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
    return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}
//_ARouter
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    try {
        //核心部分
        LogisticsCenter.completion(postcard);
    }
    //这里如果需要回调就要在入参里传入 NavigationCallback对象
    if (null != callback) {
        callback.onFound(postcard);
    }
    // greenChannel 说是配置是否忽略拦截
    //因为默认没有配置拦截器,也没有配置不忽略,所以会进入拦截处理
    if (!postcard.isGreenChannel()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
        //这个 interceptorService 就是初始化的时候 afterInit() 里面做的创建对象
        //它的创建过程和页面跳转很像,区别在于 path 不同
        //这里先假设它已经创建好了,如果需要拦截处理,应该就是通过 onInterrupt 回调方法处理
        //下一步就会调用 _navigation() 方法
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            public void onContinue(Postcard postcard) {
                _navigation(context, postcard, requestCode, callback);
            }
            public void onInterrupt(Throwable exception) {
                if (null != callback) {
                    callback.onInterrupt(postcard);
                }
            }
        }
    } else {
        return _navigation(context, postcard, requestCode, callback);
    }
}
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    final Context currentContext = null == context ? mContext : context;
    //根据 Postcard 类型会有不同的处理
    //我们就只看 activity 的,其他的还有 service, provider, fragment, ContentProvider 等
    //这里有个疑问,结合前面的分析,postcard 的 type 值没见它赋值过啊,难道忽略了哪个步骤?
    //估计在核心部分 completion 里,不急等下去好好拆开看看
    switch (postcard.getType()) {
        case ACTIVITY:
            //同样以下的 destination,携带的数据,action 等都留着等下分析
            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(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            String action = postcard.getAction();
            if (!TextUtils.isEmpty(action)) {
                intent.setAction(action);
            }
            //这个 runInMainThread 方法是它自己写的,内部我看了下就是用到了初始化时创建的那个 Handler 对象,来确保在安卓主线程
            runInMainThread(new Runnable() {
                public void run() {
                    //startActivity 也是它自己写的,里面主要兼容了是否需要考虑返回结果,以及 Activity 的切换动画,以及成功跳转后的一个回调。
                    //所以 Activity 的跳转最终还是系统的方法,不会有别的
                    startActivity(requestCode, currentContext, intent, postcard, callback);
                }
            });
    }
}          

Completion 来看下核心部分

public synchronized static void completion(Postcard postcard) {
    //从 Warehouse.routes 里取出路径相关联的 RouteMeta 对象
    //从前面初始化过程知道,最先执行这里的是创建 interceptorService 的时候,
    //interceptorService 的路径是 /arouter/service/interceptor
    //一开始肯定找不到,routeMeta 为 null
    //----------------------------------------------------------------
    //递归调用回来,这时 Warehouse.routes 里就有 /arouter/service/interceptor 这个路径对应的 RouteMeta 对象了,即 InterceptorServiceImpl 对象,这就能看前面拦截器里面所做的逻辑细节了。
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {
        //interceptorService 的 group 的值为 arouter,这在前面初始化的时候存进去了
        //所以得到的 groupMeta 对象就是 ARouter$$Group$$arouter 类型
        Class groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());
        IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
        //在这里就会加载 /arouter/service/autowired, /arouter/service/interceptor 两个路径及对应的数据
        iGroupInstance.loadInto(Warehouse.routes);
        //接着在 group 组里移除了 arouter
        Warehouse.groupsIndex.remove(postcard.getGroup());
        //递归调用
        completion(postcard);
    } else {
        //这里一顿赋值,像 interceptorService 在刚才 loadInto 的时候做了初始化
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());
        //对 uri 做了单独处理
        Uri rawUri = postcard.getUri();
        if (null != rawUri) {
            Map resultMap = TextUtils.splitQueryParameters(rawUri);
            Map paramsType = routeMeta.getParamsType();
            if (MapUtils.isNotEmpty(paramsType)) {
                for (Map.Entry params : paramsType.entrySet()) {
                    setValue(postcard,
                             params.getValue(),
                             params.getKey(),
                             resultMap.get(params.getKey()));
                }
                postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
            }
            postcard.withString(ARouter.RAW_URI, rawUri.toString());
        }
        //下面的代码逻辑和 activity 不相关就不看了
    }
}

以上是拿 interceptorService 做的分析,如果是 Activity 应该是同理的,

首先一个 group 对应一组路由(即 Warehouse.routes),刚开始 Activity 的 path 肯定也没有对应的 RouteMeta 于是找到对应 IRouteGroup 对象,通过它去 load 路由列表。不同点在于,ARouterGrouparouter 类是已经在依赖包里的,而用于 Activity 跳转的这些 IRouteGroup 类应该是通过 @Route 注解编译生成的,大概看个长相,

/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
//长的就像这样,不可编辑,因为是框架生成的
public class ARouter$$Group$$login implements IRouteGroup {
    @Override
    public void loadInto(Map atlas) {
        atlas.put("/login/fastway/login", RouteMeta.build(RouteType.ACTIVITY, FastLoginActivity.class, "/login/fastway/login", "login", null, -1, -2147483648));
    }
}

这样一来,同一个 group 下的路径都被加载了。这也看出同组间的页面跳转会比不同组之间的页面跳转省事很多。

加载了路由列表后,接下去递归的逻辑也和 interceptorService 类似。到这就能解释前面 postcard 的 getType 等值是怎么来的了。

最后,再来回顾一遍整体逻辑。首先添加依赖进行相关 gradle 配置,接着定义好页面路由路径,配置初始化。框架的加载逻辑是,先通过初始化过程完成路由表组的加载(就是根据 @Route 以及框架预先定义的组进行加载),然后在真正要进行页面跳转时,从缓存路由列表里获取路由信息,找不到则通过事先加载的路由表组进行加载路由列表,找得到则继续组装具体页面相关的一些信息,最后通过系统完成页面跳转。

题外话

源码这么看下来,发现 ARouter 和 _ARouter 的关系有点像 ContextWrapper 和 mBase 还有 PhoneWindow 和 mDecor 的关系,一个对外,一个对内。看似工作是 ARouter 做的,实际上都是 _ARouter 做的。

我觉得多看源码的好处就是可以模仿源码的代码实现,慢慢的逼格就上去了。

你可能感兴趣的:(ARouter 简单使用及原理)