ARouter原理解析之路由跳转

前言

我们在上一篇分析了ARouter注解解析器相关源码;ARouter原理解析之注解处理器,了解了ARouter通过APT技术在编译期动态生成代码以及其用途,这一篇我们着重分析下ARouter是如何实现activity之间路由跳转;

路由跳转源码分析

初始化工作

我们先找到ARouter的入口,也就是初始化的地方,对应ARouter.init

   /**
     * Init, it must be call before used router.
     */
    public static void init(Application application) {
    	//hasInit用于保证代码只初始化一次
        if (!hasInit) {
        	//logger就是一个工具类,咱不需要怎么关注
            logger = _ARouter.logger;
            _ARouter.logger.info(Consts.TAG, "ARouter init start.");
            hasInit = _ARouter.init(application);

            if (hasInit) {
                _ARouter.afterInit();
            }

            _ARouter.logger.info(Consts.TAG, "ARouter init over.");
        }
    }

从上面可以看出ARouter只是对外暴露的API类,_ARouter是其真正的实现类,这样设计是采用了装饰模式,一方面是为了解耦,另一方面还包装了日志打印功能,同时也隐藏了内部实现,相较于private修饰,这种方式灵活性更强;

接着我们进入实现类,跟下_ARouter.init(application)这段代码;

   protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor); //实际初始化的地方
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;
        mHandler = new Handler(Looper.getMainLooper());

        return true;
    }

上面代码中,重点关注LogisticsCenter.init(mContext, executor)这行,其中executor是内部自定义的线程池;

    /**
     * LogisticsCenter init, load all metas in memory. Demand initialization
     */
    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        ...
        Set<String> routerMap; //生成类的类名集合

   		//如果是debug版本或者是新版本,从apt生成的包中加载类
        if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
           	//ClassUtils.getFileNameByPackageName 是根据包名查找对应包名下的类;
            routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
            if (!routerMap.isEmpty()) {
            	//如果查找的map集合不为空,则缓存至sp中
                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 {
            //否则从sp缓存中读取类名
            routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
        }
		//判断类型,使用反射实例化对象,并调用方法,遍历获取到的 class
        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);
            }
        }
        ...

    }

上面代码中,如果没有开启debug模式,调试的过程中一般不会更改版本号,那么只会从之前的缓存中读取类信息,这样新添加的路由就不会加载到内存中,这点需要注意;

其中ROUTE_ROOT_PAKCAGE是一个常量,是编译期间使用注解动态生成的目录;

    public static final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes";

ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE)方法就是找到app的dex,然后遍历出属于com.alibaba.android.arouter.routes包名下的所有类,打包成集合并返回;
可以想象遍历整个dex查找指定类名工作量何其之大,怎么优化呢?ARouter的arouter-gradle-plugin模块中就使用ASM字节码插桩来解决这个非常消耗性能的问题;这一块不是本文的重点,这里一笔带过,有兴趣的小伙伴可以深入研究下。

上面代码可以简单总结下,初始化就是查找ROUTE_ROOT_PAKCAGE包名下的类,获取实例并强化成IRouteRoot, IInterceptorGroup, IProviderGroup, 然后调用 loadInto 方法;

对应具体的apt生成的代码如下:

/ ARouter$$Root$$app.java
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$app implements IRouteRoot {
    public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
        // 以分组做为 key, 缓存到 routes. 
        // routes 是指向 Warehouse.groupsIndex 的引用
        routes.put("service", ARouter$$Group$$service.class);
        routes.put("test", ARouter$$Group$$test.class);
    }
}

// ARouter$$Group$$service.java
/**
 * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$service implements IRouteGroup {
    @Override
    public void loadInto(Map<String, RouteMeta> atlas) {
        atlas.put("/service/hello", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648));
        ...
    }
}

小结 自此初始化工作完成,总结一下ARouter会在编译期根据注解声明分析自动生成一些注入代码, 初始化工作主要是把这些注解的对象和对注解的配置缓存到 Warehouse 的静态对象中;

跳转源码分析

我们还是先从方法入口进行分析,我们通常使用ARouter.getInstance().build("/test/activity2").navigation();这种方式进行activity跳转;我们跟进下ARouter类的build方法

	//实际调用 _ARouter单例的build方法,返回一个Postcard对象!
    public Postcard build(String path) {
        return _ARouter.getInstance().build(path);
    }

继续跟进 _ARouter.getInstance().build(path)

   protected Postcard build(String path) {
        if (TextUtils.isEmpty(path)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
        	//根据path获取group,group模式是path中第一部分内容,例如 path = /test/activity1, group就是test,如果是手动申明group的,一定要手动传递,否则找不到
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return build(path, extractGroup(path), true);
        }
    }

这里就是直接返回了Postcard对象,里面保存了pathgroup信息,Postcard是继承RouteMeta类,我们直接看核心路由代码navigation()方法的实现;

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
  			  ...
        // 如果传入的context为空,则使用mContext(初始化时的applicationContext)
        postcard.setContext(null == context ? mContext : context);

        try {
        	//验证能否找到对应的postcard.path
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
          	...
            if (null != callback) {
            	//如果没找到postcard的配置,就调用onLost回调方法,或者系统配置的"降级服务"回调
                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;
        }
		...
    }

我们来看下ogisticsCenter.completion(postcard)是如何验证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!");
        }
           // 通过postcard 的 path 查找对应的 RouteMeta
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {
          // 如果没找到, 可能是还没装载过, 需要根据 group 查找对应的 groups
            if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
            	// 如果没找到, 抛出 NoRouteFoundException 错误方法结束
                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 {
                	//动态加载路由组,重新调用completion方法,此时对应的group已经缓存完成
                    addRouteGroupDynamic(postcard.getGroup(), null);
                    completion(postcard);   // Reload
            }
        } else {
      	  // 可以查找到 routeMeta, copy routeMeta 的原始数据到 postcard 中.
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());
			...
            switch (routeMeta.getType()) {
                case PROVIDER:  
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    // 初始化 provider 对象, 并调用初始化方法, 缓存到Warehouse.providers中.
                    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());
                        }
                    }
                      // 设置一个provider 引用
                    postcard.setProvider(instance);
                      // provider 默认设置跳过拦截器
                    postcard.greenChannel();    
                    break;
                case FRAGMENT:
                  // fragment 默认设置跳过拦截器
                    postcard.greenChannel();    // Fragment needn't interceptors
                default:
                    break;
            }
        }
    }

completion方法主要是对 group 做一次懒式加载, 我们继续查看 navigation 方法下面的内容;

  protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    ...
    // 执行到这里就是找到了 postcard. 执行对应回调
    if (null != callback) {
        callback.onFound(postcard);
    }

    // 如果是"绿色通道", 则直接执行_navigation方法, 目前只有provider, fragment 默认是走绿色通道
    if (!postcard.isGreenChannel()) { 
        // interceptorService 是 ARouter 配置的默认的拦截服务
        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);
    }
    return null;
 }

interceptorServiceARouter配置的默认的拦截器,其实就是递归调用,当所有的拦截器执行完,重新回到_navigation方法;

public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
    // 用线程池异步执行
    LogisticsCenter.executor.execute(new Runnable() {
        public void run() {
            // 初始化一个同步计数类, 用拦截器的 size
            CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
            try {
                // 执行拦截操作, 看到这猜是递归调用.直到 index 满足条件, 退出递归.
                _excute(0, interceptorCounter, postcard);
            
                // 线程等待计数完成, 等待300秒...
                interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);

                // 判断退出等待后的一些状态...
                if (interceptorCounter.getCount() > 0) { 
                    callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                } else if (null != postcard.getTag()) { 
                    callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
                } else {

                    // 没有问题, 继续执行
                    callback.onContinue(postcard);
                }
            } catch (Exception e) {

                // 中断
                callback.onInterrupt(e);
            }
        }
    });
}

我们继续跟进_navigation方法

private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    final Context currentContext = null == context ? mContext : context;

    // 我们就先只分析 activity 的逻辑
    switch (postcard.getType()) {
    case ACTIVITY:
        // 初始化 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)) { 
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        }

         // 在 UI 线程进行 start activity
        new Handler(Looper.getMainLooper()).post(new Runnable() {
            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());
                }

                if (null != callback) { // Navigation over.
                    callback.onArrival(postcard);
                }
            }
        });
        break;
    }
    return null;
}

至此,activity路由跳转功能实现~~

总结

ARouter 初始化的时候会把注入的信息进行缓存到Warehouse中,在进行 navigation 的时候, 根据缓存进行懒加载, 然后获取实际对象或者跳转activity;

结语

如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值