Flutter 中加载 web 内容的方式探索
Flutter 渲染主要通过通过三种方式,第一种是自带的原生 widget ,第二种是将 html 等转为 widget 再做渲染(类似 webf 等的自渲染框架),第三种是直接使用 webview 渲染。
Widget
因为是加载已有的业务内容,所以这种方式就不说了,而且也没啥可说的。
webf 框架
首先注意 webf 的版本和 flutter 的版本是相关的(此处使用 flutter 3.13.9
,webf 0.16.0
),根据 webf 的版本选择相应版本的 flutter(反之同理) ,参照文档 中的步骤进行初始化,完整 demo (完整 demo)如下。
/*
* Copyright (C) 2019-2022 The Kraken authors. All rights reserved.
* Copyright (C) 2022-present The WebF authors. All rights reserved.
*/
import 'package:flutter/material.dart';
import 'package:webf/webf.dart';
import 'package:webf/devtools.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Kraken Browser',
// theme: ThemeData.dark(),
debugShowCheckedModeBanner: false,
home: FirstPage(title: 'Landing Bay'),
);
}
}
class FirstPage extends StatefulWidget {
const FirstPage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<StatefulWidget> createState() {
return FirstPageState();
}
}
class FirstPageState extends State<FirstPage> {
late WebFController controller;
@override
void didChangeDependencies() {
super.didChangeDependencies();
controller = WebFController(
context,
// devToolsService: ChromeDevToolsService(),
viewportWidth: 200,
viewportHeight: 200,
onLoad: (controller) {
print('onLoad');
},
onDOMContentLoaded: (controller) {
print('onDOMContentLoaded');
},
onLoadError: (flutterErr, stactTrace) {
print("onLoadError $flutterErr $stactTrace");
},
onJSError: (jserror) {
print('onJSError $jserror');
},
);
print('controller.preload');
controller.preload(WebFBundle.fromUrl('http://127.0.0.1:8080/'));
}
@override
void dispose() {
super.dispose();
controller.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return WebFDemo(controller: controller);
}));
},
child: const Text('Open WebF Page'),
),
),
);
}
}
class WebFDemo extends StatelessWidget {
final WebFController controller;
WebFDemo({required this.controller});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('WebF Demo'),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: WebF(controller: controller),
));
}
}
问题
- macos 上运行报错
[ERROR:flutter/lib/ui/ui_dart_state.cc(199)] Unhandled Exception: SocketException: Connection failed (OS Error: Operation not permitted, errno = 1), address = 100.65.182.70, port = 6000
,原因是没有开启网络权限。在macos/Runner/DebugProfile.entitlements
和文件中添加以下代码即可。Release.entitlements 文件也添加一下,不然打出的包也是会有这个问题的
<key>com.apple.security.network.client</key>
<true/>
pub.dev 上的 readme 不如文档中新,优先看官网文档
web 项目需要是 vue-cli 创建的(示例步骤仔细读就好了,tips 也很重要
体验
新启的项目估计可以使用,老项目想直接接入估计有点麻烦,尝试稍复杂些的页面就会有很多不支持的 api 报错,具体可能还得看 case
webview
通过两个插件 window_manager 和 desktop_webview_window 配合使用,前者作用是管理窗口,主要作用是将 flutter 初始化的窗口隐藏,后者作用是管理 webview,包括加载、隐藏、展示等。通过 window_manager 配置窗口在启动时隐藏,然后初始化 webview 并加载 url ,只是 webview 的方法中没有提供 show和 hide 的方法,显示&隐藏只能通过 reload 和 close 方法
另外 webview 目前未暴露可以将状态栏去掉的配置。
做的最好的插件个人认为是 flutter_inappwebview ,api 丰富,但是它只支持了 macos 上的 inappbrowser ,它在移动端上支持的 inappwebview 是可以做到嵌入 widget 的,这种嵌入的效果是我们比较需要的,可惜不能支持
webview_cef 支持两个平台,但是文档显示还在开发中
问题
desktop_webview_window 在 windows 上使用的是 webview2(基于chromium) ,macos 上使用的是 wkwebview ,和 electron 现有的chromium 略有差异,可能会有一些兼容问题