使用Flutter + V8/JsCore开发小程序引擎(二)

flutter dart 、java/kotlin、V8引擎之间交互

在前一篇《使用Flutter + V8开发小程序引擎(一)》介绍了我们是用html+css来做界面描述,交互是用js,那么具体是怎么处理这两者的关系,以达到可动态下发及渲染页面的呢?

  • 先看下整体的通讯通,下面我们再详细介绍


    image

java/kotlin 与 Dart交互,这部分应该不用说,做过flutter的同学应该都知道

java/kotlin 与 V8交互,这部分应该很少人接触过,有什么用呢?那么请继续往下看

1 java/kotlin与V8交互

1.1 java/kotlin注入V8对象及执行V8里面的js函数

首先我们先用微信小程序举例,做过微信小程序或者了解过原理的同学应该知道,小程序开发在界面描述上是使用数据绑定,则在需要动态数据的文本或者属性时,通过双大花括号写一个表达式与对应数据进行绑定。请看以下一个简单例子:

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

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

以上例子,text 组件的文本与Page中的data的message1、message2进行绑定,那么如何计算表达式message1 + message2的值呢?则需要用到V8引擎来计算

  • 首先要在V8引擎构造Page对象,为什么呢?因为这样我们才能在计算表达式的时候拿到对应data的数据

通过eval将Page加载到V8引擎(ps:这个方法是通过framework.js文件在初始化V8的时候提前注入到V8里面,具体请查看源码实现)

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

我们看下加载后在V8里面的Page对象

image

还有data里面的数据

image
  • 那么数据已经加载到V8引擎里面了,怎么计算表达数呢?那么需要在V8里面拼接表达式了,具体如下:

同上__native__evalInPage方法,__native__getExpValue也是通过framework.js文件提前注入到V8的一个方法

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;
        }

在java/kotlin调用以下方法,先获取到V8里面对应的页面对象,在通过executeFunction执行V8里面的__native__getExpValue方法

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

1.2 V8里面的JS函数回调java/kotlin

这里注入一个console的对象给到V8,即在js中执行console.log()的时候会回调到JSConsole的log方法(ps:这里只是列举一种,还有其他方式,后续有机会再介绍)

    private fun registerFunc() {
        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()
    }
class JSConsole {
    fun log(string: Any) {
        Log.d("js", string.toString())
    }
}

到这里我们就讲完了java/kotlin与V8交互的部分了

2 java/kotlin与dart交互

  • dart调用java/kotlin

在dart端通过MethodChannel调用

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

在java/kotlin端实现onMethodCall

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")
                }
            }
            ...
        }
    }

-java/kotlin通知dart

在dart端实现BasicMessageChannel消息监听

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 {
        // 实时调试socket过来的数据
        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");
    });
  }

在java/kotlin端发送消息

    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())
    }

3 总结

dart -> java/kotlin 场景(以上面text渲染的流程)

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

Page({
  data: {
    message1: "hello ",
    message2: " world"
  }
})
  • 首先,页面在初始化的时候先将Page加载到V8引擎
  • 其次,在dart端生成widget时候,遇到数据绑定的表达式,先通过MethodChannel将表达式传到java/kotlin端
  • 然后,java/kotlin端通过executeFunction执行V8里面对应Page下面的__native__getExpValue方法得到表达式的值
  • 最后,java/kotlin端通过MethodChannel.Result将表达式的值直接返回给dart端

java/kotlin -> dart 场景(例如:点击打印日志)

<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);  
  }
})
  • 具体参照上面介绍的 V8里面的JS函数回调java/kotlin

4 不足与拓展

  • 不足

从上面的介绍,我们可以看到,要实现动态下发,需要dart->java/kotlin->V8->java/kotlin->dart这样一条长链路,这样子效率相对较低,而且对Android跟iOS来说是需要分别对接与原生交互的差异部分

  • 拓展

js引擎方面,Android是用V8,iOS用的是JsCore,目前没有实现,当然,如果实现dart直接调用V8及JsCore的Api,则整个链路可以简化为dart->V8/JsCore->dart,提高效率的同时,两端也没有那么多的差异处理

《使用Flutter + V8/JsCore开发小程序引擎(一)》

《使用Flutter + V8/JsCore开发小程序引擎(三)》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342