相关文章推荐
睿智的火柴  ·  labview ...·  10 月前    · 
帅气的猴子  ·  # electron之nsis ...·  1 年前    · 

Flutter自带WebView不想说啥了,就这样吧。

反正一番周折之后选择使用第三方的InAppWebView。看源码可以看出本质上是用了Platform调回原生平台的webview,但是性能就是比自己写的要好。

1. InAppWebView.initialOptions

最基本的配置。InAppWebViewGroupOptions里面有三种,我这边用到crossPlatform和android

crossPlatform: InAppWebViewOptions(
 useShouldOverrideUrlLoading: true,
 applicationNameForUserAgent: AppConfigureManager().androidUserAgent,
android: AndroidInAppWebViewOptions(
 useHybridComposition: true,

useShouldOverrideUrlLoading就是启用shouldOverrideUrlLoading的回调,具体作用百度applicationNameForUserAgent在已有的UserAgent后面直接拼信息,这样就不需要在后续时机取已有UA再做拼接了

useHybridComposition等于使用Flutter自带WebView时的

if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();

反正就带上就是了,忘了不带会出啥问题了

2. InAppWebView.gestureRecognizers

手势识别,根据注释(Flutter的WebView也有的字段),如果不添加会只能响应点击事件。实践下来的话的确如此。因为我们的WebView放在了一个可以左右滑的视图里,如果不捕捉垂直滑动手势会导致WebView自身很难响应到上下滑手势。

gestureRecognizers: [
 Factory(() => VerticalDragGestureRecognizer()),
].toSet(),

3. InAppWebView.onWebViewCreated

onWebViewCreated: (InAppWebViewController controller) async{
 _webViewControllerAndroid = controller;
 _webViewControllerAndroid..addJavaScriptHandler(handlerName: 'flutter_bridge', callback: (args) async{
  List<String> inArgs = [];
  for(var tmp in args.first) {
   if(tmp is String) {
    inArgs.add(tmp);
   else {
    inArgs.add('');
  NativePageJumpChannel tmp= await NativePageJumpChannel().webViewBridge(inArgs);
  if((tmp.callbackStr??'').length > 0) {
   _webViewControllerAndroid.loadUrl(
    urlRequest: URLRequest(url: Uri.parse(
     tmp.callbackStr ?? '')));

要点在于InAppWebViewController提供的addJavaScriptHandler方法(Flutter的WebView不提供的),给JS提供了一个回调InAppWebView的方法。在JS中调用这个JS接口的方法:

window.flutter_inappwebview.callHandler('flutter_bridge',[参数])

flutter_inappwebview是固定的object,应该是提前注入的

flutter_bridge是自己起的handler名称

最后传参数被封装进数组args中

Flutter端在callback中来实现业务逻辑,这里是把桥接分发给原生安卓端处理

注:addJavaScriptHandler方法注明要在onWebViewCreated和onLoadStart中使用,应该是注入时机的考量

4.InAppWebView.onLoadStop

没啥好说的,可以在这时跑一个evaluateJavascript方法,就是简单的注入脚本,在网页加载完成之后,需要注意时机问题。

注:evaluateJavascript被标注为不要在onWebViewCreated和onLoadStart中使用,但实际上onLoadStart中使用也可能注入成功,但结果并不可控。

相册问题处理

表象:使用InAppWebView打开了一个H5页面,点击按钮H5调用了系统自带的相册,这时候走的是InAppWebView自己的响应逻辑,打开了其自定义的图片选择器,但是选择图片/放弃选择图片之后并没有返回结果(返回图片),再次点击也无法打开相同的照片选择界面了,变成无响应。

通过报错找到了InAppWebView响应H5打开相册事件的地方

路径:flutter_inappwebview-5.3.2/android/src/main/java/com/pichillilorenzo/flutter_inappwebview/in_app_webview/InAppWebViewChromeClient.java

能找到onShowFileChooser、openFileChooser的实现,结果回调在onActivityResult

为了用上我们自己的打开相册逻辑,直接让openFileChooser/onShowFileChooser反射调用原生项目里的相册,回调也不走onActivityResult而是使用代理实现了原生里的回调

调用调整⬇️

  protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType) {
    // startPhotoPickerIntent(filePathCallback, acceptType);
    showFileChooser(filePathCallback, null, acceptType);
  protected void openFileChooser(ValueCallback<Uri> filePathCallback) {
    // startPhotoPickerIntent(filePathCallback, "");
    showFileChooser(filePathCallback, null, null);
  protected void openFileChooser(ValueCallback<Uri> filePathCallback, String acceptType, String capture) {
    // startPhotoPickerIntent(filePathCallback, acceptType);
    showFileChooser(filePathCallback, null, acceptType);
  @TargetApi(Build.VERSION_CODES.LOLLIPOP)
  @Override
  public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
    // String[] acceptTypes = fileChooserParams.getAcceptTypes();
    // boolean allowMultiple = fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE;
    // Intent intent = fileChooserParams.createIntent();
    // return startPhotoPickerIntent(filePathCallback, intent, acceptTypes, allowMultiple);
    showFileChooser(null, filePathCallback, null);
    return true;

 随便看看⬇️

  //改动 文件选择器逻辑
  public class FileChooserHandler implements InvocationHandler{
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      Object result = null; 
      String methodName = method.getName(); 
      if(methodName.compareTo("onSelectFinish") == 0) {
        if(args.length == 2) {
          final int errorCode = (int)args[0];
          final List<String> picPath = ((List)args[1])!= null ? (List)args[1] : Arrays.asList();
          Activity activity = inAppBrowserDelegate != null ? inAppBrowserDelegate.getActivity() : plugin.activity;
          activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
              boolean hasResult = false;
              String filePath = picPath.size() > 0 ? picPath.get(0): null;
              if(errorCode == 0) {//ISelectPIcObsv.SELECT_SUCC
                hasResult = true;
                File file = new File(filePath);
                if(InAppWebViewFlutterPlugin.filePathCallbackLegacy != null) {
                  InAppWebViewFlutterPlugin.filePathCallbackLegacy.onReceiveValue(Uri.fromFile(file));
                  InAppWebViewFlutterPlugin.filePathCallbackLegacy = null;
                if(InAppWebViewFlutterPlugin.filePathCallback != null) {
                  InAppWebViewFlutterPlugin.filePathCallback.onReceiveValue(new Uri[]{Uri.fromFile(file)});
                  InAppWebViewFlutterPlugin.filePathCallback = null;
              else {
                hasResult = false;
                filePath = null;
                if(InAppWebViewFlutterPlugin.filePathCallbackLegacy != null) {
                  InAppWebViewFlutterPlugin.filePathCallbackLegacy.onReceiveValue(null);
                  InAppWebViewFlutterPlugin.filePathCallbackLegacy = null;
                if(InAppWebViewFlutterPlugin.filePathCallback != null) {
                  InAppWebViewFlutterPlugin.filePathCallback.onReceiveValue(null);
                  InAppWebViewFlutterPlugin.filePathCallback = null;
      return result;
 private void showFileChooser(ValueCallback<Uri> filePathCallback, ValueCallback<Uri[]> filePathCallback21, String acceptType) {
    InAppWebViewFlutterPlugin.filePathCallbackLegacy = filePathCallback;
    InAppWebViewFlutterPlugin.filePathCallback = filePathCallback21;
    Activity activity = inAppBrowserDelegate != null ? inAppBrowserDelegate.getActivity() : plugin.activity;
    try {
      ///TODO
      Class clz = Class.forName("com.ll.llgame.engine.image.local.PicChooseEngine");
      Method getInstance = clz.getMethod("getInstance");
      Object engine = getInstance.invoke(null);//PicChooseEngine
      Class iSelectPicObsv = Class.forName("com.ll.llgame.engine.image.local.ISelectPIcObsv");
      FileChooserHandler handler = new FileChooserHandler();
      Object fileChooserHandlerObj = Proxy.newProxyInstance(clz.getClassLoader(), new Class[]{iSelectPicObsv}, handler);
      Method selectPic = clz.getMethod("selectPic",new Class[]{Context.class, iSelectPicObsv});
      selectPic.invoke(engine, new Object[]{activity, fileChooserHandlerObj});
    } catch (Exception e) {
      e.printStackTrace();
public interface ISelectPIcObsv {
    int SELECT_SUCC = 0;
    int SELECT_CANCEL = 1;
    int SELECT_FAIL = 2;
    int SELECT_FAIL_CAMERA_GALLRY_RESULT_NOT_OK = 3;
    int SELECT_FAIL_CAMERA_NULL_NOT_EXIST = 4;
    int SELECT_FAIL_CALL_CAMERA_FAIL = 5;
    int SELECT_FAIL_CLIP_PIC_FAIL = 6;
    int SELECT_FAIL_CLIP_PIC_SAVE_FAIL = 7;
    void onSelectFinish(int errorCode, List<String> picPath);
                    Flutter自带WebView不想说啥了,就这样吧。反正一番周折之后选择使用第三方的InAppWebView。看源码可以看出本质上是用了Platform调回原生平台的webview,但是xing nen
				
Flutter浏览器应用 使用Flutter和插件提供的功能创建的全功能移动浏览器应用(例如Google Chrome移动浏览器)。 可在Google Play商店中找到它, 为 文章: 。 还可以在此处查看介绍插件的文章: 。 WebView选项卡,具有在长按链接/图像预览时自定义的功能,以及如何在不丢失WebView状态的情况下从一个选项卡移动到另一个选项卡; 具有当前URL和所有弹出菜单操作的浏览器应用栏,例如打开新标签页,新的隐身标签页,将当前URL保存到收藏夹列表,将页面保存为脱机使用,查看网站使用的SSL证书,启用桌面模式等(功能类似于Google Chrome应用); 开发人员控制台,您可以在其中执行JavaScript代码,查看一些网络信息,管理浏览器存储(例如Cookie,window.localStorage等); 设置页面,您可以在其中更新浏览
Dart SDK:“> = 2.12.0-0 <3> = 1.22.0” Android: minSdkVersion 17并添加了对androidx支持(请参阅以迁移现有应用) iOS:-- --ios-language swift ,Xcode版本>= 11 适用于Android和iOS的重要说明 如果您正在运行应用程序,并且需要在runApp()之前runApp()例如,在插件初始化期间runApp()访问二进制Messenger,则需要首先显式调用WidgetsFlutterBinding.ensureInitialized() 。 一个例子: void main () { // it should be the first line in main method WidgetsFlutterBinding . ensureInitialized (); // rest of y
webview_flutter 是官方维护的 WebView 插件,特性是基于原生和 Flutter SDK 封装,继承 StatefulWidget,因此支持内嵌于 Flutter Widget 树中,这是比较灵活的。但不支持https自制证书强制信任。 flutter_webview_plugin 则是基于原生 WebView 封装的 Flutter 插件,将原生的一些基本使用 API 封装好提供给 Flutter 调用,因此并不能内嵌于 Flutter Widget ...
flutter_inappwebview插件源码解析 只讲第一个InAppWebView 以及 js交互 (InAppWebView后面简称为IAwebview) 这里有官方提供的使用示例 InAppWebView类 实际上是将原生的android视图嵌入到flutter中 即将原生的webview封装成widget.........
name: flutter_project description: A new Flutter project. publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: sdk: '>=2.12.0 <3.0.0' dependencies: dio: ^4.0.4 cupertino_i
Flutter Blue 是一个 Flutter 库,用于在 Flutter 应用中连接和操作蓝牙设备。它并不直接处理安卓定位权限问题,但是如果你的 Flutter 应用需要访问蓝牙设备的位置信息,那么在使用 Flutter Blue 之前,你应该先在应用中处理安卓定位权限问题。 要获取安卓设备的定位权限,你需要在应用的 AndroidManifest.xml 文件中声明 ACCESS_COARSE_LOCATION 或 ACCESS_FINE_LOCATION 权限,然后在代码中请求用户授予权限。你可以使用 Flutter 插件,如 flutter_permission_handler 来简化权限请求流程。 例如,下面是使用 flutter_permission_handler 插件请求安卓定位权限的示例代码: import 'package:flutter/material.dart'; import 'package:flutter_permission_handler/flutter_permission_handler.dart'; void main() { runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: HomePage(), class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); class _HomePageState extends State<HomePage> { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: RaisedButton( onPressed: _requestLocationPermission, child: Text('请求定位权限'), void _requestLocationPermission() async { PermissionStatus permission = await PermissionHandler() .checkPermissionStatus(PermissionGroup.location); if (permission != PermissionStatus.granted) { Map<PermissionGroup, PermissionStatus> permissions = await PermissionHandler() .requestPermissions([PermissionGroup.location]); if (permissions[PermissionGroup.location] == Per