当前位置: 首页 > news >正文

【Android】美团组件化路由框架WMRouter源码解析

前言

Android无论App开发还是SDK开发,都绕不开组件化,组件化要解决的最大的问题就是组件之间的通信,即路由框架。国内使用最多的两个路由框架一个是阿里的ARouter,另一个是美团的WMRouter。这两个路由框架功能都很强大,笔者都有使用过。从源码上来看,WMRouter的架构更加清晰,可读性更强。从扩展性来讲,WMRouter更灵活,且具备很强大的无侵入式扩展性。这两个框架都使用了Android开发当中比较前沿的技术,一个是APT技术,一个是字节码插桩技术。WMRouter与Arouter最大的不同是它使用了自定义的ServiceLoader,这个ServiceLoader就是用来加载各个子工程中的服务。今天笔者就挑WMRouter来讲一讲它的实现过程。

本文基于WMRouter源码的1.2.1版本。

笔者原创,转载请注明出处:https://blog.csdn.net/devnn/article/details/136969237

WMRouter的设计与使用文档

关于WMRouter的使用还不了解,可以去githup看一看。先对它有一个整体的认识。
WMRouter设计与使用文档

简单来说,使用分为4步:
1、在Gradle中引入依赖、添加插件
2、初始化

// 创建RootHandler
val rootHandler = DefaultRootUriHandler(this)
// 初始化
Router.init(rootHandler)

3、配置路由

@RouterUri(scheme = "aaa", host = "bbb", path = ["/page1"])
class ActivityTest1 : AppCompatActivity() {
...
}

4、跳转

Router.startUri(context, "aaa://bbb/page1");

WMRouter源码解析

WMRouter初始化

使用WMRouer之前先要初始化,看一下初始化代码

//com.sankuai.waimai.router.Routerpublic class Router {@SuppressLint("StaticFieldLeak")private static RootUriHandler ROOT_HANDLER;/*** 此初始化方法必须在主线程调用。*/public static void init(@NonNull RootUriHandler rootUriHandler) {if (!Debugger.isLogSetting()) {Log.w(Debugger.LOG_TAG, "!!当前未设置Logger,建议通过 Debugger.setLogger()方法设置Logger");Log.w(Debugger.LOG_TAG, "!!并在测试环境通过 Debugger.EnableLog(true)方法开启日志");Log.w(Debugger.LOG_TAG, "!!通过Debugger.setEnableDebug(true)方法在测试环境及时抛出严重类型异常");}if (Looper.myLooper() != Looper.getMainLooper()) {Debugger.fatal("初始化方法init应该在主线程调用");}if (ROOT_HANDLER == null) {ROOT_HANDLER = rootUriHandler;} else {Debugger.fatal("请勿重复初始化UriRouter");}}/*** 此初始化方法的调用不是必须的。* 使用时会按需初始化;但也可以提前调用并初始化,使用时会等待初始化完成。* 本方法线程安全。*/public static void lazyInit() {ServiceLoader.lazyInit();getRootHandler().lazyInit();}public static RootUriHandler getRootHandler() {if (ROOT_HANDLER == null) {throw new RuntimeException("请先调用init初始化UriRouter");}return ROOT_HANDLER;}public static void startUri(UriRequest request) {getRootHandler().startUri(request);}public static void startUri(Context context, String uri) {getRootHandler().startUri(new UriRequest(context, uri));}//省略无关代码
}

可以看到,初始化主要是给ROOT_HANDLER赋值,调用startUri方法跳转时,也是委托给了初始化传入的RootUriHandler

接下来看看初始化传入的DefaultRootUriHandler:

package com.sankuai.waimai.router.common;import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;import com.sankuai.waimai.router.annotation.RouterUri;
import com.sankuai.waimai.router.components.DefaultOnCompleteListener;
import com.sankuai.waimai.router.core.RootUriHandler;
import com.sankuai.waimai.router.regex.RegexAnnotationHandler;
import com.sankuai.waimai.router.utils.LazyInitHelper;/*** 默认的RootHandler实现** Created by jzj on 2018/3/23.*/public class DefaultRootUriHandler extends RootUriHandler {private final PageAnnotationHandler mPageAnnotationHandler;private final UriAnnotationHandler mUriAnnotationHandler;private final RegexAnnotationHandler mRegexAnnotationHandler;public DefaultRootUriHandler(Context context) {this(context, null, null);}/*** @param defaultScheme {@link RouterUri} 没有指定scheme时,则使用这里设置的defaultScheme* @param defaultHost   {@link RouterUri} 没有指定host时,则使用这里设置的defaultHost*/public DefaultRootUriHandler(Context context,@Nullable String defaultScheme, @Nullable String defaultHost) {super(context);mPageAnnotationHandler = createPageAnnotationHandler();mUriAnnotationHandler = createUriAnnotationHandler(defaultScheme, defaultHost);mRegexAnnotationHandler = createRegexAnnotationHandler();// 按优先级排序,数字越大越先执行// 处理RouterPage注解定义的内部页面跳转,如果注解没定义,直接结束分发addChildHandler(mPageAnnotationHandler, 300);// 处理RouterUri注解定义的URI跳转,如果注解没定义,继续分发到后面的HandleraddChildHandler(mUriAnnotationHandler, 200);// 处理RouterRegex注解定义的正则匹配addChildHandler(mRegexAnnotationHandler, 100);// 添加其他用户自定义Handler...// 都没有处理,则尝试使用默认的StartUriHandler直接启动UriaddChildHandler(new StartUriHandler(), -100);// 全局OnCompleteListener,用于输出跳转失败提示信息setGlobalOnCompleteListener(DefaultOnCompleteListener.INSTANCE);}/*** @see LazyInitHelper#lazyInit()*/public void lazyInit() {mPageAnnotationHandler.lazyInit();mUriAnnotationHandler.lazyInit();mRegexAnnotationHandler.lazyInit();}public PageAnnotationHandler getPageAnnotationHandler() {return mPageAnnotationHandler;}public UriAnnotationHandler getUriAnnotationHandler() {return mUriAnnotationHandler;}public RegexAnnotationHandler getRegexAnnotationHandler() {return mRegexAnnotationHandler;}@NonNullprotected PageAnnotationHandler createPageAnnotationHandler() {return new PageAnnotationHandler();}@NonNullprotected UriAnnotationHandler createUriAnnotationHandler(@Nullable String defaultScheme,@Nullable String defaultHost) {return new UriAnnotationHandler(defaultScheme, defaultHost);}@NonNullprotected RegexAnnotationHandler createRegexAnnotationHandler() {return new RegexAnnotationHandler();}
}

RootUriHandler采用了责任链的设计模式,它是ChainedHandler的子类:

public class RootUriHandler extends ChainedHandler {
//代码略,主要是一个责任链模式,建议参阅源码。这个不是本文重点。
}

因此DefaultRootUriHandler只是一个壳,实际业务交给了责任链中的处理器。

回到DefaultRootUriHandler,可以看到在它的构造方法中添加了责任链的节点(或叫处理器),因此路由的跳转请求交给了这些处理器。

可以看到,一共添加了4个处理器,分别是mPageAnnotationHandlermUriAnnotationHandlermRegexAnnotationHandlernew StartUriHandler()

mPageAnnotationHandler是用来兼容老的跳转方式,它跟mUriAnnotationHandler是区别是,前者的scheme和host是固定的,即wm_router:\\page,后者的scheme和host支持可配也支持空(不配的情况下默认是空)。

mUriAnnotationHandler用来处理一般的Uri跳转。

mRegexAnnotationHandler是用来匹配正则表达式的路由跳转。

StartUriHandler是用来兜底的处理器。

大多数情况下,使用的是mUriAnnotationHandler这个Uri处理器。

UriAnnotationHandler处理Uri请求

接下来,我们关注mUriAnnotationHandler代码,它的类型是UriAnnotationHandler

package com.sankuai.waimai.router.common;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.text.TextUtils;import com.sankuai.waimai.router.annotation.RouterUri;
import com.sankuai.waimai.router.components.RouterComponents;
import com.sankuai.waimai.router.core.UriCallback;
import com.sankuai.waimai.router.core.UriHandler;
import com.sankuai.waimai.router.core.UriInterceptor;
import com.sankuai.waimai.router.core.UriRequest;
import com.sankuai.waimai.router.utils.LazyInitHelper;
import com.sankuai.waimai.router.utils.RouterUtils;import java.util.HashMap;
import java.util.Map;/*** URI跳转,通过注解 {@link RouterUri} 配置,可处理多个Scheme+Host。** 接收到 {@link UriRequest} 时, {@link UriAnnotationHandler} 根据scheme+host产生的key,* 分发给对应的 {@link PathHandler},{@link PathHandler} 再根据path分发给每个子节点。** Created by jzj on 2018/3/23.*/
public class UriAnnotationHandler extends UriHandler {private static boolean sAddNotFoundHandler = true;/*** 设置是否在每个PathHandler上添加一个NotFoundHandler,默认为true。* 如果添加了NotFoundHandler,则只要scheme+host匹配上,所有的URI都会被PathHandler拦截掉,* 即使path不能匹配,也会分发到NotFoundHandler终止分发。*/public static void setAddNotFoundHandler(boolean addNotFoundHandler) {sAddNotFoundHandler = addNotFoundHandler;}/*** key是由scheme+host生成的字符串,value是PathHandler。*/private final Map<String, PathHandler> mMap = new HashMap<>();/*** {@link RouterUri} 没有指定scheme时,则使用这里设置的defaultScheme*/private final String mDefaultScheme;/*** {@link RouterUri} 没有指定host时,则使用这里设置的defaultHost*/private final String mDefaultHost;private final LazyInitHelper mInitHelper = new LazyInitHelper("UriAnnotationHandler") {@Overrideprotected void doInit() {initAnnotationConfig();}};/*** @param defaultScheme {@link RouterUri} 没有指定scheme时,则使用这里设置的defaultScheme* @param defaultHost   {@link RouterUri} 没有指定host时,则使用这里设置的defaultHost*/public UriAnnotationHandler(@Nullable String defaultScheme, @Nullable String defaultHost) {mDefaultScheme = RouterUtils.toNonNullString(defaultScheme);mDefaultHost = RouterUtils.toNonNullString(defaultHost);}/*** @see LazyInitHelper#lazyInit()*/public void lazyInit() {mInitHelper.lazyInit();}protected void initAnnotationConfig() {RouterComponents.loadAnnotation(this, IUriAnnotationInit.class);}public PathHandler getPathHandler(String scheme, String host) {return mMap.get(RouterUtils.schemeHost(scheme, host));}/*** 给指定scheme和host的节点设置path前缀*/public void setPathPrefix(String scheme, String host, String prefix) {PathHandler pathHandler = getPathHandler(scheme, host);if (pathHandler != null) {pathHandler.setPathPrefix(prefix);}}/*** 给所有节点设置path前缀*/public void setPathPrefix(String prefix) {for (PathHandler pathHandler : mMap.values()) {pathHandler.setPathPrefix(prefix);}}public void register(String scheme, String host, String path,Object handler, boolean exported, UriInterceptor... interceptors) {// 没配的scheme和host使用默认值if (TextUtils.isEmpty(scheme)) {scheme = mDefaultScheme;}if (TextUtils.isEmpty(host)) {host = mDefaultHost;}String schemeHost = RouterUtils.schemeHost(scheme, host);PathHandler pathHandler = mMap.get(schemeHost);if (pathHandler == null) {pathHandler = createPathHandler();mMap.put(schemeHost, pathHandler);}pathHandler.register(path, handler, exported, interceptors);}@NonNullprotected PathHandler createPathHandler() {PathHandler pathHandler = new PathHandler();if (sAddNotFoundHandler) {pathHandler.setDefaultChildHandler(NotFoundHandler.INSTANCE);}return pathHandler;}/*** 通过scheme+host找对应的PathHandler,找到了才会处理*/private PathHandler getChild(@NonNull UriRequest request) {return mMap.get(request.schemeHost());}@Overridepublic void handle(@NonNull UriRequest request, @NonNull UriCallback callback) {mInitHelper.ensureInit();super.handle(request, callback);}@Overrideprotected boolean shouldHandle(@NonNull UriRequest request) {return getChild(request) != null;}@Overrideprotected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {PathHandler pathHandler = getChild(request);if (pathHandler != null) {pathHandler.handle(request, callback);} else {// 没找到的继续分发callback.onNext();}}@Overridepublic String toString() {return "UriAnnotationHandler";}
}

可以看到它的handle方法中,首先调用了 mInitHelper.ensureInit(),用来确保路由组件已经注册:

//com.sankuai.waimai.router.common.UriAnnotationHandler@Overridepublic void handle(@NonNull UriRequest request, @NonNull UriCallback callback) {mInitHelper.ensureInit();super.handle(request, callback);}

如何保证路由组件的注册正是它的技术难点,也是不同于Arouter的地方。

然后在handleInternal方法中找到跟SchemHost匹配的PathHandler,然后将请求交给对应的PathHandler。

//com.sankuai.waimai.router.common.UriAnnotationHandler@Overrideprotected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) {PathHandler pathHandler = getChild(request);if (pathHandler != null) {pathHandler.handle(request, callback);} else {// 没找到的继续分发callback.onNext();}}

其中,PathHandler是根据SchemeHost匹配的处理器:

//com.sankuai.waimai.router.common.UriAnnotationHandler/*** 通过scheme+host找对应的PathHandler,找到了才会处理*/private PathHandler getChild(@NonNull UriRequest request) {return mMap.get(request.schemeHost());}

路由注册方法是register,它并不需要我们手动来调用:

//com.sankuai.waimai.router.common.UriAnnotationHandlerpublic void register(String scheme, String host, String path,Object handler, boolean exported, UriInterceptor... interceptors) {// 没配的scheme和host使用默认值if (TextUtils.isEmpty(scheme)) {scheme = mDefaultScheme;}if (TextUtils.isEmpty(host)) {host = mDefaultHost;}String schemeHost = RouterUtils.schemeHost(scheme, host);PathHandler pathHandler = mMap.get(schemeHost);if (pathHandler == null) {pathHandler = createPathHandler();mMap.put(schemeHost, pathHandler);}pathHandler.register(path, handler, exported, interceptors);}

由于它是支持同一个SchemHost可以有多个path,所以它将同一个SchemeHost下的请求全部交给对应的PathHandler。第一次先创建新的PathHandler,然后放到Map中缓存起来,下次从缓存中取出PathHandler。

实际的路由注册是PathHandlerresgiter方法。

接下来就是PathHandler的源码:

//com.sankuai.waimai.router.common.PathHandlerpublic void register(String path, Object target, boolean exported,UriInterceptor... interceptors) {if (!TextUtils.isEmpty(path)) {path = RouterUtils.appendSlash(path);UriHandler parse = UriTargetTools.parse(target, exported, interceptors);UriHandler prev = mMap.put(path, parse);if (prev != null) {Debugger.fatal("[%s] 重复注册path='%s'的UriHandler: %s, %s", this, path, prev, parse);}}}//省略无关代码

UriTargetToolsparse方法中生成了path对应的真正处理跳转的UriHandler,比如处理Activity跳转的UriHandler是ActivityClassNameHandler:

package com.sankuai.waimai.router.components;import android.app.Activity;import com.sankuai.waimai.router.activity.ActivityClassNameHandler;
import com.sankuai.waimai.router.activity.ActivityHandler;
import com.sankuai.waimai.router.common.NotExportedInterceptor;
import com.sankuai.waimai.router.core.UriHandler;
import com.sankuai.waimai.router.core.UriInterceptor;import java.lang.reflect.Modifier;/*** 跳转目标,支持ActivityClass, ActivityClassName, UriHandler。** Created by jzj on 2018/3/26.*/public class UriTargetTools {public static UriHandler parse(Object target, boolean exported,UriInterceptor... interceptors) {UriHandler handler = toHandler(target);if (handler != null) {if (!exported) {handler.addInterceptor(NotExportedInterceptor.INSTANCE);}handler.addInterceptors(interceptors);}return handler;}private static UriHandler toHandler(Object target) {if (target instanceof UriHandler) {return (UriHandler) target;} else if (target instanceof String) {return new ActivityClassNameHandler((String) target);} else if (target instanceof Class && isValidActivityClass((Class) target)) {//noinspection uncheckedreturn new ActivityHandler((Class<? extends Activity>) target);} else {return null;}}private static boolean isValidActivityClass(Class clazz) {return clazz != null && Activity.class.isAssignableFrom(clazz)&& !Modifier.isAbstract(clazz.getModifiers());}
}

关于实际处理跳转的代码请参阅源码,它并不是重点。以上介绍了Router源码的大致流程,其中最重要的还没有讲,就是它是如何自动注册路由的。

UriAnnotationHandler如何注册路由

我们看一下UriAnnotationHandlerregister方法在哪里调用的。

//com.sankuai.waimai.router.common.UriAnnotationHandlerpublic void register(String scheme, String host, String path,Object handler, boolean exported, UriInterceptor... interceptors) {...}

通过IDE的调用跟踪功能,找到了两处调用:

package com.sankuai.waimai.router.generated;import com.sankuai.waimai.router.common.IUriAnnotationInit;
import com.sankuai.waimai.router.common.UriAnnotationHandler;public class UriAnnotationInit_193ec81a29c7c951924b68ab1dc340aa implements IUriAnnotationInit {public void init(UriAnnotationHandler handler) {handler.register("aaa", "bbb", "/page1", "com.devnn.library1.ActivityTest1", false);handler.register("aaa", "bbb", "/page3", "com.devnn.library1.ActivityTest3", false);}
}

它的位置是对应library1工程的build目录下:
在这里插入图片描述
可见,这是APT帮我们生成的代码。

在library1工程中笔者之前声明过两个路由组件:

package com.devnn.library1import androidx.appcompat.app.AppCompatActivity
import com.sankuai.waimai.router.annotation.RouterUri@RouterUri(scheme = "aaa", host = "bbb", path = ["/page1"])
class ActivityTest1 : AppCompatActivity() {
...
}
package com.devnn.library1import androidx.appcompat.app.AppCompatActivity
import com.sankuai.waimai.router.annotation.RouterUri@RouterUri(scheme = "aaa", host = "bbb", path = ["/page3"])
class ActivityTest3 : AppCompatActivity() {...
}

UriAnnotationInit_193ec81a29c7c951924b68ab1dc340aa正是Router帮我们生成的初始化类。它的init方法帮我们完成了这两个Activity的路由注册。

那么它的init初始化方法是在哪里调用的呢?

通过跟踪是在DefaultAnnotationLoader中调用的:

package com.sankuai.waimai.router.components;import com.sankuai.waimai.router.Router;
import com.sankuai.waimai.router.core.UriHandler;import java.util.List;/*** 使用ServiceLoader加载注解配置** Created by jzj on 2018/4/28.*/
public class DefaultAnnotationLoader implements AnnotationLoader {public static final AnnotationLoader INSTANCE = new DefaultAnnotationLoader();@Overridepublic <T extends UriHandler> void load(T handler,Class<? extends AnnotationInit<T>> initClass) {List<? extends AnnotationInit<T>> services = Router.getAllServices(initClass);for (AnnotationInit<T> service : services) {service.init(handler);}}
}

DefaultAnnotationLoader的loader方法是在UriAnnotationHandler的handle方法中调用的:

//com.sankuai.waimai.router.common.UriAnnotationHandler@Overridepublic void handle(@NonNull UriRequest request, @NonNull UriCallback callback) {mInitHelper.ensureInit();super.handle(request, callback);}

handle方法的第一行代码mInitHelper.ensureInit()调用了doInit方法:

//com.sankuai.waimai.router.common.UriAnnotationHandlerprivate final LazyInitHelper mInitHelper = new LazyInitHelper("UriAnnotationHandler") {@Overrideprotected void doInit() {initAnnotationConfig();}};/*** @param defaultScheme {@link RouterUri} 没有指定scheme时,则使用这里设置的defaultScheme* @param defaultHost   {@link RouterUri} 没有指定host时,则使用这里设置的defaultHost*/public UriAnnotationHandler(@Nullable String defaultScheme, @Nullable String defaultHost) {mDefaultScheme = RouterUtils.toNonNullString(defaultScheme);mDefaultHost = RouterUtils.toNonNullString(defaultHost);}/*** @see LazyInitHelper#lazyInit()*/public void lazyInit() {mInitHelper.lazyInit();}protected void initAnnotationConfig() {RouterComponents.loadAnnotation(this, IUriAnnotationInit.class);}

接着看 RouterComponents.loadAnnotation方法:

//com.sankuai.waimai.router.components.RouterComponents@NonNullprivate static AnnotationLoader sAnnotationLoader = DefaultAnnotationLoader.INSTANCE;public static <T extends UriHandler> void loadAnnotation(T handler, Class<? extends AnnotationInit<T>> initClass) {sAnnotationLoader.load(handler, initClass);}

可以看到调用的正是DefaultAnnotationLoaderload方法。

DefaultAnnotationLoaderload方法(参见上文)调用了Router.getAllServcie(initClass)方法。

那么Router.getAllServices(initClass)是如何找到AnnotationInit的实现类呢?
这又回到了门面类Router:

//com.sankuai.waimai.router.Routerpublic static <I, T extends I> List<T> getAllServices(Class<I> clazz) {return ServiceLoader.load(clazz).getAll();}

可见它是使用ServiceLoader来加载服务的。

经过以上分析,路由组件是在ServiceLoader中注册的。

然而,这并不是jdk中的java.util.ServiceLoader,而是一个自定义的ServiceLoader,不要被它的名字迷惑了。

WMRouter中的ServiceLoader
package com.sankuai.waimai.router.service;import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;import com.sankuai.waimai.router.annotation.RouterProvider;
import com.sankuai.waimai.router.components.RouterComponents;
import com.sankuai.waimai.router.core.Debugger;
import com.sankuai.waimai.router.interfaces.Const;
import com.sankuai.waimai.router.utils.LazyInitHelper;
import com.sankuai.waimai.router.utils.SingletonPool;import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** 通过接口Class获取实现类* <p>* Created by jzj on 2018/3/29.** @param <I> 接口类型*/
public class ServiceLoader<I> {private static final Map<Class, ServiceLoader> SERVICES = new HashMap<>();private static final LazyInitHelper sInitHelper = new LazyInitHelper("ServiceLoader") {@Overrideprotected void doInit() {try {// 反射调用Init类,避免引用的类过多,导致main dex capacity exceeded问题Class.forName(Const.SERVICE_LOADER_INIT).getMethod(Const.INIT_METHOD).invoke(null);Debugger.i("[ServiceLoader] init class invoked");} catch (Exception e) {Debugger.fatal(e);}}};/*** @see LazyInitHelper#lazyInit()*/public static void lazyInit() {sInitHelper.lazyInit();}/*** 提供给InitClass使用的初始化接口** @param interfaceClass 接口类* @param implementClass 实现类*/public static void put(Class interfaceClass, String key, Class implementClass, boolean singleton) {ServiceLoader loader = SERVICES.get(interfaceClass);if (loader == null) {loader = new ServiceLoader(interfaceClass);SERVICES.put(interfaceClass, loader);}loader.putImpl(key, implementClass, singleton);}/*** 根据接口获取 {@link ServiceLoader}*/@SuppressWarnings("unchecked")public static <T> ServiceLoader<T> load(Class<T> interfaceClass) {sInitHelper.ensureInit();if (interfaceClass == null) {Debugger.fatal(new NullPointerException("ServiceLoader.load的class参数不应为空"));return EmptyServiceLoader.INSTANCE;}ServiceLoader service = SERVICES.get(interfaceClass);if (service == null) {synchronized (SERVICES) {service = SERVICES.get(interfaceClass);if (service == null) {service = new ServiceLoader(interfaceClass);SERVICES.put(interfaceClass, service);}}}return service;}/*** key --> class name*/private HashMap<String, ServiceImpl> mMap = new HashMap<>();private final String mInterfaceName;private ServiceLoader(Class interfaceClass) {if (interfaceClass == null) {mInterfaceName = "";} else {mInterfaceName = interfaceClass.getName();}}private void putImpl(String key, Class implementClass, boolean singleton) {if (key != null && implementClass != null) {mMap.put(key, new ServiceImpl(key, implementClass, singleton));}}/*** 创建指定key的实现类实例,使用 {@link RouterProvider} 方法或无参数构造。对于声明了singleton的实现类,不会重复创建实例。** @return 可能返回null*/public <T extends I> T get(String key) {return createInstance(mMap.get(key), null);}/*** 创建指定key的实现类实例,使用Context参数构造。对于声明了singleton的实现类,不会重复创建实例。** @return 可能返回null*/public <T extends I> T get(String key, Context context) {return createInstance(mMap.get(key), new ContextFactory(context));}/*** 创建指定key的实现类实例,使用指定的Factory构造。对于声明了singleton的实现类,不会重复创建实例。** @return 可能返回null*/public <T extends I> T get(String key, IFactory factory) {return createInstance(mMap.get(key), factory);}/*** 创建所有实现类的实例,使用 {@link RouterProvider} 方法或无参数构造。对于声明了singleton的实现类,不会重复创建实例。** @return 可能返回EmptyList,List中的元素不为空*/@NonNullpublic <T extends I> List<T> getAll() {return getAll((IFactory) null);}/*** 创建所有实现类的实例,使用Context参数构造。对于声明了singleton的实现类,不会重复创建实例。** @return 可能返回EmptyList,List中的元素不为空*/@NonNullpublic <T extends I> List<T> getAll(Context context) {return getAll(new ContextFactory(context));}/*** 创建所有实现类的实例,使用指定Factory构造。对于声明了singleton的实现类,不会重复创建实例。** @return 可能返回EmptyList,List中的元素不为空*/@NonNullpublic <T extends I> List<T> getAll(IFactory factory) {Collection<ServiceImpl> services = mMap.values();if (services.isEmpty()) {return Collections.emptyList();}List<T> list = new ArrayList<>(services.size());for (ServiceImpl impl : services) {T instance = createInstance(impl, factory);if (instance != null) {list.add(instance);}}return list;}/*** 获取指定key的实现类。注意,对于声明了singleton的实现类,获取Class后还是可以创建新的实例。** @return 可能返回null*/@SuppressWarnings("unchecked")public <T extends I> Class<T> getClass(String key) {return (Class<T>) mMap.get(key).getImplementationClazz();}/*** 获取所有实现类的Class。注意,对于声明了singleton的实现类,获取Class后还是可以创建新的实例。** @return 可能返回EmptyList,List中的元素不为空*/@SuppressWarnings("unchecked")@NonNullpublic <T extends I> List<Class<T>> getAllClasses() {List<Class<T>> list = new ArrayList<>(mMap.size());for (ServiceImpl impl : mMap.values()) {Class<T> clazz = (Class<T>) impl.getImplementationClazz();if (clazz != null) {list.add(clazz);}}return list;}@SuppressWarnings("unchecked")@Nullableprivate <T extends I> T createInstance(@Nullable ServiceImpl impl, @Nullable IFactory factory) {if (impl == null) {return null;}Class<T> clazz = (Class<T>) impl.getImplementationClazz();if (impl.isSingleton()) {try {return SingletonPool.get(clazz, factory);} catch (Exception e) {Debugger.fatal(e);}} else {try {if (factory == null) {factory = RouterComponents.getDefaultFactory();}T t = factory.create(clazz);Debugger.i("[ServiceLoader] create instance: %s, result = %s", clazz, t);return t;} catch (Exception e) {Debugger.fatal(e);}}return null;}@Overridepublic String toString() {return "ServiceLoader (" + mInterfaceName + ")";}public static class EmptyServiceLoader extends ServiceLoader {public static final ServiceLoader INSTANCE = new EmptyServiceLoader();public EmptyServiceLoader() {super(null);}@NonNull@Overridepublic List<Class> getAllClasses() {return Collections.emptyList();}@NonNull@Overridepublic List getAll() {return Collections.emptyList();}@NonNull@Overridepublic List getAll(IFactory factory) {return Collections.emptyList();}@Overridepublic String toString() {return "EmptyServiceLoader";}}
}

ServiceLoaderload方法第一行代码就是初始化ServiceLoader:

//com.sankuai.waimai.router.service.ServiceLoaderpublic static <T> ServiceLoader<T> load(Class<T> interfaceClass) {sInitHelper.ensureInit();...}
//com.sankuai.waimai.router.service.ServiceLoader
private static final LazyInitHelper sInitHelper = new LazyInitHelper("ServiceLoader") {@Overrideprotected void doInit() {try {// 反射调用Init类,避免引用的类过多,导致main dex capacity exceeded问题Class.forName(Const.SERVICE_LOADER_INIT).getMethod(Const.INIT_METHOD).invoke(null);Debugger.i("[ServiceLoader] init class invoked");} catch (Exception e) {Debugger.fatal(e);}}};

这正是这个自定义ServiceLoader玄机所在,它通过反射调用
com.sankuai.waimai.router.generated.ServiceLoaderInit这个固定的类,调用它的init方法来初始化ServiceLoader。WMRouter源码中并没有ServiceLoaderInit这个类,build目录也没有,因此大胆猜测它是由gradle插件帮我们生成的。

我们将生成的apk反编译,找到了ServiceLoaderInit这个类,它的字节码内容如下:

.method public static init()V.registers 0invoke-static {}, Lcom/sankuai/waimai/router/generated/service/ServiceInit_7ba49f44b4136fbacadf8b749184ccb8;->init()Vinvoke-static {}, Lcom/sankuai/waimai/router/generated/service/ServiceInit_569998d498513846731787b941d88272;->init()Vinvoke-static {}, Lcom/sankuai/waimai/router/generated/service/ServiceInit_915e2ffdfef22c5fbf4a1c47a37e69a5;->init()Vreturn-void
.end method

可见,它就是将APT生成的各种ServiceInit_xxx类的init方法调用了一遍。

到这里,我们已经知道了路由组件是如何自动注册的。

看流程有点复杂也有点绕,其实最有技术含量的代码就是ServiceLoader

WMRouter的ServiceLoader功能类似于jdk中java.util.ServiceLoader,用来加载服务的,然后又不同于java.util.ServiceLoader

不同点在于WMRouter的ServiceLoader可以自定义服务的构造方法,而且可以加载特定的服务(带有key的服务),而不是所有服务。

另一个不同点是WMRouter的ServiceLoader加载服务方式并没有采用SPI技术,而且采用反射机制加载ServiceLoaderInit类。ServiceLoaderInit采用字节码插桩技术动态生成,它是用来初始化APT生成的ServiceInit_xxx类,初始化函数实际上就是往ServiceLoader中添加服务,这样就完成了服务的注册。在ServiceInit_xxx类中提供了注册接口,ServiceLoaderInit类中完成注册接口的调用。ServiceLoaderInit是在ServiceLoader的load方法首次调用时通过反射加载。

笔者原创,转载请注明出处:https://blog.csdn.net/devnn/article/details/136969237

@RouterUri注解的作用

在library1工程中使用@RouterUri给两个Activity分别是ActivityTest1ActivityTest3添加注解,在library1的build/generated/source/kapt/debug目录下会生成IUriAnnotationInit服务的实现类:

package com.sankuai.waimai.router.generated;import com.sankuai.waimai.router.common.IUriAnnotationInit;
import com.sankuai.waimai.router.common.UriAnnotationHandler;public class UriAnnotationInit_193ec81a29c7c951924b68ab1dc340aa implements IUriAnnotationInit {public void init(UriAnnotationHandler handler) {handler.register("aaa", "bbb", "/page1", "com.devnn.library1.ActivityTest1", false);handler.register("aaa", "bbb", "/page3", "com.devnn.library1.ActivityTest3", false);}
}

同时在该目录下会生成IUriAnnotationInit服务的注册类:

package com.sankuai.waimai.router.generated.service;import com.sankuai.waimai.router.common.IUriAnnotationInit;
import com.sankuai.waimai.router.service.ServiceLoader;public class ServiceInit_7ba49f44b4136fbacadf8b749184ccb8 {public static void init() {ServiceLoader.put(IUriAnnotationInit.class, "com.sankuai.waimai.router.generated.UriAnnotationInit_193ec81a29c7c951924b68ab1dc340aa", com.sankuai.waimai.router.generated.UriAnnotationInit_193ec81a29c7c951924b68ab1dc340aa.class, false);}
}
@RouterService注解的作用

当使用@RouterService注解会生成Service的注册类。

示例:在library1工程中声明一个@RouterService组件。

package com.devnn.library1import android.util.Log
import com.devnn.baselibrary.IWMService
import com.sankuai.waimai.router.annotation.RouterService@RouterService(interfaces=[IWMService::class], key =["WMLib1Service"])
class WMLib1Service:IWMService{override fun init() {Log.d("IWMService", "WMLib1Service_init")}}

在libary1工程的build/generated/source/kapt/debug目录下生成的类:

package com.sankuai.waimai.router.generated.service;import com.devnn.baselibrary.IWMService;
import com.devnn.library1.WMLib1Service;
import com.sankuai.waimai.router.service.ServiceLoader;public class ServiceInit_569998d498513846731787b941d88272 {public static void init() {ServiceLoader.put(IWMService.class, "WMLib1Service", WMLib1Service.class, false);}
}

示例:在library2工程中声明一个@RouterService组件。

package com.devnn.library2import android.util.Log
import com.devnn.baselibrary.IWMService
import com.sankuai.waimai.router.annotation.RouterService@RouterService(interfaces=[IWMService::class], key =["WMLib2Service"])
class WMLib2Service:IWMService {override fun init() {Log.d("IWMService", "WMLib2Service_init")}
}

在libary2工程的build/generated/source/kapt/debug目录下生成的类:

package com.sankuai.waimai.router.generated.service;import com.devnn.baselibrary.IWMService;
import com.devnn.library2.WMLib2Service;
import com.sankuai.waimai.router.service.ServiceLoader;public class ServiceInit_915e2ffdfef22c5fbf4a1c47a37e69a5 {public static void init() {ServiceLoader.put(IWMService.class, "WMLib2Service", WMLib2Service.class, false);}
}

Gradle插件自动生成的ServiceLoaderInit类的字节码上文已经贴出,笔者将它转成了Java代码:

package com.sankuai.waimai.router.generated.ServiceLoaderInit;public class ServiceLoaderInit {public ServiceLoaderInit() {com.sankuai.waimai.router.generated.service.ServiceInit_7ba49f44b4136fbacadf8b749184ccb8.init();com.sankuai.waimai.router.generated.service.ServiceInit_569998d498513846731787b941d88272.init();com.sankuai.waimai.router.generated.service.ServiceInit_915e2ffdfef22c5fbf4a1c47a37e69a5.init();}
}

关于如何生成ServiceLaoderInit的代码,有兴趣可以参阅源码:
https://github.com/meituan/WMRouter/blob/master/WmPlugin/plugin/src/main/java/com/sankuai/waimai/router/plugin/WMRouterTransform.java

总结

经过上文分析,WMRouter的Uri跳转是由UriAnnotationHandler处理器完成的。在UriAnnotationHandler的handle方法中首先初始化由@RouterUri注解生成的服务。初始化功能即完成路由组件的注册。这个过程分两步,第一步是先找到服务,这个是由ServiceLoader完成的。第二步是注册,这个是服务的init(...)方法完成的。整体流程如下:

Created with Raphaël 2.3.0 Router.startUri(...) UriAnnotationHandler.handle(...) initAnnotationConfig() RouterComponents.loadAnnotation(this, IUriAnnotationInit.class); DefaultAnnotationLoader.load(this, IUriAnnotationInit.class); Router.getAllServices(initClass); 通过`ServiceLoader`加载initClass即IUriAnnotationInit的所有实现; 遍历这些实现类,调用init方法(将UriAnnotationHandler传给它) init方法中将路由组件注册到UriAnnotationHandler中 UriAnnotationHandler的getChild方法找到SchemeHost对应的PathHandler 完成跳转

笔者原创,转载请注明出处:https://blog.csdn.net/devnn/article/details/136969237

相关文章:

【Android】美团组件化路由框架WMRouter源码解析

前言 Android无论App开发还是SDK开发&#xff0c;都绕不开组件化&#xff0c;组件化要解决的最大的问题就是组件之间的通信&#xff0c;即路由框架。国内使用最多的两个路由框架一个是阿里的ARouter&#xff0c;另一个是美团的WMRouter。这两个路由框架功能都很强大&#xff0…...

python知识点总结(九)

python知识点总结九 1、TCP中socket的实现代码实现TCP协议a、服务端b、客户端&#xff1a; 2、写装饰器&#xff0c;限制函数被执行的频率&#xff0c;如10秒一次3、请实现一个装饰器&#xff0c;通过一次调用函数重复执行5次4、写一个登录装饰器对一下函数进行装饰&#xff0c…...

浅谈Linux中的软锁定(soft lockup)和硬件监视器(watchdog)

目录 1. 问题所示2. 基本知识3. 进阶知识 1. 问题所示 跑深度学习的时候遇到卡顿卡机 hostname kernel:watchdog BUG:soft lockup - CPU#16 stuck for 130s![P2PMain-72:4030570]界面如下所示&#xff1a; 大概意思是&#xff1a; watchdog_thresh参数是硬件监视器的超时阈值…...

数据库的四个特性?MySQL是如何实现的?

首先MySQL中&#xff0c;数据库的四个特性分为&#xff1a; 原子性一致性隔离性持久性 也就是我们常说的ACID。 那么这四个特性数据库是如何实现的呢&#xff1f; 持久性---> redo log&#xff1a; redo log&#xff08;重做日志&#xff09;&#xff1a; redolog本身是…...

Jupyter R绘图 汉字显示乱码的解决办法

1.Jupyte中&#xff0c;R绘图&#xff0c;汉字显示乱码 2.如何解决&#xff1f; (1)R中安装showtext 登录linux服务器 #R > install.packages(“showtext”) … 出错 (2)退出R,安装freetype-config #apt install libfreetype6-dev 出错 &#xff08;3&#xff09;进入R&…...

推荐几个值得一读的Qt开源项目

VNote - 基于Qt的免费开源笔记软件&#xff0c;适合那些寻找跨平台笔记解决方案的用户。项目地址&#xff1a;https://github.com/vnotex/vnote Qt NodeEditor - 类似于UE4蓝图的节点编辑器&#xff0c;对于想要深入了解Qt图形编辑和节点系统的人来说&#xff0c;这是一个极好…...

【XR806开发板试用】使用PWM模块模拟手机呼吸灯提示功能

一般情况下&#xff0c;我们的手机在息屏状态&#xff0c;当收到消息处于未读状态时&#xff0c;会有呼吸灯提醒&#xff0c;这次有幸抽中XR806开发板的试用&#xff0c;经过九牛二虎之力终于将环境搞好了&#xff0c;中间遇到各种问题&#xff0c;在我的另一篇文章中已详细描述…...

Mysql——索引下推

MySQL的索引下推&#xff08;Index Condition Pushdown, ICP&#xff09;是一种查询优化技术&#xff0c;它允许MySQL在存储引擎层执行部分WHERE子句中的过滤条件&#xff0c;而非全部在MySQL服务器层执行。这使得在扫描索引过程中就可以剔除不满足条件的记录&#xff0c;从而减…...

Springboot项目之mybatis-plus多容器分布式部署id重复问题之源码解析

mybatis-plus 3.3.2 部署多个pod id冲突问题 配置&#xff1a; # 设置随机 mybatis-plus.global-config.worker-id: ${random.int(1,31)} mybatis-plus.global-config.datacenter-id: ${random.int(1,31)}源码解析&#xff1a;MybatisSqlSessionFactoryBean 重点&#xff1a…...

微信答题小程序云开发--实现云函数上传题目图片 base64功能

需求功能 题目带有图片&#xff0c;需要支持上传图片功能。微信答题小程序云开发&#xff0c;实现云函数上传题目图片、存储功能、查询显示等功能。 云函数开发遇到的问题 在微信云开发环境当中&#xff0c;普通的用户并没有往云存储内写入文件的权限。 所以普通用户想要使用…...

学会Sass的高级用法,减少样式冗余

在当今的前端开发领域&#xff0c;样式表语言的进步已经显著提升了代码组织性和可维护性。Sass&#xff08;Syntactically Awesome Style Sheets&#xff09;作为CSS预处理器的翘楚&#xff0c;以其强大的变量、嵌套规则、混合宏&#xff08;mixin&#xff09;、循环和函数等高…...

【Java初阶(五)】类和对象

❣博主主页: 33的博客❣ ▶文章专栏分类: Java从入门到精通◀ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; 目录 1. 前言2.面向对象的认识3.类的认识4. 类的实例化4.1什么是实例化4.2类和对象的说明 5.this引用6.对象初始化6.1 构造方法 7.static关键字8.代码块8.1 …...

AWTK-MODBUS 服务器

AWTK-MODBUS 服务器 1. 介绍 AWTK-MODBUS 提供了一个简单的 MODBUS 服务器&#xff0c;可以通过配置文件来定义寄存器和位的数量和初始值。 启动方法&#xff1a; bin/modbus_server_ex config/default.json2. 配置文件 配置文件使用JSON格式。 url: 连接地址auto_inc_in…...

JavaScript快速入门笔记之一(基本概念)

JavaScript快速入门笔记之一&#xff08;基本概念&#xff09; 前端三大语言&#xff1a; HTML&#xff1a;专门编写网页内容的语言CSS&#xff1a;专门美化网页样式的语言JavaScript&#xff1a;专门编写网页交互的语言 名词解释&#xff1a; 交互&#xff1a;输入数据&#…...

前端学习之css基本网格布局

网格布局 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>网格布局</title><style>.a{/* grid网格布局 */display: grid;width: 400px;height: 400px;border: 1px solid red;/* 设置当前…...

[网鼎杯2018]Unfinish 两种方法 -----不会编程的崽

网鼎杯太喜欢搞二次注入了吧。上次是无列名盲注&#xff0c;这次又是二次注入盲注。。。不知道方法还是挺难的。哎&#xff0c;网鼎嘛&#xff0c;能理解透彻就很强了。能自己做出来那可太nb了。 又是熟悉的登录框。不知道这是第几次看见网鼎杯的登录框了。后台扫描一下&#x…...

安防监控视频汇聚平台EasyCVR在银河麒麟V10系统中的启动异常及解决方法

安防监控视频平台EasyCVR具备较强的兼容性&#xff0c;它可以支持国标GB28181、RTSP/Onvif、RTMP&#xff0c;以及厂家的私有协议与SDK&#xff0c;如&#xff1a;海康ehome、海康sdk、大华sdk、宇视sdk、华为sdk、萤石云sdk、乐橙sdk等。平台兼容性强&#xff0c;支持Windows系…...

了解云原生

声明&#xff1a;内容来自AI&#xff0c;未经验证&#xff0c;仅供参考! 1、云原生学习路线 学习云原生(Cloud Native)技术涉及了解和掌握一系列的概念、技术和工具。云原生是一种构建和运行应用程序的方法&#xff0c;旨在充分利用云计算的灵活性、可伸缩性和弹性。以下是一…...

【go从入门到精通】for和for range的区别

作者简介&#xff1a; 高科&#xff0c;先后在 IBM PlatformComputing从事网格计算&#xff0c;淘米网&#xff0c;网易从事游戏服务器开发&#xff0c;拥有丰富的C&#xff0c;go等语言开发经验&#xff0c;mysql&#xff0c;mongo&#xff0c;redis等数据库&#xff0c;设计模…...

【C语言】【Leetcode】88. 合并两个有序数组

文章目录 一、题目二、思路再思考 一、题目 链接: link 二、思路 这题属于简单题&#xff0c;比较粗暴的做法就是直接比较两个数组&#xff0c;先把第二个数组加到第一个的后面&#xff0c;如何冒泡排序&#xff0c;这种方法简单粗暴但有效&#xff0c;可是不适用于这题&…...

Android Wi-Fi 连接失败日志分析

1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分&#xff1a; 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析&#xff1a; CTR…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI

前一阵子在百度 AI 开发者大会上&#xff0c;看到基于小智 AI DIY 玩具的演示&#xff0c;感觉有点意思&#xff0c;想着自己也来试试。 如果只是想烧录现成的固件&#xff0c;乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外&#xff0c;还提供了基于网页版的 ESP LA…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

Spring数据访问模块设计

前面我们已经完成了IoC和web模块的设计&#xff0c;聪明的码友立马就知道了&#xff0c;该到数据访问模块了&#xff0c;要不就这俩玩个6啊&#xff0c;查库势在必行&#xff0c;至此&#xff0c;它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据&#xff08;数据库、No…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...