preface

In mobile development, the demand and pace of development are getting faster and faster, while Native App is a little clumsy in this pace. Developers suffer from long development cycle, slow user upgrade and long application market review time. At this time, many developers put forward the concept of Hybrid App, which has the advantages of flexible iteration, multi-terminal unification, short development cycle and fast online. However, Hybrid App also has its disadvantages. In terms of performance, it is difficult to reach the level of Native App, and it is not easy to access the hardware on the device. There are many solutions to these problems, such as Facebook’s React Native, lightweight framework, and ionic. For mature products with too many Web pages, it is difficult to migrate. You can also use VasSonic to improve the WebView experience, and then call Native through JS. At present, the company adopts the latter way to realize the project due to historical reasons, but there are several problems such as diversified communication methods, chaotic call and poor security in the process of using because there is no unified management. The following focuses on how to solve these problems by redesigning the JS invocation framework.

Android WebView JS interaction

First of all, I will introduce how JS and Native call each other in WebView and the differences between them.

Android called JS

WebView calls JS in the following two ways:

  • Through the WebView. LoadUrl ()

  • Through the WebView. EvaluateJavascript ()

Prior to API 19 JavaScript could only be called via webView.loadURL (). In 19 when new API provides a WebView. EvaluateJavascript (), its operation efficiency than loadUrl () is high, can also pass in a callback object, convenient access to the back information on the Web.

webView.evaluateJavascript("fromAndroid()", new ValueCallback<String>() {       @Override    public void onReceiveValue(String value) {            //do something    }});Copy the code

JS calls Android code

JS calls Native code in the following three ways:

  • Through the WebView. AddJavascriptInterface ()

  • Through the WebViewClient. ShouldOverrideUrlLoading ()

  • Through the WebChromeClient. OnJsAlert (), onJsConfirm (), onJsPrompt ()

WebView. AddJavascriptInterface () is the official recommended practice, by default WebView is closed JavaScript calls, you need to call WebSetting. SetJavaScriptEnabled (true) to open it. This method requires a JavaScript Interface of type Object, and then uses @javascriptInterface to annotate the provided JavaScript method. Here is an example provided by Google:

public class AppJavaScriptProxy {    private Activity activity = null;    public AppJavaScriptProxy(Activity activity) {        this.activity = activity;    }    @JavascriptInterface    public void showMessage(String message) {        Toast toast = Toast.makeText(this.activity.getApplicationContext(),                message,                Toast.LENGTH_SHORT);        toast.show();    }}Copy the code
WebView. AddJavascriptInterface (new AppJavaScriptProxy (this), "androidAppProxy");Copy the code
// JS code calls if(typeof androidAppProxy! == "undefined"){ androidAppProxy.showMessage("Message from JavaScript"); } else { alert("Running outside Android app"); }Copy the code

In this way, JS can be implemented to call Android code, users only need to pay attention to the implementation of the method called by JS, the process of calling is unknown. There are a few things to note when using:

  1. Methods provided for JS calls must be of type public

  2. In API 17 and above, methods provided for JS calls must be annotated with @javascriptInterface

  3. This method is not called in the main thread

WebViewClient. ShouldOverrideUrlLoading () is the way by intercepting the Url to implement the interaction with JS. ShouldOverrideUrlLoading (), when it returns true, intercepts the request and lets us handle it ourselves. ShouldOverrideUrlLoading (), when it returns false, does not intercept the request and lets the WebView handle it.

, onJsConfirm WebChromeClient. OnJsAlert () (), onJsPrompt () three ways and WebViewClient shouldOverrideUrlLoading (), is by intercepting achieve interactive function request.

Summary: These three approaches can actually be reduced to two: JavascriptInterface and intercepting requests, each with its own advantages and disadvantages.

  • JavascriptInterface is the way provided by the system. It is definitely superior to the latter in efficiency and reliability, and will be maintained and optimized continuously in the future. The disadvantage is that the expansibility and management are not very strong, and there are loopholes in Android 4.2. Some interfaces provided by the system need to be removed, and the interfaces provided should be carefully handled.

  • The advantage of request interception is that it is easy to manage and expand, and can be designed according to its own business. It is convenient to deal with complex logic, and can guarantee security. The main drawback is that there is no official support for this approach, and it is possible to migrate the entire logic in later versions. There is also a lack of efficiency, H5 quickly call multiple requests may be lost.

JS call framework design

In order to solve the problems mentioned in the preface, such as various communication modes, chaotic invocation and poor security, we need to redesign the JS invocation framework to separate the whole process from WebView and achieve the purpose of low coupling. After consideration, we decided to follow the previous solution in the project and implement it by intercepting WebView requests. The interception approach also needs to consider the communication protocol before designing the framework.

Protocol design

As shown above, multi-terminal unified communication can be achieved by designing communication protocols. The protocol can refer to the existing communication protocol, or design a set of common protocols according to the project requirements and front-end. A simple existing protocol is recommended: uniform Resource Identifier (UNIFORM Resource Identifier).

jsbridge://method1? a=123&b=345&jsCall=jsMethod1"Copy the code

This identifier allows users to interact with resources on the network (commonly referred to as the World Wide Web) over a specific protocol. Only three fields are used instead of all of them. Scheme is defined as JsBridge and is used to differentiate network requests. The authority is used to define the methods that the JS needs to access. The following query is used to pass parameters, and if you need the client callback information to the front end, you can add the parameter jsCall=jsMethod1, which can then be called back from the WebView.

WebView.loadUrl("javascript:jsMethod1(result=1)")Copy the code

This defines a simple interaction mode that allows JS and Native to have basic interaction capabilities. If you need to transfer files, you can transfer them to Base64 and then communicate with them. Of course, if the files are too large, this method has memory risks. Here’s another way to intercept WebView requests for resources and stream files:

webView.setWebViewClient(new WebViewClient(){     @Override     public WebResourceResponse shouldInterceptRequest(WebView view, String url) {             return new WebResourceResponse("image/jpeg", "UTF-8", new FileInputStream(new File("xxxx");     }}Copy the code

The framework design

In the design, the following points were mainly considered:

  1. Security: Prevent third-party websites from obtaining users’ private information and communications from being intercepted by third parties. (Domain name whitelist, data encryption)

  2. Ease of use: All design frameworks need to consider ease of use, convenient and practical. As with Android’s JavascriptInterface approach, the user only cares about the implementation of the invoked method. (See Android JavascriptInterface)

  3. Portability: The Android system is changing rapidly, with each version having major changes and optimizations, so it’s easy to migrate the entire JsBridge solution if a better solution or feature comes along. (Clear responsibilities in design)

  4. Scalability: Easy to expand business logic. (Add middleware)

By analyzing the whole communication process and combining with the needs of the project, five roles in the communication process are roughly abstracted:

  1. JsBridge: Manages the entire Js framework, provides external interfaces, and connects the Processor and JsProvider.

  2. JsCall: Abstracts a request, contains the content and context of a request, and has callback information to the front-end interface.

  3. IProcessor: protocol abstraction. Because the project needs to be compatible with multiple protocols, the protocol is abstracted to classify and parse requests.

  4. JsProvider: JsProvider is a provider of Js methods. It is of the Object type and facilitates migration of existing code. It is the same as JavascriptInterface and facilitates future migration.

  5. @jsMethod: an annotation to a Js method, similar to @javascriptInterface.

This mode is basically the same as the JavascriptInterface mode provided by the system, but we can do more things than the JavascriptInterface mode, and the whole system is clearly decoupled. However, this structure actually lacks many things and cannot achieve the design goal. There is no extensibility in the process, no interception and no secondary processing mechanism.

You can add an interceptor before executing JsMethod to increase extensibility.

The security aspect can also be achieved by adding interceptors that intercept JS requests before executing jsMethods, and the security level of each JsMethod can be annotated with extended annotation parameters. For example, in the following code, add a Permission field to indicate the security level of the method.

 @JsMethod(permission = "high") public void requestInfo(IJsCall jsCall) {         // do something }Copy the code

After the framework is built, some optimization design is needed:

  1. Log system: Add log switch to print critical logs.

  2. Thread conversion: as the WebViewClient. ShouldOverrideUrlLoading is performed in the main thread, you can refer to the Android, JS method in other threads to do, does not affect the level of fluency in the page.

  3. Exception mechanism: After unified management of exceptions that occur in the framework, they are thrown to the framework caller.

  4. . (Combined with business design)

Implementation effect

Finally, the framework is generally designed, the implementation is relatively simple. Now look at the usage. First, JS makes a request:

var iframe = document.createElement('iframe'); iframe.setAttribute('src', 'jsbridge://method1? a=123&b=345'); document.body.appendChild(iframe); iframe.parentNode.removeChild(iframe); iframe = null;Copy the code

The client simply intercepts WebView requests.

@Override public boolean shouldOverrideUrlLoading(WebView webView, String url) { boolean handle = super.shouldOverrideUrlLoading(webView, url); if (! handle) { handle = JSBridge.parse(activity, webView, url); } return handle; }Copy the code

Create an object that parses the current protocol, which can be reused later:

public class JsProcessor implements IProcessor { public static final int TYPE_PROCESSOR = 1; @override public int getType() {return TYPE_PROCESSOR; } @param url @override public Boolean isProtocol(String url) {return! TextUtils.isEmpty(url) && url.startsWith("jsbridge"); } /** * parse protocol * @param context * @param webView * @param URL * @param webViewContext webView * @return */ @override public IJsCall parse(Context context, WebView webView, final String url, Object webViewContext) { return new IJsCall<RequestBean, ResponseBean>() { private String mMethodName; @Override public void callback(ResponseBean data, WebView webView) { JSBridge.callback(data, webView); } @Override public String url() { return null; } @Override public RequestBean parseData() { if (TextUtils.isEmpty(url)) { return null; } Uri uri = Uri.parse(url); String methodName = uri.getPath(); methodName = methodName.replace("/", ""); mMethodName = methodName; return new RequsetBean(url); } @Override public String method() { return mMethodName; }}; }}Copy the code

Create an object that provides A JS method, add annotation @jsMethod to the externally provided method, and annotate the protocol number, method name and permission level of the method. The information needed in the method is obtained through IJsCall. After processing, the information is called back to JS through IJsCall.

public class JsProvider {    @JsMethod(processorType = JsProcessor.TYPE_PROCESSOR, name = "method1", permission = "high")    public void method1(IJsCall jsCall) {        // do anything        // ...        // ...        // ...        jsCall.callback("xxxx");    }}Copy the code

The above completes a communication between JS and Native. The details of the entire communication are not open to the public, and the user only has to focus on the development of the method. The information of the method is carried through annotations, which can be resolved by generating code at compile time to improve efficiency. Whitelisting and data encryption are implemented directly through interceptors. The whole system perfectly solves the problems in the previous project and is convenient for future business development.

conclusion

Hybrid App is the future trend, and the business logic between JS and Native will become more and more heavy. Therefore, the design of this part in the project is also very important. It needs to be adjusted according to business constantly to ensure its stability and strong expansion ability.

Refer to the link

https://www.jianshu.com/p/93cea79a2443https://zh.wikipedia.org/zh-hans/%E7%BB%9F%E4%B8%80%E8%B5%84%E6%BA%90%E6%A0%87%E5% BF%97%E7%AC%A6