【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个处理器,分别是mPageAnnotationHandler
、mUriAnnotationHandler
、mRegexAnnotationHandler
、new 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。
实际的路由注册是PathHandler
的resgiter
方法。
接下来就是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);}}}//省略无关代码
UriTargetTools
的parse
方法中生成了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如何注册路由
我们看一下UriAnnotationHandler
的register
方法在哪里调用的。
//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);}
可以看到调用的正是DefaultAnnotationLoader
的load
方法。
DefaultAnnotationLoader
的load
方法(参见上文)调用了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";}}
}
ServiceLoader
的load
方法第一行代码就是初始化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分别是ActivityTest1
和ActivityTest3
添加注解,在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(...)
方法完成的。整体流程如下:
笔者原创,转载请注明出处:https://blog.csdn.net/devnn/article/details/136969237
相关文章:

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

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

浅谈Linux中的软锁定(soft lockup)和硬件监视器(watchdog)
目录 1. 问题所示2. 基本知识3. 进阶知识 1. 问题所示 跑深度学习的时候遇到卡顿卡机 hostname kernel:watchdog BUG:soft lockup - CPU#16 stuck for 130s![P2PMain-72:4030570]界面如下所示: 大概意思是: watchdog_thresh参数是硬件监视器的超时阈值…...
数据库的四个特性?MySQL是如何实现的?
首先MySQL中,数据库的四个特性分为: 原子性一致性隔离性持久性 也就是我们常说的ACID。 那么这四个特性数据库是如何实现的呢? 持久性---> redo log: redo log(重做日志): redolog本身是…...

Jupyter R绘图 汉字显示乱码的解决办法
1.Jupyte中,R绘图,汉字显示乱码 2.如何解决? (1)R中安装showtext 登录linux服务器 #R > install.packages(“showtext”) … 出错 (2)退出R,安装freetype-config #apt install libfreetype6-dev 出错 (3)进入R&…...
推荐几个值得一读的Qt开源项目
VNote - 基于Qt的免费开源笔记软件,适合那些寻找跨平台笔记解决方案的用户。项目地址:https://github.com/vnotex/vnote Qt NodeEditor - 类似于UE4蓝图的节点编辑器,对于想要深入了解Qt图形编辑和节点系统的人来说,这是一个极好…...

【XR806开发板试用】使用PWM模块模拟手机呼吸灯提示功能
一般情况下,我们的手机在息屏状态,当收到消息处于未读状态时,会有呼吸灯提醒,这次有幸抽中XR806开发板的试用,经过九牛二虎之力终于将环境搞好了,中间遇到各种问题,在我的另一篇文章中已详细描述…...
Mysql——索引下推
MySQL的索引下推(Index Condition Pushdown, ICP)是一种查询优化技术,它允许MySQL在存储引擎层执行部分WHERE子句中的过滤条件,而非全部在MySQL服务器层执行。这使得在扫描索引过程中就可以剔除不满足条件的记录,从而减…...
Springboot项目之mybatis-plus多容器分布式部署id重复问题之源码解析
mybatis-plus 3.3.2 部署多个pod id冲突问题 配置: # 设置随机 mybatis-plus.global-config.worker-id: ${random.int(1,31)} mybatis-plus.global-config.datacenter-id: ${random.int(1,31)}源码解析:MybatisSqlSessionFactoryBean 重点:…...

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

学会Sass的高级用法,减少样式冗余
在当今的前端开发领域,样式表语言的进步已经显著提升了代码组织性和可维护性。Sass(Syntactically Awesome Style Sheets)作为CSS预处理器的翘楚,以其强大的变量、嵌套规则、混合宏(mixin)、循环和函数等高…...

【Java初阶(五)】类和对象
❣博主主页: 33的博客❣ ▶文章专栏分类: Java从入门到精通◀ 🚚我的代码仓库: 33的代码仓库🚚 目录 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 服务器,可以通过配置文件来定义寄存器和位的数量和初始值。 启动方法: bin/modbus_server_ex config/default.json2. 配置文件 配置文件使用JSON格式。 url: 连接地址auto_inc_in…...
JavaScript快速入门笔记之一(基本概念)
JavaScript快速入门笔记之一(基本概念) 前端三大语言: HTML:专门编写网页内容的语言CSS:专门美化网页样式的语言JavaScript:专门编写网页交互的语言 名词解释: 交互:输入数据&#…...

前端学习之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 两种方法 -----不会编程的崽
网鼎杯太喜欢搞二次注入了吧。上次是无列名盲注,这次又是二次注入盲注。。。不知道方法还是挺难的。哎,网鼎嘛,能理解透彻就很强了。能自己做出来那可太nb了。 又是熟悉的登录框。不知道这是第几次看见网鼎杯的登录框了。后台扫描一下&#x…...

安防监控视频汇聚平台EasyCVR在银河麒麟V10系统中的启动异常及解决方法
安防监控视频平台EasyCVR具备较强的兼容性,它可以支持国标GB28181、RTSP/Onvif、RTMP,以及厂家的私有协议与SDK,如:海康ehome、海康sdk、大华sdk、宇视sdk、华为sdk、萤石云sdk、乐橙sdk等。平台兼容性强,支持Windows系…...

了解云原生
声明:内容来自AI,未经验证,仅供参考! 1、云原生学习路线 学习云原生(Cloud Native)技术涉及了解和掌握一系列的概念、技术和工具。云原生是一种构建和运行应用程序的方法,旨在充分利用云计算的灵活性、可伸缩性和弹性。以下是一…...
【go从入门到精通】for和for range的区别
作者简介: 高科,先后在 IBM PlatformComputing从事网格计算,淘米网,网易从事游戏服务器开发,拥有丰富的C,go等语言开发经验,mysql,mongo,redis等数据库,设计模…...

【C语言】【Leetcode】88. 合并两个有序数组
文章目录 一、题目二、思路再思考 一、题目 链接: link 二、思路 这题属于简单题,比较粗暴的做法就是直接比较两个数组,先把第二个数组加到第一个的后面,如何冒泡排序,这种方法简单粗暴但有效,可是不适用于这题&…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...

业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
C++八股 —— 单例模式
文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全(Thread Safety) 线程安全是指在多线程环境下,某个函数、类或代码片段能够被多个线程同时调用时,仍能保证数据的一致性和逻辑的正确性…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...