如何移植 JsBridge 到鸿蒙

相信大多数小伙伴的项目都已经有了线上稳定运行的 JsBridge 方案,那么对于鸿蒙来说,最好的方案肯定是不需要前端同学的改动,就可以直接运行,这个兼容任务就得我们自己来做了。

关于 JsBridge 的通信原理,在 这篇文章 中已经介绍过了,现在主流的技术方案有 拦截 URL对象注入 两种,我们分别看一下如何在鸿蒙上实现。

拦截 URL

在安卓上,拦截 URL 这个技术方案的代表作一定是 github.com/lzyzsd/JsBr... ,相信有不少小伙伴都使用了这个开源库。

我这里就以该开源库为例,介绍一下如何在鸿蒙上无缝迁移。

首先,在页面加载完成后注入通信需要的 JS 代码。在 Android 中,是 WebViewClient.onPageFinished(),在鸿蒙中对应 Web组件的 onPageEnd()方法。

kotlin 复制代码
Web({ src: this.url, controller: this.controller })
  .onPageEnd(() => {
        this.onPageEnd()
        BridgeUtil.webViewLoadLocalJs(getContext(), this.controller, BridgeUtil.toLoadJs)
      })

鸿蒙中本地资源文件放在 resouce/rawfile 目录下,通过以下代码读取:

csharp 复制代码
rawFile2Str(context: Context, file: string): string {
    try {
      let data = context.resourceManager.getRawFileContentSync(file)
      let decoder = util.TextDecoder.create("utf-8")
      let str = decoder.decodeWithStream(data, { stream: false })
      return str
    } catch (e) {
      return ""
    }
  }

读取到的 JS 代码,通过系统能力动态执行。在 Android 中,通过 WebView.loadUrl() 或者 WebView.evaluateJavaScript() 来实现。在鸿蒙中,对应的是 WebviewController.runJavaScriptExt()

typescript 复制代码
webViewLoadLocalJs(context: Context, controller: WebviewController, path: string) {
    let jsContent = BridgeUtil.rawFile2Str(context, path)
    controller.runJavaScriptExt(BridgeUtil.JAVASCRIPT_STR + jsContent, (err, result) => {
      ...
    })
  }

JS 代码注入完成后,就是核心的拦截 URL 了。在 Android 中,通过 WebViewClient.shouldOverrideUrlLoading() 实现,看一下具体的代码:

typescript 复制代码
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { 
            webView.handlerReturnData(url);
            return true;
        } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { //
            webView.flushMessageQueue();
            return true;
        } else {
            return super.shouldOverrideUrlLoading(view, url);
        }
    }

拦截到所有的 URL,判断是否是 H5 通过 iFrame.src 发送的指定特征的 URL,来完成通信流程。

在鸿蒙中,对应的是 Web 组件的 onInterceptRequest()方法。

kotlin 复制代码
Web({ src: this.url, controller: this.controller })
  .onInterceptRequest((event) => {
        if (event) {
          let url = event.request.getRequestUrl()
          if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) {
            this.ytoJsBridge.handlerReturnData(url)
          } else if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) {
            this.ytoJsBridge.flushMessageQueue()
          } else {
            return null
          }
        }
        return null
      })

核心逻辑就这样,剩下的工作量就是苦逼的翻译代码。好在代码量并不大,大概五六个文件。

移植过程中,也踩了一些坑,印象最深的是 ArkTs 中关于接口的写法。

php 复制代码
export interface CallBackFunction {
 onCallBack(data: string): void
}

这是在 Java/Kotlin 中很常见的一种写法,顺手在 ArkTs 也这么写,但是在使用过程中尝试去写实现的时候就犯了难。如果直接按照传统的前端写法:

csharp 复制代码
let responseFunction: CallBackFunction
if (callBackId != undefined) {
  responseFunction = {
    onCallBack: (data: string): void => {
      ...
    }
  }
}

你会得到一个 lint 错误 Object literal must correspond to some explicitly declared class or interface (arkts-no-untyped-obj-literals) 。

你可以使用箭头函数来解决这个问题。

typescript 复制代码
export interface CallBackFunction {
  onCallBack: (data: string) => void
  // onCallBack(data: string): void
}

这也是 ArkTs 目前比较割裂的地方,基于 TS,但是禁用了很多特性。

设想一下如果可以继续兼容 Java/Kotlin,那么这篇文章都不会存在了,压根不存在迁移成本,海量移动端类库无缝衔接......

对象注入

对象注入在 Android WebView 中的实现是 WebView.addJavascriptInterface(Object object, String name) 方法 。

kotlin 复制代码
addJavascriptInterface(JsBridge(this@MainActivity, webView), "JsBridge")

class JsBridge(private val activity: Activity, private val webView: WebView) {  
  
    @JavascriptInterface  
    fun webCallNative(message: String) {  
        Log.e("JsBridge", "webCallNative: ${Thread.currentThread().name}")  
        Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()  
    }  
}

在鸿蒙中,可以通过 Web 组件的 javaScriptProxy() 方法,或者 WebviewController.registerJavaScriptProxy() 方法。

kotlin 复制代码
      Web({ src: this.url, controller: this.controller })
        .javaScriptAccess(true)
        .javaScriptProxy({
          object: this.testObj,
          name: "objName",
          methodList: ["test", "toString"],
          controller: this.controller,
      })

这种方式只支持注入一个对象,如果需要注入多个对象,要用 WebviewController.registerJavaScriptProxy()

kotlin 复制代码
this.controller.registerJavaScriptProxy(this.testObjtest, "objName", ["test", "toString", "testNumber", "testBool"]);
this.controller.registerJavaScriptProxy(this.webTestObj, "objTestName", ["webTest", "webString"]);

这个方法的调用时机需要注意,必须发生在 controller 和 Web 组件绑定之后,建议放在 Web.onPageEnd()。注册之后需要调用 WebviewController.refresh() 才会生效。

总结

一入鸿蒙深似海,波涛汹涌无尽头。

云涛翻滚遮日月,雾霭弥漫掩星楼。

仙禽异兽齐飞舞,灵草神木共清幽。

鸿蒙奥秘难穷尽,探寻真道意未休。

Write by 文心一言,如有雷同,请...

相关推荐
excel1 小时前
如何解决 Nuxt DevTools 中关于 unstorage 包的报错
前端
jinanwuhuaguo1 小时前
OpenClaw工程解剖——RAG、向量织构与“记忆宫殿”的索引拓扑学(第十三篇)
android·开发语言·人工智能·kotlin·拓扑学·openclaw
Rust研习社1 小时前
使用 Axum 构建高性能异步 Web 服务
开发语言·前端·网络·后端·http·rust
C澒1 小时前
AI 生码 - API2Code:接口智能匹配与 API 自动化生码全链路设计
前端·低代码·ai编程
浔川python社1 小时前
HTML头部元信息避坑指南技术文章大纲
前端·html
IT_陈寒2 小时前
SpringBoot配置加载顺序把我坑惨了
前端·人工智能·后端
maaath2 小时前
【maaath】Flutter for OpenHarmony 跨平台工程集成密码加密能力
flutter·华为·harmonyos
kyriewen2 小时前
Next.js部署:从本地跑得欢,到线上飞得稳
前端·react.js·next.js
Moment2 小时前
面试官:给 llm 传递上下文,有哪几个身份 role ❓❓❓
前端·后端·面试
跨境数据猎手2 小时前
跨境独立站系统技术拆解(附带源码)
服务器·前端·php