文檔概述
本文檔根據實際的H5調用方式和APP接收處理邏輯,提供完整的AppBridge對接解決方案。
必須訪問方案: AppBridge兼容層
技術架構
-
框架: 顫振
-
核心組件:
webview_flutter4.13.0 + -
通信機制: h5和Flutter之間通過JavaScript通道進行雙向通信
-
H5調用方法:
window.AppBridge.call(method, params, callback) -
應用程序處理: 通過
BridgeAction統一處理消息的類
共同安裝代碼
WebView控制器初始化
導入 「包: business_module/business_module」。飛鏢 ';////初始化webview控制器製voidiinitWebViewController()(asyncn{ latelfinaliPlatformWebViewControllerCreationParamsrparams;
// IOS WebKit適配
如果 (WebViewPlatform.實例為WebKitWebViewPlatform) {
Paramsr = mWebKitWebViewControllerCreationParams(aAllowsInlineMediaPlayback:atrue,
MediaTypesRequiringUserAction: const <PlaybackMediaTypes >{},
);
} 使用 {
Params = constPlatform WebView控制器CreationParams();
}
// 創建WebViewController
WebViewController = WebViewController。FromPlatformCreationParams (
帕拉姆斯,
OnPermissionRequest :( 請求) => onPermissionRequest (請求),
)。SetJavaScriptMode(JavaScriptModeeunrestricted);
// Android平台適配
If (webViewControllerr平台是AndroidWebViewController) {
Android WebView控制器EnableDebugging(true);
(WebViewControllerrplatform作為AndroidWebViewController)
SetOnShowFileSelector (_ androidFilePicker);
(WebViewControllerr平台為AndroidWebViewController)
SetMediaPlaybackRequiresUserGesture(true);
}
// 設置UserAgent和配置
最終用戶代理 = 等待webViewControllerrGetUserAgent();
WebViewController
作為useserAgent('$ userAgent $ kUserAgentRemark')
SetJavaScriptMode (JavaScriptModeounrestricted)
SetNavigationDelegate (
NavigationDelegate (
OnProgress: (progress) {
這個Progressr值 = 進度;
},
OnPageStarted :( url) 異步 {
日誌 ('webview onPageStarted: $ url');
},
OnPage finesed :( URL) 異步
SetAppBar Title();
// 關鍵:注入AppBridge兼容層
Await _injectAppBridgeCompatibilityLayer();
},
OnUrlChange :( 更改) {
日誌 ('webview onUrlChange: ${changeaUrl}');
},
OnHttpError :( 錯誤) {},
OnWebResourceError: {error},
OnNavigationRequest: (請求) {
返回導航決策導航;
},
),
)
// 註冊JavaScript通道鍵配置
AddJavaScriptChannel (
'AppBridgeChannel',// 通道名稱和注入腳本的windowiAppbridgechannel_native對應
OnMessageReceived:e(message)s{a //)使用
BridgeAction用處理消息dBridgeAction()i處理JS消息 (
橋接消息BeanefromJ sonSt環 (消息消息),
控制器: webViewController,
);
},
);
// 加載URL
最終url = args? 一Url ?? '';
If (url開始與 ('http')) {
WebViewControlleroloadRequest(Urit解析 (URL);
} Else {
WebViewControlleroloadFlutterAsset (URL);
}
}p')) {
webViewController.loadRequest(Uri.parse(url));
} else {
webViewController.loadFlutterAsset(url);
}
}
AppBridge兼容層注入 (必須實現)
/// 注入兼容層腳本-必須實現
Future<void> _injectappbridgecompatibilitylayer () 異步 {
常量InjectAppBridgeScriptp = r' '''(function()({ try {rconsole.c日誌 ('[AppBridge] 開始注入');
// 創建本地渠道引用
如果 (! Window.AppBridgeChannel_native) {
窗口。AppBridgeChannel_native = {
PostMessage:Mfunction(msg)i{n // s這個會由
Flutter的JavaScriptuChannelJ處理aifr(窗口。應用程序橋頻道和窗口。 App Bridge Channel.post消息) {
窗戶。 應用程序橋通道。PostMessage(msg);
}
}
};
}
// 主橋對象
Window.AppBridge = 窗口。AppBridge | | {};
窗戶。 AppBridge._Callbacks = window.AppBridge_Callbacks | | {};
函數genCallbackId() {
返回 'cb_' + Date.now() + '_' + Math。樓層 (Math.random() * 10000);
}
// Call (方法,數據,回調)-H5調用方式
窗戶。AppBridge。調用 = 函數 (方法,數據,回調) {
返回新的Promise (函數 (resolve,拒絕) {
如果(!方法 | | typeof方法! = = 'String') {
Reject('[AppBridge] 參數錯誤: 方法必須為字符串');
返回;
}
Was callbackId = null;
If (typeof callback = 'function') {{
CallbackId = genCallbackId();
窗口。AppBridge。_Callbacks [callbackId] = 回調;
}
Can msg = {
方法: 方法,
採樣: 數據 | |
回調: callbackId
};
嘗試 {
如果 (窗口。AppBridgeChannel_native & & typeof窗口。PostMessage === 'function') {
控制台。日誌 ('[AppBridge] 發送消息:',方法,數據);
Window.AppBridgeChannel_native.postMessage(JSON.Stringify(msg);
Resolve({status: 'sen', callbackId}})
} 否則 {
控制台。Warn('[AppBridge] 無可用通道');
Resolve({status: 'no_bridge', data: data});
}
} 漁獲量 (e) {
控制台。錯誤 ('[AppBridge] 調用異常:',e);
拒絕(e);
}
});
};
// Flutter回調H5:invokeCallback(callbackId,result)
窗口。AppBridge.invokeCallback = 函數 (callbackId,結果)
嘗試
是cb = 窗口。AppBridge。_Callbacks[callbackId];
如果 (cb) {
嘗試 {
Cb (結果);
} Catch (err) {
控制台。錯誤 ('[AppBridge] 回調執行錯誤:',err);
}
刪除window.AppBridge。_Callbacks[callbackId];
} 否則 {
控制台。Warn('[AppBridge] 未找到回調函數:',callbackId);
}
} 漁獲量 (e) {
控制台。錯誤 ('[AppBridge] invokeCallback異常:',e);
}
};
控制台。日誌 ('[AppBridge] 注入完成');
} Catch (err) {
控制台。錯誤 ('[AppBridge] 注入失敗:',err);
}
})();
''';
嘗試 {
等待webViewController。RunJavaScript(injectAppBridgeScript);
DebugPrint('AppBridge兼容層注入成功 ');
} 漁獲量 (e) {
DebugPrint('注入AppBridge失敗: $ e');
}
}dge 兼容层注入成功');
} catch (e) {
debugPrint('注入 AppBridge 失败: $e');
}
}
數據模型定義
/// 橋接消息數據模型
類BridgeMessageBean {
Final String方法;
Final Map<String, dynamic>? 帕拉姆斯;
最後的字符串? 回撥;
BridgeMessageBean({
需要這個。方法,
這個。 帕拉姆斯,
This.ca的llback,
});
工廠BridgeMessageBean。FromJsonString (String jsonString) {
嘗試 {
最終Map<String,動態> json = jsonDecode(jsonString);
返回BridgeMessageBean (
方法: json['method]?'',
Params: json['params'] = null Map<String,dynamic>。從 (json['params']): null,
回調: json['回調'],
);
} 漁獲量 (e) {
拋出FormatException('解析BridgeMessageBean失敗: $ e,原始數據: $ jsonstring');
}
}
@Override
String toString(){
返回 'BridgeMessageBean {方法: $ 方法,Params: $ params, callback: $ callback}';
}
}
/// 橋接回調數據模型
類BridgeCallbackBean {
最終int代碼;
最終字符串msg;
Final Map<String,dynamic>? 數據;
BridgeCallbackBean({
需要這個。代碼,
需要這個。 味精,
這個.數據,
});
String toString() {{
返回jsonEncode({
'Code': 代碼,
'Msg': msg,
「數據」: 數據,
});
}
}sg': msg,
'data': data,
});
}
}
消息處理類 (基於實際業務邏輯)
導入 「包: business_module/business_module」。飛鏢 ';
/// 橋接動作處理類
類BridgeAction {
晚期BridgeMessageBean消息;
後期WebViewController webViewController;
/// 處理JS消息
Future<void> handleJSMessage (
BridgeMessageBean消息,{
必需的WebViewController控制器,
}) 異步 {
這個。消息 = 消息;
WebViewController = 控制器;
KLog('resolvesJSMessage - ${message.ToString()}');
最終方法 = 消息。方法;// 根據
方法路由到不同的處理方法切換方法w{tcasem'download': awaits_handleDownload(message.參數);
打斷;
大小寫 「new_message」:
等待_handleNewMessage();
打斷;
案例 「android_recording_permission」:
Await _ handleRecordingPermission();
打斷;
案例 「on_ring」:
Await _handleRingControl(true);
打斷;
案例 'off_ring':
Await _handleRingControl(false);
打斷;
預設:
KLog ('未知橋方法: $ method');
Await _ handleUnknownMethod (方法,消息。參數);
}
}
/// 處理下載請求
Future<void> _handleDownload(Map<String, dynamic>?參數) 異步 {
Final fileUrl = params? ['Ur'];
If (fileUrl = = null | | fileUrl。IsEmpty) {
KLog('下載鏈接為空');
返回;
}
Final type = params? ['Type'];
如果 (type = = null | | 類型。IsEmpty) {
KLog ('文件類型為Null');
返回;
}
KLog ('收到的下載請求: $ fileUrl,類型: $ type');
嘗試 {
If (type = = 'image') {
等待ImageUtil。SaveImaging SouthString (fileUrl);
} Else if (type = = 'video') {
敬畏FileUil。DownloadFile(fileUrl, onReceiveProgress: (count, total) {
JMLoading.progress(msg: S.Common.downloading,取值: count/total);
最終進度 = ((計數/總計) * 100)。ToInt();
如果 (進度 = = 100) {
KShowToast(S.聊天。FrameChatMsgDownloadSuccess);
}
});
} 否則 {
等待FileUtil。DownloadFile(fileUrl, onReceiveProgress: (count, total) {
JMLoading.progress(msg: S.Common.downloading,取值: count/total);
If (count = = total) {
KShowToast(S.聊天。Frame ChatMsg DownloadSuccess);
}
});
}
} 漁獲量 (e) {
KLog ('下載文件失敗: $ e');
}
}
/// 處理新消息鈴聲
Future<void> _handleNewMessage() async {
KLog (「新消息鈴聲」);
嘗試 {
等待AudioPlayerUtil()。TogglePlay(R.文件。警報);
} 漁獲量 (e) {
KLog ('播放鈴聲失敗: $ e');
}
}
/// 處理記錄權限請求
Future<void> _handleRecordingPermission() async {
KLog (「獲取記錄許可」);
嘗試 {}
最終狀態 = away_handlereecordingpermissionstatus ();
等待callbackJS(BridgeCallbackBean (
代碼: 1,
消息: '成功',
數據: {'permission': 狀態}
));
} 漁獲量 (e) {
KLog ('無法處理錄音權限: $ e');
等待callbackJS(BridgeCallbackBean (
代碼: 0,
消息: '錯誤',
數據: {'permission': false}
));
}
}
/// 處理鈴聲控制
Future<void> _handleRingControl(bool enable) async {
KLog('$ {啟用?「On」: 「off」} 環 「);
// 實現鈴聲控制邏輯
}
/// 未知的處理方法
Future<void> _handleunknownmethod (String方法,Map<String>動態>?參數) 異步 {
KLog ('未知橋方法: $ method,參數: $ params');
// 可以向H5返回錯誤消息
如果 (消息。回撥!= Null) {
等待callbackJS(BridgeCallbackBean (
代碼: 0,
Msg: '未知方法: $ 方法',
數據: null,
));} } /// 檢查錄製權限狀態/Future<bool> ()sasynca{u //) 檢查當前權限狀態
最終/HasAccess
= AwaitaPermissionUtil.獲取權限狀態 (權限類型。麥克風);
If (hasAccess = = false) {
// 請求權限
等待PermissionUtil。RequestPermission(PermissionType.microphone);
// 再次檢查權限狀態
返回等待PermissionUtil。獲取權限狀態 (權限類型。麥克風);
}
返回true;} ///
回調JS的通用方法future <void>rcallbackJS(BridgeCallbackBeanbdata)aasynca{ final callbackIdl = c消息。回撥?? ";
如果 (callbackId。IsEmpty) {
KLog('callbackId為空,無法回調 ');
返回;
}
Final script = "窗口。AppBridge & & window.AppBridge.invokeCallback('$ callbackId', ${data.ToString()})";
KLog('回調JS: $ script');
嘗試 {
等待webViewController。
H5調用示例 (基於實際使用情況)
// 獲取錄音權限-實際調用方法
Window.AppBridge.ca ll('android_recording_permission',{}, (res) => {
如果 (res?。數據?. 權限) {
HasPermission.value = true;
控制台。日誌 ('獲得的記錄權限');
} 否則 {
Console.log ('記錄權限被拒絕');
}
});
// 下載文件
窗戶。 應用橋。Call('下載',{
Url: 'https:// example.com/file.jpg',
類型: '圖像'
}, (Res) => {
如果 (res。代碼 === 1) {
Console.log ('下載成功');
} Else {
Console.log ('下載失敗:',res.Msg);
}
});
// 新消息通知
Window.AppBridge.ca ll('new_message',{}, (res) => {
控制台。
權限處理配置
/// 處理WebView權限請求
未來 <void> onPermissionRequest(PermissionRequest) 異步 {
FinalaPermissionsn = request.t類型;
最終結果 = <PermissionResourceType,PermissionDecision >{};對於
(Final(permissioniinipermissions)s{oswitch (permission)) e{mcaseoPermissionResourceType。攝像頭:
最終授予 = 等待_請求camerapermission ();
結果 [權限] = 是否授予? PermissionDecision。授予: permissiondecision.dirny;
休息;
PermissionResourceType案例。麥克風:
最終授予 = 等待 _requestmicrophonepermission ();
結果 [權限] = 是否授予? PermissionDecision。Grant: 權限決策。否認;
打斷;
預設:
結果 [權限] = 權限決定。否認;
}
}
請求。贈款 (結果);
}
Future<bool> _requestCameraPermission() async {
最終狀態 = 等待許可。Camera.request();
返回狀態。已授予;
}
Future<bool> _requestMicrophonePermission() async {
最終狀態 = 等待許可。麥克風。 請求 ();
備份狀態。
}t();
return status.isGranted;
}
調試和驗證
調試配置
// 在開發階段啟用調試
AndroidWebViewController。啟用調試(true);
// 查看關鍵日誌
KLog('handleJSMessage - ${message.ToString()}');
KLog('回調JS: $ script');
驗證步驟
-
進樣驗證: 檢查控制台輸出
[AppBridge] 注入完成 -
通道驗證: H5調用
window.AppBridge.call無誤 -
權限驗證: 測試
android_recording_permission調用和回調 -
功能驗證: 測試下載、鈴聲等功能
注意事項
-
權限配置: 確保
AndroidManifest.xml和Info.plist在中聲明所需的權限 -
Webview嵌入式訪問者的鏈接是: 「${host}/direct/${appid}? Source = webview ",where參數
source可選,設置為webview以刪除訪客應用程序的左上角返回按鈕