APP 內嵌訪客端 WebView
當你將訪客端聊天頁面嵌入 App 的 WebView 時,可透過 AppBridge 實現 H5 與原生層的雙向通訊。
訪客端連結格式:${host}/direct/${appid}?source=webview
source=webview參數可隱藏訪客端左上角的返回按鈕。
技術架構
- 框架: Flutter +
webview_flutter4.13.0+ - 通訊機制: JavaScript Channel(通道名稱
AppBridgeChannel) - H5 呼叫方式:
window.AppBridge.call(method, params, callback) - APP 處理: 透過
BridgeAction類統一路由訊息
必須實作:注入 AppBridge 相容層
每次頁面載入完成後(onPageFinished),都必須注入相容層腳本,否則 window.AppBridge 無法使用。
WebView 初始化(關鍵設定)
import 'package:business_module/business_module.dart';
void initWebViewController() async {
// ... 平台相容設定略,請參考完整文件
webViewController
..addJavaScriptChannel(
'AppBridgeChannel',
onMessageReceived: (message) {
BridgeAction().handleJSMessage(
BridgeMessageBean.fromJsonString(message.message),
controller: webViewController,
);
},
)
..setNavigationDelegate(NavigationDelegate(
onPageFinished: (url) async {
await _injectAppBridgeCompatibilityLayer();
},
));
}
相容層注入腳本
Future<void> _injectAppBridgeCompatibilityLayer() async {
const script = r'''
(function() {
window.AppBridgeChannel_native = {
postMessage: function(msg) {
if (window.AppBridgeChannel?.postMessage) {
window.AppBridgeChannel.postMessage(msg);
}
}
};
window.AppBridge = window.AppBridge || {};
window.AppBridge._callbacks = window.AppBridge._callbacks || {};
window.AppBridge.call = function(method, data, callback) {
return new Promise(function(resolve, reject) {
var callbackId = null;
if (typeof callback === 'function') {
callbackId = 'cb_' + Date.now() + '_' + Math.floor(Math.random() * 10000);
window.AppBridge._callbacks[callbackId] = callback;
}
var msg = { method: method, params: data || {}, callback: callbackId };
window.AppBridgeChannel_native.postMessage(JSON.stringify(msg));
resolve({ status: 'sent', callbackId: callbackId });
});
};
window.AppBridge.invokeCallback = function(callbackId, result) {
var cb = window.AppBridge._callbacks[callbackId];
if (cb) { cb(result); delete window.AppBridge._callbacks[callbackId]; }
};
})();
''';
await webViewController.runJavaScript(script);
}
資料模型
class BridgeMessageBean {
final String method;
final Map<String, dynamic>? params;
final String? callback;
factory BridgeMessageBean.fromJsonString(String jsonString) {
final json = jsonDecode(jsonString);
return BridgeMessageBean(
method: json['method'] ?? '',
params: json['params'] != null ? Map<String, dynamic>.from(json['params']) : null,
callback: json['callback'],
);
}
}
class BridgeCallbackBean {
final int code;
final String msg;
final Map<String, dynamic>? data;
String toString() => jsonEncode({'code': code, 'msg': msg, 'data': data});
}
支援的橋接方法
download — 下載檔案
window.AppBridge.call('download', { url: 'https://...', type: 'image' }, (res) => {
console.log(res.code === 1 ? '下載成功' : res.msg);
});
| 參數 | 類型 | 說明 |
|---|---|---|
url | string | 檔案地址 |
type | string | image / video / 其他 |
new_message — 播放新訊息提示音
window.AppBridge.call('new_message', {});
android_recording_permission — 取得錄音權限
window.AppBridge.call('android_recording_permission', {}, (res) => {
if (res?.data?.permission) {
console.log('已取得錄音權限');
}
});
回呼資料:{ code: 1, msg: 'success', data: { permission: true/false } }
on_ring / off_ring — 鈴聲控制
window.AppBridge.call('on_ring', {}); // 開啟鈴聲
window.AppBridge.call('off_ring', {}); // 關閉鈴聲
APP 端訊息處理
class BridgeAction {
late BridgeMessageBean message;
late WebViewController webViewController;
Future<void> handleJSMessage(BridgeMessageBean message, {required WebViewController controller}) async {
this.message = message;
webViewController = controller;
switch (message.method) {
case 'download': await _handleDownload(message.params); break;
case 'new_message': await AudioPlayerUtil().togglePlay(R.files.alert); break;
case 'android_recording_permission': await _handleRecordingPermission(); break;
case 'on_ring': await _handleRingControl(true); break;
case 'off_ring': await _handleRingControl(false); break;
}
}
Future<void> callbackJS(BridgeCallbackBean data) async {
final callbackId = message.callback ?? '';
if (callbackId.isEmpty) return;
await webViewController.runJavaScript(
"window.AppBridge?.invokeCallback('$callbackId', ${data.toString()})"
);
}
}
權限設定
在使用錄音、相機等功能前,需先於原生層宣告對應權限:
- Android: 在
AndroidManifest.xml中加入RECORD_AUDIO、CAMERA權限 - iOS: 在
Info.plist中加入麥克風與相機的使用說明
除錯
開發階段可啟用 WebView 除錯:
AndroidWebViewController.enableDebugging(true);
若保留 console.log,注入成功後控制台應輸出 [AppBridge] 注入完成。