接入 WebView

更新时间: 2025/01/03 17:57:37

本文介绍 Android、iOS、Windows、macOS、鸿蒙客户端通过 WebView 接入互动白板的方法。

背景说明

网易云信分别提供了互动白板和录像回放的 WebView 页面。WebView 是一个用于在移动应用或网页中显示网页内容的组件。每个 WebView 又额外提供了带虚拟控制台(vconsole)的页面。vconsole 页面在 WebView 中挂载一个虚拟的开发者控制台,方便开发者查看开发中遇到的问题。

功能原理

互动白板 WebView 和录像回放 WebView 均通过 jsBridge 和原生客户端进行通信和数据交换。这种桥梁使得 Web 页面能够访问原生应用的功能,同时也让原生应用能够控制 Web 页面的行为。消息流如下图所示。

graph LR
classDef default fill:#337EFF,stroke:#337EFF,stroke-width:0px,color:#FFFFFF;
WebView --window.jsBridge.NativeFunctio--> 原生客户端
原生客户端 --window.WebJSBridge--> WebView

WebView 向原生客户端发送消息:

  1. 原生客户端在 WebView 注册 window.jsBridge.NativeFunction 方法。
  2. WebView 通过该方法,将消息传递给原生客户端处理。

Native 客户端向 WebView 发送消息:

  1. WebView 在页面上注册 window.WebJSBridge 方法。(WebView 已处理,您无需处理)
  2. 原生客户端通过该方法,将消息传递给 WebView。

注意事项

  • Windows 和 macOS 客户端推荐使用 Electron 框架接入。若使用 Qt 框架接入,集成音视频播放功能可能存在问题。
  • 如果使用 Flutter 客户端接入互动白板,请将 WebView 发送给客户端的消息 注册在 window.jsBridge.postMessage 方法上,不要将消息注册在 window.jsBridge.NativeFunction 方法上。
  • 如果客户端无法直接在 window 对象上注册 NativeFunction,可以选择注册在 window.jsBridge.NativeFunction 上。

部署 WebView

  1. 下载 WebView,具体请参考 下载 SDK 和 Demo

  2. 在业务服务器上创建 WebView 的项目文件夹,并将解压后的 WebView 脚本文件拷贝至文件夹中。

    WebView 文件夹中的主要页面说明如下:

    • g2/webview.html:网易云信互动白板的 WebView 地址。
    • g2/webview_vconsole.html:网易云信互动白板的虚拟控制台。
    • g2/webview.record.html:网易云信互动白板回放的 WebView 地址。
    • g2/webview_vconsole.record.html:网易云信互动白板回放的虚拟控制台。

安卓接入示例

  1. 客户端为了接收 WebView 的数据,需要在 Webwindow 对象上注册 NativeFunction

    Java/**
    * 安卓接入示例
    */
    
    //MainActivity.java
    webView.addJavascriptInterface(new WebAppInterface(this, webView), "jsBridge");
    
    //WebAppInterface.java
    public class WebAppInterface {
        MainActivity mContext;
    
        @JavascriptInterface
        public void NativeFunction(String toast) throws JSONException {
            JSONObject obj = new JSONObject(toast);
            String action = obj.getString("action");
            JSONObject param = obj.getJSONObject("param");
    
            switch (action) {
                case "webPageLoaded":
                    login();
                    break;
                case "webJoinWBSucceed":
                    enableDraw();
                    break;
                case "webLoginIMFailed":
                case "webJoinWBFailed":
                case "webCreateWBFailed":
                case "webLeaveWB":
                    /**
                    * WebView 已经退出了白板房间,客户端此时应该销毁 WebView
                    */
                    destroyWebViewAndExitActivity();
                    break;
                case "webGetAuth":
                    sendAuthInfo();
                    break;
            }
        }
    }
    
  2. WebView 页面载入后,会在 window 上挂载 WebJSBridge 对象。客户端通过 WebJSBridge 向 WebView 发送消息。

    传给 WebView 的参数为 JSON 字符串。

    Java//WebAppInterface.java
    public class WebAppInterface {
        MainActivity mContext;
        webview webView;
    
        /** Instantiate the interface and set the context */
        WebAppInterface(Context c, webview w) {
            mContext = (MainActivity)c;
            webView = w;
        }
    
        private void runJs(final String param) {
            final String escapedParam = param.replaceAll("\"", "\\\\\"");
            webView.post(new Runnable() {
                @Override
                public void run() {
                    Log.d("native function call js", "javascript:WebJSBridge(\"" + escapedParam + "\")");
                    webView.loadUrl("javascript:WebJSBridge(\"" + escapedParam + "\")");
                }
            });
        }
    
        private void enableDraw() throws JSONException {
            JSONObject jsParam = new JSONObject();
            JSONObject param = new JSONObject();
            jsParam.put("action", "jsDirectCall");
            jsParam.put("param", param);
            param.put("target", "drawPlugin");
            param.put("funcName", "enableDraw");
            param.put("arg1", true);
            runJs((jsParam.toString()));
        }
    }
    

鸿蒙接入示例

TypeScriptimport { webview } from '@kit.ArkWeb';
import { JSON } from '@kit.ArkTS';
import fetch, { FetchResponse } from '@system.fetch';

interface NativeFunctionInterface {
  action: string;
}

interface jsJoinWBParam {
  uid: number;
  debug: boolean;
  channelName: string;
  appKey: string;
  platform: string;
  record: boolean;
}

interface jsSendAuthParam {
  code: number;
  nonce: string;
  curTime: string;
  checksum: string;
}

interface jsDirectCallParam {
  target: string;
  funcName: string;
  arg1: boolean
}

interface wbResponseData {
  wbAppKey : string,
  wbNonce: string,
  wbCheckSum: string,
  wbCurtime: string,
}
interface ResponseData {
  code: number,
  data: wbResponseData
}

@Entry
@Component
struct Index {
  controller: webview.WebviewController = new webview.WebviewController();
  uid = 1314; //自定义 uid
  channelName = 'test111'; //自定义 channelName
  appKey = ''; //白板 appkey
  checkSumUrl = ''; //鉴权地址
  webviewUrl = ""; //网易云信互动白板的 webview 地址
  jsJoinWB() {
    const param: jsJoinWBParam = {
      uid: this.uid,
      debug: true,
      channelName: this.channelName,
      appKey: this.appKey,
      platform: 'android',
      record: false
    }
    this.controller.runJavaScript(`WebJSBridge({action: "jsJoinWB", param:${JSON.stringify(param)}})`)
  }

  enableDraw() {
    const param: jsDirectCallParam = {
      target: 'drawPlugin',
      funcName: 'enableDraw',
      arg1: true
    }
    this.controller.runJavaScript(`WebJSBridge({action: "jsDirectCall", param:${JSON.stringify(param)}})`)
  }

  destroyWebView() {
  }

  sendAuthInfo() {
    const wbAppKey = this.appKey;
    const roomId = this.channelName;
    const uid = this.uid;
    const url = this.checkSumUrl;
    fetch.fetch({
      url,
      method: 'POST',
      header: {
        'content-type': 'application/json'
      },
      data: JSON.stringify({
        roomId,
        uid,
        wbAppKey
      }),
      success: (res: FetchResponse) => {
        const data = JSON.parse(res.data as string) as ResponseData
        const param: jsSendAuthParam = {
          code: 200,
          nonce: data.data.wbNonce,
          curTime:data.data.wbCurtime,
          checksum:data.data.wbCheckSum,
        }
        this.controller.runJavaScript(`WebJSBridge({action: "jsSendAuth", param:${JSON.stringify(param)}})`)
      },
      fail: (data: object, code: number) => {
        console.error('getCheckSum fail', JSON.stringify(data), code)
      }
    })
  }

  build() {
    Column() {
      Web({
        src: this.webviewUrl,
        controller: this.controller
      }).javaScriptProxy({
        object: {
          NativeFunction: (channelType: string) => {
            const obj = JSON.parse(channelType) as NativeFunctionInterface
            switch (obj.action) {
              case 'webPageLoaded':
                console.log('webPageLoaded, run jsJonWB()')
                this.jsJoinWB()
                break;
              case "webJoinWBSucceed":
                this.enableDraw();
                break;
              case "webLoginIMFailed":
              case "webJoinWBFailed":
              case "webCreateWBFailed":
              case "webLeaveWB":
                /**
                 * webview 已经退出了白板房间,客户端此时应该销毁 webview
                 */
                this.destroyWebView();
                break;
              case "webGetAuth":
                this.sendAuthInfo();
                break;
            }
          },

        },
        name: 'jsBridge',
        methodList: ['NativeFunction'],
        controller: this.controller,
      })
        .domStorageAccess(true)
        .fileAccess(true)
    }
  }
}
此文档是否对你有帮助?
有帮助
去反馈
  • 背景说明
  • 功能原理
  • 注意事项
  • 部署 WebView
  • 安卓接入示例
  • 鸿蒙接入示例