child: InAppWebView(
initialUrlRequest: URLRequest(
url: Uri.parse("https://example.com"),
method: 'POST',
body: Uint8List.fromList(utf8.encode("name=FooBar")),
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
不幸的是,在Android上,POST请求将忽略headers属性,因为没有任何本地的API来加载带有headers的POST请求,如loadUrl 。这是因为本地方法android.webkit.WebView.postUrl是唯一可以发送这种类型请求的方法。
iOS<11.0版本对Cookies的支持有限
由于WKHTTPCookieStore只在iOS≥11.0的情况下可用,所以通过JavaScript对iOS<11.0的情况下增加了有限的支持,所以,你无法获取、创建或更改HttpOnly cookies。另外,对于session-only cookie,你需要使用新的iosBelow11WebViewController参数(如果你想使用的那个Cookie Manager方法可用)。
什么是UserScript?我们可以说UserScript类相当于WKUserScript的iOS原生类。
当我可以使用evaluateJavascript方法来注入我的JavaScript代码时,我为什么要使用它呢?嗯,是的,但是不对!UserScript为你提供了注入JavaScript代码的可能性。UserScript为您提供了在加载其他资源之前注入JavaScript的可能性,例如,将injectionTime属性设置为UserScriptInjectionTime.at_DOCUMENT_START(相当于WKUserScriptInjectionTime.atDocumentStart iOS原生属性)。
不过,我在这里应该为Android做一个精确的规定。我说的在iOS上是可以保证的,但在Android上就不行了(你知道iOS !=Android吗?🤷♂️)!
这是因为Android端不存在相应的原生类/功能,所以InAppWebView会尝试尽快注入所有的用户脚本。你可以把它想象成这样。
要管理UserScripts,你可以使用相应的方法,比如addUserScript、removeUserScript等。
从iOS 14.0+开始,WebKit引入了WKContentWorlds的概念,它是一个为JavaScript代码定义执行范围的对象,你用它来防止不同脚本之间的冲突。
这个概念也被引入到flutter_inappwebview插件中,新的,你猜对了,ContentWorld类。
但是,就像我之前说的,iOS !=Android,而且,在Android上,这个概念并不原生存在。所以,就用<iframe>
HTML元素的用法来实现。
你可能会问,为什么我不使用类似LiquidCore库或者类似JavaScriptCore iOS框架的东西来实现它。
使用这些库/框架的问题是,您当然不能访问当前网页的窗口或文档 JavaScript 对象。
相反,使用 WKContentWorld,您可以访问这些对象,因此,您可以与网页本身进行交互。
在 Android 上使用 iframes,您可以创建一个新的 JavaScript 上下文,而不与网页的主 JavaScript 上下文冲突(例如,您可以拥有 2 个同名的变量,因为它们存在于 2 个不同的上下文/内容世界中),并实现这种内容世界,如在 iOS 上。所以,这个插件会创建一个id属性等于flutter_inappwebview_[Content World Name HERE]的<iframe>
并附加到只包含内联脚本的网页内容上,以便为JavaScript代码定义一个新的执行范围。
显然,这有一些限制/缺点。
对于任何ContentWorld,除了ContentWorld.PAGE(即网页本身),如果你需要访问window或document全局对象,你需要使用window.top和window.top.document,因为代码在iframe内运行。
内联脚本的执行可能会被Content-Security-Policy头所阻止。
一个简单的例子。
child: InAppWebView(
initialUrlRequest: URLRequest(
url: Uri.parse("https://flutter.dev"),
onLoadStop: (controller, url) async {
await controller.evaluateJavascript(source: "var foo = 49;");
await controller.evaluateJavascript(source: "var bar = 19;",
contentWorld: ContentWorld.PAGE);
print(await controller.evaluateJavascript(source: "foo + bar;"));
print(await controller.evaluateJavascript(source: "bar;",
contentWorld: ContentWorld.DEFAULT_CLIENT));
await controller.evaluateJavascript(source: "var bar = 2;",
contentWorld: ContentWorld.DEFAULT_CLIENT);
print(await controller.evaluateJavascript(source: "bar;",
contentWorld: ContentWorld.DEFAULT_CLIENT));
if (Platform.isIOS) {
await controller.evaluateJavascript(
source: "document.body.innerHTML = 'LOL';",
contentWorld: ContentWorld.world(name: "MyWorld"));
} else {
await controller.evaluateJavascript(
source: "window.top.document.body.innerHTML = 'LOL';",
contentWorld: ContentWorld.world(name: "MyWorld"));
onConsoleMessage: (controller, consoleMessage) {
print(consoleMessage);
这个例子的证明就留给读者吧。
苹果支付API
iOS 13.0+新增了一个WebView选项,以启用JavaScript Apple Pay API:applePayAPIEnabled。正如Safari 13官方发布说明中写的那样,如果使用了任何脚本注入API(如evaluJavascript或UserScript),它将无法工作。
所以,当这个属性为真时,iOS上所有使用JavaScript实现的方法、选项和事件都不会被调用或不会做任何事情,结果永远是空的,但是,嘿,你会从你的用户那里得到报酬🤑(这很好,不是吗?
查看flutter_inappwebview官方文档,了解受此影响的API的完整列表!
评估Async JavaScript代码
从iOS 14.0+开始,WebKit增加了新的callAsyncJavaScript方法,该方法允许将源代码作为异步JavaScript函数执行(参见MDN - async函数)。这个方法已经在Android和iOS平台上实现。
在iOS上,你可以从iOS 10.3+开始使用这个函数,因为正如这里所说:async函数 浏览器兼容性,从该版本开始应该已经支持async函数。因此,在iOS版本的[10.3, 14.0)
范围内,使用evaluateJavascript方法,并使用包含标识符和回调的Map来实现,回调将在异步函数执行结束时调用返回的结果。在Android上,它也是以同样的方式实现的!
返回类型与evaluateJavascript方法不同,而是一个CallAsyncJavaScriptResult实例,其中value属性包含成功值(如果有的话),error属性包含一个代表失败值(如果有的话)的String。
下面是一个例子。
child: InAppWebView(
initialUrlRequest: URLRequest(
url: Uri.parse("https://flutter.dev"),
onLoadStop: (controller, url) async {
final String functionBody = """
var p = new Promise(function (resolve, reject) {
window.setTimeout(function() {
if (x >= 0) {
resolve(x);
} else {
reject(y);
}, 1000);
await p;
return p;
""";
var result = await controller.callAsyncJavaScript(
functionBody: functionBody,
arguments: {'x': 49, 'y': 'error message'});
print(result);
result = await controller.callAsyncJavaScript(
functionBody: functionBody,
arguments: {'x': -49, 'y': 'error message'});
print(result);
这将打印{value.49, error: null}和{value: null, error: error: null}。49, error: null}和{value: null, error: "错误信息"}。
服务工作者API
在Android上,AndroidServiceWorkerController和AndroidServiceWorkerClient类可以用来拦截请求。在使用这些类或它们的方法之前,你应该检查你要使用的服务工作者功能是否被支持,例如。
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
if (Platform.isAndroid) {
await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
var swAvailable = await AndroidWebViewFeature.isFeatureSupported(AndroidWebViewFeature.SERVICE_WORKER_BASIC_USAGE);
var swInterceptAvailable = await AndroidWebViewFeature.isFeatureSupported(AndroidWebViewFeature.SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST);
if (swAvailable && swInterceptAvailable) {
AndroidServiceWorkerController serviceWorkerController = AndroidServiceWorkerController.instance();
serviceWorkerController.serviceWorkerClient = AndroidServiceWorkerClient(
shouldInterceptRequest: (request) async {
print(request);
return null;
runApp(MyApp());
相反,在iOS上,从iOS 14.0+开始,JavaScript Service Worker API是可用的。
要在iOS上启用这个JavaScript API,只有2种方法。
使用 "App-Bound Domains"
您的应用程序建议自己作为一个可能的 "默认浏览器",如iOS Safari或Google Chrome。
App-Bound Domains:请阅读 WebKit - App-Bound Domains 一文了解详情。你可以使用新的Info.plist键WKAppBoundDomains来指定最多10个 "应用绑定 "域,例如。
<key>WKAppBoundDomains</key>
<array>
<string>flutter.dev</string>
<string>github.com</string>
</array>
</dict>
之后,你需要将limitsNavigationsToAppBoundDomains iOS特定的WebView选项设置为true,例如。
InAppWebViewGroupOptions(
ios: IOSInAppWebViewOptions(
limitsNavigationsToAppBoundDomains: true
iOS默认浏览器:请阅读准备你的应用程序成为默认浏览器或电子邮件客户端的文章,了解详情。
网络消息通道和网络消息监听器
除了JavaScript处理程序之外,还有两种新的方式与JavaScript进行通信。
Web消息通道:它们是HTML5消息通道的代表。更多细节请参见 Channel Messaging API。要创建Web消息通道,你需要使用InAppWebViewController.createWebMessageChannel方法。该方法应在页面加载时调用,例如,当WebView.onLoadStop事件被触发时。
Web消息监听器。它允许在WebMessageListener监听的每个框架中注入一个JavaScript对象。要添加一个Web消息监听器,你需要使用InAppWebViewController.addWebMessageListener方法。这个方法应该在使用它的网页被加载之前被调用,例如当WebView.onWebViewCreated事件被触发时。
这个插件的官方网站已经发布在inappwebview.dev上了!
所有在repository的README.md上写的初始设置和配置都将被移到那里。我将添加一个入门部分和其他文档来帮助你开始使用它!
另外,它还包含一个展示部分,有一个用Flutter和Flutter InAppWebView构建的应用程序的开放列表。目前,因为网站是新的,所以只有一个App,那就是Flutter浏览器App。
你是否在使用这个插件?通过提交App页面提交你的App,并按照说明操作吧!
查看CHANGELOG.md文件,查看更改和修复的完整列表!
今天就到这里吧!
我要感谢所有以任何方式支持这个项目的人! 非常感谢大家的支持! 💙