Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the method of NetEase koala Android client routing bus design?

2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article introduces the relevant knowledge of "what is the method of NetEase koala Android client routing bus design". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

1. Preface at present, there are many Android routing frameworks, which are springing up like bamboo shoots after a spring rain, probably because the concept of Android componentization was put forward last year. When the business scale of a product rises to a certain extent, or when it is developed across teams, the problem of cooperation between teams / modules will be exposed. How to keep the business between teams? How not to affect or interfere with each other's development progress? How to invoke the function of the business side? Componentization provides an answer to the above questions. The core problem of componentization is decoupling, and routing appears in order to solve the decoupling between modules.

1.1 traditional page jump

There are three main types of page jumps, namely, App page jump, H5 jump back to App page, and App jump to H5.

Jump between App pages

For beginners to jump between App pages, the following code is generally used on the redirected page:

Intent intent = new Intent (this, MainActivity.class); intent.putExtra ("dataKey", "dataValue"); startActivity (intent)

For experienced programmers, they will generate their own jump methods in the jump class:

Public class OrderManagerActivity extends BaseActivity {public static void launch (Context context, int startTab) {Intent I = new Intent (context, OrderManagerActivity.class); i.putExtra (INTENT_IN_INT_START_TAB, startTab); context.startActivity (I);}}

Either way, the essence is to generate an Intent and then jump to the page via Context.startActivity (Intent) / Activity.startActivityForResult (Intent, int). The disadvantage of this approach is that when there are multiple modules, but the modules are not interdependent, the jump becomes quite difficult. If you know the class names and corresponding paths of other modules, you can start the pages of other modules through the Intent.setComponent (Component) method, but often the class names of modules may change. Once the business side changes the name of the module, this hidden Bug is broken for the heart of the development. On the other hand, this repetitive template code, write at least two lines at a time to achieve page jump, the code is redundant.

H5-App page jump

For e-commerce applications such as koalas, the activity page is timely and real-time, both of which need to be guaranteed at any time. The operator may change the activity page at any time, or it may require you to click on a link to jump to an App page. The traditional approach is to intercept WebViewClient.shouldOverrideUrlLoading (WebView, String) to determine whether the url has a corresponding App page to jump, and then take out the params in the url to encapsulate into an Intent delivery and start the App page.

Feel the following code that once appeared in the koala App project:

Public static Intent startActivityByUrl (Context context, String url, boolean fromWeb, boolean outer) {if (StringUtils.isNotBlank (url) & & url.startsWith (StringConstants.REDIRECT_URL)) {try {String realUrl = Uri.parse (url) .getQueryParameter ("target"); if (StringUtils.isNotBlank (realUrl)) {url = URLDecoder.decode (realUrl, "UTF-8") } catch (Exception e) {e.printStackTrace ();}} Intent intent = null; try {Uri uri = Uri.parse (url); String host = uri.getHost (); List pathSegments = uri.getPathSegments (); String path = uri.getPath (); int segmentsLength = (pathSegments = null? 0: pathSegments.size ()) If (! host.contains (StringConstants.KAO_LA)) {return null;} if ((StringUtils.isBlank (path) {do something... Return intent;} if (segmentsLength = = 2 & & path.startsWith (StringConstants.JUMP_TO_GOODS_DETAIL)) {do something... } else if (path.startsWith (StringConstants.JUMP_TO_SPRING_ACTIVITY_TAB)) {do something... } else if (path.startsWith (StringConstants.JUMP_TO_SPRING_ACTIVITY_DETAIL) & & segmentsLength = = 3) {do something... } else if (path.startsWith (StringConstants.START_CART) & & segmentsLength = = 1) {do something... } else if (path.startsWith (StringConstants.JUMP_TO_COUPON_DETAIL) | | (path.startsWith (StringConstants.JUMP_TO_COUPON) & & segmentsLength = = 2)) {do something... } else if (canOpenMainPage (host, uri.getPath () {do something... } else if (path.startsWith (StringConstants.START_ORDER)) {if (! UserInfo.isLogin (context)) {do something... } else {do something... }} else if (path.startsWith (StringConstants.START_SAVE)) {do something... } else if (path.startsWith (StringConstants.JUMP_TO_NEW_DISCOVERY)) {do something... } else if (path.startsWith (StringConstants.JUMP_TO_NEW_DISCOVERY_2) & & segmentsLength = = 3) {do something... } else if (path.startsWith (StringConstants.START_BRAND_INTRODUCE) | | path.startsWith (StringConstants.START_BRAND_INTRODUCE2)) {do something... } else if (path.startsWith (StringConstants.START_BRAND_DETAIL) & & segmentsLength = = 2) {do something... } else if (path.startsWith (StringConstants.JUMP_TO_ORDER_DETAIL)) {if (! UserInfo.isLogin (context) & & outer) {do something... } else {do something... }} else if (path.startsWith ("/ cps/user/certify.html")) {do something... } else if (path.startsWith (StringConstants.IDENTIFY)) {do something... } else if (path.startsWith ("/ album/share.html")) {do something... } else if (path.startsWith ("/ album/tag/share.html")) {do something... } else if (path.startsWith ("/ live/roomDetail.html")) {do something... } else if (path.startsWith (StringConstants.JUMP_TO_ORDER_COMMENT)) {if (! UserInfo.isLogin (context) & & outer) {do something... } else {do something... }} else if (openOrderDetail (url, path)) {if (! UserInfo.isLogin (context) & & outer) {do something... } else {do something... }} else if (path.startsWith (StringConstants.JUMP_TO_SINGLE_COMMENT)) {do something... } else if (path.startsWith ("/ member/activity/vip_help.html")) {do something... } else if (path.startsWith ("/ goods/search.html")) {do something... } else if (path.startsWith ("/ afterSale/progress.html")) {do something... } else if (path.startsWith ("/ afterSale/apply.html")) {do something... } else if (path.startsWith ("/ order/track.html")) {do something... }} catch (Exception e) {e.printStackTrace ();} if (intent! = null & &! (context instanceof Activity)) {intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);} return intent;}

This code has a total of 260 lines, and I was devastated when I saw the code. The disadvantages of this approach are:

The judgment is unreasonable. The above code only determines whether HOST contains StringConstants.KAO_LA, and then distinguishes which page to jump to according to PATH, and PATH only judges the beginning, which is likely to cause misjudgment when there are more and more URL.

Too much coupling. References to all known intercepted pages must be available, otherwise they cannot be redirected

The code is messy. There are a lot of PATH. Matching multiple known App pages from many PATH must be solved by writing a lot of functions to judge the matching rules.

The interception process is opaque. It is difficult for developers to add their own business logic in the process of URL interception, such as typing, adding specific Flag before starting Activity, etc.

There is no concept of priority, nor can it be degraded. The same URL, as long as the first match to the App page, can only open this page, not by adjusting the priority to jump to another page or using H5 to open.

App Page-H5 Jump

Needless to say, just start a WebViewActivity.

1.2 the significance of page routing

Routing was first applied to the network. Routing is defined as the activity of transmitting information from the source address to the destination address through the interconnected network. Page jump is also equivalent to the process of jumping from the source page to the target page, each page can be defined as a uniform resource identifier (URI), in the network can be accessed by others, can also access the defined page. There are several common usage scenarios for routing:

App receives a notification. Click on the notification to open a page of App (OuterStartActivity).

Click a link in the browser App to open a page of App (OuterStartActivity)

App's H5 activity page opens a link, which may be an H5 jump or a jump to a native page (WebViewActivity)

Some conditions are required to open the page. Verify the conditions first, and then open that page (login required)

The jump in App can reduce the cost of manually building Intent, and at the same time, you can uniformly carry some parameters to the next page (click)

In addition, the use of routing can avoid the above disadvantages and reduce the cost of developer page jump.

two。 Koala routing bus 2.1 routing Framework

The koala routing framework is mainly divided into three modules: route collection, route initialization and page routing. In the route collection phase, annotations based on Activity classes are defined, routing information is collected through Android Processing Tool (hereinafter referred to as "APT") and routing table classes are generated; in the route initialization stage, a route dictionary is injected according to the generated routing table information; in the page routing stage, route information is found through a route dictionary, and different routing strategies are customized according to the search results.

2.2 Route design ideas

Generally speaking, koala routing design pursues decoupling of functional modules, being able to achieve basic routing functions, and being simple enough for developers to use. The first two stages of koala routing are almost cost-free for routing users. You only need to define a class annotation @ Router on the page where routing is used. The use of page routing is also quite simple, which will be described in more detail later.

Function design

To some extent, routing is similar to network request, which can be divided into three stages: request, processing and response. These three stages can be transparent to the user or intercept during the routing process. The koala routing framework currently supports the following features:

1. Support basic Activity startup and startActivityForResult callback; ✅

two。 Support different protocols to perform different jumps; (kaola://, http (s): / /, native://, etc.) ✅ 3. Support multiple SCHEME/HOST/PATH to jump to the same page; ((pre.) .kaola.com (.HK)) ✅

4. Support regular matching of routes; ✅

5. Support for Activity startup using different Flag; ✅

6. Support priority configuration for routing; ✅

7. Support dynamic interception, monitoring and degradation of routes; ✅

The above functions ensure the decoupling between koala business modules and meet the needs of current products and operations.

A good module or framework needs to design the interface in advance and reserve enough permissions for the caller to meet a variety of needs. Koala routing framework uses common design patterns, such as Builder pattern, Factory pattern, Wrapper pattern, etc., and follows some design principles. (after watching Effective Java for the second time recently, I have a deep understanding of the following principles. I recommend you to take a look.)

Programming for interfaces, not for implementation

The reason why this rule is at the top of the list is that programming for interfaces is really good and harmless for both developers and users. In the iterative process of the routing version, no matter what modifications the bottom layer makes to the interface, it will not affect the upper layer calls. For business, the use of routing is imperceptible.

The koala routing framework does not fully follow this principle in the design process, and the next version of iteration will try to follow this principle. However, interfaces are reserved for the key steps in the routing process, including:

RouterRequestCallback

Public interface RouterRequestCallback {void onFound (RouterRequest request, RouterResponse response); boolean onLost (RouterRequest request);}

Whether the callback of the routing information can be matched in the routing table. If it can be matched, the callback onFound () will be called, and if it cannot be matched, onLost () will be returned. The result of onLost () is defined by the developer. If the returned result is true, the developer handles the result of the route mismatch, and the final result of returning RouterResult is a successful route.

RouterHandler

Public interface RouterHandler extends RouterStarter {RouterResponse findResponse (RouterRequest request);}

The routing processing and startup interface looks up the routing information according to the given routing request and distributes it to the corresponding initiator to perform the subsequent page jump according to the result of the routing response. The design of this interface is not reasonable and its function is not perfect. Later, the interface will be redesigned to give the caller permission to intervene in the process of finding routes.

RouterResultCallback

Public interface RouterResultCallback {boolean beforeRoute (Context context, Intent intent); void doRoute (Context context, Intent intent, Object extra); void errorRoute (Context context, Intent intent, String errorCode, Object extra);}

After matching the routing information, the callback of the routing process is actually performed. The beforeRoute () method is a callback before the actual route. If the developer returns true, the routing information is considered to have been intercepted by the caller, and the subsequent doRoute () will not be called back and the route will be executed. Any exception that occurs during the routing process will call back the errorRoute () method, when the route is interrupted.

ResponseInvoker

Public interface ResponseInvoker {void invoke (Context context, Intent intent, Object...) Args);}

Route executor. If the developer needs to perform some global operations before performing the routing, such as adding additional information to the next Activity, you can implement this interface yourself. The routing framework provides a default implementation: ActivityInvoker. Developers can also inherit ActivityInvoker, override the invoke () method, implement their own business logic first, and then call the super.invoke () method.

OnActivityResultListener

Public interface OnActivityResultListener {void onActivityResult (int requestCode, int resultCode, Intent data);}

Put special emphasis on this Listener. Originally, the purpose of this callback is to facilitate the caller to inform the result through callback when executing startActivityForResult. However, due to no activity restriction, the listener cannot be saved (saveInstanceState) by the system after leaving the page, so it is not recommended to use callback in Activity/Fragment, but in non-Activity components / modules, such as View/Window/Dialog. This process has been implemented by CoreBaseActivity in the core package. When you develop and use it, you can call CoreBaseActivity.startActivityForResult (intent, requestCode, onActivityResultListener) directly or through KaolaRouter.with (context) .url (url) .startForResult (requestCode, onActivityResultListener). For example, to launch the order management page and call back:

KaolaRouter.with (context) .url (url) .data ("orderId", "replace url param key.") .startForResult (1, new OnActivityResultListener () {@ Override public void onActivityResult (int requestCode, int resultCode, Intent data) {DebugLog.e (requestCode + "" + resultCode + "" + data.toString ());}}))

RouterResult

Public interface RouterResult {boolean isSuccess (); RouterRequest getRouterRequest (); RouterResponse getRouterResponse ();}

Tell the result of the route, the result of the route can be interfered, such as RouterRequestCallback.onLost (), and when the true is returned, the route is also successful. This interface returns regardless of the success or failure of the route.

Do not expose unnecessary API at will

"to distinguish between well-designed modules and poorly designed modules, the most important factor is whether the module talks about other external modules and whether it hides its internal data and other implementation details. A well-designed module hides all the implementation details and clearly separates its API from its implementation. Then, modules communicate only through their API, and one module does not need to know the internal operation of other modules. This is called encapsulation. " (from Effective Java, P58)

For example, the koala routing framework limits the input parameters of routing calls, which cannot be modified once the parameters are entered, and the caller does not need to know how to use these parameters to achieve the desired functions of the caller. In implementation, RouterRequestWrapper inherits from RouterRequestBuilder, and the latter constructs relevant parameters for users through Builder mode. The former decorates all variables in RouterRequestBuilder through Wrapper pattern, and provides get functions of all parameters in the RouterRequestWrapper class for use by the routing framework.

Single responsibility

Both classes and methods need to follow the principle of single responsibility. A class implements a function and a method does a thing. For example, KaolaRouterHandler is the processor of koala routing, which implements the RouterHandler interface to find and forward routes; RouterRequestBuilder is used to collect the parameters required for routing requests; and RouterResponseFactory is used to generate the results of routing responses.

Provides a default implementation

The advantage of interface programming is that the implementation can be replaced at any time, and the koala routing framework provides a default implementation for all monitoring, interception, and routing processes in the routing process. Users can not only ignore the underlying implementation logic, but also replace the relevant implementation as needed.

2.3 implementation principle of koala routing

The koala routing framework collects routing information based on annotations and realizes the dynamic generation of the routing table through APT, which is similar to the practice of ButterKnife, which imports routing table information at run time, finds routes through regular expressions, and realizes the final page jump according to the routing results.

Collect routing information

First define an annotation @ Router that includes the routing protocol, routing host, routing path, and routing priority.

@ Target (ElementType.TYPE) @ Retention (RetentionPolicy.CLASS) public @ interface Router {/ * URI protocol, which already provides default values, and implements four protocols by default: https, http, kaola, native * / String scheme () default "(https | http | kaola | native): / /" / * URI host, the default value * / String host () default "(pre\\.)? (\\ w+\\.)? kaola\\ .com (\\ .HK)?; / * URI path, optional. If default value is used, only local routing is supported, but url interception of * / String value () default" is not supported. / * routing priority, default is 0. * / int priority () default 0;}

For pages that need to use routing, just add this comment to the class declaration to indicate the routing path corresponding to the page. For example:

@ Router ("/ app/myQuestion.html") public class MyQuestionAndAnswerActivity extends BaseActivity {. }

Then the url of the page generated by APT is a regular expression:

(https | http | kaola | native): / / (pre\.)? (\ w +\.)? kaola\ .com (\ .HK)? / app/myQuestion\ .html

The routing table is made up of several such regular expressions.

Generate routing tabl

The generation of the routing table needs to use the APT tool and Square's open source javapoet class library. The purpose is to let the machine "write code" for us according to the Router annotation we defined, and generate a routing table of Map type, where key generates the corresponding regular expression based on the information of the Router annotation, and value is the information collection of the corresponding class. First define a RouterProcessor that inherits from AbstractProcessor

Public class RouterProcessor extends AbstractProcessor {@ Override public synchronized void init (ProcessingEnvironment processingEnv) {super.init (processingEnv); / / initialize relevant environment information mFiler = processingEnv.getFiler (); elementUtil = processingEnv.getElementUtils (); typeUtil = processingEnv.getTypeUtils (); Log.setLogger (processingEnv.getMessager ());} @ Override public Set getSupportedAnnotationTypes () {Set supportAnnotationTypes = new HashSet () / / get the annotation types to be processed. Currently, only Router annotations supportAnnotationTypes.add (Router.class.getCanonicalName ()); return supportAnnotationTypes;} @ Override public boolean process (Set destination) {String rawUri = SCHEME_NATIVE + destination.getCanonicalName (); return rawUri.replaceAll ("\\.", "\.");}

As you can see, the key of the route collection is a regular expression that includes the url interception rule and the custom native jump rule that contains the standard class name. For example, the final key generated by keyMyQuestionAndAnswerActivity is

((https | http | kaola | native): / / (pre\\.)? (\\ w+\\.)? kaola\. Com (\\ .HK)? / app/myQuestion.html) | (native://com.kaola.modules.answer.myAnswer.MyQuestionAndAnswerActivity)

In this way, the caller can not only pass the default intercept rules

(https | http | kaola | native): / / (pre\\.)? (\\ w+\\.)? kaola\. Com (\\ .HK)? / app/myQuestion.html)

Jump to the corresponding page, or you can use the

(native://com.kaola.modules.answer.myAnswer.MyQuestionAndAnswerActivity).

The advantage is that jumps between modules can also be used without relying on reference classes. On the other hand, the native jump transfer specifically generates a class RouterConst to record, as follows:

/ * DO NOT EDIT THIS FILECTROPUBG! IT WAS GENERATED BY KAOLA PROCESSOR. * / public class RouterConst {public static final String ROUTE_TO_ActivityDetailActivity = "native://com.kaola.modules.activity.ActivityDetailActivity"; public static final String ROUTE_TO_LabelDetailActivity = "native://com.kaola.modules.albums.label.LabelDetailActivity"; public static final String ROUTE_TO_MyQuestionAndAnswerActivity = "native://com.kaola.modules.answer.myAnswer.MyQuestionAndAnswerActivity"; public static final String ROUTE_TO_CertificatedNameActivity = "native://com.kaola.modules.auth.activity.CertificatedNameActivity" Public static final String ROUTE_TO_CPSCertificationActivity = "native://com.kaola.modules.auth.activity.CPSCertificationActivity"; public static final String ROUTE_TO_BrandDetailActivity = "native://com.kaola.modules.brands.branddetail.ui.BrandDetailActivity"; public static final String ROUTE_TO_CartContainerActivity = "native://com.kaola.modules.cart.CartContainerActivity"; public static final String ROUTE_TO_SingleCommentShowActivity = "native://com.kaola.modules.comment.detail.SingleCommentShowActivity" Public static final String ROUTE_TO_CouponGoodsActivity = "native://com.kaola.modules.coupon.activity.CouponGoodsActivity"; public static final String ROUTE_TO_CustomerAssistantActivity = "native://com.kaola.modules.customer.CustomerAssistantActivity"; … }

Initialize rout

Route initialization occurs synchronously during the Application process. Generate the instance directly by getting the class of RouterGenerator, and save the routing information in the sRouterMap variable.

Public static void init () {try {sRouterMap = new HashMap (); (RouterProvider) (Class.forName (ROUTER_CLASS_NAME). GetConstructor (). NewInstance ()) .loadRouter (sRouterMap);} catch (Exception e) {e.printStackTrace ();}}

Page routing

Given a url and context, routing can be used. The calling method is as follows:

KaolaRouter.with (context) .url (url) .start ()

Page routing is divided into three steps: route request generation, route lookup and route result execution. At present, the routing request is relatively simple, only encapsulating a RouterRequest interface

Public interface RouterRequest {Uri getUriRequest ();}

The route lookup process is relatively complex, in addition to traversing the routing table imported into memory after route initialization, it is also necessary to judge a variety of preconditions. There are comments in the specific condition judgment code.

@ Overridepublic RouterResponse findResponse (RouterRequest request) {if (null = = sRouterMap) {return null; / / throw new IllegalStateException (/ / String.format ("Router has not been initialized, please call% s.init () first.", / / KaolaRouter.class.getSimpleName ());} if (mRouterRequestWrapper.getDestinationClass ()! = null) {RouterResponse response = RouterResponseFactory.buildRouterResponse (null, mRouterRequestWrapper) ReportFoundRequestCallback (request, response); return response;} Uri uri = request.getUriRequest (); String requestUrl = uri.toString (); if (! TextUtils.isEmpty (requestUrl)) {for (Map.Entry entry: sRouterMap.entrySet ()) {if (RouterUtils.matchUrl (requestUrl, entry.getKey () {Route routerModel = entry.getValue () If (null! = routerModel) {RouterResponse response = RouterResponseFactory.buildRouterResponse (routerModel, mRouterRequestWrapper); reportFoundRequestCallback (request, response); return response;}} return null } @ Overridepublic RouterResult start () {/ / determine whether the Context reference still exists WeakReference objectWeakReference = mContextWeakReference; if (null = = objectWeakReference) {reportRouterResultError (null, null, RouterError.ROUTER_CONTEXT_REFERENCE_NULL, null); return getRouterResult (false, mRouterRequestWrapper, null);} Context context = objectWeakReference.get (); if (context = = null) {reportRouterResultError (null, null, RouterError.ROUTER_CONTEXT_NULL, null) Return getRouterResult (false, mRouterRequestWrapper, null);} / determine whether the routing request is valid if (! checkRequest (context)) {return getRouterResult (false, mRouterRequestWrapper, null);} / / ergodic search routing result RouterResponse response = findResponse (mRouterRequestWrapper); / / determine the routing result and execute the intercept if (null = = response) {boolean handledByCallback = reportLostRequestCallback (mRouterRequestWrapper) when the routing result is empty If (! handledByCallback) {reportRouterResultError (context, null, RouterError.ROUTER_RESPONSE_NULL, mRouterRequestWrapper.getRouterRequest ());} return getRouterResult (handledByCallback, mRouterRequestWrapper, null);} / / Interface ResponseInvoker responseInvoker = getResponseInvoker (context, response) to get the routing result; if (responseInvoker = = null) {return getRouterResult (false, mRouterRequestWrapper, response);} Intent intent Try {intent = RouterUtils.generateResponseIntent (context, response, mRouterRequestWrapper);} catch (Exception e) {reportRouterResultError (context, null, RouterError.ROUTER_GENERATE_INTENT_ERROR, e); return getRouterResult (false, mRouterRequestWrapper, response);} / generate corresponding Intent if (null = = intent) {reportRouterResultError (context, null, RouterError.ROUTER_GENERATE_INTENT_NULL, response); return getRouterResult (false, mRouterRequestWrapper, response) } / / get the routing result callback API. If it is empty, the default implementation RouterResultCallback routerResultCallback = getRouterResultCallback () is used; / / the user handles if (routerResultCallback.beforeRoute (context, intent)) {return getRouterResult (true, mRouterRequestWrapper, response);} try {responseInvoker.invoke (context, intent, mRouterRequestWrapper.getRequestCode (), mRouterRequestWrapper.getOnActivityResultListener ()) RouterResultCallback.doRoute (context, intent, null); return getRouterResult (true, mRouterRequestWrapper, response);} catch (Exception e) {reportRouterResultError (context, intent, RouterError.ROUTER_INVOKER_ERROR, e); return getRouterResult (false, mRouterRequestWrapper, response);}} eventually the ResponseInvoker.invoke () method is called to perform routing.

This is the end of the content of "NetEase koala Android client routing bus design method". Thank you for your reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report