WMRouter Documentation

repository·master·Indexed 25 days ago

https://github.com/meituan/wmrouter

An Android routing framework for componentized architectures. It provides URI-based page dispatching with support for regex matching and interceptors, as well as an SPI-based ServiceLoader for decoupled inter-module communication. Key features include support for hybrid Native/H5 development, external URI management via proxy activities, and flexible implementation of custom UriHandlers and UriInterceptors.

Tokens
10K
Snippets
18
Records
33
Agent score
81%

What's inside WMRouter

  1. Overview of WMRouter

    master

    WMRouter is an Android routing framework designed for component-based architectures. It provides two primary capabilities to facilitate communication and navigation in modularized Android applications:

    1. URI Dispatching: Enables page navigation between different modules or via dynamic URI links. It supports multiple schemes, hosts, and paths, URI regex matching, and interceptors for pre-navigation logic (like authentication or location checks).
    2. ServiceLoader: An enhanced implementation of the SPI (Service Provider Interface) pattern. It allows modules to call code via interfaces without direct dependencies, enabling decoupled inter-module communication and dependency injection-like functionality.

    Key features include optimized Gradle plugin performance, compile-time and runtime configuration checks, and robust debugging tools.

  2. Common Use Cases for WMRouter

    master

    WMRouter is particularly useful in the following scenarios:

    • Hybrid (Native + H5) Development: Unify navigation logic across different protocols (HTTP, HTTPS, custom schemes) and use UriInterceptor to inject parameters into H5 URLs.
    • External URI Management: Centralize external URI jumps through a single Activity to ensure the App completes initialization (login, location) before navigating to the target page.
    • Complex Navigation Logic: Use UriInterceptor to handle repetitive business logic (e.g., requiring login or location) across multiple pages instead of duplicating code in every Activity.
    • Componentized/Multi-Project Development: Facilitate inter-module communication and code reuse in large-scale, modularized Android projects.
    • Business Analytics (Tracking): Use global OnCompleteListener to implement unified business event tracking for all page jumps.
    • High Availability & Reliability: Implement fallback/degradation strategies to automatically open a safe page if a primary feature fails or crashes.
    • A/B Testing & Dynamic Configuration: Use UriInterceptor to return a 301 UriResult for redirection, allowing different URIs to map to different pages based on remote configurations or A/B test strategies.
  3. Core Capabilities of WMRouter

    master

    URI Dispatching

    Used for page jumping between multiple projects or via dynamic URI links.

    • Flexible Matching: Supports multiple schemes, hosts, paths, and URI regex.
    • Registration: Pages can be registered dynamically via Java code or automatically via annotations.
    • Interceptors: Supports global and local interceptors to perform synchronous or asynchronous operations (e.g., login, positioning) before navigation.
    • Customization: Allows setting Intent Extras/Flags, transition animations, and custom StartActivity operations for single jumps.
    • Control & Safety: Supports Exported control to prevent external jumps to specific pages, and provides global/local fallback (degradation) strategies.
    • Monitoring: Supports single-jump and global jump listeners.

    ServiceLoader

    Based on the SPI design pattern to decouple modules.

    • Automatic Configuration: Uses annotations for setup.
    • Flexible Retrieval: Can fetch all implementations of an interface, a specific implementation by Key, or retrieve a Class or instance.
    • Construction Options: Supports no-arg constructors, Context-based constructors, or custom Factory/Provider constructors.
    • Lifecycle: Supports singleton management and method invocation.
  4. How URI跳转 core design works

    master

    WMRouter's URI navigation is modeled after network requests. Every navigation is a UriRequest that is dispatched through a chain of UriHandlers.

    • UriRequest: Contains the Context, the URI, and a Fields map (HashMap<String, Object>). The Fields map acts as a communication channel between components, storing data like Intent extras, request codes, or custom listeners.
    • UriHandler: Responsible for processing a request. Handlers are asynchronous. A handler can either:
      • Call callback.onComplete(resultCode) to finish the navigation process.
      • Call callback.onNext() to pass the request to the next handler in the chain.
    • UriInterceptor: Interceptors perform synchronous or asynchronous operations (like checking login status or modifying URI parameters) before the UriHandler executes its logic. Each UriHandler can have multiple interceptors.
    public interface UriCallback {
        /**
         * 处理完成,继续后续流程。
         */
        void onNext();
    
        /**
         * 处理完成,终止分发流程。
         *
         * @param resultCode 结果
         */
        void onComplete(int resultCode);
    }
  5. How URI dispatching works in WMRouter

    master

    When a UriRequest is received, the DefaultRootUriHandler attempts to dispatch it through a specific sequence of handlers. The order of priority is:

    1. PageAnnotationHandler: Handles URIs matching the wm_router://page/* pattern. These are used for internal page jumps configured via @RouterPage.
    2. UriAnnotationHandler: Matches the URI's scheme + host, then finds a PathHandler which matches the path via @RouterUri.
    3. RegexAnnotationHandler: Attempts to match the URI against patterns defined by @RouterRegex based on their priority.
    4. StartUriHandler: A fallback that uses standard Android implicit intents for URIs that don't match the above (e.g., tel:*, mailto:*).
  6. Install WMRouter via Gradle

    master

    Follow these steps to integrate WMRouter into your Android project:

    1. Add Base Library Dependency

    In your base library module:

    repositories {
        jcenter()
    }
    dependencies {
        compile 'com.sankuai.waimai.router:router:1.x'
    }

    2. Configure Annotation Processor

    In every module using annotations (including Application and Library modules):

    Java Modules:

    repositories {
        jcenter()
    }
    dependencies {
        annotationProcessor 'com.sankuai.waimai.router:compiler:1.x'
    }

    Kotlin Modules:

    apply plugin: 'com.android.library'
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-android-extensions'
    apply plugin: 'kotlin-kapt'
    
    repositories {
        jcenter()
    }
    dependencies {
        kapt 'com.sankuai.waimai.router:compiler:1.x'
    }

    3. Configure WMRouter Plugin

    In your project's root build.gradle:

    buildscript {
        repositories {
            jjecter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.2.1'
            classpath "com.sankuai.waimai.router:plugin:1.x"
        }
    }

    Note: If the WMRouter plugin causes your Android Gradle Plugin version to be upgraded unexpectedly, use an exclude block to prevent it.

    In your Application module's build.gradle:

    apply plugin: 'com.android.application'
    apply plugin: 'WMRouter'
    // Example of excluding Android Gradle plugin if version conflict occurs
    classpath("com.sankuai.waimai.router:plugin:1.x") {
        exclude group: 'com.android.tools.build'
    }
  7. Extend WMRouter core components

    master

    You can customize the routing behavior by extending RootUriHandler and UriRequest.

    Customizing RootUriHandler: Inherit from RootUriHandler and use addHandler() to register custom handlers (e.g., UriAnnotationHandler or custom HTTP handlers).

    Customizing UriRequest: Inherit from UriRequest to add custom properties to a request. Use putField(key, value) to store data.

    Integration: Pass your custom RootUriHandler to Router.init().

    // 1. Define Custom RootUriHandler
    public class CustomRootUriHandler extends RootUriHandler {
        public CustomRootUriHandler() {
            addHandler(new UriAnnotationHandler());
            addHandler(new CustomHttpHandler());
        }
    }
    
    // 2. Define Custom UriRequest
    public class CustomUriRequest extends UriRequest {
        public CustomUriRequest setCustomProperties(String s) {
            putField("custom_properties", s);
            return this;
        }
    }
    
    // 3. Initialize and Use
    Router.init(new CustomRootUriHandler());
    
    CustomUriRequest request = new CustomUriRequest(mContext, url)
        .setCustomProperties("xxx");
    Router.startUri(request);
  8. Optimize initialization with lazy loading

    master

    WMRouter performs resource reading, reflection, and instance creation during initialization, which can impact App startup time. To mitigate this, use Router.lazyInit() in a background thread.

    Initialization Steps:

    1. Mandatory: Call Router.init(RootUriHandler) on the Main Thread.
    2. Optional: Call Router.lazyInit() on a Background Thread to pre-load lazy-loaded components.
    void initRouter(Context context) {
        // Mandatory: must be on Main Thread
        Router.init(new DefaultRootUriHandler(context));
        
        // Background thread for lazy loading
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void[] objects) {
                Router.lazyInit();
                return null;
            }
        }.execute();
    }
  9. Use RxJava and Coroutines with WMRouter

    master

    WMRouter provides extensions for modern asynchronous programming:

    • RxJava Support: Use RxRouterExtension to convert requests into RxJava operators. This is best used in conjunction with the ForResultActivityLauncher.
    • Coroutine Support: Use SuspendRequestExtension to convert UriRequest into a suspending function. This is recommended for use with lifecycle-viewmodel-ktx.
  10. Migrate Group ID for WMRouter

    master

    Due to the deprecation of JCenter, starting from version 1.2.1, the Group ID has changed. When configuring your project, ensure you use the new Group ID:

    • Old Group ID: com.sankuai.waimai.router
    • New Group ID: io.github.meituan-dianping
  11. Configure Proguard for WMRouter

    master

    When using Proguard/R8, you must ensure that classes annotated with @RouterService (or used by the router) are not removed or obfuscated in a way that breaks reflection.

    Requirements:

    1. Keep Annotations: Prevent annotations from being stripped during the shrink phase so they remain available for the obfuscate phase.
    2. Keep Members: For classes implementing RouterService, you must prevent Proguard from removing or obfuscating constructors and methods required for reflection.

    Note: The class name itself can be obfuscated, but the internal members used for reflection must remain intact.

  12. Handle External URI via Proxy Activity

    master

    For external URI jumps (e.g., from a browser), it is recommended to use a UriProxyActivity as an intermediary. This activity should be exported=true and contain an <intent-filter> for your scheme. The proxy activity handles the asynchronous logic and closes itself once the navigation is complete.

    <!-- AndroidManifest.xml -->
    <activity android:name=".UriProxyActivity" android:exported="true">
        <intent-filter>
            <data android:scheme="demo"/>
        </intent-filter>
    </activity>
    // UriProxyActivity.java
    public class UriProxyActivity extends BaseActivity {
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            DefaultUriRequest.startFromProxyActivity(this, new OnCompleteListener() {
                @Override
                public void onSuccess(@NonNull UriRequest request) {
                    finish();
                }
    
                @Override
                public void onError(@NonNull UriRequest request, int resultCode) {
                    finish();
                }
            });
        }
    }