preface

Recently, I have seen a lot of interaction between JS and Java in Android. Of course, there are several ways of interaction between them, but I think there are some shortcomings in these ways of interaction. This is the key reason why I decided to write the library SimpleJavaJsBridge.

I will proceed with this article in the following order:

  1. The existing COMMUNICATION scheme between JS and Java and its deficiency
  2. Js and Java perfect communication scheme design
  3. SimpleJavaJsBridge

Now let’s get down to business

1. Existing COMMUNICATION schemes between JS and Java and their shortcomings

Js can send messages to Java, and Java can also send messages to JS. Then come to the communication scheme between them again and again.

LoadUrl (String) {loadUrl(String) {loadUrl(String);}

// Example: call javascript's test(param) method webview.loadurl ("javascript:test(1)");Copy the code

Call method is very simple, “javascript:”+ JS method name + method parameter value concatenated into a string can send a message to JS, as if in direct call JS method.

1.2 JS sends messages to Java Js sends messages to Java in fact there are only two schemes, to analyze the two schemes in turn. 1.2.1 Official method:

Public void invokeByJs(String MSG){}} public void invokeByJs(String MSG){}} Also act as an alias webView. AddJavascriptInterface (new JSBridge (), "JSBridge"); / / js call a Java method window. JsBridge. InvokeByJs (' hello Java);Copy the code

This method essentially injects an object into a WebView, and js sends a message to Java in this way

JavaMethod (param...) ;Copy the code

This is also like calling Java methods in Java code, because Java provides JS with the method name, method parameters, js when sending messages, method name and parameter must be the same, which is why the Java code can not be confused.

However, there is a serious bug in this method, and although the official solution is available in android4.4, this bug still needs to be fixed in android4.4 and later versions, so a few giants started to figure out how to fix this hole, and a second method was born.

1.2.2 The main principle of this scheme is that JS passes the agreed string to Java:

  • Find an entry where JS can send messages to Java (onJsPrompt,onJsAlert, etc.)
  • Through the entry, JS concatenates the message into a string and passes it to Java according to the predefined rules
  • Java parses strings according to established rules
  • Call your own methods through reflection based on parsed data

This method is more difficult to use than the official method (the first method).

1.3 existing deficiencies above introduced the JS and Java communication method, then I will analyze the deficiencies I think exist. 1.3.1 Disadvantages of Java’s method of sending messages to JS and js’s official method of sending messages to Java 1.3.1.1 Strong dependence on Java’s method of sending messages to JS and JS’s official method of sending messages to Java both have the problem of strong dependence, and they both highly depend on each other’s method name and method parameters. Strong dependency occurs in the same module, which I think is not a problem or even a reflection of high cohesion. However, Java and JS can be said to be in two different modules or two different worlds. As long as the methods provided by JS to Java change, Java also has to change, and so does the methods provided by Java to JS. Being in two different modules knowing as little about each other as possible will reduce the coupling, not to mention the benefits of reduced coupling.

1.3.1.2 Strong dependency causes JS to be compatible with different systems

Function location(){if(isIOS){if(isIOS){if(isIOS); }else if(isAndroid){send a message to Android; }}Copy the code

The above code shows that JS uses the native positioning function of the code, because js sends messages to different systems in different ways, there will be compatible statements like if else if. Js code is currently only available on ios and Android, if else if it will be used on Windows or PC. The main reason for this problem is that THE JS code is communicating with different systems in their own unique communication mode.

When Java sends a message to a nonexistent js interface, it doesn’t even know that the interface doesn’t exist. Java just waits. Similarly, when JS sends a message to a Java interface that does not exist, it is possible for JS to catch an exception to know that the interface does not exist, but this is not the best solution. Sending a message to a nonexistent interface with no feedback results in javascript code filled with if else if statements.

Function location(){if(androidAppVersion > '1.1'){send a message to Java; }else{remind the user that location function is not supported at present; }}Copy the code

This is a SECTION of JS code that calls Java for positioning. The positioning function was added in Android App version 1.1, so this function is not supported for versions below 1.1. Therefore, it is necessary to judge the js code according to the version number. This is just a small microcosm of if else if due to versioning problems. There are also other situations where an if else if can occur such as a single js code being used by multiple businesses.

1.3.2 Js to Java to send messages to the second method has shortcomings mentioned above js to Java to send messages to the second method, it solves the existing vulnerabilities, but this method, the use of more complex than the first method, Java will do the following work:

  • Parses the string passed by JS to Java, and parses the interface and parameters of the call
  • Map the interface and parameters of the call to the corresponding method

Regardless of whether the strings that JS passes to Java are in JSON format or some other format, parsing such strings is bound to be a tedious and repetitive manual effort.

If we want to solve the above problems, it is necessary to design a perfect communication scheme.

2. Perfect communication scheme design between JS and Java

2.1 A perfect communication scheme between JS and Java should meet the following points:

  • The less js and Java know about each other, the better, and the less they are coupled. How much is better? Personally, I think it’s enough to give each other a chance. In this way, the communication between JS and native is similar to communication through a pipe or similar to socket communication (reducing strong dependency).

  • Communication between JS and Java needs to define a set of communication protocols or communication rules to transfer communication protocols between pipes. In this way, they communicate with each other over a set of defined protocols, not over each system’s own unique communication mode (the benefit is that js does not have if else if code compatible with different systems).

  • When actively sending a message to the other party, the other party must respond to the message, even if the sender is not interested in the feedback message. (Feedback can remove if else if compatibility code due to version compatibility etc.)

2.2 Then we will start to design the communication scheme between JS and Java. 2.2.1 Give each other an interface

  • Js provides Java with a unique interface that can be written dead on the Java side or passed from JS (which is more flexible). All messages sent to JS (request messages and feedback messages) go through this interface
  • Java provides a unique interface for JS. Since the official method has a vulnerability, we use the onJsPrompt method to receive all messages sent by JS. Of course, you can choose other methods to receive JS messages.

2.2.2 Formulation of communication protocol between JS and Java

Communication between JS and Java is particularly similar to network requests, in that the action of initiating a message can be called request and the feedback to the message can be called response.

Request A request encapsulates the interface of the requesting party and the parameters required by the interface.

Response A response encapsulates state information (to know whether the result of the process was successful or failed) and the result of the process.

How do I receive response messages from the peer party?

It would have occurred to everyone to pass a callback interface when sending messages, but because js and Java are cross-language, especially Java can not pass the callback interface to JS, js can pass but there will be problems, so there is a solution:

  • When sending a request message to the other party, generate a unique ID value for the callback interface and send the ID value into the request.
  • Also cache the callback interface.
  • When response is received, the ID value is parsed from Response and the callback interface is searched based on the ID value.

Therefore, request and Response must also contain the value of the callback ID.

Communication protocol format Request Data format:

{/ / interface name "interfaceName" : "test", / / callback id value "callbackId" : "c_111111", / / parameters passed "params" : {... }}Copy the code

Response data format:

{// Callback id, ResponseId :"c_111111", //response data" data":{// status data": "1", "MSG ":"ok", // Response result data "values":{...... }}}Copy the code

At this point the communication protocol has been defined.

You can see that the communication protocols request and Response are both JSON formats. It is repetitive labor to parse data from JSON or package data into JSON. This is a pain point THAT I’ve been trying to address, and the way I’ve solved it is by taking inspiration from Retrofit and using annotations.

So much for designing js to communicate perfectly with Java, which is the core idea of the SimpleJavaJsBridge library, let’s take a look at SimpleJavaJsBridge.

3. SimpleJavaJsBridge

SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge: SimpleJavaJsBridge It is also very simple to use, as you will see in a later example, so the word simple is used. Through it Java can send messages to JS, and receive JS response messages; At the same time, JS can also send messages to Java, and also receive Java response messages. It is therefore a bridge between Java and JS, hence its name SimpleJavaJsBridge.

3.1 How to solve the tedious and boring repetitive drudgery? The solution to this problem comes from the famous Retrofit, which uses annotations to solve the problem of building a request and parsing a response, so annotations can also solve the problem I have now. So let’s look at these notes. InvokeJSInterface is used to mark the way Java sends messages to JS, JavaCallback4JS is used to annotate the callback methods that Java provides to JS. JavaInterface4JS is used to annotate the interface that Java provides to JS. Its value represents the interface name of the function Param used to annotate the parameter or instance property of the class, its value represents the key value of the parameter stored in JSON, and its needConvert represents whether the current parameter needs to be converted. The JsonObject class can only store basic data and JsonObject and JsonArray data types. The other types need to be converted. So any type that can’t be stored directly in a JsonObject must be true. ParamCallback is used to label the parameters of the callback type. For example, in a method that sends a request to a JS, a callback parameter is required. The ParamResponseStatus parameter is used to label parameters of the response status type, such as statusCode, StatusMsg, etc., whose value is the key value in json.

3.2 SimpleJavaJsBridge Build an instance of SimpleJavaJsBridge using 3.2.1

    SimpleJavaJsBridge instance = new SimpleJavaJsBridge.Builder()
          .addJavaInterface4JS(javaInterfaces4JS)                                       
          .setWebView(webView)                                   
           .setJSMethodName4Java("_JSBridge._handleMessageFromNative")                                   
          .setProtocol("niu","receive_msg").create();Copy the code

Through SimpleJavaJsBridge. Builder to build a SimpleJavaJsBridge object,

  • AddJavaInterface4JS is used to add the interface that Java provides to JS
  • SetWebView sets the WebView and that’s what you have to do
  • SetJSMethodName4Java sets js to the only exposed method name in Java
  • SetProtocol sets the protocol field, which is also required. This field is set primarily for ios

There are other methods you can call to set Up SimpleJavaJsBridge

3.2.2 Java provides an Interface to JS Java provides an interface with no parameters to JS

Public interface IResponseStatusCallback {void callbackResponse(@paramResponseStatus ("status")) int  status, @ParamResponseStatus("msg") String msg); } // The "TES4" interface that Java provides to JS, @paramCallback Callback @javaInterface4js ("test4") public void test3(@paramCallback IResponseStatusCallback JsCallback) {handle accordingly... ; / / send response messages to js jsCallback. CallbackResponse (1, "ok"); _jsnativeBridge. _doSendRequest("test4", {}, function(responseData){});Copy the code

Java provides an interface with parameters to JS

Public interface IResponseStatusCallback {void callbackResponse(@paramResponseStatus ("status")) int  status, @ParamResponseStatus("msg") String msg); Public static class Person {@param ("name") String name;} public static class Person {@param ("name") String name; @Param("age") public int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; }} needConvert must be true;}} Person cannot be stored directly in JsonObject. @javaInterface4js ("test1") public void test(@param (needConvert = true) Person personInfo, @paramCallback IResponseStatusCallback jsCallback) {Process the received data.... ; jsCallback.callback(1, "ok"); _jsnativeBridge. _doSendRequest("test1", {"name":"niu","age":10} function(responseData){ });Copy the code

3.2.3 Sending Messages to JS

// The method for sending messages to js is defined in an interface, since this process is modeled after Retrofit's public interface IInvokeJS {// complex type, Public static class City{@param ("cityName") public String cityName; @Param("cityProvince") public String cityProvince; public int cityId; } // Send data to js "exam" interface, @invokejsInterface ("exam") void exam(@param ("test") String testContent, @Param("id") int id,@ParamCallback IJavaCallback2JS iJavaCallback2JS); // Send data to the js interface "exam1" @invokejsInterface ("exam1") void exam1(@param (needConvert = true) @ParamCallback IJavaCallback2JS iJavaCallback2JS); } // use in the same way as Retrofit, Start by using SimpleJavaJsBridge's //createInvokJSCommand instance method to generate an IInvokeJS instance IInvokeJS invokeJs = simpleJavaJsBridge.createInvokJSCommand(IInvokeJS.class); Exam ("hello js",20, new IJavaCallback2JS{// The callback method that receives the response data sent by JS can be named any way, Public void callback(@paramResponseStatus (" MSG "))String statusMsg, @param (" MSG ") String msg) { } }); City city = new City(); CityName = "cityName "; city.cityId = 11; City. Citytype = "citytype "; // Send a message to exam1 of js, Invokejs.exam1 (city, new IJavaCallback2JS{ @JavaCallback4JS public void callback(@ParamResponseStatus("msg")String statusMsg,@Param("msg") String msg) { } });Copy the code

conclusion

The SimpleJavaJsBridge library brings the following benefits in JS-Java communication:

  • Js code is no longer compatible with if else if statements due to system or APP version or even business reasons
  • Java doesn’t have to worry about encapsulating data into JSON or parsing data from JSON
  • Make communication between JS and Java easier

Download SimpleJavaJsBridge to try it out if you’re tempted

Reference: big head ghost github.com/niuxiaowei/…