欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Flutter與WebView通信方案示例詳解

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

背景

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

WebView組件選擇

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

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

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

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

Flutter -> WebView通信方式

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

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

問題

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

從錯(cuò)誤信息來看,是執(zhí)行js語(yǔ)法的錯(cuò)誤。這個(gè)問題是安卓端處理的問題。解決方案是對(duì)傳輸?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ù)對(duì)數(shù)據(jù)做解碼處理
const message = JSON.parse(decodeURIComponent(atob(jsonStr)));

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

WebView -> Flutter通信方式

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

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

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

JSBridge通信模塊封裝

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

發(fā)布訂閱

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

請(qǐng)求響應(yīng)

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

代碼實(shí)現(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';
  }
}
// 注冊(cè)響應(yīng)句柄
class RegisterResponseHandle {
  final ResponseCallback registerResponseCallback; // 注冊(cè)的回調(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) {
            // 處理請(qǐng)求消息
            _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');
    }
  }
  /// 注冊(cè)響應(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對(duì)象
  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通信對(duì)象
        jsBridge.setWebViewController(webViewController);
        /// 4、注冊(cè)響應(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');
      },
    );
  }
}

代碼實(shí)現(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));
        }
      }
    };
  }
  // 請(qǐng)求響應(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)超時(shí)
        this.requestCallbackMap[id] && this.requestCallbackMap[id](JSON.stringify({ code: -1, data: '訪問超時(shí)' }));
      }, 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對(duì)象
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消息,請(qǐng)參考上面原生端代碼
            jsBridge.publisher('test', { data: '這是H5端發(fā)布消息' });
          }}
        >
          發(fā)布消息
        </Button>
      </div>
      <div styleName="delete-button">
        <Button
          type="primary"
          onClick={async () => {
            // 5、發(fā)送請(qǐng)求消息:“/test”,異步接收響應(yīng)數(shù)據(jù)。native端注冊(cè)響應(yīng)消息,請(qǐng)參考上面原生端代碼
            const res = await jsBridge.request('/test', { data: '這是H5端請(qǐng)求消息' });
            console.info('web收到一條響應(yīng)消息:eventName=/test, res=', res.data);
          }}
        >
          請(qǐng)求消息
        </Button>
      </div>
    </div>
  );
}
export default Test;

結(jié)尾

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

相關(guān)文章

  • android studio與手機(jī)連接調(diào)試步驟詳解

    android studio與手機(jī)連接調(diào)試步驟詳解

    這篇文章主要為大家詳細(xì)介紹了android studio與手機(jī)連接調(diào)試步驟,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-07-07
  • Android ViewPager實(shí)現(xiàn)智能無限循環(huán)滾動(dòng)回繞效果

    Android ViewPager實(shí)現(xiàn)智能無限循環(huán)滾動(dòng)回繞效果

    這篇文章主要為大家詳細(xì)介紹了Android ViewPager實(shí)現(xiàn)智能無限循環(huán)滾動(dòng)回繞效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • Flutter生命周期超詳細(xì)講解

    Flutter生命周期超詳細(xì)講解

    和其他的視圖框架比如android的Activity一樣,flutter中的視圖Widget也存在生命周期,生命周期的回調(diào)函數(shù)提現(xiàn)在了State上面。理解flutter的生命周期,對(duì)我們寫出一個(gè)合理的控件至關(guān)重要
    2023-04-04
  • 淺談?wù)凙ndroid 圖片選擇器

    淺談?wù)凙ndroid 圖片選擇器

    近段時(shí)間有項(xiàng)目要求寫一個(gè)類似于微信發(fā)送圖片時(shí),用來選擇照片的一個(gè)圖片瀏覽器。相信有很多網(wǎng)友也有這樣的需求,這里分享給大家
    2015-12-12
  • Fresco加載手機(jī)圖片墻

    Fresco加載手機(jī)圖片墻

    這篇文章主要為大家詳細(xì)介紹了Fresco加載手機(jī)圖片墻,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-04-04
  • Android Compose實(shí)現(xiàn)底部按鈕以及首頁(yè)內(nèi)容詳細(xì)過程

    Android Compose實(shí)現(xiàn)底部按鈕以及首頁(yè)內(nèi)容詳細(xì)過程

    這篇文章主要介紹了如何利用compose框架制作app底部按鈕以及首頁(yè)內(nèi)容的詳細(xì)代碼,具有一定價(jià)值,感興趣的可以了解一下
    2021-11-11
  • Android 按指定大小讀取圖片的實(shí)例

    Android 按指定大小讀取圖片的實(shí)例

    本文主要介紹Android 按指定大小讀取圖片,在Android開發(fā)過程中經(jīng)常會(huì)遇到圖片超過屏幕,或者圖片過小問題,這里給一個(gè)實(shí)例解決讀取圖片大小的問題,希望能幫助有需要的小伙伴
    2016-07-07
  • Android集成Unity的兩種方案

    Android集成Unity的兩種方案

    現(xiàn)在市面上的形形色色Android客戶端,為了更優(yōu)的用戶體驗(yàn),我們開發(fā)的上游產(chǎn)品和交互往往會(huì)在界面里設(shè)計(jì)很多動(dòng)效,傳統(tǒng)的一頁(yè)頁(yè)的靜態(tài)展示頁(yè)面已經(jīng)不足以滿足用戶的審美需求了,本文將給大家分享Android集成Unity的兩種方案,感興趣的朋友可以參考下
    2024-05-05
  • Android App中進(jìn)行語(yǔ)言的切換

    Android App中進(jìn)行語(yǔ)言的切換

    這篇文章主要介紹了Android App中如何進(jìn)行語(yǔ)言的切換,幫助大家更好的理解和學(xué)習(xí)使用Android app,感興趣的朋友可以了解下
    2021-03-03
  • Android編程實(shí)現(xiàn)動(dòng)態(tài)更新ListView的方法

    Android編程實(shí)現(xiàn)動(dòng)態(tài)更新ListView的方法

    這篇文章主要介紹了Android編程實(shí)現(xiàn)動(dòng)態(tài)更新ListView的方法,結(jié)合實(shí)例形式詳細(xì)分析了ListView的布局及動(dòng)態(tài)更新實(shí)現(xiàn)方法,需要的朋友可以參考下
    2016-02-02

最新評(píng)論