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.xdnf.cn/news/147436.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

德国自动驾驶卡车公司【Fernride】完成1900万美元A轮融资

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于德国沃尔夫斯堡的自动驾驶卡车公司【Fernride】今日宣布已完成1900万美元A轮融资&#xff0c;本轮融资完成后Fernride的融资金额已经达到了达到5000万美元。 本轮融资由Deep Tech and Cli…

推荐算法——Apriori算法原理

0、前言&#xff1a; 首先名字别读错&#xff1a;an pu ruo ao rui 【拼音发音】Apriori是一种推荐算法推荐系统&#xff1a;从海量数据中&#xff0c;帮助用户进行信息的过滤和选择。主要推荐方法有&#xff1a;基于内容的推荐、协同过滤推荐、基于关联规则的推荐、基于知识的…

多线程(pthread库)

POSIX线程库 引言 前面我们提到了Linux中并无真正意义上的线程 从OS角度来看&#xff0c;这意味着它并不会提供直接创建线程的系统调用&#xff0c;它最多给我们提供创建轻量级进程LWP的接口 但是从用户的角度来看&#xff0c;用户只认识线程啊&#xff01; 因此&#xff0c;…

wxWidgets(1):在Ubuntu 环境中搭建wxWidgets 库环境,安装库和CodeBlocks的IDE,可以运行demo界面了,继续学习中

1&#xff0c;选择使用 wxWidgets 框架 选择这个主要是因为完全的开源&#xff0c;不想折腾 Qt的库&#xff0c;而且打包的文件比较大。 网络上面有很多的对比&#xff0c;而且使用QT的人比较多。 但是我觉得wxwidgets 更加偏向 c 语法本身&#xff0c;也有助学习C。 没有太多…

【算法分析与设计】回溯法(上)

目录 一、学习要点1.1 回溯法1.2 问题的解空间1.3 0-1背包问题的解空间1.4 旅行售货员问题的解空间1.5 生成问题状态的基本方法 二、回溯法的基本思想三、回溯算法的适用条件四、递归回溯五、迭代回溯六、子集树与排列树七、装载问题八、批处理作业调度问题 一、学习要点 理解回…

Kotlin前置检测判断check,require,requireNotNull

Kotlin前置检测判断check&#xff0c;require&#xff0c;requireNotNull &#xff08;1&#xff09;check fun main(args: Array<String>) {val b falsecheck(b) {println("check $b")}println("end") } check监测到值非真时候&#xff0c;抛出一…

【数据结构与算法】通过双向链表和HashMap实现LRU缓存 详解

这个双向链表采用的是有伪头节点和伪尾节点的 与上一篇文章中单链表的实现不同&#xff0c;区别于在实例化这个链表时就初始化了的伪头节点和伪尾节点&#xff0c;并相互指向&#xff0c;在第一次添加节点时&#xff0c;不需要再考虑空指针指向问题了。 /*** 通过链表与HashMa…

Python 无废话-基础知识元组Tuple详讲

“元组 Tuple”是一个有序、不可变的序列集合&#xff0c;元组的元素可以包含任意类型的数据&#xff0c;如整数、浮点数、字符串等&#xff0c;用()表示&#xff0c;如下示例&#xff1a; 元组特征 1) 元组中的各个元素&#xff0c;可以具有不相同的数据类型&#xff0c;如 T…

Python-Flask:编写自动化连接demo脚本:v1.0.0

主函数&#xff1a; # _*_ Coding : UTF-8 _*_ # Time : 13:14 # Author : YYZ # File : Flask # Project : Python_Project_爬虫 import jsonfrom flask import Flask,request,jsonify import sshapi Flask(__name__)# methods: 指定请求方式 接口解析参数host host_info[…

05. 机器学习入门 - 动态规划

文章目录 从一个案例开始动态规划 Hi, 你好。我是茶桁。 咱们之前的课程就给大家讲了什么是人工智能&#xff0c;也说了每个人的定义都不太一样。关于人工智能的不同观点和方法&#xff0c;其实是一个很复杂的领域&#xff0c;我们无法用一个或者两个概念确定什么是人工智能&a…

在visual studio里配置Qt插件并运行Qt工程

Qt插件&#xff0c;也叫qt-vsaddin&#xff0c;它以*.vsix后缀名结尾。从visual studio 2010版本开始&#xff0c;VS支持Qt框架的开发&#xff0c;Qt以插件方式集成到VS里。这里在visual studio 2019里配置Qt 5.14.2插件&#xff0c;并配置Qt环境。 1 下载VS2019 下载VS2019,官…

跟着顶级科研报告IPCC学绘图:温度折线/柱图/条带/双y轴

复现IPCC气候变化过程图 引言 升温条带Warming stripes&#xff08;有时称为气候条带&#xff0c;目前尚无合适且统一的中文释义&#xff09;是数据可视化图形&#xff0c;使用一系列按时间顺序排列的彩色条纹来视觉化描绘长期温度趋势。 在IPCC报告中经常使用这一方案 IPCC是…

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石④

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石④ 第十九章 驱动程序基石④19.7 工作队列19.7.1 内核函数19.7.1.1 定义 work19.7.1.2 使用 work&#xff1a;schedule_work19.7.1.3 其他函数 19.7.2 编程、上机19.7.3 内部机制19.7.3.1 Linux 2.x的工作队列创建过程19.7.3…

BASH shell脚本篇2——条件命令

这篇文章介绍下BASH shell中的条件相关的命令&#xff0c;包括&#xff1a;if, case, while, until, for, break, continue。之前有介绍过shell的其它基本命令&#xff0c;请参考&#xff1a;BASH shell脚本篇1——基本命令 1. If语句 if语句用于在顺序执行语句的流程中执行条…

八大排序(三)堆排序,计数排序,归并排序

一、堆排序 什么是堆排序&#xff1a;堆排序&#xff08;Heap Sort&#xff09;就是对直接选择排序的一种改进。此话怎讲呢&#xff1f;直接选择排序在待排序的n个数中进行n-1次比较选出最大或者最小的&#xff0c;但是在选出最大或者最小的数后&#xff0c;并没有对原来的序列…

Python无废话-办公自动化Excel修改数据

如何修改Excel 符合条件的数据&#xff1f;用Python 几行代码搞定。 需求&#xff1a;将销售明细表的产品名称为PG手机、HW手机、HW电脑的零售价格分别修改为4500、5500、7500&#xff0c;并保存Excel文件。如下图 Python 修改Excel 数据&#xff0c;常见步骤&#xff1a; 1&…

docker 基本操作

目录 一、docker 概述 二、容器 2.1容器的特性 2.2namespace的六项隔离 三、docker与虚拟机的区别 四、Docker核心概念 五、docker 基本操作命令 镜像操作 1、搜索镜像 2、获取镜像 3、查看镜像信息 ​编辑 4、查看下载的镜像文件信息 5、查看下载到本地的所有镜…

搭建智能桥梁,Amazon CodeWhisperer助您轻松编程

零&#xff1a;前言 随着时间的推移&#xff0c;人工智能技术以惊人的速度向前发展&#xff0c;正掀起着全新的编程范式革命。不仅仅局限于代码生成&#xff0c;智能编程助手等创新应用也进一步提升了开发效率和代码质量&#xff0c;极大地推动着软件开发领域的快速繁荣。 当前…

SpringCloud(一)Eureka、Nacos、Feign、Gateway

文章目录 概述微服务技术对比 Eureka服务远程调用服务提供者和消费者Eureka注册中心搭建注册中心服务注册服务发现Ribbon负载均衡负载均衡策略饥饿加载 NacosNacos与Eureka对比Nacos服务注册Nacos服务分集群存储NacosRule负载均衡服务实例权重设置环境隔离 Nacos配置管理配置热…

用于自然语言处理的 Python:理解文本数据

一、说明 Python是一种功能强大的编程语言&#xff0c;在自然语言处理&#xff08;NLP&#xff09;领域获得了极大的普及。凭借其丰富的库集&#xff0c;Python 为处理和分析文本数据提供了一个全面的生态系统。在本文中&#xff0c;我们将介绍 Python for NLP 的一些基础知识&…