Read WMRouter source code, mainly to clarify two questions:
-
Registration of routes
How does the framework register and centrally manage the route scans we configure
-
The routing process
How to implement a route through router. startUri
0. WMRouter access
- Based on library
- Libraries that use annotations
- Gradle plug-in
- Confusing rules
- For version 1.2.1, replace the package name
- Application initialization
// create RootHandler DefaultRootUriHandler RootHandler = new DefaultRootUriHandler(context); // Initialize router.init (rootHandler);Copy the code
The routing process
1. Routing processor & Routing registration
1.1, UriHandler
Handles a class of URIs. Provide a shouldHandle abstract method that subclasses can override to determine if they can handle the current UriRequest. provide
HandleInternal abstract methods are used by subclasses to implement concrete processing logic. UriHandle itself provides a handle method that specifies all handlers
Call shouldHandle to see if the current Handler needs processing. It then checks whether the Handler is configured with an interceptor. If there is an interceptor, hand it to the interceptor first; otherwise, call handleInternal.
public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
if (shouldHandle(request)) {
Debugger.i("%s: handle request %s", this, request);
if (mInterceptor != null && !request.isSkipInterceptors()) {
mInterceptor.intercept(request, new UriCallback() {
@Override
public void onNext() {
handleInternal(request, callback);
}
@Override
public void onComplete(int result) {
callback.onComplete(result);
}
});
} else {
handleInternal(request, callback);
}
} else {
Debugger.i("%s: ignore request %s", this, request);
callback.onNext();
}
}
Copy the code
1.2, DefaultRootUriHandler
The default root route handler registered in Appliction can also be seen in the figure provided above
1.3, PageAnnotationHandler
Inheriting PathHandler (inheriting UriHandler)
-
shouldHandle
- This parameter is processed only when the URI format is Wm_router ://page/ XXXX
-
The handle method, a copy of the handle method, calls ensureInit to initialize, and the subsequent logic is still the handle logic in UriHandler
- EnsureInit is implemented via LazyInitHelper
- ServiceLoaderInit (generated by the Gradle plugin) scans all ServiceInit_XXX and calls the init method to pass PageAnnotationInit_XXX and UriAnnotationInit_XXX ServiceLoader registered
- EnsureInit gets the above registered XXXAnnotation_XXX implementation class and calls init to register the corresponding page Handler
-
handlerInteral
public static final String SCHEME = "wm_router"; public static final String HOST = "page"; public static final String SCHEME_HOST = RouterUtils.schemeHost(SCHEME, HOST); @Override protected boolean shouldHandle(@NonNull UriRequest request) { return SCHEME_HOST.matches(request.schemeHost()); } @Override public void handle(@NonNull UriRequest request, @NonNull UriCallback callback) { mInitHelper.ensureInit(); super.handle(request, callback); } private final LazyInitHelper mInitHelper = new LazyInitHelper("PageAnnotationHandler") { @Override protected void doInit() { initAnnotationConfig(); }}; PageAnnotationInit_XXX protected void initAnnotationConfig() { RouterComponents.loadAnnotation(this, IPageAnnotationInit.class); } public static <T extends UriHandler> void loadAnnotation(T handler, Class<? extends AnnotationInit<T>> initClass) { sAnnotationLoader.load(handler, initClass); } public class DefaultAnnotationLoader implements AnnotationLoader {public static final AnnotationLoader INSTANCE = new DefaultAnnotationLoader(); @Override public <T extends UriHandler> void load(T handler, Class<? Extends AnnotationInit<T>> initClass) {// ServiceLoader: List<? extends AnnotationInit<T>> services = Router.getAllServices(initClass); For (AnnotationInit<T> service: services) {service.init(handler); } } } public class PageAnnotationInit_bfd2eeb94778305f7d7586fccf7b2630 implements IPageAnnotationInit { public void init(PageAnnotationHandler handler) { handler.register("/fragment/kroom/feed", new FragmentTransactionHandler("com.netease.cloudmusic.singroom.feed.ui.fragments.SingRoomFeedFragment")); }}Copy the code
PathHandler
Distribute URIs to child nodes based on path. Supported child nodes include ActivityClassName, ActivityClass, UriHandler (official annotation)
- Scheme + host: corresponds to a PathHandler
- The mMap inside the PathHandler records the corresponding path and Handler (registered by calling the generated bytecode during init).
UriAnnotationHandler
StartUriHandler
1.4, UriAnnotationHandler
The implementation is similar to PageAnnotationHandler, which supports different schemes and internally constructs a PathHandler to complete the registration. Unlike PageAnnotationHandler, PageAnnotationHandler is itself a PathHandler type.
public static UriHandler parse(Object target, int priority, Map<String, Integer> params, Class<? extends UriInterceptor>... interceptors) { UriHandler handler = toHandler(target, params); if (handler ! = null) { if (UriSourceTools.FROM_INTERNAL == priority) { handler.addInterceptor(SourceInternalInterceptor.class); } else if (UriSourceTools.FROM_EXTERNAL == priority) { handler.addInterceptor(SourceExternalInterceptor.class); } else if (UriSourceTools.FROM_WEBVIEW == priority) { handler.addInterceptor(SourceWebViewInterceptor.class); } handler.addInterceptors(interceptors); } return handler; } private static UriHandler toHandler(Object target, Map<String, Integer> params) { if (target instanceof UriHandler) { return (UriHandler) target; } else if (target instanceof String) {return new ActivityClassNameHandler((String) target, params); } else if (target instanceof Class) { Class clazz = (Class) target; if (isValidActivityClass(clazz)) { //noinspection unchecked return new ActivityHandler((Class<? extends Activity>) target, params); } else if (isValidUriHandlerClass(clazz)) { try { if (isSingleInstance(clazz)) { return (UriHandler) SingletonPool.get(clazz, null); } else { return (UriHandler) clazz.newInstance(); } } catch (Exception e) { e.printStackTrace(); } return null; } } return null; }Copy the code
1.5. Fragment and Activity handlers
- FragmentTransactionHandler
Registered by the PageAnnotationHandler when it loads the PageAnnotationInit_xxxxxx generated by the annotation handler
- ActivityClassNameHandler
Registered by the UriAnnotationHandler when it loads the UriAnnotationInit_xxxxxx generated by the annotation handler
1.6. The Gradle plugin generates the initial registration code
WMRouterTransform
public void transform(TransformInvocation invocation) { WMRouterLogger.info(TRANSFORM + "start..." ); long ms = System.currentTimeMillis(); If (! invocation.isIncremental()) { try { invocation.getOutputProvider().deleteAll(); } catch (IOException e) { WMRouterLogger.fatal(e); } } Set<String> initClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); Set<String> deleteClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); BaseTransform baseTransform = new BaseTransform(invocation, new TransformCallBack() { @Nullable @Override public byte[] process(@NotNull String className, @Nullable byte[] classBytes) { String checkClassName = ClassUtils.path2Classname(className); if (checkClassName.startsWith(Const.GEN_PKG_SERVICE)) { WMRouterLogger.info(TRANSFORM + "className = %s, checkClassName = %s", className, checkClassName); initClasses.add(className); } return null; } }, false); baseTransform.setDeleteCallBack(new DeleteCallBack() { @Override public void delete(String className, byte[] bytes) { String checkClassName = ClassUtils.path2Classname(className); if (checkClassName.startsWith(Const.GEN_PKG_SERVICE)) { deleteClasses.add(className); }}}); baseTransform.openSimpleScan(); baseTransform.startTransform(); File dest = invocation.getOutputProvider().getContentLocation( "WMRouter", TransformManager.CONTENT_CLASS, ImmutableSet.of(QualifiedContent.Scope.PROJECT), Format.DIRECTORY); generateServiceInitClass(dest.getAbsolutePath(), initClasses, deleteClasses); WMRouterLogger.info(TRANSFORM + "cost %s ms", System.currentTimeMillis() - ms); } private void generateServiceInitClass(String directory, Set<String> classes, Set<String> deleteClass) { if (classes.isEmpty()) { WMRouterLogger.info(GENERATE_INIT + "skipped, no service found"); return; } File dest = new File(directory, INIT_SERVICE_PATH + SdkConstants.DOT_CLASS); if (! dest.exists()) { try { WMRouterLogger.info(GENERATE_INIT + "start..." ); long ms = System.currentTimeMillis(); ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); ClassVisitor cv = new ClassVisitor(Opcodes.ASM5, writer) { }; String className = consent.service_loader_init. replace('.', '/'); cv.visit(50, Opcodes.ACC_PUBLIC, className, null, "java/lang/Object", null); / / 2, the init method statement (static) open & MethodVisitor mv = CV. VisitMethod (Opcodes. ACC_PUBLIC | Opcodes. ACC_STATIC, Const. INIT_METHOD, "()V", null, null); // call mv.visitcode () through the classes argument; for (String clazz : classes) { String input = clazz.replace(".class", ""); input = input.replace(".", "/"); mv.visitMethodInsn(Opcodes.INVOKESTATIC, input, "init", "()V", false); } mv.visitMaxs(0, 0); mv.visitInsn(Opcodes.RETURN); mv.visitEnd(); cv.visitEnd(); Dest.getparentfile ().mkdirs(); new FileOutputStream(dest).write(writer.toByteArray()); WMRouterLogger.info(GENERATE_INIT + "cost %s ms", System.currentTimeMillis() - ms); } catch (IOException e) { WMRouterLogger.fatal(e); } } else { try { modifyClass(dest, classes, deleteClass); } catch (IOException e) { e.printStackTrace(); }}}Copy the code
- BaseTransform will all to com. Sankuai. Waimai. The router. Generated. The service at the beginning of class is added to a Set in the collection, It is then passed as a parameter to the generateServiceInitClass method;
- The generateServiceInitClass method generates the ServiceLoaderInit class and calls some of the column init methods APT generates to register routes.
3. Routing process
Router.startUri ==> rooturiHandler. startUri (DefaultRootUriHandler
DefaultRootUriHandler creates the Request and calls handle) ==> urihandle.handle
DefaultRootUriHandler inherits RootUriHandler inherits ChainedHandler inherits UriHandler
The handle method first checks if it shouldHandle shouldHandle (in ChainedHandler, it shouldHandle as long as the mHandlers list is not empty, mHandlers are assigned when they are initialized)
public DefaultRootUriHandler(Context context, @Nullable String defaultScheme, @Nullable String defaultHost) { super(context); mPageAnnotationHandler = createPageAnnotationHandler(); mUriAnnotationHandler = createUriAnnotationHandler(defaultScheme, defaultHost); AddChildHandler (mPageAnnotationHandler, 300); addChildHandler(mPageAnnotationHandler, 300); AddChildHandler (mUriAnnotationHandler, 200); // Handle the URI jump defined by the RouterUri annotation. // Add other user-defined handlers... // StartUri addChildHandler(new StartUriHandler(), -100) with default StartUriHandler; } public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) { if (shouldHandle(request)) { Debugger.i("%s: handle request %s", this, request); if (mInterceptor ! = null && ! request.isSkipInterceptors()) { mInterceptor.intercept(request, New UriCallback() {@override public void onNext() {// HandleInternal (Request, callback) is called after the interceptor call is complete; } @Override public void onComplete(int result) { callback.onComplete(result); }}); } else {// No interceptor directly handleInternal handleInternal(request, callback); } } else { Debugger.i("%s: ignore request %s", this, request); callback.onNext(); }}Copy the code
Since UriHandler is inherited from UriHandler, this Handler code is equivalent to a template method pattern, and the processing of a UriRequest is basically decomposed into three steps:
Step 1: Call shouldHandle to see if it should be handled;
Step 2: Check whether the current Handler has an interceptor. If so, send UriRequest to the interceptor first.
Step 3: Hand the request to handlerInternal for processing.
The actual type of our Handler so far is DefaultRootUriHandler, whose internal handleris implemented in ChainedHandler.
@Override protected void handleInternal(@NonNull final UriRequest request, @NonNull final UriCallback callback) { next(mHandlers.iterator(), request, callback); } private void next(@NonNull final Iterator<UriHandler> iterator, @NonNull final UriRequest request, @NonNull final UriCallback callback) { if (iterator.hasNext()) { UriHandler t = iterator.next(); t.handle(request, new UriCallback() { @Override public void onNext() { next(iterator, request, callback); } @Override public void onComplete(int resultCode) { callback.onComplete(resultCode); }}); } else { callback.onNext(); }}Copy the code
It basically calls its handler methods in the order of the UriHandler, which is initialized when DefaultRootUriHandler is constructed and stored in a priority queue. PageAnnotationHandler, UriAnnotationHandler, and StartUriHandler are listed in order of priority.
3, PageAnnotationHandler
public void handle(@NonNull UriRequest request, @NonNull UriCallback callback) { mInitHelper.ensureInit(); super.handle(request, callback); } protected void handleInternal(final UriRequest request, UriHandler h = getChild(request); if (h ! // Call the handle method of the Handler (request, new UriCallback() { @Override public void onNext() { handleByDefault(request, callback); } @Override public void onComplete(int resultCode) { callback.onComplete(resultCode); }}); } else {// call the handle method handleByDefault(request, callback); Private UriHandler getChild(@nonNULL UriRequest Request) {String path = request.getUri().getPath(); UriHandler handler; if (TextUtils.isEmpty(path)) { return mMap.get("/"); } if (! TextUtils.isEmpty(mPathPrefix)) { path = path.substring(mPathPrefix.length()); } path = Util.appendSlash(path); handler = mMap.get(path); If (handler == null) {//modify by zyh If the normal routing table does not match the route information, search for the matched route information from the restfulMap. Handler = getRestfulChild(request); } if (handler == null) { handler = mMap.get("/"); } return handler; }Copy the code
-
First call minithelper.ensureinit () to determine whether it is initialized or not. If it is not initialized, initialization is performed. Initialization is actually the process of registering the final route processor with the boilerplate code generated by the scanning annotation processor above.
-
After that, the handle method of the base class UriHandler will be called. According to the above idea, the handleInternal will be called eventually, and the PageAnnotationHandler inherits from the PathHandler, which implements the method.
-
Call the handler method of a uriAnnotationHandler that can be found in mMap, otherwise the handle method that calls the default handler (PageAnnotationHandler) does not have a default handler
4, FragmentTransactionHandler
By PageAnnotationHandler FragmentTransactionHandler is a routing processor type, used to route to a page Fragment. Similarly, we’ll focus on the handleInternal method.
protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) { if (TextUtils.isEmpty(mClassName)) { The Debugger. Fatal (" FragmentTransactionHandler handleInternal () should be returned with the ClassName "); callback.onComplete(UriResult.CODE_BAD_REQUEST); return; } StartFragmentAction action = request.getField(StartFragmentAction.class, StartFragmentAction.START_FRAGMENT_ACTION); If (action = = null) {Debugger. Fatal (" FragmentTransactionHandler. HandleInternal () should return with StartFragmentAction "); callback.onComplete(UriResult.CODE_BAD_REQUEST); return; } if (! Request. hasField(FRAGMENT_CLASS_NAME)) {// Make a judgment to replace request.putField(FRAGMENT_CLASS_NAME, mClassName); } // Extra Bundle extra = request.getField(Bundle.class, FIELD_INTENT_EXTRA); boolean success = action.startFragment(request, extra); // Complete callback. OnComplete (success? UriResult.CODE_SUCCESS : UriResult.CODE_BAD_REQUEST); } public boolean startFragment(@NonNull UriRequest request, @NonNull Bundle bundle) throws ActivityNotFoundException, SecurityException { String fragmentClassName = request.getStringField(FragmentTransactionHandler.FRAGMENT_CLASS_NAME); try { Fragment fragment = Fragment.instantiate(request.getContext(), fragmentClassName, bundle); if (fragment == null) { return false; } FragmentTransaction transaction = mFragmentManager.beginTransaction(); switch (mStartType) { case TYPE_ADD: transaction.add(mContainerViewId, fragment, mTag); break; case TYPE_REPLACE: transaction.replace(mContainerViewId, fragment, mTag); break; } if (mAllowingStateLoss) { transaction.commitAllowingStateLoss(); } else { transaction.commit(); } return true; } catch (Exception e) { Debugger.e("FragmentTransactionUriRequest",e); return false; }}Copy the code
- An instance of StartFragmentAction is created and the startFragment method is invoked.
- StartFragmentAction is an interface, and let’s look at one of the implementations.
- Instantiate a Fragment object with a FragmentTransaction call to Add or replace to display it.
5, UriAnnotationHandler
If the PageAnnotationHandler of the previous step is not processed, the next step is handled by the UriAnnotationHandler. The handling logic is similar to that of PageAnnotationHandler, which is initialized first and then calls UriHandler’s handle method. Unlike the PageAnnotationHandler, which directly inherits from the UriAnnotationHandler, the UriAnnotationHandler directly inherits from the PathHandler.
public void handle(@NonNull UriRequest request, @NonNull UriCallback callback) { mInitHelper.ensureInit(); super.handle(request, callback); } protected void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback) { PathHandler pathHandler = getChild(request); if (pathHandler ! = null) { pathHandler.handle(request, callback); } else {// call callback.onNext(); } } private final Map<String, PathHandler> mMap = new HashMap<>(); private PathHandler getChild(@NonNull UriRequest request) { return mMap.get(request.schemeHost()); }Copy the code
-
From the above analysis, it can be seen that the handleInternal method is finally entered. First, the corresponding PathHandler is obtained from mMap through Scheme + host, and then its handle method (also the handle of UriHandler) is called.
-
It also ends up calling the handleInternal method of PathHandler
-
In UriAnnotationHandler there is a PathHandler for each Scheme + host, and multiple routes can be registered in a PathHandler.
-
There’s only one PathHandler in PageAnnotationHandler (which is itself, with its scheme and host fixed)
-
The following processing is similar to that in PageAnnotationHandler: the specific route handler is found in the PathHandler and the corresponding handle method is called
6, ActivityClassNameHandler
This Handler is parsed and registered by the UriAnnotationHandler. Inherits the AbsActivityHandler and ultimately hands it off to its handleInternal method.
protected void handleInternal(@NonNull UriRequest request, @nonnull UriCallback callback) {// createIntent Intent = createIntent(request); if (intent == null || intent.getComponent() == null) { Debugger. Fatal (" AbsActivityHandler createIntent () should be returned with the ClassName explicit jump Intent "); callback.onComplete(UriResult.CODE_ERROR); return; } intent.setData(request.getUri()); UriSourceTools.setIntentSource(intent, request); PutFieldIfAbsent (ActivityLauncher.FIELD_LIMIT_PACKAGE, limitPackage())); // Start Activity Request.putFieldiFabsent (ActivityLauncher. int resultCode = RouterComponents.startActivity(request, intent); // Call onActivityStartComplete(request, resultCode); / / complete callback. The onComplete (the resultCode); }Copy the code
4, other
WMRouter provides ServiceLoader functionality in addition to page routing, as well as the @AutoWired annotation to automatically inject routing parameters.