一, 路由方案
原生的路由方案缺点:
显式: 直接的类依赖, 耦合严重
隐式: 规则集中式管理, 协作困难
Manifest 扩展性较差
跳转过程无法控制
失败无法降级
ARouter 的优势:
使用注解, 实现了映射关系自动注册 与 分布式路由管理
编译期间处理注解, 并生成映射文件, 没有使用反射, 不影响运行时性能
映射关系按组分类, 多级管理, 按需初始化
灵活的降级策略, 每次跳转都会回调跳转结果, 避免 StartActivity() 一旦失败将会抛出运营级异常
自定义拦截器, 自定义拦截顺序, 可以对路由进行拦截, 比如登录判断和埋点处理
支持依赖注入, 可单独作为依赖注入框架使用, 从而实现 跨模块 API 调用
支持直接解析标准 URL 进行跳转, 并自动注入参数到目标页面中
支持获取 Fragment
支持多模块使用, 支持组件化开发
.......
这么多好处, 是时候来了解一下 ARouter 了.
文末有 Android 进阶开发资料及面试资料领取~~~
二, ARouter 框架
上图是根据 ARouter 一次基本的路由导航过程, 整理的基本框架图, 涉及到主要流程, 下面进行详细介绍.
三, 路由管理
1. 注册
通过注解, 在编译时收集使用了注解的类或变量并经过 Android Process Tool 处理进行统一管理.
包含三种注解 @Autowired,@Interceptor,@Route.
@Route
注解定义
- String path();// 路径 URL 字符串
- String group() default "";// 组名, 默认为一级路径名; 一旦被设置, 跳转时必须赋值
- String name() default "undefined";// 该路径的名称, 用于产生 JavaDoc
- int extras() default Integer.MIN_VALUE;// 额外配置的开关信息; 譬如某些页面是否需要网络校验, 登录校验等
- int priority() default -1;// 该路径的优先级
实现 @Route 注解
- BlankFragment @Route(path = "/test/fragment")
- Test1Activity @Route(path = "/test/activity1")
该注解主要用于描述路由中的路径 URL 信息, 使用该注解标注的类将被自动添加至路由表中.
@Autowired
注解定义
- boolean required() default false;
- String desc() default "No desc.";
实现 @Autowired 注解
- @Autowired
- int age = 10;
- @Autowired
- HelloService helloService;
该注解是在页面跳转时参数传递用的. 目标 Class 中使用该注解标志的变量, 会在页面被路由打开的时候, 在调用 inject() 后自动赋予传递的参数值.
@Interceptor
注解定义
- int priority();// 该拦截器的优先级
- String name() default "Default";// 该拦截器的名称, 用于产生 JavaDoc
实现 @Interceptor 注解
一般应用于 IInterceptor 的实现类, 是路由跳转过程中的拦截器, 不分 module, 应用全局.
- @Interceptor(priority = 7)
- public class Test1Interceptor implements IInterceptor {
- @Override
- public void process(final Postcard postcard, final InterceptorCallback callback) {
- ............
- }
- }
2. 收集
在编译期间自动生成映射文件, arouter-compiler 实现了一些注解处理器, 目标在于生成映射文件与辅助文件.
三种类型的注解处理器, 都实现了 AbstractProcessor, 主要功能如下:
首先通过注解处理器扫出被标注的类文件
按照不同种类的源文件进行分类
按照固定的命名格式生成映射文件
这样就可以在运行期初始化的时候通过固定的包名来加载映射文件.
关于注解处理的源码详解见阿里路由框架 --ARouter 源码解析之 Compiler.
以官方 demo 为例, 通过注解处理器, 按照固定的命名格式生成映射文件.
具体以 ARouter$$Root$$App 为例, 看下注解处理器生成的类文件的内容:
- public class ARouter$$Root$$App implements IRouteRoot {
- @Override
- public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
- routes.put("service", ARouter$$Group$$service.class);
- routes.put("test", ARouter$$Group$$test.class);
- }
- }
通过调用 loadInto() 方法将其管理的 group 类文件加载到集合中, 方便后续路由查找.
3. 加载
前面的收集都是在编译器处理获得的, 那么加载就是到了运行期. ARouter 为了避免内存和性能损耗, 提出了 "分组管理, 按需加载" 的方式. 在前面的编译处理的过程中, 已经按照不同种类生成对应的映射文件.
以官方 demo 为示例, 一个 App 模块有一个 Root 结点, 管理各个 Group 分组, 每个 Group 分组下有着多个界面; 此外 App 模块下还有着 Interceptor 结点, 以及 provider 结点.
其中 Interceptor 结点对应于自定义的拦截器, provider 结点对应于 IoC, 以实现跨模块 API 调用.
ARouter 在初始化的时候只会一次性地加载所有的 root 结点, 而不会加载任何一个 Group 结点, 这样就会极大地降低初始化时加载结点的数量. 当某一个分组下的某一个页面第一次被访问的时候, 整个分组的全部页面都会被加载进去.
初始加载
ARouter 其实是一个代理类, 它的所有函数实现都交给_ARouter 去实现, 两个都是单例模式.
- public static void init(Application application) {// 静态函数进行初始化, 不依赖对象
- if (!hasInit) {
- logger = _ARouter.logger; // 持有 日志打印的 全局静态标量
- _ARouter.logger.info(Consts.TAG, "ARouter init start.");// 打印 ARouter 初始化日志
- hasInit = _ARouter.init(application);// 移交 _ARouter 去 初始化
- if (hasInit) {
- _ARouter.afterInit();
- }
- _ARouter.logger.info(Consts.TAG, "ARouter init over.");// 打印 ARouter 初始化日志
- }
- }
继续看一下_ARouter 的初始化方法
- protected static synchronized boolean init(Application application) {
- mContext = application;// Application 的上下文
- LogisticsCenter.init(mContext, executor);// 移交逻辑中心进行初始化, 并传入线城池对象
- logger.info(Consts.TAG, "ARouter init success!");// 打印日志
- hasInit = true;// 标示是否初始化完成
- // It's not a good idea.
- // if (Build.VERSION.SDK_INT> Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- // application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
- // }
- return true;
- }
继续往下走, 看 LogisticsCenter 的初始化方法
- public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
- mContext = context; // 静态持有 Application 的上下文
- executor = tpe;// 静态持有 线城池
- try {
- // These class was generate by arouter-compiler.
- // 通过指定包名 com.alibaba.Android.arouter.routes, 找到所有 编译期产生的 routes 目录下的类名 (不包含装载类)
- List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
- for (String className : classFileNames) {// 组别列表 com.alibaba.Android.arouter.routes.ARouter\$\$Root
- 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)) {// 模块内的拦截器列表 com.alibaba.Android.arouter.routes.ARouter\$\$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)) {//IoC 的动作路由列表 com.alibaba.Android.arouter.routes.ARouter\$\$Providers
- // Load providerIndex
- ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
- }
- }
- 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() + "]");
- }
- }
通过上述代码, 实现了 "分组管理, 按需加载" 的方式, 加载了对应的三个注解处理器生成的类中管理的结点到路由集合中.
其中内存仓库 Warehouse 缓存了全局应用的组别的清单列表, IoC 的动作路由清单列表, 模块内的拦截器清单列表, 3 个 map 对象.
- class Warehouse {
- // Cache route and metas
- static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();// 组别的列表 包含了组名与对应组内的路由清单列表 Class 的映射关系
- static Map<String, RouteMeta> routes = new HashMap<>();// 组内的路由列表 包含了对应分组下的, 路由 URL 与目标对象 Class 的映射关系
- // Cache provider
- static Map<Class, IProvider> providers = new HashMap<>(); // 缓存 IoC 目标 class 与已经创建了的对象
- static Map<String, RouteMeta> providersIndex = new HashMap<>();//IoC 的动作路由列表包含了使用依赖注入方式的某 class 的 路由 URL 与 class 映射关系
- // Cache interceptor
- // 模块内的拦截器列表 包含了某个模块下的拦截器 与 优先级的映射关系
- static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
- static List<IInterceptor> interceptors = new ArrayList<>();// 已排序的拦截器实例对象
- }
四, 路由查找
ARouter.getInstance().build("/test/activity2").navigation();</pre>
以上述例子为例, 看一下 ARouter 路由查找的过程. 首先看一下 build 过程
- 1.build()
- public Postcard build(String path) {
- return _ARouter.getInstance().build(path);
- }
- 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));
- }
- }
其使用了代理类_ARouter 的 build() 并构建和返回 PostCard 对象. 一个 Postcard 对象就对应了一次路由请求, 作用于本次路由全过程.
这部分代码主要包含两个部分:
使用 IoC byType() 方式寻找 PathReplaceService.class 接口的实现类, 该实现类的作用就是实现 "运行期动态修改路由".
继续进行本次路由导航
首先来看一下 PathReplaceService.class 接口:
- public interface PathReplaceService extends IProvider {
- /**
- * For normal path.
- *
- * @param path raw path
- */
- String forString(String path);
- /**
- * For uri type.
- *
- * @param uri raw uri
- */
- Uri forUri(Uri uri);
- }
主要包含 forString() 和 forUri 两个方法, 针对路径进行预处理, 实现 "运行期动态修改路由".
接下下, 继续通过 build(path, extractGroup(path)) 进行路由导航, 其中 extractGroup() 是从路径中获取默认的分组信息.
然后 build() 方法会返回一个 Postcard 对象, 并把对应的路径和分组信息传入该对象.
分析完上面的过程, 下面来详细看下 PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class); 中的 navigation() 方法, 该方法实际调用了代理类_ARouter 的 navigation(Class<? extends T> service) 方法.
- 2.
- navigation(Class<? extends T> service)
- protected <T> T navigation(Class<? extends T> service) {
- try {
- Postcard postcard = LogisticsCenter.buildProvider(service.getName());
- // Compatible 1.0.5 compiler sdk.
- if (null == postcard) { // No service, or this service in old version.
- postcard = LogisticsCenter.buildProvider(service.getSimpleName());
- }
- LogisticsCenter.completion(postcard);
- return (T) postcard.getProvider();
- } catch (NoRouteFoundException ex) {
- logger.warning(Consts.TAG, ex.getMessage());
- return null;
- }
- }
首先 LogisticsCenter.buildProvider(service.getName()) 根据 Warehouse 保存的 providersIndex 的信息查找并构建返回一个 PostCard 对象
然后执行 LogisticsCenter.completion(postcard), 该方法会根据 Warehouse 保存的 routes 的路由信息完善 postcard 对象, 该方法在下面还会出现, 到时候具体介绍
再回到上文介绍 ARouter.getInstance().build("/test/activity2").navigation(), 返回 PostCard 对象后, 开始调用对应的 navigation() 方法.
3.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);
- }
- public void navigation(Activity mContext, int requestCode) {
- navigation(mContext, requestCode, null);
- }
- public void navigation(Activity mContext, int requestCode, NavigationCallback callback) {
- ARouter.getInstance().navigation(mContext, this, requestCode, callback);
- }
最终调用了 ARouter 中的 navigation() 方法, 在其中其实是调用了_ARouter 中的 navigation() 方法.
该方法包含查找回调的调用, 降级处理, 拦截器处理具体路由操作.
- protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
- try {
- LogisticsCenter.completion(postcard);
- } catch (NoRouteFoundException ex) {
- logger.warning(Consts.TAG, ex.getMessage());
- if (debuggable()) { // Show friendly tips for user.
- 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);
- }
- return null;
- }
其中最重要的两个方法就是 LogisticsCenter.completion() 和_navigation(), 下面详细介绍.
- public synchronized static void completion(Postcard postcard) {
- if (null == postcard) {
- throw new NoRouteFoundException(TAG + "No postcard!");
- }
- // 根据路径 URL 获取到路径元信息
- 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 {
- // 将该组的组内清单列表加入到内存仓库中, 并把组别移除
- 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); // 再次触发完善逻辑
- }
- } else {
- postcard.setDestination(routeMeta.getDestination());// 目标 class
- 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;
- }
- }
- }
该方法就是完善 PostCard, 来实现一次路由导航.
接下来介绍另一个方法_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:// 如果是 Acitvity, 则实现 Intent 跳转
- // 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);
- }
- // 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 ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
- ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
- }
- if (null != callback) { // Navigation over.
- callback.onArrival(postcard);
- }
- }
- });
- break;
- case PROVIDER:// 如果是 IoC, 则返回目标对象实例
- return postcard.getProvider();
- case BOARDCAST:
- case CONTENT_PROVIDER:
- case FRAGMENT:// 如果是 Fragment, 则返回实例, 并填充 bundle
- 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;
- }
至此我们就完成了一次路由跳转.
自己是从事了七年开发的 Android 工程师, 不少人私下问我, 2019 年 Android 进阶该怎么学, 方法有没有?
没错, 年初我花了一个多月的时间整理出来的学习资料, 希望能帮助那些想进阶提升 Android 开发, 却又不知道怎么进阶学习的朋友.[包括高级 UI, 性能优化, 架构师课程, NDK,Kotlin, 混合式开发 (ReactNative+Weex),Flutter 等架构技术资料] , 希望能帮助到您面试前的复习且找到一个好的工作, 也节省大家在网上搜索资料的时间来学习.
来源: http://www.jianshu.com/p/522540b2499c