Flutter InAppWebview JS与App交互

2,483 阅读2分钟

QQ截图20230827120625.png

完整代码

inappwebview文档

前期准备

定义交互规范

首先确定好js与App交互规范,方便与前端开发与App开发沟通

我们定义jsBridgeName = NativeBridge

js调用app

方法名参数(json字符串)返回值(json字符串)
getAppInfo{ "info": "AppInfo"}
appAlert{"text":"appAlert"}

app调用js

方法名参数(json字符串)返回值(json字符串)
getJsInfo{ "info": "JsInfo"}
jsAlert{"text":"jsAlert"}

前端处理

编写一份bridge.js代码,供网页使用

function appAlert() {
    window.NativeBridge.appAlert(JSON.stringify({"text": "appAlert"}));
}

function getAppInfo() {
    window.NativeBridge.getAppInfo().then(result => {
        alert(result);
    });
}

function jsAlert(params) {
    console.log(typeof params)
    alert(params);
}

function getJsInfo() {
    return JSON.stringify({
        "info": "JsInfo",
    })
}

网页使用h5.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width" initial-scale="1"/>
    <title>Title</title>
    <script src="bridge.js"></script>
</head>
<body>
<button onclick="appAlert()">appAlert</button>
<button onclick="getAppInfo()">getAppInfo</button>
</body>
</html>

App端处理

解决InAppWebView不支持自定义JSBridgeName

问题

查阅文档发现,InAppWebView 不支持自定义JSBridgeName,我们无法使用window.JSBridgeName.method这样的方法调用app方法

<script>
            window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
                window.flutter_inappwebview.callHandler('handlerFoo')
                  .then(function(result) {
                    // print to the console the data coming
                    // from the Flutter side.
                    console.log(JSON.stringify(result));
                    
                    window.flutter_inappwebview
                      .callHandler('handlerFooWithArgs', 1, true, ['bar', 5], {foo: 'baz'}, result);
                });
            });
</script>
_webViewController.addJavaScriptHandler(handlerName:'handlerFoo', callback: (args) {
                        // return data to JavaScript side!
                        return {
                          'bar': 'bar_value', 'baz': 'baz_value'
                        };
                      });

解决

我们只需要将window.NativeBridge中的方法指向window.flutter_inappwebview.callHandler("NativeBridge", 'appAlert', ...args),注入如下js代码即可

window.NativeBridge = {
     appAlert: (...args) => window.flutter_inappwebview.callHandler("NativeBridge", 'appAlert', ...args),
     getAppInfo: (...args) => window.flutter_inappwebview.callHandler("NativeBridge", 'getAppInfo', ...args),
}

在flutter中,我们注入上面js代码

InAppWebView(
  initialUserScripts: UnmodifiableListView<UserScript>([
    UserScript(
      source: getInjectJS(bridgeName, JsCallAppMethod.values.map((e) => e.name).toList()),
      injectionTime: UserScriptInjectionTime.AT_DOCUMENT_START,
    ),
  ]),
)
    
  ///flutter_inappwebview默认只有window.flutter_inappwebview.callHandler方法,不能自定义
  ///
  ///这里做一个中转注入,即可使用window.youName.youMethod(args)
  ///
  ///生成代码如下
  ///
  ///window.NativeBridge = {
  ///     appAlert: (...args) => window.flutter_inappwebview.callHandler("NativeBridge", 'appAlert', ...args),
  ///     getAppInfo: (...args) => window.flutter_inappwebview.callHandler("NativeBridge", 'getAppInfo', ...args),
  ///}
  ///
  String getInjectJS(String bridgeName, List<String> methodNames) {
    final js = StringBuffer();
    js.write("window.$bridgeName = {");
    for (var name in methodNames) {
      js.write('$name: (...args) => window.flutter_inappwebview.callHandler("$bridgeName", "$name", ...args),');
    }
    js.write("}");
    return js.toString();
  }

交互方法

为方便后续的处理,我们使用enum对交互方法做规范化处理

enum JsCallAppMethod {
  getAppInfo,
  appAlert,
  ;

  static JsCallAppMethod? parse(String name) {
    try {
      return JsCallAppMethod.values.firstWhere((e) => e.name == name);
    } catch (e) {
      return null;
    }
  }
}

enum AppCallJsMethod {
  getJsInfo,
  jsAlert,
  ;
}

定义App端的交互方法

  void onWebViewCreated(InAppWebViewController controller) {
    this.controller = controller;
    InAppWebViewController.setWebContentsDebuggingEnabled(true);
    controller.loadFile(assetFilePath: Assets.htmlH5);
    controller.addJavaScriptHandler(
      handlerName: bridgeName,
      callback: (List<dynamic> arguments) {
        //这里做了处理,返回的结果为[methodName,...args]
        if (arguments.isNotEmpty) {
          var method = arguments[0];
          Map<String, dynamic>? param;
          if (arguments.length > 1) {
            param = jsonDecode(arguments[1]);
          }
          if (method is String) {
            var parse = JsCallAppMethod.parse(method);
            if (parse == null) {
              //未实现对应方法
            } else {
              return jsCallApp(method: parse, params: param);
            }
          }
        }
      },
    );
  }

  jsCallApp({required JsCallAppMethod method, Map<String, dynamic>? params}) {
    switch (method) {
      case JsCallAppMethod.getAppInfo:
        return jsonEncode({
          "info": "AppInfo",
        });
      case JsCallAppMethod.appAlert:
        alert(params);
        break;
    }
  }

  Future<dynamic> appCallJs({required AppCallJsMethod method, Map<String, dynamic>? params}) {
    String json = jsonEncode(params);
    var js = "window.${method.name}('$json')";
    return controller.evaluateJavascript(source: js);
  }

调试验证

webview调试

为方便进行调试和验证,开启webview调试

InAppWebViewController.setWebContentsDebuggingEnabled(true);

打开Chorme,输入网址chrome://inspect/#devices,点击inspect进入调试页面,我们可以直接调试App内的网页了,可以在控制台查看日志信息和编写代码

20230827110340.png QQ截图20230827113431.png

实现效果

Screenshot_2023-08-27-12-25-26-939_com.example.fl.jpg