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