Wmrouter: Android Open Source routing framework for us-American takeout

Source: Internet
Author: User
Tags event listener rfc email protocols notification center





Wmrouter is an Android routing framework that is flexible and easy to use, based on a modular design approach.



Wmrouter was originally used to solve the actual problems in the business evolution of the C-terminal app, and then it was gradually extended to other US apps, so we decided to open it up and hope that more technology peers could develop it and apply it to a wider range of scenarios. GitHub project address and usage documentation see Github.com/meituan/wmrouter.



This paper introduces briefly the function and application of Wmrouter, then introduces the development background and process of wmrouter in detail.


Feature Introduction


Wmrouter mainly provides the URI distribution, the Serviceloader two major functions.



The URI distribution function can be used for the multi-project page jump, dynamically issued a URI link to jump and other scenes, characterized as follows:


    1. Supports multiple scheme, host, path
    2. Support for URI regular matching
    3. Page configuration supports dynamic registration of Java code, or annotation configuration auto-Registration
    4. Supports configuring global and local interceptors to perform synchronous/asynchronous operations, such as locating, logging, etc., before jumping
    5. Support single Jump Special operation: Intent set Extra/flags, set jump rotate picture, custom startactivity operation, etc.
    6. Support page exported control, the specific page does not allow external jump
    7. Support for configuring global and local demotion policies
    8. Support for configuring single and global Jump snooping
    9. Fully modular design with scalable, on-demand combination of core components for flexible and powerful functions


Based on the design idea of the SPI (Service Provider Interfaces), Wmrouter provides serviceloader modules, similar to those in Javajava.util.ServiceLoader, but with improved functionality. With Serviceloader, you can invoke code between multiple modules in an app to decouple the modules, facilitate component-to-module communication, and provide similar functionality to dependency injection. Its characteristics are as follows:


    1. Using Annotations to configure automatically
    2. Supports obtaining all implementations of an interface, or obtaining a specific implementation based on key
    3. Support for acquiring class or getting instances
    4. Supports non-parametric constructs, context constructs, or custom factory, provider constructs
    5. Support for single-case management
    6. Support for method calls


Other Features:


    1. Optimized Gradle plug-ins with little impact on compilation time-consuming
    2. Compile-time and runtime configuration checks to avoid configuration conflicts and errors
    3. Proguard obfuscation rules are added automatically at compile time, eliminating the tedious manual configuration
    4. Complete debugging functions to help identify problems in a timely manner
Applicable scenarios


Wmrouter is applicable but not limited to the following scenarios:


  1. NATIVE+H5 Hybrid Development mode, you need to make a jump between pages, or flexible operation jump link issued. can use Wmrouter Unified page jump logic, according to different protocols (HTTP, HTTPS, for native page custom protocol) jump to the corresponding page, and in the jump process can use Uriinterceptor to change the jump link, For example, when jumping H5 pages, add parameters to the URL.

  2. Unified management of URI jumps from outside the app. Uri jump from outside the app, if using Android native manifest configuration, will start the matching activity directly, and many times want to start the app normally open the first page, complete the general initialization process (such as login, location, etc.) before jumping to the target page. At this point, you can use unified activity to receive all the external URI jumps, and then use Wmrouter to start the target page when you go to the homepage.

  3. The page jumps to a scene with complex judgment logic. For example, multiple pages need to be logged in first, first positioned before allowing open, if you use the general scheme, these pages need to deal with the same business logic, and the use of wmrouter, only need to develop good uriinterceptor and configure to each page.

  4. Multi-engineering, component-based, platform-based development. Multi-engineering development requires that each project can communicate with each other, and may encounter similar code reuse, dependency injection, compilation and other problems, which can be solved by using Wmrouter's URI distribution and Serviceloader module.

  5. A scenario where there is a strong need for a business burial point. Page jumps, as one of the most common business logic, often require burying points. Configure a URI for each page, use Wmrouter Unity for page jumps, and bury the dots in the global oncompletelistener.

  6. Scenarios that require high app availability. On the one hand, the page jump failure can be buried point monitoring escalation, timely detection of online problems; On the other hand, the page jumps can execute the judgment logic, found that the exception (such as server exception, client crashes, etc.) will automatically open the degraded page, to ensure that the key functions of the normal work, or user-friendly tips.

  7. Page A/B testing, dynamic configuration and other scenarios. A small number of development configurations on the basis of the interface provided by Wmrouter can be implemented by jumping different page implementations according to A/b test strategy issued, and dynamically issuing a set of routing tables according to different needs. The same URI jumps to a different set of pages (the implementation facet can be customized uriinterceptor, which returns 301 of the Uriresult to the matching URI to redirect the jump).

Basic concept Explanation


The following is a brief introduction to Wmrouter's development background and process. In order to facilitate the understanding of the text, we first briefly understand and review a few basic concepts.


Routing


According to Wikipedia, routing (routing) can be understood as the process of transmitting information from a source address to a destination in a connected network through a specific protocol. A typical example of this is that in the Internet, routers can send data to specific computers based on IP protocols.


Uri


A URI (Uniform Resource Identifier, Uniform Resource Identifier) is a string that identifies the name of an Internet resource. The composition of the URI is as shown.






Some common URI examples are as follows, including URLs, IP addresses, FTP addresses, files, phone calls, email protocols, etc., which are often used.


    • Http://www.meituan.com
    • http://127.0.0.1:8080
    • Ftp://example.org/resource.txt
    • file:///Users/
    • tel:863-1234
    • Mailto:chris@example.com


Also available in Android is theandroid.net.Uritool class used to process the URIs used in uri,android, mostly scheme, host, path, and query.


Intent jump in Android


The intent jump in Android is divided into two types: explicit jump and implicit jump.



An explicit jump is a intent jump that specifies the ComponentName (class name), which is typically passed through the bundle, and the sample code is as follows:


 
Intent intent = new Intent(context, TestActivity.class);
intent.putExtra("param", "value")
startActivity(intent);


The implicit jump, which does not specify the ComponentName intent jump, finds a matching component by Intentfilter, Intentfilter supports matching of action, category, and data, where data is the URI. For example, the following code will launch the system default browser to open the Web page:


 
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.meituan.com"))
startActivity(intent);


The activity configures the Intentfilter through manifest, for example, the following configuration can match alldemo_scheme://demo_host/***the URI of the shape.


 
<activity android:name=".app.UriProxyActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>

        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>

        <data android:scheme="demo_scheme" android:host="demo_host"/>
    </intent-filter>
</activity>
URI Jump


During the early development of the C-terminal, the product hopes to pass the background URI control client jump to specify the page, so as to achieve flexible operation configuration. The takeaway app uses NATIVE+H5 's hybrid development model, the native page defines a dedicated URI, and the H5 page is loaded with Http/https links in a dedicated WebView container, and the two link jump logic is different and cumbersome to implement.



The URI jump of the native page starts with the Android native Intentfilter, which is started by an implicit jump, but there are problems with poor flexibility, missing functions, and many bugs. For example:


    1. When an external (browser, etc) jumps out of the URI, the system will open the corresponding activity directly, without the normal startup process of the Welcome page, some code logic may not be executed, such as the positioning logic.

    2. There are many pages before opening need to ensure that the user first login or positioning, each page is written once to determine the login, location logic is very troublesome, improve the development and maintenance costs.

    3. Operators may be wrong URI, page jump failure, some jump to the place did not do try-catch processing, will produce crash; some places, although added Try-catch, but the jump failed after no response, the user experience is poor; jump failure is not monitored, Inability to detect and resolve online business anomalies in a timely manner.


To solve these problems, we would like to have an Android URI distribution component that can be processed differently depending on the scheme, host, and path of the URI, while allowing for more flexible intervention during page jumps. Research has found that some of the existing Android routing components are mainly to solve the problem of decoupling between multiple projects, and URIs are often only supported through path distribution, page jump configuration is not flexible enough to meet the actual needs. So we decided to design our own implementation.


Core Design Ideas


The core design ideas of URI distribution mechanism in Wmrouter are presented. Referring to the mechanism of the network request, each URI jump in Wmrouter is considered as initiating a urirequest;uri jump request that is wmrouter distributed to a series of urihandler for processing. Each urihandler can be intercepted by uriinterceptor and inserted into some special operations before processing.





Page Jump Source


A common page jump source is as follows:


    1. Jump from the app's internal native page
    2. Jump from the In-app Web container, which is the H5 page-initiated jump
    3. Invoke the app's jump from outside the app via a URI, such as from the browser, etc.
    4. Invoke the app's jump from notification center push


For jumps from the inside of the app and the Web container, we uniformly change all the jump codes to call Wmrouter processing, while the jumps from the external and push notifications are all received using a separate brokered activity and then called Wmrouter Processing.


Urirequest


The urirequest contains the context, URI, and fields, where fields are hashmap<string, object= "", and any data can be stored by key. For simplicity, the Urirequest class assumes the response function, and the result of the jump request is also saved in the fields. Fields can be customized as needed, some of which are common examples:


    • Intent extra parameter, bundle type
    • Type of requestcode,int for Startactivityforresult
    • Page Toggle Animation Resource for Overridependingtransition method, int[] Type
    • Listener for this jump result, Oncompletelistener type


Each URI jump request will have a resultcode (similar to HTTP request Responsecode), which indicates the result of the jump and is also stored in fields. Common code is as follows, and users can customize code:


    • 200: Jump Success
    • 301: Redirect to another URI, jump again
    • 400: Request error, usually context or URI is empty
    • 403: No jumps, such as HTTP links outside of the jump whitelist, exported of the activity are false, etc.
    • 404: Target not found (activity or urihandler)
    • 500: Error occurred


In summary, Urirequest is used to implement communication functions between all components in a URI jump.


Urihandler


The Urihandler is used to process URI jump requests, which can be nested to distribute and process requests at the layer level. Urihandler is an asynchronous structure that receives urirequest post-processing (such as jump activity, and so on), calls and passes in ResultCode if processing is complete, andcallback.onComplete()if no processing is done, the callcallback.onNext()continues to be distributed. The following example code shows an implementation of the Urihandler that handles only HTTP links:


 
Public interface UriCallback {

    /**
     * Processing is complete and the follow-up process continues.
     */
    Void onNext();

    /**
     * Processing is complete and the distribution process is terminated.
     *
     * @param resultCode result
     */
    Void onComplete(int resultCode);
}

Public class DemoUriHandler extends UriHandler {
    Public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
        Uri uri = request.getUri();
        / / Handle HTTP links
        If ("http".equalsIgnoreCase(uri.getScheme())) {
            Try {
                / / Call the system browser
                Intent intent = new Intent();
                intent.setAction(Intent.ACTION_VIEW);
                intent.setData(uri);
                request.getContext().startActivity(intent);
                // Jump succeeded
                callback.onComplete(UriResult.CODE_SUCCESS);
            } catch (Exception e) {
                // Jump failed
                callback.onComplete(UriResult.CODE_ERROR);
            }
        } else {
            // Non-HTTP links are not processed and continue to be distributed
            callback.onNext();
        }
    }
    // ...
}
Uriinterceptor


Uriinterceptor is an interceptor, does not do the final URI jump operation, but can be in the final jump before the various synchronous/asynchronous operations, common operation examples are as follows:


    • URI Jump Intercept, prohibit specific URI jump, directly return 403 (such as prohibit jump to non-Meituan domain name HTTP link)
    • URI parameter modification (for example, adding the query parameter at the end of the HTTP link)
    • various intermediate processing (e.g. open login page login, get location, send network request)
    • ......


You can add several uriinterceptor to each urihandler. In the Urihandler base class, the handle () method first calls the abstract methodshouldHandle()to determine if the urirequest is to be processed, and if processing is required, executes the interceptor one by one and then invokes thehandleInternal()method for the jump operation.


 
Public abstract class UriHandler {

    // ChainedInterceptor combines multiple UriInterceptors into one
    Protected ChainedInterceptor mInterceptor;

    Public UriHandler addInterceptor(@NonNull UriInterceptor interceptor) {
        If (interceptor != null) {
            If (mInterceptor == null) {
                mInterceptor = new ChainedInterceptor();
            }
            mInterceptor.addInterceptor(interceptor);
        }
        Return this;
    }

    Public void handle(@NonNull final UriRequest request, @NonNull final UriCallback callback) {
        If (shouldHandle(request)) {
            If (mInterceptor != null) {
                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 {
            callback.onNext();
        }
    }

    /**
     * Whether to process a given uri. Called before the Interceptor.
     */
    Protected abstract boolean shouldHandle(@NonNull UriRequest request);

    /**
     * Process uri. Called after the Interceptor.
     */
    Protected abstract void handleInternal(@NonNull UriRequest request, @NonNull UriCallback callback);
}
Distribution and demotion of URIs


The URI distribution in the out-of-the-box C app is shown. All URI jumps are distributed to Rooturihandler and then distributed to different sub-handler according to different scheme. For example, the Waimai protocol is distributed to Wmurihandler and then further distributed to the sub-handler according to different paths, thereby initiating the corresponding ACTIVITY;HTTP/HTTPS protocol distribution to HttpHandler, starting the WebView container , for other types of URIs (tel, mailto, and so on), the previous few handler are not processed and are distributed to Starturihandler, attempting to start the System app using an Android native implicit jump.



Each urihandler can implement a downgrade strategy based on actual needs, or it can continue to be distributed to other urihandler without processing. Rooturihandler provides a global distribution completion event listener that performs a global downgrade policy when Urihandler processing fails to return an exception resultcode or if all child urihandler are not processed.





Platform and end multiplexing


With the evolution of the C-terminal business, the team members expanded several times, super and other vertical categories of separation, as well as the establishment of a remote research and development team, the client platform was put on the agenda. For more detailed information on the platform for Takeaway, refer to the team's previously published articles on Android platform-based architecture evolution practice.



In order to meet the actual development needs, after a long period of exploration, gradually formed a three-storey engineering structure.






The original single project is divided into several projects, it is unavoidable to involve the coupling problem between multiple projects, mainly including communication problems, reuse problems, Dependency injection, compilation problems, detailed introduction below.


Communication issues


When the original project was split into the various business libraries, the pages between the business libraries needed to communicate, the main scenario being the page jump and passing parameters through intent.



The original page jumps using explicit jumps, there are strong references between activities, when the activity is split into different business libraries, the business library can not directly depend on each other, and therefore need to be decoupled.



Using the URI distribution mechanism of wmrouter, this problem can be solved easily. The activity of all business libraries is registered to Wmrouter, and the pages can be redirected between the various business libraries.






At this point, Wmrouter has two functions:


    1. Operation URI Jump () issued by backgroundwaimai://*
    2. Internal page jump, to replace the original explicit jump, to achieve engineering decoupling (wm_router://page/*)


Because the background is issued by the URI and products, operations, H5, iOS and other end of the unified development of the Agreement, supported pages, formats, parameters, etc. can not be arbitrarily changed, and the internal page jump using the URI, it needs to be configured according to the actual development needs, two sets of URI protocol can not be compatible, Therefore, different scheme is used.


Multiplexing problems and Serviceloader modules


There is often a need to reuse code between business libraries. Some common code logic can sink to the platform layer to be reused, such as a non-business-agnostic view component, while some code is not suitable for sinking to the platform layer, such as business library A to use an interface module in Business Library B, which is tightly coupled with the interface module and business Library B. Specific to the takeaway in the actual business scenario, the Merchant page in the business break will show a list of recommended business, its style and home page is the same (), and two pages are not in a project, business page want to be able to directly from the home business Library to get the implementation of the listing.









To solve these problems, we investigated the design ideas and tools of the SPI (Service Provider Interfaces) in Javajava.util.ServiceLoader, and also learned about the Serviceloader components developed by the US-based platform to solve similar problems.



Considering the difference in actual demand, we re-developed our own Serviceloader implementation. Compared to the implementation in Java, Wmrouter's implementation draws on the design ideas of the US-based platform, not only supports the acquisition of all implementation classes through the interface, but also supports the acquisition of specific implementation classes through interfaces and unique keys. In addition, the Wmrouter implementation also supports the functions of directly loading the class of implementation classes, creating objects with factory and provider, Singleton management, method invocation, and so on. In the realization idea of gradle plug-in, it draws on the serviceloader of the American group platform and makes the performance optimization, and puts forward the improvement suggestions to the platform.



The need for code reuse between business libraries signals that business library a needs to reuse Serviceimpl in business Library B but not directly, so it is loaded by Wmrouter:


    1. The extraction interface (or parent class, which is no longer repeated) sinks to the platform layer, implements the class Serviceimpl implementation of the interface, and declares a key (string type).
    2. The caller accesses the implementation class through an interface and key, which is loaded by the Serviceloader implementation class.





URI jumps and Serviceloader seem to have no correlation, but the nature of the communication and multiplexing requirements can be understood as routing, and the protocol that the page is distributed by URI is Activity+uri, where the Serviceloader protocol is interface+ Key.


Dependency Injection


To be compatible with both ends of the takeaway standalone app and the app takeaway channel, the platform layer interfaces are implemented in two main projects and injected to the bottom. Conventional Java code injection is tedious to write, and using Wmrouter's serviceloader functionality makes it easier to implement and rely on injection-like effects.



For Wmrouter, all projects that depend on it (including the main project, the Business library, the Platform library) are the same, and any library that wants to invoke code from other libraries can be forwarded through Wmrouter routing. The preceding communication and multiplexing issues are called by Wmrouter between the peers ' business libraries, while dependency injection is the underlying library that calls the upper library through Wmrouter, and its nature and implementation are the same.



The Serviceloader module provides a single-instance management function when loading implementation classes, which can be used to manage some global service/manager, such as user account management classesUserManager.





Compilation issues


Because of the historical reasons, as a shell project without business logic, the main project has more dependence on the business database, especially the initial configuration of the business library is cumbersome and tightly coupled with each business library. On the other hand, the Wmrouter jump page, the loaded implementation class, needs to be registered in the Wmrouter when the application is initialized, and also increases the coupling between the main project and the Business Library. During the development process, the various business libraries are frequently updated, and frequently there are problems that cannot be compiled, which seriously affects development.



To solve this problem, on the one hand Wmrouter added annotation support, configure annotations on the Activity class, Serviceloader implementation class, you can automatically generate code during compilation to register to Wmrouter.


 
// When there is no annotation, you need to register each page when the application is initialized. The coupling is serious.
Register("/home", HomeActivity.class);
Register("/order", OrderListActivity.class);
Register("/shop", ShopActivity.class)
Register("/account", MyAccountActivity.class);
Register("/address", MyAddressActivity.class);
// ...
// After adding annotations, you only need to configure them through annotations on each Activity.
@RouterUri(path = "/shop")
Public class ShopActivity extends BaseActivity {

}

On the other hand, Serviceloader also supports specifying interfaces to load all implementation classes, and the master project can load and initialize all implementation classes in each business library through a unified interface, resulting in complete isolation of the implementation and the Business library.






During the development process, the various business libraries can be integrated into the main project as required by the plug-in, which can greatly reduce the problems that cannot be compiled, and the compilation speed can be improved by skipping the unwanted business libraries at compile time.





The promotion of Wmrouter


After Wmrouter solved the problem of the takeaway C-end app, it found that other apps within the company and even outside the company had encountered similar problems and needs, and decided to promote and open the Wmrouter.



Since Wmrouter is an open component framework, Urirequest can store arbitrary data, Urihandler, Uriinterceptor can be fully customized, different Urihandler can be arbitrarily combined, with great flexibility. But being too flexible can lead to a drop in ease of use, even for the most general and simplest applications, requiring complex configuration to complete functionality.



In order to balance flexibility and ease of use, on the one hand, wmrouter a reasonable division of the package structure, the core interface and implementation class provides the basic common ability, as far as possible to retain the maximum flexibility; On the other hand, encapsulates a series of common implementation classes and combines them into a set of default implementations. So as to meet the overwhelming majority of conventional usage scenarios, minimize the cost of access to other apps and facilitate promotion.


Summarize


Some of the existing Android routing frameworks in the industry do not meet the actual needs of the takeaway C-terminal app in the development process, so we have developed the Wmrouter routing framework. Based on the idea of network request, the URI distributing mechanism is designed on the basis of Urirequest, Urihandler and uriinterceptor, while ensuring the function is flexible and powerful while minimizing the use difficulty; On the other hand, the design idea of SPI is borrowed, Java and the Serviceloader implementation of the platform, the development of their own Serviceloader module, to solve the four problems in the process of takeaway platform (communication problems, reuse problems, Dependency injection, compilation problems). After nearly a year of continuous iterative refinement, Wmrouter has become one of the core infrastructure components in many U.S. apps.


Resources
    1. Routing-wikipedia
    2. Uniform Resource Identifier-Wikipedia
    3. RFC 3966-the Tel URI for telephone Numbers
    4. RFC 6068-the ' mailto ' URI Scheme
    5. Intent and Intent Filters
    6. Introduction to the Service Provider Interfaces
    7. The evolution of Android platform-based architecture for US-group takeout
About the author


Zijian, senior Engineer, joined the United States in 2015, has been responsible for the sale of clients home, merchant containers, evaluation and other business modules development and maintenance, as well as platform, performance optimization and other technical work.



Profound, American Regiment senior engineer, joined the United States in 2016, currently as a takeaway business-side Android app development, mainly responsible for business-side and bee-side service technology needs development.



Cloud Chi, the United States Regiment senior engineer, joined the United States in 2016, is currently responsible for the takeaway client search, IM and other business libraries, and the multi-terminal unified work.


Recruitment


US-Group Takeaway Android, IOS, FE Senior/Senior engineers and technical experts, base Beijing, Shanghai, Chengdu, interested students are welcome to deliver resumes to wukai05@meituan.com.



Find Articles with errors, questions about the content, you can pay attention to the United States Group technical Team public Number (Meituantech), in the background to leave us a message. We pick out an enthusiastic little partner every week and send a nice little gift. Come and sweep the code and follow us!


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.