最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Flutter與WebView通信方案示例詳解

 更新時間:2023年01月04日 10:40:15   作者:SugarTurboS  
這篇文章主要為大家介紹了Flutter與WebView通信方案示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

背景

最近做Flutter應(yīng)用開發(fā),需要通過WebView嵌入前端web頁面,而且Flutter與前端web有數(shù)據(jù)通信的需求。因此,筆者關(guān)于Flutter與WebView通信方式做了調(diào)研,并封裝了一套支持請求響應(yīng)和發(fā)布訂閱的兩套通信模式的JSBridge SDK。

WebView組件選擇

Flutter三方庫,使用最多的WebView組件,如下兩款:

兩款組件都支持WebView與Flutter通信,flutter_inappwebview 比 webview_flutter提供的原生接口更豐富一些。

由于webview_flutter滿足筆者需求,接下來文章的內(nèi)容,都是以webview_flutter為準(zhǔn)。

webview_flutter通信方式調(diào)研

Flutter -> WebView通信方式

可以使用WebViewController對象的執(zhí)行js腳本的函數(shù)runJavascript(String javaScriptString)。具體代碼實現(xiàn)如下:

// web注冊native端調(diào)用的通信函數(shù)“javascriptChannel”
window['javascriptChannel'] = function(jsonStr) { ... }
// native端通過“runJavascript”執(zhí)行web注冊的通信函數(shù)“javascriptChannel”傳值,完成通信
WebView(
  javascriptMode: JavascriptMode.unrestricted,
  onWebViewCreated: (WebViewController webViewController) async {
    await webViewController.runJavascript('window["javascriptChannel"](${json.encode({...})})');
  },
),

問題

筆者在安卓平臺,F(xiàn)lutter端使用webViewController.runJavascript('window"javascriptChannel"')傳輸json字符串參數(shù),發(fā)現(xiàn)web端允許報錯,如下:

從錯誤信息來看,是執(zhí)行js語法的錯誤。這個問題是安卓端處理的問題。解決方案是對傳輸?shù)淖址鼍幋a處理,例如,base64編碼,如下:

String str = Uri.encodeComponent(json.encode({...}));
List<int> content = utf8.encode(str);
String data = base64Encode(content);
await webViewController.runJavascript('window["javascriptChannel"](${data})');
// web端收到數(shù)據(jù)對數(shù)據(jù)做解碼處理
const message = JSON.parse(decodeURIComponent(atob(jsonStr)));

注:window.atob不支持中文,因此需要encodeComponent/decodeURIComponent轉(zhuǎn)義中文字符,避免中文亂碼。

WebView -> Flutter通信方式

可以通過注冊WebView JavascriptChannel通信對象的方式。具體代碼實現(xiàn)如下:

// native端注冊web端調(diào)用的通信對象“nativeChannel”
WebView(
  javascriptMode: JavascriptMode.unrestricted,
  javascriptChannels: <JavascriptChannel>[
    JavascriptChannel(
      name: 'nativeChannel', // 注冊web調(diào)用的對象
      onMessageReceived: (JavascriptMessage msg) async {
        jsonDecode(msg.message)
      },
    ),
  ].toSet(),
)
// web端通過“nativeChannel”通信對象,調(diào)用函數(shù)“postMessage”傳值
window['nativeChannel'].postMessage(JSON.stringify(...));

注:通信傳值都是字符串的形式,native和web端需要自行解析字符串,因此建議采用json字符串的固定格式傳值

JSBridge通信模塊封裝

對于相對復(fù)雜需要頻繁進(jìn)行Flutter與web通信的場景,WebView提供的Flutter與web的通信接口簡單,不方便使用。因此基于常見的兩種通信方式:發(fā)布訂閱和請求響應(yīng),封裝一套標(biāo)準(zhǔn)的JSBridge通信的SDK。

發(fā)布訂閱

發(fā)布訂閱是一種標(biāo)準(zhǔn)的消息通信模式,主要用于兩個不相關(guān)聯(lián)解耦的模塊進(jìn)行數(shù)據(jù)通信。“訂閱方”只需要向“發(fā)布訂閱模塊”訂閱消息,當(dāng)“發(fā)布訂閱模塊”接收到“發(fā)布方”消息時,則把消息轉(zhuǎn)發(fā)到所有“訂閱方”,如下圖所示:

請求響應(yīng)

“請求方”發(fā)起一個請求消息,“響應(yīng)方”接收到請求消息,做一些邏輯處理,回應(yīng)一個響應(yīng)消息到“請求方”。例如:http協(xié)議就屬于請求響應(yīng)模式,可以把web端作為客戶端,flutter端作為服務(wù)端。如下圖所示:

代碼實現(xiàn)——Flutter端

1.JSBridge

import 'dart:convert';
import 'package:webview_flutter/webview_flutter.dart';
typedef SubscribeCallback = void Function(dynamic value);
typedef ResponseCallback = void Function(dynamic value, Function(dynamic value) next);
// 傳輸消息體
class BridgeMessage {
  static const String MESSAGE_TYPE_REQUEST = 'request';
  static const String MESSAGE_TYPE_PUBLISHER = 'publisher';
  String id = '';
  String type = '';
  String eventName = '';
  dynamic params;
  BridgeMessage({
    required this.id,
    required this.type,
    required this.eventName,
    required this.params,
  });
  BridgeMessage.fromJson(json) {
    id = json['id'] ?? '';
    type = json['type'];
    eventName = json['eventName'];
    params = json['params'];
  }
  dynamic toJson() {
    return {
      'id': id,
      'type': type,
      'eventName': eventName,
      'params': params,
    };
  }
  String toString() {
    return 'id=$id type=$type eventName=$eventName params=$params';
  }
}
// 注冊響應(yīng)句柄
class RegisterResponseHandle {
  final ResponseCallback registerResponseCallback; // 注冊的回調(diào)
  final Function(BridgeMessage message) callback; // 中間觸發(fā)的回調(diào)
  RegisterResponseHandle({
    required this.registerResponseCallback,
    required this.callback,
  });
}
class JSBridge {
  static const String NATIVE_CHANNEL = 'nativeChannel'; // 原生通信通道名稱
  static const String JAVASCRIPT_CHANNEL = 'javascriptChannel'; // js通信通道名稱
  WebViewController? _controller;
  Map<String, List<SubscribeCallback>> _subscribeCallbackMap = {};
  Map<String, List<RegisterResponseHandle>> _registerResponseHandleMap = {};
  /// 設(shè)置WebViewController 必須
  void setWebViewController(WebViewController controller) {
    _controller = controller;
  }
  /// webView設(shè)置JavascriptChannel
  Set<JavascriptChannel> getJavascriptChannel() {
    return <JavascriptChannel>[
      JavascriptChannel(
        name: NATIVE_CHANNEL,
        onMessageReceived: (JavascriptMessage msg) async {
          BridgeMessage message = BridgeMessage.fromJson(jsonDecode(msg.message));
          if (message.type == BridgeMessage.MESSAGE_TYPE_PUBLISHER) {
            // 處理訂閱消息
            _subscribeCallbackMap[message.eventName]?.forEach((callback) => callback(message.params));
          } else if (message.type == BridgeMessage.MESSAGE_TYPE_REQUEST) {
            // 處理請求消息
            _registerResponseHandleMap[message.eventName]?.forEach((element) => element.callback(message));
          }
        },
      ),
    ].toSet();
  }
  /// 發(fā)送消息
  Future postMessage(BridgeMessage bridgeMessage) async {
    String str = Uri.encodeComponent(json.encode(bridgeMessage.toJson()));
    List<int> content = utf8.encode(str);
    String data = base64Encode(content);
    try {
      await _controller?.runJavascript("""window['$JAVASCRIPT_CHANNEL']('$data')""");
    } catch (e) {
      print('runJavascript error: $e');
    }
  }
  /// 注冊響應(yīng)
  void registerResponse(String eventName, ResponseCallback callback) {
    if (_registerResponseHandleMap[eventName] == null) {
      _registerResponseHandleMap[eventName] = [];
    }
    _registerResponseHandleMap[eventName]?.add(
      RegisterResponseHandle(
        callback: (BridgeMessage message) {
          callback(
            message.params,
            (dynamic params) => postMessage(
              BridgeMessage(
                id: message.id,
                type: message.type,
                eventName: message.eventName,
                params: {'code': 0, 'data': params}, // code == 0表示響應(yīng)成功
              ),
            ),
          );
        },
        registerResponseCallback: callback,
      ),
    );
  }
  /// 注銷響應(yīng)
  void logoutResponse(String eventName, ResponseCallback callback) {
    List<RegisterResponseHandle>? registerResponseHandle = _registerResponseHandleMap[eventName];
    registerResponseHandle?.forEach(
      (item) {
        if (item.callback == callback) {
          registerResponseHandle.remove(item);
        }
      },
    );
  }
  /// 發(fā)布消息
  Future publisher(String eventName, dynamic params) async {
    await postMessage(BridgeMessage(
      id: '',
      type: BridgeMessage.MESSAGE_TYPE_PUBLISHER,
      eventName: eventName,
      params: params,
    ));
  }
  /// 訂閱消息,@return 取消訂閱回調(diào)
  Function subscribe(String eventName, SubscribeCallback callback) {
    if (_subscribeCallbackMap[eventName] == null) {
      _subscribeCallbackMap[eventName] = [];
    }
    _subscribeCallbackMap[eventName]?.add(callback);
    return () => unsubscribe(eventName, callback);
  }
  /// 取消訂閱
  void unsubscribe(String eventName, SubscribeCallback callback) {
    _subscribeCallbackMap[eventName]?.remove(callback);
  }
}

2.使用方式

class WebViewWidget extends StatefulWidget {
  @override
  _WebViewWidget createState() => _WebViewWidget();
}
class _WebViewWidget extends State<WebViewWidget> {
  /// 1、創(chuàng)建jsBridge對象
  JSBridge jsBridge = JSBridge();
  @override
  void initState() {
    super.initState();
    if (Platform.isAndroid) WebView.platform = AndroidWebView();
  }
  @override
  Widget build(BuildContext context) {
    return WebView(
      debuggingEnabled: true,
      javascriptMode: JavascriptMode.unrestricted,
      /// 2、設(shè)置 javascriptChannels 通道
      javascriptChannels: jsBridge.getJavascriptChannel(),
      onWebViewCreated: (WebViewController webViewController) async {
        /// 3、設(shè)置jsBridge webViewController通信對象
        jsBridge.setWebViewController(webViewController);
        /// 4、注冊響應(yīng)事件:"/test"
        jsBridge.registerResponse('/test', (value, next) {
          // TODO 處理響應(yīng)
          next('flutter響應(yīng)消息');
        });
        Function? unsubscribe;
        /// 5、訂閱消息事件:"test"
        unsubscribe = jsBridge.subscribe('test', (value) {
          /// TODO 處理訂閱
          unsubscribe?.call(); // 取消訂閱
          /// 6、發(fā)布消息事件:"test"
          jsBridge.publisher('test', '這是一條訂閱消息');
        });
        webViewController.loadFlutterAsset('assets/webview_static/index.html');
      },
    );
  }
}

代碼實現(xiàn)——web端

1.JSBridge

import { v1 as uuid } from 'uuid';
export type SubscribeCallback = (params?: any) => void;
const MESSAGE_TYPE_REQUEST = 'request';
const MESSAGE_TYPE_PUBLISHER = 'publisher';
const NATIVE_CHANNEL = 'nativeChannel'; // 原生通信通道名稱
const JAVASCRIPT_CHANNEL = 'javascriptChannel'; // js通信通道名稱
const REQUEST_TIME_OUT = 20000;
interface BridgeMessage {
  id: string;
  type: string;
  eventName: string;
  params: any;
}
class JSBridge {
  private native: any = window[NATIVE_CHANNEL];
  private subscribeCallbackMap = {};
  private requestCallbackMap = {};
  constructor() {
    window[JAVASCRIPT_CHANNEL] = (jsonStr) => {
      const message = JSON.parse(decodeURIComponent(atob(jsonStr))) as BridgeMessage;
      const id = message.id;
      const type = message.type;
      const eventName = message.eventName;
      const params = message.params;
      if (type === MESSAGE_TYPE_REQUEST) {
        this.requestCallbackMap[id] && this.requestCallbackMap[id](params);
      } else if (type === MESSAGE_TYPE_PUBLISHER) {
        const callbacks = this.subscribeCallbackMap[eventName];
        if (callbacks) {
          callbacks.forEach((callback) => callback(params));
        }
      }
    };
  }
  // 請求響應(yīng)
  request = (eventName: string, params: any, timeout = REQUEST_TIME_OUT): Promise<any> => {
    return new Promise((resolve: any) => {
      const id: string = uuid();
      let timer;
      this.requestCallbackMap[id] = (params) => {
        clearTimeout(timer);
        delete this.requestCallbackMap[id];
        resolve(params);
      };
      timer = setTimeout(() => {
        // code == -1表示響應(yīng)超時
        this.requestCallbackMap[id] && this.requestCallbackMap[id](JSON.stringify({ code: -1, data: '訪問超時' }));
      }, timeout);
      this.native &&
        this.native.postMessage(JSON.stringify({ type: 'request', id: id, eventName: eventName, params: params }));
    });
  };
  // 發(fā)布
  publisher = (eventName: string, params: any): void => {
    this.native && this.native.postMessage(JSON.stringify({ type: 'publisher', eventName: eventName, params: params }));
  };
  // 訂閱
  subscribe = (eventName: string, callback: SubscribeCallback): SubscribeCallback => {
    if (!this.subscribeCallbackMap[eventName]) {
      this.subscribeCallbackMap[eventName] = [];
    }
    this.subscribeCallbackMap[eventName].push(callback);
    return () => this.unsubscribe(eventName, callback);
  };
  // 取消訂閱
  unsubscribe = (eventName: string, callback: SubscribeCallback): void => {
    const callbacks = this.subscribeCallbackMap[eventName];
    if (callbacks) {
      callbacks.forEach((item, index) => {
        if (item === callback) {
          callbacks.splice(index, 1);
        }
      });
    }
  };
}
export default JSBridge;

2.使用方式

import React, { useEffect } from 'react';
import { Button } from 'antd';
import JSBridge from '@common/JSBridge';
import './index.less';
// 1、創(chuàng)建JSBridge對象
const jsBridge = new JSBridge();
function Test() {
  useEffect(() => {
     // 2、訂閱消息:“test”
    const unsubscribe = jsBridge.subscribe('test', (params) => {
      console.info('web收到一條訂閱消息:eventName=test, params=', params);
    });
    return () => {
      // 3、取消訂閱消息:“test”
      unsubscribe();
    };
  });
  return (
    <div styleName="container">
      <div styleName="add-button">
        <Button
          type="primary"
          onClick={() => {
            // 4、發(fā)布訂閱消息:“test”。native端訂閱test消息,請參考上面原生端代碼
            jsBridge.publisher('test', { data: '這是H5端發(fā)布消息' });
          }}
        >
          發(fā)布消息
        </Button>
      </div>
      <div styleName="delete-button">
        <Button
          type="primary"
          onClick={async () => {
            // 5、發(fā)送請求消息:“/test”,異步接收響應(yīng)數(shù)據(jù)。native端注冊響應(yīng)消息,請參考上面原生端代碼
            const res = await jsBridge.request('/test', { data: '這是H5端請求消息' });
            console.info('web收到一條響應(yīng)消息:eventName=/test, res=', res.data);
          }}
        >
          請求消息
        </Button>
      </div>
    </div>
  );
}
export default Test;

結(jié)尾

以上就是Flutter與WebView通信方案示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Flutter WebView通信方案的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android編程實現(xiàn)系統(tǒng)重啟與關(guān)機(jī)的方法

    Android編程實現(xiàn)系統(tǒng)重啟與關(guān)機(jī)的方法

    這篇文章主要介紹了Android編程實現(xiàn)系統(tǒng)重啟與關(guān)機(jī)的方法,較為詳細(xì)的分析了Android運行原理與源碼剖析,講述了Android編程實現(xiàn)系統(tǒng)重啟與關(guān)機(jī)的相關(guān)技巧與注意事項,需要的朋友可以參考下
    2016-02-02
  • Android scrollview如何監(jiān)聽滑動狀態(tài)

    Android scrollview如何監(jiān)聽滑動狀態(tài)

    這篇文章主要介紹了Android scrollview監(jiān)聽滑動狀態(tài)的實例代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-12-12
  • Android數(shù)據(jù)存儲之SQLite使用

    Android數(shù)據(jù)存儲之SQLite使用

    SQLite是D.Richard Hipp用C語言編寫的開源嵌入式數(shù)據(jù)庫引擎。它支持大多數(shù)的SQL92標(biāo)準(zhǔn),并且可以在所有主要的操作系統(tǒng)上運行
    2016-01-01
  • Android自定義ViewGroup多行多列效果

    Android自定義ViewGroup多行多列效果

    這篇文章主要為大家詳細(xì)介紹了Android自定義ViewGroup多行多列效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • Android布局之幀布局FrameLayout詳解

    Android布局之幀布局FrameLayout詳解

    這篇文章主要為大家詳細(xì)介紹了Android布局之幀布局FrameLayout ,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • Android嵌套滾動和協(xié)調(diào)滾動的多種實現(xiàn)方法

    Android嵌套滾動和協(xié)調(diào)滾動的多種實現(xiàn)方法

    嵌套的滾動主要方式就是這些,這些簡單的效果我們用協(xié)調(diào)滾動,如?CoordinatorLayout?也能實現(xiàn)同樣的效果,這篇文章主要介紹了Android嵌套滾動和協(xié)調(diào)滾動的多種實現(xiàn)方法,需要的朋友可以參考下
    2022-06-06
  • 深入剖析Android消息機(jī)制原理

    深入剖析Android消息機(jī)制原理

    本篇文章主要介紹了Android消息機(jī)制,深入的了解了android的消息機(jī)制,需要的朋友可以了解一下、
    2016-11-11
  • Android中GIF動圖的播放控制和監(jiān)聽詳解

    Android中GIF動圖的播放控制和監(jiān)聽詳解

    android下播放gif圖片功能似乎并不常用,很多時候還是以展示靜態(tài)圖片為主,可能是由于gif圖體積比較大吧。不過像表情動畫什么的,可能還是需要gif圖的。本文主要給大家介紹了關(guān)于Android中GIF動圖的播放控制和監(jiān)聽的相關(guān)資料,需要的朋友可以參考下。
    2017-05-05
  • Android實現(xiàn)接近傳感器

    Android實現(xiàn)接近傳感器

    這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)接近傳感器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • Andorid基于ZXing實現(xiàn)二維碼生成與掃描的示例代碼

    Andorid基于ZXing實現(xiàn)二維碼生成與掃描的示例代碼

    ZXing是一個開源的條碼和二維碼掃描庫,它可以用于Android開發(fā)中,通過ZXing庫可以實現(xiàn)Android設(shè)備上的條碼和二維碼掃描功能,開發(fā)者可以輕松地在Android應(yīng)用中集成條碼和二維碼掃描功能,本文主要給大家介紹了Andorid?ZXing實現(xiàn)二維碼,感興趣的朋友可以參考下
    2023-08-08

最新評論

保定市| 涞源县| 遂平县| 武邑县| 扎赉特旗| 洱源县| 随州市| 饶阳县| 芮城县| 海宁市| 宜兰市| 确山县| 广西| 怀仁县| 新营市| 成武县| 吉隆县| 胶州市| 厦门市| 鄂伦春自治旗| 中西区| 乐陵市| 静安区| 阳谷县| 赤水市| 商城县| 潢川县| 弥勒县| 吴堡县| 定襄县| 连江县| 淮南市| 玉树县| 柘荣县| 渝北区| 渭源县| 巴南区| 随州市| 遵义市| 剑阁县| 莱州市|