Android学习之路(22) ARouter原理解析

1.ARouter认知

首先我们从命名来看:ARouter翻译过来就是一个路由器

官方定义

一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦

那么什么是路由呢? 简单理解就是:一个公共平台转发系统

工作方式:

arouter原理.png

  • 1.注册服务:将我们需要对外暴露的页面或者服务注册到ARouter公共平台中
  • 2.调用服务:调用ARouter的接口,传入地址和参数,ARouter解析传入的地址和参数转发到对应的服务中

通过ARouter形成了一个无接触解耦的调用过程

2.ARouter架构解析

我们来看下ARouter的源码架构:

ARouter源码架构.png

  • app:是ARouter提供的一个测试Demo
  • arouter-annotation:这个lib模块中声明了很多注解信息和一些枚举类
  • arouter-api:ARouter的核心api,转换过程的核心操作都在这个模块里面
  • arouter-compiler:APT处理器,自动生成路由表的过程就是在这里面实现的
  • arouter-gradle-plugin:这是一个编译期使用的Plugin插件,主要作用是用于编译器自动加载路由表,节省应用的启动时间。

3.原理讲解

这里我们不会一开始就大篇幅对源码进行讲解: 我们先来介绍ARouter中的几个重要概念:有了这几个概念,后面在去看源码就会轻松多了

前置基础概念:

概念1:PostCard(明信片)

既然是明信片要将信件寄到目的人的手上就至少需要:收件人的姓名和地址,寄件人以及电话和地址等

ARouter就是使用PostCard这个类来存储寄件人和收件人信息的。

java
复制代码
public final class Postcard extends RouteMeta {// Baseprivate Uri uri; //如果使用Uri方式发起luyouprivate Object tag;             // A tag prepare for some thing wrong. inner params, DO NOT USE!private Bundle mBundle;         // 需要传递的参数使用bundle存储private int flags = 0;         // 启动Activity的标志:如NEW_FALGprivate int timeout = 300;      // 路由超时private IProvider provider;     // 使用IProvider的方式跳转private boolean greenChannel;	//绿色通道,可以不经过拦截器private SerializationService serializationService; //序列化服务serializationService:需要传递Object自定义类型对象,就需要实现这个服务private Context context;        // May application or activity, check instance type before use it.private String action;			//Activity跳转的Action// Animationprivate Bundle optionsCompat;    // The transition animation of activityprivate int enterAnim = -1;private int exitAnim = -1;...
}

PostCard继承了RouteMeta

java
复制代码
public class RouteMeta {private RouteType type;         // 路由类型:如Activity,Fragment,Provider等private Element rawType;        // 路由原始类型,在编译时用来判断private Class<?> destination;   // 目的Class对象private String path;            // 路由注册的pathprivate String group;           // 路由注册的group分组private int priority = -1;      // 路由执行优先级,priority越低,优先级越高,这个一般在拦截器中使用private int extra;              // Extra dataprivate Map<String, Integer> paramsType;  //  参数类型,例如activity中使用@Autowired的参数类型private String name; //路由名字,用于生成javadocprivate Map<String, Autowired> injectConfig;  // 参数配置(对应paramsType).}

RouteMeta:主要存储的是一些目的对象的信息,这些对象是在路由注册的时候才会生成。

概念2:Interceptor拦截器

了解OkHttp的都知道,其内部调用过程就是使用的拦截器模式,每个拦截器执行的对应的任务。

ARouter中也是如此,所有的路由调用过程在到达目的地前都会先经过自定义的一系列拦截器,实现一些AOP切面编程。

java
复制代码
public interface IInterceptor extends IProvider {/*** The operation of this interceptor.** @param postcard meta* @param callback cb*/void process(Postcard postcard, InterceptorCallback callback);
}

IInterceptor是一个接口,继承了IProvider,所以其也是一个服务类型

只需要实现process方法就可以实现拦截操作。

概念3:greenChannel:绿色通道

设置了绿色通道的跳转过程,可以不经过拦截器

概念4:Warehouse:路由仓库

Warehouse意为仓库,用于存放被 @Route、@Interceptor注释的 路由相关的信息,也就是我们关注的destination等信息

举个例子:

moduleB发起路由跳转到moduleA的activity,moduleB没有依赖moduleA,只是在moduleA的activity上增加了@Route注解。 由于进行activity跳转需要目标Activity的class对象来构建intent,所以必须有一个中间人,把路径"/test/activity"翻译成Activity的class对象,然后moduleB才能实现跳转。(因此在ARouter的使用中 moduleA、moduleB 都是需要依赖 arouter-api的)

这个中间人那就是ARouter了,而这个翻译工所作用到的词典就是Warehouse,它存着所有路由信息。

java
复制代码
class Warehouse {//所有IRouteGroup实现类的class对象,是在ARouter初始化中赋值,key是path第一级//(IRouteGroup实现类是编译时生成,代表一个组,即path第一级相同的所有路由,包括Activity和Provider服务)static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>(); //所有路由元信息,是在completion中赋值,key是path//首次进行某个路由时就会加载整个group的路由,即IRouteGroup实现类中所有路由信息。包括Activity和Provider服务static Map<String, RouteMeta> routes = new HashMap<>();//所有服务provider实例,在completion中赋值,key是IProvider实现类的classstatic Map<Class, IProvider> providers = new HashMap<>();//所有provider服务的元信息(实现类的class对象),是在ARouter初始化中赋值,key是IProvider实现类的全类名。//主要用于使用IProvider实现类的class发起的获取服务的路由,例如ARouter.getInstance().navigation(HelloService.class)static Map<String, RouteMeta> providersIndex = new HashMap<>();//所有拦截器实现类的class对象,是在ARouter初始化时收集到,key是优先级static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("...");//所有拦截器实例,是在ARouter初始化完成后立即创建static List<IInterceptor> interceptors = new ArrayList<>();
...
}

Warehouse存了哪些信息呢?

  • groupsIndex:存储所有路由组元信息:

1661149556811.jpg

js
复制代码
key:group的名称
value:路由组的模块类class类:
赋值时机:初始化的时候
  • routes:存储所有路由元信息。切记和上面路由组分开,路由是单个路由,路由组是一批次路由

routes.png

js
复制代码
key:路由的path
value:路由元信息
赋值时机:LogisticsCenter.completion中赋值
备注:首次进行某个路由时就会加载整个group的路由,即IRouteGroup实现类中所有路由信息。包括Activity和Provider服务
  • providers:存储所有服务provider实例。
js
复制代码
key:IProvider实现类的class
value:IProvider实例
赋值时机:在LogisticsCenter.completion中赋值
  • providersIndex:存储所有provider服务元信息(实现类的class对象)。

provider.png

js
复制代码
key:IProvider实现类的全类名
value:provider服务元信息
赋值时机:ARouter初始化中赋值。
备注:用于使用IProvider实现类class发起的获取服务的路由,例如ARouter.getInstance().navigation(HelloService.class)
  • interceptorsIndex:存储所有拦截器实现类class对象。

Interceptor.png

js
复制代码
key:优先级
value:所有拦截器实现类class对象
赋值时机:是在ARouter初始化时收集到
  • interceptors,所有拦截器实例。是在ARouter初始化完成后立即创建

其中groupsIndex、providersIndex、interceptorsIndex是ARouter初始化时就准备好的基础信息,为业务中随时发起路由操作(Activity跳转、服务获取、拦截器处理)做好准备。

概念5:APT注解处理器

ARouter使用注解处理器,自动生成路由帮助类: 我们使用ARouter编译后,会在对应模块下自动生成以下类: 这些类的生成规则都是通过APT在编译器自动生成的,关于APT在ARouter中的使用方式,后面会单独拿一节出来讲解:

  • Android开源系列-组件化框架Arouter-(三)APT技术详解
概念6:AGP插件

ARouter使用了一个可选插件:“com.alibaba:arouter-register:1.0.2” 使用这个插件可以在编译器在包中自动检测以及加载路由表信息,而不需要在运行启动阶段再使用包名去dex文件中加载,提高app启动效率 关于这块的,后面会在:

  • Android开源系列-组件化框架Arouter-(四)AGP插件详解

有了以上几个概念做基础现在我们再到源码中去看看ARouter是如何跨模块运行起来的

源码分析:

首先我们来看路由过程:

  • 步骤1:初始化ARouter
js
复制代码
ARouter.init(this)
  • 步骤2:注册Activity路由
js
复制代码
@Route(path = "/test/activity1", name = "测试用 Activity")
public class Test1Activity extends BaseActivity {@Autowiredint age = 10;protected void onCreate(Bundle savedInstanceState) {ARouter.getInstance().inject(this);}
}
  • 步骤3:通过path启动对应的Activity
js
复制代码
ARouter.getInstance().build("/test/activity2").navigation();

下面我们分别来分析以上过程:

步骤1分析:ARouter.init(this)
java
复制代码
/*** Init, it must be call before used router.*/
public static void init(Application application) {if (!hasInit) {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同名init方法,进入看看

java
复制代码
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;
}

内部初始化了一些mContext,mHandler以及字段信息 最重要的是LogisticsCenter.init(mContext, executor):这句 进入看看:

java
复制代码
/*** LogisticsCenter init, load all metas in memory. Demand initialization*/
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {try {//使用AGP插件进行路由表的自动加载loadRouterMap();//如果registerByPlugin被设置为true,说明使用的是插件加载,直接跳过if (registerByPlugin) {logger.info(TAG, "Load router map by arouter-auto-register plugin.");} else {//如果是false,则调用下面步骤加载Set<String> routerMap;// 如果是debug模式或者是新版本的,则每次都会去加载routerMap,这会是一个耗时操作if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {logger.info(TAG, "Run with debug mode or new install, rebuild router map.");// These class was generated by arouter-compiler.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);    // Save new version name when router map update finishes.} else {//如果是其他的情况,则直接去文件中读取。logger.info(TAG, "Load router map from cache.");routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));}//这里循环获取routerMap中的信息for (String className : routerMap) {//如果className = "com.alibaba.android.arouter.routes.ARouter$$Root"格式,则将路由组信息添加到Warehouse.groupsIndex中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);//如果className = "com.alibaba.android.arouter.routes.ARouter$$Interceptors"格式,则将拦截器信息添加到Warehouse.interceptorsIndex中} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {// Load interceptorMeta((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);//如果className = "com.alibaba.android.arouter.routes.ARouter$$Providers"格式,则将服务Provider信息添加到Warehouse.providersIndex中} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {// Load providerIndex((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);}}}} catch (Exception e) {throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");}
}

总结_ARouter的init操作:

  • 1.优先使用插件加载路由表信息到仓库中,如果没有使用插件,则使用包名com.alibaba.android.arouter.routes去dex文件中查找对应的类对象 查找到后,保存到sp文件中,非debug或者新版本的情况下,下次就直接使用sp文件中缓存的类信息即可。
  • 2.查找到对应的类文件后,使用反射调用对应的类的loadInto方法,将路由组,拦截器以及服务Provider信息加载到Warehouse仓库中

继续看init方法中给的_ARouter.afterInit

java
复制代码
static void afterInit() {// Trigger interceptor init, use byName.interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}

找到/arouter/service/interceptor注解处

java
复制代码
@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService 

这里给ARouter创建了一个InterceptorServiceImpl服务的实例对象,后面讲到拦截器的时候会用到

步骤2分析:注册Activity路由

我们注册的Activity,Provider等路由信息,会在编译器被注解处理器处理后生成对应的路由表:

路由表在步骤1中ARouter初始化的时候被加载到Warehouse

步骤3分析:通过path启动对应的Activity
java
复制代码
ARouter.getInstance().build("/test/activity2").navigation();

这里我们拆分成三个部分:getInstancebuildnavigation

  • 3.1:getInstance
java
复制代码
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;}
}

做了init检查并创建了一个ARouter对象

  • 3.2:build
java
复制代码
public Postcard build(String path) {return _ARouter.getInstance().build(path);
}
调用了_ARouter的同名build方法
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), true);}
}
1.使用PathReplaceService,可以替换原path为新的path
继续看build方法:
protected Postcard build(String path, String group, Boolean afterReplace) {if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {throw new HandlerException(Consts.TAG + "Parameter is invalid!");} else {if (!afterReplace) {PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);if (null != pService) {path = pService.forString(path);}}return new Postcard(path, group);}
}

看到这里创建了一个Postcard,传入path和group,对Postcard前面有讲解,这里不再重复

  • 3.3:navigation

最后会走到_ARouter中的同名navigation方法中:

java
复制代码
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {//预处理服务PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {// Pretreatment failed, navigation canceled.return null;}try {//完善PostCard信息 留个点1LogisticsCenter.completion(postcard);} catch (NoRouteFoundException ex) {logger.warning(Consts.TAG, ex.getMessage());if (debuggable()) {// Show friendly tips for user.runInMainThread(new Runnable() {@Overridepublic void run() {Toast.makeText(mContext, "There's no route matched!\n" +" Path = [" + postcard.getPath() + "]\n" +" Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();}});}//没有找到路由信息,则直接返回callback.onLostif (null != callback) {callback.onLost(postcard);} else {// 没有callback则调用全局降级服务DegradeService的onLost方法DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);if (null != degradeService) {degradeService.onLost(context, postcard);}}return null;}//回调callback.onFound提醒用户已经找到pathif (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*/@Overridepublic void onContinue(Postcard postcard) {_navigation(postcard, requestCode, callback);}/*** Interrupt process, pipeline will be destory when this method called.** @param exception Reson of interrupt.*/@Overridepublic void onInterrupt(Throwable exception) {if (null != callback) {callback.onInterrupt(postcard);}logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());}});} else {//绿色通道直接调用_navigationreturn _navigation(postcard, requestCode, callback);}return null;
}

方法任务:

  • 1.预处理服务
  • 2.完善PostCard信息
  • 3.如果是非绿色通道,则使用拦截器处理请求
  • 4.调用_navigation处理

这里我们看下第3点:拦截器处理

java
复制代码
interceptorService.doInterceptions{public void onContinue(Postcard postcard) {_navigation(postcard, requestCode, callback);}public void onInterrupt(Throwable exception) {if (null != callback) {callback.onInterrupt(postcard);}}
}

如果被拦截回调callback.onInterrupt 如果没有就执行_navigation方法

进入interceptorService.doInterceptions看下:

前面分析过interceptorService是InterceptorServiceImpl对象

java
复制代码
@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {private static boolean interceptorHasInit;private static final Object interceptorInitLock = new Object();@Overridepublic void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {checkInterceptorsInitStatus();if (!interceptorHasInit) {callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));return;}LogisticsCenter.executor.execute(new Runnable() {@Overridepublic void run() {//使用CancelableCountDownLatch计数器CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());try {_execute(0, interceptorCounter, postcard);interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);if (interceptorCounter.getCount() > 0) {    // Cancel the navigation this time, if it hasn't return anythings.//拦截器处理超时callback.onInterrupt(new HandlerException("The interceptor processing timed out."));} else if (null != postcard.getTag()) {    // Maybe some exception in the tag.//拦截器过程出现异常callback.onInterrupt((Throwable) postcard.getTag());} else {//继续执行下面任务onContinuecallback.onContinue(postcard);}} catch (Exception e) {callback.onInterrupt(e);}}});} else {callback.onContinue(postcard);}}private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {if (index < Warehouse.interceptors.size()) {IInterceptor iInterceptor = Warehouse.interceptors.get(index);iInterceptor.process(postcard, new InterceptorCallback() {@Overridepublic void onContinue(Postcard postcard) {// Last interceptor excute over with no exception.counter.countDown();//递归调用_execute执行拦截器_execute(index + 1, counter, postcard);  // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.}@Overridepublic void onInterrupt(Throwable exception) {// Last interceptor execute over with fatal exception.postcard.setTag(null == exception ? new HandlerException("No message.") : exception);    // save the exception message for backup.counter.cancel();// Be attention, maybe the thread in callback has been changed,// then the catch block(L207) will be invalid.// The worst is the thread changed to main thread, then the app will be crash, if you throw this exception!
//                    if (!Looper.getMainLooper().equals(Looper.myLooper())) {    // You shouldn't throw the exception if the thread is main thread.
//                        throw new HandlerException(exception.getMessage());
//                    }}});}}
}

拦截器总结:

  • 1.使用计数器对拦截器技术,执行开始计数器+1,执行结束计数器-1,如果拦截器执行时间到,计数器数大于0,则说明还有未执行完成的拦截器,这个时候就超时了退出
  • 2.拦截器执行使用递归的方式进行
  • 3.拦截器执行完成继续执行_navigation方法

我们来看_navigation方法:

java
复制代码
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {final Context currentContext = postcard.getContext();switch (postcard.getType()) {case ACTIVITY:// Build intentfinal Intent intent = new Intent(currentContext, postcard.getDestination());intent.putExtras(postcard.getExtras());// Set flags.int flags = postcard.getFlags();if (0 != flags) {intent.setFlags(flags);}// Non activity, need FLAG_ACTIVITY_NEW_TASKif (!(currentContext instanceof Activity)) {intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);}// Set ActionsString action = postcard.getAction();if (!TextUtils.isEmpty(action)) {intent.setAction(action);}// Navigation in main looper.runInMainThread(new Runnable() {@Overridepublic void run() {startActivity(requestCode, currentContext, intent, postcard, callback);}});break;case PROVIDER:return postcard.getProvider();case BOARDCAST:case CONTENT_PROVIDER:case FRAGMENT: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;
}

这个方法其实就是根据PostCardtype来处理不同的请求了

  • 1.Activity,直接跳转
  • 2.Fragment,Provider,BroadcaseReceiver和ContentProvider,直接返回类的实例对象。

整个过程我们就基本了解了。 上面还留了一个点:

留的点1ARouter是如何完善PostCard信息

看LogisticsCenter.completion(postcard);

进入这个方法:

java
复制代码
public synchronized static void completion(Postcard postcard) {if (null == postcard) {throw new NoRouteFoundException(TAG + "No postcard!");}//去Warehouse.routes去取路由元数据,开始肯定是没有的RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());//没获取到if (null == routeMeta) {// Maybe its does't exist, or didn't load.//判断Warehouse.groupsIndex路由组中是否有这个groupif (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");} else {try {//动态添加路由元信息到路由中addRouteGroupDynamic(postcard.getGroup(), null);} catch (Exception e) {              }//重新加载。这个时候就会有路由元信息了completion(postcard);   // Reload}} else {//给postcard设置目的地,设置类型,设置优先级,设置Extra等信息postcard.setDestination(routeMeta.getDestination());postcard.setType(routeMeta.getType());postcard.setPriority(routeMeta.getPriority());postcard.setExtra(routeMeta.getExtra());Uri rawUri = postcard.getUri();...switch (routeMeta.getType()) {case PROVIDER:  // if the route is provider, should find its instance// Its provider, so it must implement IProviderClass<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();IProvider instance = Warehouse.providers.get(providerMeta);if (null == instance) { // There's no instance of this providerIProvider provider;try {provider = providerMeta.getConstructor().newInstance();provider.init(mContext);Warehouse.providers.put(providerMeta, provider);instance = provider;} catch (Exception e) {logger.error(TAG, "Init provider failed!", e);throw new HandlerException("Init provider failed!");}}postcard.setProvider(instance);postcard.greenChannel();    // Provider should skip all of interceptorsbreak;case FRAGMENT:postcard.greenChannel();    // Fragment needn't interceptorsdefault:break;}}}进入addRouteGroupDynamic
public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {if (Warehouse.groupsIndex.containsKey(groupName)){// If this group is included, but it has not been loaded// load this group first, because dynamic route has high priority.Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);Warehouse.groupsIndex.remove(groupName);}// cover old group.if (null != group) {group.loadInto(Warehouse.routes);}
}

看上面代码可知: 数据完善过程是通过组名group去groupsIndex获取对应的组的class对象,然后调用class对象的loadInto方法,将路由元数据加载到Warehouse.routes 然后重新调用completion完善方法去Warehouse.routes中取出路由信息并加载到PostCard中,这样PostCard中就获取到了目的地址信息。

下面我画了一张图描述了上面的调用过程 一图胜千言

arouter调用过程.png

总结

本文先介绍了ARouter使用过程中 的一些基本概念,理解了这些概念后,我们再从使用步骤触发,对每个使用节点进行了介绍。 最后使用一张图总结了整个使用原理过程: 这里我们还有一些悬念:

  • 1.ARouter帮助类是如何生成的,这里使用到了APT注解处理器的技术 关于APT我们会在下一章:

Android开源系列-组件化框架Arouter-(三)APT技术详解

  • 这里还有个有趣的现象,我们在调用路由表加载的时候: 使用了loadRouterMap加载,但是查看里面代码:
java
复制代码
private static void loadRouterMap() {registerByPlugin = false;// auto generate register code by gradle plugin: arouter-auto-register// looks like below:// registerRouteRoot(new ARouter..Root..modulejava());// registerRouteRoot(new ARouter..Root..modulekotlin());
}

居然是空的。。 呃呃呃 没关系看注解:

java
复制代码
auto generate register code by gradle plugin: arouter-auto-register

可以看到这里使用了arouter-auto-register插件中自动生成注册代码的方式: 这里其实就是使用到了字节码插庄技术,动态添加了代码,这里留到:

Android开源系列-组件化框架Arouter-(四)AGP插件详解

好了,本篇就到这里了。

作者:小余的自习室
链接:https://juejin.cn/post/7134632360087126023
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/639926.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件系统测试方案-word

2. 测试策略 2.1. 测试完成标准 2.2. 测试类型 2.2.1. 功能测试 2.2.2. 性能测试 2.2.3. 安全性与访问控制测试 2.3. 测试工具 3. 测试技术 4. 测试资源 4.1. 人员安排 4.2. 测试环境 4.2.1. 硬件环境 4.2.2. 软件环境 4.3. 进度安排 5. 功能测试 6. 性能测试 7. 安全性与访问控…

[Unity] Tilemap瓦片左右翻转(上下翻转)

Tile&#xff08;瓦片&#xff09;左右翻转感觉是很常用的一个功能啊&#xff01;看了一些教程都没有提及&#xff0c;心想难道要把每张Sprite再做一张对称的、再做成瓦片吗&#xff1f; 图片量x2 、瓦片量x2、不现实&#xff01;一定有方法&#xff01; 搜索了了半天没找到方…

架构篇04:复杂度来源 - 高性能

文章目录 单机复杂度集群的复杂度小结 从本篇开始&#xff0c;我们一起深入分析架构设计复杂度的 6 个来源&#xff0c;先来聊聊复杂度的来源之一高性能。 对性能孜孜不倦的追求是整个人类技术不断发展的根本驱动力。例如计算机&#xff0c;从电子管计算机到晶体管计算机再到集…

springsecurity集成kaptcha功能

前端代码 本次采用简单的html静态页面作为演示&#xff0c;也可结合vue前后端分离开发&#xff0c;复制就可运行测试 项目目录 登录界面 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</…

【设计模式-9】装饰模式的代码实现及使用场景

装饰器模式类比生活中房屋装修的场景&#xff0c;可以在毛坯房的基础上加以各种装饰&#xff0c;使得房屋的居住属性增强。装饰器模式能够在运行期间&#xff0c;动态地为原始对象增加一些额外的功能&#xff0c;使其功能更为丰富。 1. 概述 装饰模式 可以动态的为某些对象增…

深入Matplotlib:画布分区与高级图形展示【第33篇—python:Matplotlib】

文章目录 Matplotlib画布分区技术详解引言方法一&#xff1a;plt.subplot()方法二&#xff1a;简略写法方法三&#xff1a;plt.subplots()实例展示添加更多元素 进一步探索Matplotlib画布分区自定义子图布局3D子图结语 Matplotlib画布分区技术详解 引言 Matplotlib是一个强大…

代码随想录27期|Python|Day35|435. 无重叠区间|763.划分字母区间|56. 合并区间

435. 无重叠区间 和昨天的射爆气球是一样的处理方式&#xff1a; 由于不需要进行不重合的时候的计算&#xff0c;只需要对重合进行处理&#xff0c;所以反而更加简单。 1、按照区间左边界从小到大排序&#xff1b; 2、从索引1开始遍历&#xff0c;对于i-1的右边界大于i的左边…

网页无法访问但是有网什么原因

目录 1.运行网络诊断&#xff0c;确认原因 原因A.远程计算机或设备将不接受连接(该设备或资源(Web 代理)未设置为接受端口“7890”上的连接 原因B.DNS服务器未响应 场景A.其他的浏览器可以打开网页&#xff0c;自带的Edge却不行 方法A&#xff1a;关闭代理 Google自带翻译…

用户头像上传

将用户上传的头像存储在腾讯云存储桶里 注册腾讯云 https://cloud.tencent.com/login 创建存储桶 配置跨域 来源 * (任何都可以访问) put get post 请求都可以 点击概览&#xff0c;查看存储桶基本信息 记录保存存储桶名称和地域 找到api密钥管理&#xff0c;新建密钥 ht…

Git学习笔记(第7章):IDEA实现Git操作(VSCode)

目录 7.1 配置忽略文件 7.2 初始化本地库 7.3 添加暂存区、提交本地库 7.4 修改文件 补充&#xff1a;工具栏简介 7.1 配置忽略文件 问题引入 在版本控制系统中&#xff0c;有些文件或目录是不需要纳入版本管理的&#xff0c;比如编译产生的临时文件、日志文件、缓存文件等…

项目篇:基于UDP通信模型的网络聊天室

思维导图 基于UDP通信模型的网络聊天室 消息分类及数据包结构 服务器端 #include <head.h> #define SER_PORT 8888 #define SER_IP "192.168.232.133" typedef struct mb {struct sockaddr_in cin;char name[20];struct mb *next; }*member; //群发消息 int …

【window】Windows11:该文件没有与之关联的应用来执行该操作

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff1a;人工智能 之前win10升级win11后&#xff0c;受不了桌面软件图标的的小箭头&#xff0c;所以弄掉了&#xff0c;但是随之而来产…

9个好习惯教会你设计商业模式的电商干货,真心分享丨运营逻辑

9个好习惯教会你设计商业模式的电商干货&#xff0c;真心分享丨运营逻辑 文丨微三云营销总监胡佳东&#xff0c;点击上方“关注”&#xff0c;为你分享市场商业模式电商干货。 - 最近&#xff0c;有很多新认识的朋友&#xff0c;都在询问我最近市场上&#xff0c;有什么好的…

定类变量的频率分析(SPSS

目录 1.导入数据&#xff1a;2.频率分析&#xff1a;3.结果分析4.表格导出为excel小结&#xff1a; 1.导入数据&#xff1a; 直接把表格拖入spss 然后点确定 下面会有变量视图&#xff0c;查看各个指标的类型和属性&#xff1a; 2.频率分析&#xff1a; 点击频率分析 选择…

json-server的基础使用

本篇文章与另一篇文章有关系axios的基本使用&#xff0c;大家可以看看这篇文章 json-server 是什么? 用来快速搭建模拟的 REST API 的工具包 可以30秒内快速为我们搭建一个假的基于 REST API的服务 我们要如何使用呢&#xff1f; 1.先安装 //全局安装 npm i -g json-server …

伪原创文章生成器软件免费使用的方法

写文章不仅消耗时间&#xff0c;而且还容易出现写不出内容的问题&#xff0c;随着技术的发展&#xff0c;越来越多的人开始不再亲历亲为的去写文章了&#xff0c;而是用起了伪原创文章生成器软件&#xff0c;对于还不了解自动生成文章软件的人&#xff0c;可不要小瞧这个它了&a…

JOSEF约瑟 零序电流继电器 JL-8D/2X122A4(S) 0-30AAC 220VDC

系列型号 JL-8D/3X1定时限电流继电器&#xff1b;JL-8D/3X111A2定时限电流继电器&#xff1b; JL-8D/3X121A2定时限电流继电器&#xff1b;JL-8D/3X211A2定时限电流继电器&#xff1b; JL-8D/3X221A2定时限电流继电器&#xff1b;JL-8D/3X2定时限电流继电器&#xff1b; JL-8D/…

[小程序]Http网络请求

一、数据请求限制 出于安全性(bushi)考虑&#xff0c;小程序请求的数据接口必须具备以下两个条件&#xff1a; ①只能请求Https类型 ②必须将接口域名添加到信任列表中 1.配置request合法域名 配置步骤如下&#xff1a;小程序管理后台->开发->开发设置->服务器域名-&g…

幻兽帕鲁专用服务器

随着幻兽帕鲁这款游戏的热度持续升温&#xff0c;我们遍寻全网&#xff0c;带给各位玩家一个全新的、高品质的游戏体验——莱卡云服务器。有幻兽帕鲁的热衷者们无需再为了服务器的选取困扰&#xff0c;因为我们可以肯定地说&#xff1a;选择莱卡云&#xff0c;你不会失望。 首先…

java SSM政府采购管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM政府采购管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代 码和数据库&#xff0c;系统主要采…