ARouter源码解析(二)-- 路由跳转流程分析

文章目录

  • 路由跳转流程分析
    • ARouter.getInstance()
    • ARouter.build
    • _ARouter.build
      • _ARouter.extractGroup
    • _ARouter.build(String path, String group)
    • _ARouter.navigation
      • LogisticsCenter.completion
        • Warehouse
      • 完善Postcard信息
      • _ARouter._navigation
  • 总结

上一章中分析了ARouter初始化流程的源码,本章分析路由跳转流程。

路由跳转流程分析

ARouter.getInstance().build("/test/activity2").navigation();

引用ARouter demo项目中一段页面跳转代码。

  • ARouter.getInstance 获取ARouter单例对象
  • 调用ARouter.build("/test/activity2")"/test/activity2"参数是在@Route注解中配置path路径。返回值是一个Postcard对象。
  • 调用Postcard.navigation(),完成一次路由跳转。

就这么简短的一段代码就完成了一次路由操作,下面分析一下其中做了哪些事情。

ARouter.getInstance()

public static ARouter getInstance() {
	if (!hasInit) {
		throw new InitException("ARouter::Init::Invoke init(context) first!");
	} else {
		if (instance == null) {
			synchronized (ARouter.class) {
				if (instance == null) {
					instance = new ARouter();
				}
			}
		}
		return instance;
	}
}

采用双重锁来实例化ARouter单例对象。

ARouter.build

public Postcard build(String path) {
	return _ARouter.getInstance().build(path);
}
  • 返回值类型是Postcard,继承自RouteMeta,扩展了一些路由信息。它贯穿整个路由过程,一次路由就有对应的一个Postcard
  • 入参path是路由地址,与@Route注解中的path对应。需要确保唯一性
  • 调用了_ARouter.build方法

_ARouter.build

protected Postcard build(String path) {
	...
	//预留的一个前置处理path的服务类。可以实现该接口,对所有path做处理。
		PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
		if (null != pService) {
			path = pService.forString(path);
		}
		return build(path, extractGroup(path));
	...
}
  • PathReplaceService是框架中预留的一个Path地址处理服务,可以统一处理Path地址。获取服务流程在后面服务篇中详细讲解。
  • extractGroup(path)。根据path提取默认的group字段。

_ARouter.extractGroup

path中提取默认group逻辑。当在@Route中设置了group字段时,发起路由时需要填入设置的字段。

private String extractGroup(String path) {
	...
		String defaultGroup = path.substring(1, path.indexOf("/", 1));
		if (TextUtils.isEmpty(defaultGroup)) {
			throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!");
		} else {
			return defaultGroup;
		}
	}
	...
}
  • 校验路由path的格式
  • 需要'/'开头,并且至少有两个'/'。不然会抛出异常。

_ARouter.build(String path, String group)

调到生成Postcard的方法。


protected Postcard build(String path, String group) {
	...
	//对path做二次处理
		PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
		if (null != pService) {
			path = pService.forString(path);
		}
		return new Postcard(path, group);
	...
}
  • 创建了一个Postcard对象,后续的路由都跟这个对象有关。

_ARouter.navigation

分析完build流程,接下来看看navigation方法做了哪些操作。在build流程中生成Postcard对象,直接看Postcard.navigation方法。经过一连串的调用,可以看到最后调用了_ARouter.navigation。很长的一段逻辑,拆分成几段来分析

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) 
  • 返回类型是Object,可以看出有很多不同类型的返回类型包括了Fragment,自定义的服务类(实现了IProvider接口,可以做一些通用的服务)
  • 参数中可以看一下NavigationCallback这个接口。监听整个路由过程。
public interface NavigationCallback {
    void onFound(Postcard postcard);
    void onLost(Postcard postcard);
    void onArrival(Postcard postcard);
    void onInterrupt(Postcard postcard);
}

看方法名就可以很清晰的看出来回调的时机了。
OK,继续下面的代码。

PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
	// Pretreatment failed, navigation canceled.
	return null;
}
  • PretreatmentService类是框架中预留的一个服务类(需要自己实现),可以在路由前做一些操作,比如可以cancel掉这次的路由跳转。触发时机比拦截器更早。
//完成对Postcard的最终封装
try {
	LogisticsCenter.completion(postcard);
}catch (NoRouteFoundException ex) {
	...
	if (null != callback) {
		callback.onLost(postcard);
	} else {
		// 对没有实现NavigationCallback接口的路由操作,可以统一有一个降级操作
		DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
		if (null != degradeService) {
			degradeService.onLost(context, postcard);
		}
	}
}

这段代码主要两个关键点:

  • LogisticsCenter.completion(postcard) 。在这个方法中完成了对Postcard的最终封装,对后面路由操作起到了最关重要的作用,下面会详细分析。
  • DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class) 这个服务可以做到对项目中的所有没有监听的路由操作做降级操作。比如说路由地址写错了,这在运营配置跳转地址的时候很容易发生,这个时候可以通过这个服务做一个容错处理,不会导致应用出现异常。
if (!postcard.isGreenChannel()) {
	// 拦截器在异步中执行,避免耗时操作导致ANR
	interceptorService.doInterceptions(postcard, new InterceptorCallback() {
			public void onContinue(Postcard postcard) {
				_navigation(context, postcard, requestCode, callback);
			}
			@Override
			public void onInterrupt(Throwable exception) {
				...
			}
		}
	);
} else {
	return _navigation(context, postcard, requestCode, callback);
}
  • postcard.isGreenChannel() 判断这个路由操作是否走绿色通道,也就是不受拦截器的处理。默认的获取Fragment实例和Provider是不受拦截器拦截的。当然也可以自己设置走绿色通过。
  • 需要注意的是自定义拦截器中的操作都是在 异步线程 中完成的。执行完之后的回调是在主线程处理的。
  • InterceptorCallback 接口中 onContinue是执行最开始的路由操作(也可能会有改动)。onInterrupt 中表示被拦截器拦截,cancel掉了最开始的路由操作。拦截器的详细处理后面章节会详细分析。
    最后让我们来看一下 LogisticsCenter.completion 方法是如何完成对Postcard的封装的。

LogisticsCenter.completion

在分析这段逻辑前,先看一下 Warehouse 数据类的组成。

Warehouse

class Warehouse {
    // Cache route and metas
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    static Map<String, RouteMeta> routes = new HashMap<>();

    // Cache provider
    static Map<Class, IProvider> providers = new HashMap<>();
    static Map<String, RouteMeta> providersIndex = new HashMap<>();

    // 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<>();

    static void clear() {
        routes.clear();
        groupsIndex.clear();
        providers.clear();
        providersIndex.clear();
        interceptors.clear();
        interceptorsIndex.clear();
    }
}

Warehouse类里面代码不多,我们重点分析一下groupsIndex变量。其他变量都是同一个逻辑。
先找到groupsIndex添加元素的地方。

//伪代码
LogisticsCenter.init{
	...
	Set<String> routerMap;
	...
	//通过遍历dex文件中com.alibaba.android.arouter.routes包名下的类,获取到对应的全类名
	routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
	...
	for (String className : routerMap) {
		...
		//在loadInto中对Warehouse.groupsIndex添加元素
		((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
	}
}
  • 可以看到IRouteRoot是一个接口,来看一下具体的实现类有哪些(基于ARouter框架GitHub上的demo项目)
    ARouter源码解析(二)-- 路由跳转流程分析_第1张图片
    都是实现了IRouteRoot接口的类。类名的最后一段字符串就是项目中每个模块的模块名。app就是application库所在的模块的模块名。四个类就对应项目中的四个模块。类生成的规则在后续章节会详细讲解。
    中间的字符串Root代表的就是这个类实现的接口类型。

挑第一个ARouter$$Root$$app类来看一下里面的代码。

public class ARouter$$Root$$app implements IRouteRoot {
  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("test", ARouter$$Group$$test.class);
    routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
  }
}
  • 先看入参,routes就是这个小节要分析的Warehouse.groupsIndex变量。
  • 代码很简单就是传入了key-value到Warehouse.groupsIndex变量中。

里面的key-value该如何理解呢?
其实这里的key对应的就是注解@Route中path字段所对应的group值。value就是这个group对应的类,是一个容器类,模块内的group必须是唯一的。从这里就是可以看出来Warehouse.groupsIndex变量存储的是项目中所有模块的分组信息。

接下来看一下ARouter$$Group$$test类里面做了什么处理。

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
    atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", null, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
  }
}

这里把所有的标注了注解@Route的类汇集到了一起。把信息都汇总到RouteMeta中,包括了类型,具体的类对象,路由路径,分组信息,传递的参数信息,优先级,以及跳转动画
可以看到所有的路由对象信息都汇总到了atlas这个入参中,那么这个入参是从哪里传入的呢?回到Warehouse类中,看变量名,先猜测一下atlas这个入参所对应的变量应该就是routes了(下面分析来看,其实就是这个)

其他变量不难看出:

  • providers和providersIndex肯定是项目中服务类信息的汇总。
  • interceptorsIndex和interceptors是项目中拦截器类信息的汇总。
  • 说明一下这两者跟前面的groupsIndexroutes还是略有不同的。不同的下文会详细讲到。

所以Warehouse类其实就是一个容器类,包含了所有的路由信息(路由跳转,服务类,拦截器)。可以通过Warehouse拿到不同类型的路由信息。

完善Postcard信息

RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());

从上文分析Warehouse可以看出Warehouse.routes中拿到path对应的路由跳转对象信息。

if (null == routeMeta) {
	// 当前路由信息可能不存在或者未加载
	Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());
	...
		// 加载路由信息到内存中
		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);
		// 重新完善Postcard信息
	..
}

如果获取到的路由跳转对象缓存是null就从新解析一遍。

  • 首先是根据group拿到groupMeta对象。如果为空抛出异常
  • 通过调用无参构造器实例化groupMeta对象。执行loadInto方法。此时所有的路由跳转对象信息都存到了Warehouse.routes中。
  • 移除Warehouse.groupsIndexgroupMeta对应的组对象。减少内存占用。
  • 递归重新执行一遍当前逻辑。
// 实际类对象
postcard.setDestination(routeMeta.getDestination());
// 路由对象类型。可以在RouteType枚举类中看到所有类型
postcard.setType(routeMeta.getType());
//优先级
postcard.setPriority(routeMeta.getPriority());
//额外字段
postcard.setExtra(routeMeta.getExtra());
//通过uri来设置额外字段
Uri rawUri = postcard.getUri();
if (null != rawUri) {
	...
	}
	// Save raw uri
	postcard.withString(ARouter.RAW_URI, rawUri.toString());
}
  • routeMeta中路由对象信息存入到Postcard中。注释中列出了详细信息。
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;
}
  • case PROVIDER 中根据routeMeta中的Destination对象来创建IProvider(服务类,拦截器实例都在这被创建)
    • 需要注意的是这里的mContext是在初始化中传入的Application对象。
    • 设置了greenChannel绿色通道
  • case FRAGMENT 中也设置greenChannel绿色通道

_ARouter._navigation

最终路由执行的方法

switch (postcard.getType()) {
	case ACTIVITY:
	// Build intent
		...
	// Navigation in main looper.
		runInMainThread(
			...
		);
		break;
	case PROVIDER:
		return postcard.getProvider();
	case BOARDCAST:
	case CONTENT_PROVIDER:
	case FRAGMENT:
		Class fragmentMeta = postcard.getDestination();
		try {
			Object instance = fragmentMeta.getConstructor().newInstance();
			//设置Fragment参数
			...
			return instance;
		}
		catch (Exception ex) {
			logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
		}
	...
}

根据不同的postcard.getType来做不同的路由操作。

  • case ACTIVITY 当前路由类型是activity时,会执行页面跳转操作。有几点需要注意的。
    • currentContext不是activity类型时,会设置Flag Intent.FLAG_ACTIVITY_NEW_TASK)
    • 虽然拦截器中的逻辑是异步执行的,但是最后触发跳转仍时在主线程中。
  • case PROVIDER 返回provider实例对象。
  • case BOARDCAST ;case CONTENT_PROVIDER ;case FRAGMENT 这个三种类型会调用无参构造器创建实例对象,并返回。
    • fragment中传递参数的话跟activity是一致的。

总结

到此ARouter整个路由过程就分析完了。可以看到中间涉及到了很多服务拦截器的调用,后续文章就来分析一下 对这些服务和拦截器会分析。

对于文中有疑惑的地方,或者有任何意见和建议的地方都可以评论留言,我会第一时间回复~与君共勉。

上一篇:ARouter源码解析(一)-- 初始化分析
下一篇:ARouter源码解析(三)-- Provider和Interceptor源码分析

你可能感兴趣的:(源码分析)