当前位置: 首页 > news >正文

webview_flutter

查看webview内核

​https://liulanmi.com/labs/core.html​

h5中获取设备

https://cloud.tencent.com/developer/ask/sof/105938013

https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/mediaDevices

web资源部署后navigator获取不到mediaDevices实例的解决方案(navigator.mediaDevices为undefined)_navigator.mediadevices不存在_乐辞的博客-CSDN博客

自已遇到的坑:

需求是app进入webview 使用h5网页获取摄像头进行人脸识别。但是h5那边一直获取不到摄像头权限

H5 页面通过navigator.mediaDevices.getUserMedia调用手机摄像头拍照上传_navigator.webkitgetusermedia-CSDN博客

web资源部署后navigator获取不到mediaDevices实例的解决方案(navigator.mediaDevices为undefined)_navigator.mediadevices不存在_乐辞的博客-CSDN博客

h5 navigator.mediaDevices 一直是undefined 

原因1: 由于浏览器的安全策略导致了这个问题,目前经尝试,在以下几种情况中 navigator.mediaDevices 可以正常使用 

1. 地址为localhost:// 访问时 2. 地址为https:// 时 3. 为文件访问file:///

原因2:排除上面的问题, 使用了navigator兼容性写法获取 getUserMedia 摄像头设备 但是 getUserMedia 依旧为underfined--》怀疑是app的webview 的问题

怀疑是webview版本问题

解决方式1: 我将webview升级到最新版 发现问题不是webview的版本问题❌

最后发现flutter  app中 申请权限 使用   permission_handler: ^10.3.0

await Permission.camera.request() ->

if (await KPermiseeUtil.checkAndDoDefault(Permission.camera)) {_callCamera();}

安卓 谷歌内核:-》方向权限在安卓声明文件  AndroidManifest.xml 什么了对应权限 app内权限是有的。那说明只是webview的权限问题

安卓权限配置 和webview权限配置

android - How to access the camera from within a Webview? - Stack Overflow

按照上面的 依旧没有解决

最后发现 webview有个权限申请   

controller.platform.setOnPlatformPermissionRequest

虽然app 进入webview申请了权限 方式 webview内部也需要进行权限申请 

默认setOnPlatformPermissionRequest 这个函数回调是拒绝的 所以加下面的配置 解决

安卓不能使用h5打开摄像头的问题。

controller.platform.setOnPlatformPermissionRequest((request) {request.grant();},);

苹果 wkwebview的内核 

如果开始有权限 流程正常,如果开始app没有权限 申请权限后 webview 依旧没有权限 需要退出app重新进才会有权限

原因解决:

之前在ios的info.plist 中声明的是这样的

	<key>NSCameraUsageDescription</key><string></string>

app内申请权限 依旧可以正常使用 并获取到,但是webview不行

<key>NSCameraUsageDescription</key>
<string>Konnect wants to use your camera, is that allowed?</string>

原因是<string></string> =》

对flutter app请求权限做了如下封装<string>Konnect wants to use your camera, is that allowed?</string>

权限虽然声明了 但是,没有说明权限的使用用途,这个描述提示会 展示在app申请权限的弹窗底部

加上了 就解决了 上面的问题:

注意:

ios权限声明 必须添加描述 权限使用来干什么  不然app上架会被拒绝。

NSCameraUsageDescription_camera usage d-CSDN博客

import 'dart:io';import 'package:app/common/util/k_log_util.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';class KPermiseeUtil {static final bool isIos = Platform.isIOS;static final bool isAndroid = Platform.isAndroid;// 检查权限 并做相应的处理static Future<bool> checkAndDoDefault(Permission permission) async {final status = await permission.status;KLogUtil.log(["status", status]);if (isIos) {switch (status) {case PermissionStatus.granted:return true;case PermissionStatus.permanentlyDenied:openAppSettings();return false;case PermissionStatus.denied:case PermissionStatus.limited:case PermissionStatus.restricted:default:final newStatus = await permission.request();if (newStatus == PermissionStatus.granted) {return true;}return false;}} else if (isAndroid) {switch (status) {case PermissionStatus.granted:return true;default:final newStatus = await permission.request();KLogUtil.log(["newStatus", newStatus]);if (newStatus == PermissionStatus.granted) {return true;} else if (newStatus == PermissionStatus.denied) {return false;} else if (newStatus == PermissionStatus.permanentlyDenied) {openAppSettings();return false;}return false;}}return await permission.status == PermissionStatus.granted;}static Future<bool> checkStatusAndDoDefault(PermissionState status, Permission permission) async {KLogUtil.log(["status", status]);if (isIos) {switch (status) {// notDetermined 未设置授权case PermissionState.notDetermined:return true;// 该应用程序未被授权访问照片库,用户也无法授予此类权限。case PermissionState.restricted:openAppSettings();return false;// 用户明确拒绝此应用程序访问照片库。case PermissionState.denied:// 用户明确授予此应用程序访问照片库的权限。case PermissionState.authorized:case PermissionState.limited:default:final newStatus = await permission.request();if (newStatus == PermissionStatus.granted) {return true;}return false;}} else if (isAndroid) {switch (status) {case PermissionStatus.granted:return true;default:final newStatus = await permission.request();KLogUtil.log(["newStatus", newStatus]);if (newStatus == PermissionStatus.granted) {return true;} else if (newStatus == PermissionStatus.denied) {return false;} else if (newStatus == PermissionStatus.permanentlyDenied) {openAppSettings();return false;}return false;}}return await permission.status == PermissionStatus.granted;}
}

最后自己的封装如下 (最新版本webview)

  webview_flutter: ^4.2.4

  webview_flutter_android: ^3.10.1

  webview_flutter_wkwebview: ^3.7.4

import 'dart:convert';
import 'dart:io';import 'package:app/common/theme/app_theme.dart';
import 'package:app/common/util/k_log_util.dart';
import 'package:app/entity/wallet/wallet_entity.dart';
import 'package:app/gen/assets.gen.dart';
import 'package:app/pages/widget/placeholder_widget.dart';
import 'package:app/pages/widget/top_appbar.dart';
import 'package:app/sql/wallet_sql/wallet_sql.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:webview_flutter/webview_flutter.dart';import 'placeholder_type.dart';
import 'package:webview_flutter_android/webview_flutter_android.dart';
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';// ignore: must_be_immutable
class CommonWebView extends StatefulWidget {final String title;final String url;bool isHiddenBar;CommonWebView({super.key,required this.title,required this.url,this.isHiddenBar = false,});@overrideState<CommonWebView> createState() => _CommonWebViewState();
}class _CommonWebViewState extends State<CommonWebView> {late double progress = 0.01; //H5加载进度值final double VISIBLE = 2;final double GONE = 0;late double progressHeight = GONE; //H5加载进度条高度late WebViewController controller;late final PlatformWebViewControllerCreationParams params;@overridevoid initState() {webViewInit();super.initState();}webViewInit() {if (WebViewPlatform.instance is WebKitWebViewPlatform) {params = WebKitWebViewControllerCreationParams(allowsInlineMediaPlayback: true,mediaTypesRequiringUserAction: const <PlaybackMediaTypes>{},);} else {params = const PlatformWebViewControllerCreationParams();}controller = WebViewController.fromPlatformCreationParams(params);if (controller.platform is AndroidWebViewController) {AndroidWebViewController.enableDebugging(true);(controller.platform as AndroidWebViewController).setMediaPlaybackRequiresUserGesture(false);}controller.platform.setOnPlatformPermissionRequest((request) {request.grant();},);controller..setJavaScriptMode(JavaScriptMode.unrestricted)..addJavaScriptChannel("konnect", onMessageReceived: onMessageReceived)..setBackgroundColor(AppTheme.themeColor_black)..enableZoom(true)..setNavigationDelegate(NavigationDelegate(onProgress: (int progress) {// Update loading bar.//加载H5页面时触发多次,progress值为0-100this.progress = progress.toDouble() / 100.0; //计算成0.0-1.0之间的值},onPageStarted: (String url) {//H5页面开始加载时触发setProgressVisible(GONE); //VISIBLE 显示加载进度条},onPageFinished: (String url) {//H5页面加载完成时触发setProgressVisible(GONE); //隐藏加载进度条},onWebResourceError: (WebResourceError error) {KLogUtil.log(["error", error]);if (error.isForMainFrame == true) {switch (error.errorType) {case WebResourceErrorType.badUrl:case WebResourceErrorType.timeout:break;case WebResourceErrorType.hostLookup:Get.off(() => const PageError404());default:break;}}},onNavigationRequest: (NavigationRequest request) {if (request.url.startsWith('https://www.youtube.com/')) {return NavigationDecision.prevent;}return NavigationDecision.navigate;},),)..loadRequest(Uri.parse(widget.url));}//显示或隐藏进度条void setProgressVisible(double isVisible) {setState(() {progressHeight = isVisible;});}// 接受h5发送来的数据onMessageReceived(message) async {//接收H5发过来的数据String sendMesStr = message.message;print("H5发过来的数据1: $sendMesStr");KLogUtil.log(["H5发过来的数据1", sendMesStr]);Map<String, dynamic> msg = json.decode(sendMesStr);int type = msg["type"] ?? -1;String method = msg["method"] ?? "";Map<String, dynamic> data = msg["data"] ?? {};if (type != -1) {switch (type) {case 1:break;case 2:break;case 3:default:break;}}if (method.isNotEmpty) {switch (method) {case "back":KLogUtil.log(["back", data, msg]);Get.back<Map<String, dynamic>>(result: data);break;case "getWalletAddress":WalletEntity? myWallet = await WalletSql.getDefaultWallet();if (myWallet != null) {String walletAddress = myWallet.walletAddress;String signature = myWallet.signature ?? "";controller.runJavaScript("appCallMethod('walletAddress','$walletAddress')");controller.runJavaScript("appCallMethod('signature','$signature')");KLogUtil.log(["获取到钱包", walletAddress, signature]);} else {KLogUtil.log(["没有获取到钱包"]);}default:break;}}}backPress() async {Get.back();return;// 有问题// String? cur = await _webControl.currentUrl();// KLogUtil.log(["backPress", initUrl, cur]);// if (initUrl == cur) {//   // 最后一页//   KLogUtil.log(["最后一页"]);//   Get.back();// }// if (await _webControl.canGoBack()) {//   _webControl.goBack(); //返回上个网页//   return false;// }// Get.back();}@overrideWidget build(BuildContext context) {return SafeArea(child: Scaffold(backgroundColor: AppTheme.themeColor_black,appBar: widget.isHiddenBar? null: WebViewTopAppBar(title: widget.title, backPress: backPress),body: WebViewWidget(controller: controller),),);}
}class PageError404 extends StatefulWidget {const PageError404({super.key});@overrideState<PageError404> createState() => _PageError404State();
}class WebViewTopAppBar extends PreferredSize {WebViewTopAppBar({key,required String title, //标题final Color? titleColor, //title颜色final Color? backgroundColor, //导航栏背景颜色final Widget? leadWidget, //左边按钮final PreferredSizeWidget? bottom, //底部组件final double? elevation, //AppBar底部阴影效果,0为无阴影。final bool statusBarColor = true, //控制状态栏的颜色true为灰色,false为白色final List<Widget>? rightActions, //右边按钮组final double? preferredSize, //状态栏高度final double bottomHeight = 0.0, //appBar底部高度final TextStyle? titleStyle,Function()? backPress, //返回按钮回调函数}) : super(key: key,preferredSize: Size.fromHeight(44.h),child: AppBar(title: Text(title,style: titleStyle ??TextStyle(fontSize: 16.sp,fontWeight: FontWeight.bold,color: titleColor ?? Colors.white,),),centerTitle: true,leading: leadWidget ??InkWell(onTap: backPress,child: Padding(padding: EdgeInsets.all(6.r),child: Assets.icon.arrowBack.image(),),),actions: rightActions,bottom: bottom,elevation: elevation ?? 0,// titleTextStyle: Theme.of(context).textTheme.bodySmall,backgroundColor: backgroundColor ?? AppTheme.themeColor_black,systemOverlayStyle: SystemUiOverlayStyle(statusBarColor: Colors.transparent,statusBarBrightness:Platform.isAndroid ? Brightness.light : Brightness.dark,statusBarIconBrightness:Platform.isAndroid ? Brightness.light : Brightness.dark,systemNavigationBarIconBrightness: Brightness.light,systemNavigationBarColor: AppTheme.themeColor_black,),),);
}class _PageError404State extends State<PageError404> {@overrideWidget build(BuildContext context) {return SafeArea(child: Scaffold(backgroundColor: AppTheme.themeColor_black,appBar: TopAppBar(context, "404"),body: PlaceholderWidget.emptyPlaceholder(placeholderType: PlaceholderType.notFound404),),);}
}

http://www.lryc.cn/news/180767.html

相关文章:

  • 【GESP考级C++】1级样题 闰年统计
  • CentOS密码重置
  • Tomcat Servlet
  • 国庆day2---select实现服务器并发
  • Grafana 开源了一款 eBPF 采集器 Beyla
  • 亲测可用国产GPT人工智能
  • 适配器模式详解和实现(设计模式 四)
  • IDEA的使用
  • CSS详细基础(二)文本样式
  • win10系统任务栏图标变成白色的解决办法
  • hadoop生态现状、介绍、部署
  • 二、EFCore 数据库表的创建和迁移
  • 在nodejs中使用typescript
  • 数据结构与算法基础(青岛大学-王卓)(8)
  • 【生物信息学】使用谱聚类(Spectral Clustering)算法进行聚类分析
  • CSS基础语法第二天
  • ThreeJS - 封装一个GLB模型展示组件(TypeScript)
  • HashMap面试题
  • Java编程技巧:swagger2、knif4j集成SpringBoot或者SpringCloud项目
  • 第三章:最新版零基础学习 PYTHON 教程(第九节 - Python 运算符—Python 中的除法运算符)
  • 【python】导出mysql数据,输出excel!
  • 【Java 进阶篇】JDBC ResultSet 遍历结果集详解
  • 华为数通方向HCIP-DataCom H12-831题库(单选题:161-180)
  • 【VsCode】SSH远程连接Linux服务器开发,搭配cpolar内网穿透实现公网访问
  • java并发编程 守护线程 用户线程 main
  • wxWidgets(1):在Ubuntu 环境中搭建wxWidgets 库环境,安装库和CodeBlocks的IDE,可以运行demo界面了,继续学习中
  • [VIM]VIM初步学习-3
  • RocketMQ Dashboard说解
  • 【RabbitMQ实战】05 RabbitMQ后台管理
  • PHP8中final关键字的应用-PHP8知识详解