The Flutter DART, Java/Kotlin, and V8 engines interact with each other

In the previous article “Developing a small application Engine with Flutter + V8 (I)”, we used HTML + CSS for interface description and JS for interaction. How do we handle the relationship between the two to achieve dynamic rendering and rendering of the page?

  • Let’s take a look at the overall communication, and then we’ll talk about it in detail

Java/Kotlin interacts with Dart. This should go without saying, as those of you who have made Flutter know

Java/Kotlin interacted with V8, a part that few people have probably touched, so what’s the use? So read on

Java/Kotlin interacts with V8

1.1 Java/Kotlin inject V8 objects and execute JS functions in V8

First of all, let’s use wechat applet for example. Those who have done wechat applet or know the principle should know that the development of small program uses data binding in interface description. Then, when the text or attribute of dynamic data is needed, write an expression through double curly braces to bind the corresponding data. Here’s a simple example:

<text>{{message1 + message2}}</text>

Page({
  data: {
    message1: "hello ",
    message2: " world"
  },
  onLoad(e) {},
  onUnload() {}})Copy the code

In this example, the text of the text component is bound to message1 and message2 of the data in the Page. How to calculate the value of message1 + message2? You need a V8 engine to do that

  • The Page object is constructed in the V8 engine first. Why? Because that’s how we get the corresponding data when we evaluate the expression

To load a Page into V8 using eval (ps: this method is injected into V8 when V8 is initialized using framework.js)

        this.__native__evalInPage = function (jsContent) {
            if(! jsContent) {console.log("js content is empty!");
            }
            eval(jsContent);
        }
Copy the code

Let’s take a look at the Page object loaded in V8

And the data in data

  • Now that the data is loaded into the V8 engine, how do you count the representation? We need to concatenate expressions in V8 as follows:

As with the __native__evalInPage method above, __native__getExpValue is also a method pre-injected into V8 via the framework.js file

this.__native__getExpValue = function (script) {
            const expFunc = exp= > {
                return new Function(' '.'with(this){' + exp + '} ').bind(
                    this.data
                )();
            };
            var value = expFunc(script);
            if (value instanceof Object) {
                return JSON.stringify(value);
            }
            if (value instanceof Array) {
                return JSON.stringify(value);
            }
            return value;
        }
Copy the code

Call the following method in Java/Kotlin, get the corresponding page object from V8, and execute the __native__getExpValue method from V8 via executeFunction

    fun handleExpression(pageId: String, expression: String): String? {
        val page = getV8Page(pageId)
        returnpage? .executeFunction("__native__getExpValue", V8Array(V8Manager.v8).push(expression)).toString()
    }
Copy the code
    val pageId = "0x0001"
    val exp = "return message1 + message2"
    val result = handleExpression(pageId, exp)
Copy the code

The JS functions in 1.2 V8 call back to Java/Kotlin

Here, a console object is injected into V8, which is the JSConsole log method that is called back to when console.log() is executed in JS.

    private fun registerFunc(a) {
        val v8Console = V8Object(v8)
        v8.add("console", v8Console)
        val jsConsole = JSConsole()
        v8Console.registerJavaMethod(jsConsole, "log"."log", arrayOf<Class<*>>(java.lang.Object::class.java))
        v8Console.release()
    }
Copy the code
class JSConsole {
    fun log(string: Any) {
        Log.d("js", string.toString())
    }
}
Copy the code

This concludes the section where Java/Kotlin interacts with V8

2 Java/Kotlin interacts with DART

  • The dart invokes the Java/kotlin

Called on the DART side through the MethodChannel

  var _methodChannel = MethodChannel("com.cc.hybrid/method");
  
  void _initScript(String script) {
    _methodChannel.invokeMethod(
        "attach_page", {"pageId": _pageId, "script": decodeBase64(script)});
  }
Copy the code

Implement onMethodCall on the Java/Kotlin side

override fun onMethodCall(methodCall: MethodCall, result: MethodChannel.Result) {
        when (methodCall.method) {
            Methods.ATTACH_PAGE -> {
                if (methodCall.hasArgument("pageId") && methodCall.hasArgument("script")) {
                    val id = methodCall.argument<String>("pageId")
                    val script = methodCall.argument<String>("script")
                    Logger.d("lry"."attach_page pageId = $id script = $script") JSPageManager.attachPageScriptToJsCore(id!! , script!!) result.success("success")}}... }}Copy the code
  • Java/dart kotlin notice

Implement BasicMessageChannel message listening on the DART side

var _basicChannel = BasicMessageChannel<String> ('com.cc.hybrid/basic', StringCodec());
_initBasicChannel() async {
    _basicChannel.setMessageHandler((String message) {
      print('Flutter Received: $message');
      var jsonObj = jsonDecode(message);
      var pageId = jsonObj['pageId'];
      MessageHandler handler = _handlers[pageId];
      if (null! = handler) { handler.onMessage(jsonObj); }else {
        // Debug incoming socket data in real time
        var jsonObject = jsonDecode(jsonObj['message']);
        var pageCode = jsonObject['pageCode'];
        var content = jsonObject['content'];
        _pages.putIfAbsent(pageCode, () => content);
        _handlers.forEach((k, v) {
          if(k.startsWith(pageCode)) { v.onMessage(jsonObj); }}); }return Future<String>.value("success");
    });
  }
Copy the code

Send messages on the Java/Kotlin side

    private fun sendMessage2Flutter(type: Int, pageId: String, content: String) {
        val jsonObject = JSONObject()
        jsonObject.put("type", type)
        jsonObject.put("pageId", pageId)
        jsonObject.put("message", content)
        channel.send(jsonObject.toString())
    }
Copy the code

3 summary

Dart -> Java/Kotlin scene (render flow with text above)

<text>{{message1 + message2}}</text>

Page({
  data: {
    message1: "hello ",
    message2: " world"}})Copy the code
  • First, the Page is loaded into the V8 engine when initialized
  • Second, when generating the widget on the DART side, the expression for the data binding is passed to the Java/Kotlin side using MethodChannel
  • The Java/Kotlin side then executes the __native__getExpValue method under Page in V8 via executeFunction to get the value of the expression
  • Finally, the Java/Kotlin side returns the value of the expression directly to the DART side via methodChannel. Result

Java/Kotlin -> DART scenario (for example: Click Print log)

<raisedbutton onclick="onclick">
    <text>{{message1 + message2}}</text>
</raisedbutton>

Page({
  data: {
    message1: "hello ",
    message2: " world"}, onclick(e) { var msg = this.data.message1 + this.data.message2; console.log(msg); }})Copy the code
  • Refer to the JS function callback Java/Kotlin in V8 above

4. Deficiency and expansion

  • insufficient

Dart -> Java/Kotlin ->V8-> Java/Kotlin -> DART -> Dart ->V8-> Java/Kotlin -> DART

  • expand

In terms of JS engine, Android uses V8 and iOS uses JsCore, which is not implemented at present. Of course, if DART is implemented to directly call V8 and JsCore Api, the whole link can be simplified as DART ->V8/JsCore-> DART, which improves efficiency at the same time. There are not so many differences between the two ends

  • Source code address: portal

  • Series of articles:

Development of a Small Application Engine with Flutter + V8 (PART 1)

Development of a Small Application Engine with Flutter + V8 (part 3)