在阅读前需要了解
android Webview的使用以及和js的交互, 见网页
flutter的plugin开发流程, 见网页
flutter中使用原生视图的流程, 见网页
这是官网提供的主要类, 本文只讲第一个InAppWebView 以及 js交互
下图是插件中InAppWebView类在dart端和android端的关系 (InAppWebView后面简称为IAwebview) 下图是我自己画的 下图来自参考文章1
dart端主要类解读
这里有官方提供的使用示例
// dart语言, csdn没有高亮
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:url_launcher/url_launcher.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
if (Platform.isAndroid) {
await AndroidInAppWebViewController.setWebContentsDebuggingEnabled(true);
}
runApp(MaterialApp(
home: new MyApp()
));
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State
final GlobalKey webViewKey = GlobalKey();
InAppWebViewController? webViewController;
InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
useShouldOverrideUrlLoading: true,
mediaPlaybackRequiresUserGesture: false,
),
android: AndroidInAppWebViewOptions(
useHybridComposition: true,
),
ios: IOSInAppWebViewOptions(
allowsInlineMediaPlayback: true,
));
late PullToRefreshController pullToRefreshController;
String url = "";
double progress = 0;
final urlController = TextEditingController();
@override
void initState() {
super.initState();
pullToRefreshController = PullToRefreshController(
options: PullToRefreshOptions(
color: Colors.blue,
),
onRefresh: () async {
if (Platform.isAndroid) {
webViewController?.reload();
} else if (Platform.isIOS) {
webViewController?.loadUrl(
urlRequest: URLRequest(url: await webViewController?.getUrl()));
}
},
);
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Official InAppWebView website")),
body: SafeArea(
child: Column(children:
TextField(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search)
),
controller: urlController,
keyboardType: TextInputType.url,
onSubmitted: (value) {
var url = Uri.parse(value);
if (url.scheme.isEmpty) {
url = Uri.parse("https://www.google.com/search?q=" + value);
}
webViewController?.loadUrl(
urlRequest: URLRequest(url: url));
},
),
Expanded(
child: Stack(
children: [
InAppWebView(
key: webViewKey,
initialUrlRequest:
URLRequest(url: Uri.parse("https://inappwebview.dev/")),
initialOptions: options,
pullToRefreshController: pullToRefreshController,
onWebViewCreated: (controller) {
webViewController = controller;
},
onLoadStart: (controller, url) {
setState(() {
this.url = url.toString();
urlController.text = this.url;
});
},
androidOnPermissionRequest: (controller, origin, resources) async {
return PermissionRequestResponse(
resources: resources,
action: PermissionRequestResponseAction.GRANT);
},
shouldOverrideUrlLoading: (controller, navigationAction) async {
var uri = navigationAction.request.url!;
if (![ "http", "https", "file", "chrome",
"data", "javascript", "about"].contains(uri.scheme)) {
if (await canLaunch(url)) {
// Launch the App
await launch(
url,
);
// and cancel the request
return NavigationActionPolicy.CANCEL;
}
}
return NavigationActionPolicy.ALLOW;
},
onLoadStop: (controller, url) async {
pullToRefreshController.endRefreshing();
setState(() {
this.url = url.toString();
urlController.text = this.url;
});
},
onLoadError: (controller, url, code, message) {
pullToRefreshController.endRefreshing();
},
onProgressChanged: (controller, progress) {
if (progress == 100) {
pullToRefreshController.endRefreshing();
}
setState(() {
this.progress = progress / 100;
urlController.text = this.url;
});
},
onUpdateVisitedHistory: (controller, url, androidIsReload) {
setState(() {
this.url = url.toString();
urlController.text = this.url;
});
},
onConsoleMessage: (controller, consoleMessage) {
print(consoleMessage);
},
),
progress < 1.0
? LinearProgressIndicator(value: progress)
: Container(),
],
),
),
ButtonBar(
alignment: MainAxisAlignment.center,
children:
ElevatedButton(
child: Icon(Icons.arrow_back),
onPressed: () {
webViewController?.goBack();
},
),
ElevatedButton(
child: Icon(Icons.arrow_forward),
onPressed: () {
webViewController?.goForward();
},
),
ElevatedButton(
child: Icon(Icons.refresh),
onPressed: () {
webViewController?.reload();
},
),
],
),
]))
);
}
}
InAppWebView类
实际上是将原生的android视图嵌入到flutter中 即将原生的webview封装成widget Flutter 支持两种集成模式:虚拟显示模式 (Virtual displays) 和混合集成模式 (Hybrid composition) 。
InAppWebView默认使用第一种 也可以设置widget的参数来指定使用哪一种 源码判断模式的代码: useHybridComposition =widget.initialOptions?.android.useHybridComposition ?? false;
其构造函数中可以传入很多回调函数, 并在其中获取到类实例对应的InAppWebViewController 实例
InAppWebViewController 类
此类可以控制 InAppWebView
相当于是android原生webview的方法和Setings, WebViewClient 以及WebChromeClient的方法加起来
实际上使用此类时其方法还是通过方法通道 去调用原生的extends WebView的InAppWebView的方法和这三个类的方法 同时也将原生webview的周期回调函数通过方法通道接收和处理, 处理中调用使用者传入的函数
android端主要类解读
插件类的源码入口就在继承了FlutterPlugin的类中
public class InAppWebViewFlutterPlugin implements FlutterPlugin, ActivityAware {
@Override
public void onAttachedToEngine(FlutterPluginBinding binding) {
this.flutterAssets = binding.getFlutterAssets();
// 在其中注册webviewFactory
onAttachedToEngine(
binding.getApplicationContext(), binding.getBinaryMessenger(), this.activity, binding.getPlatformViewRegistry(), null);
}
//-----next class--------------------
// webviewFactory的create()返回一个flutterWebView对象
public class FlutterWebView implements PlatformWebView {
// 构造函数中初始化了 channel
// 此channel在dart接口中被in_app_webview_controller使用
channel = new MethodChannel(plugin.messenger, "com.pichillilorenzo/flutter_inappwebview_" + id);
// 设置其方法调用的handler
methodCallDelegate = new InAppWebViewMethodHandler(webView);
channel.setMethodCallHandler(methodCallDelegate);
//-----next class--------------------
// ❗ ❗ ❗ ❗ ❗ ❗ methodChannel的handler, 重要代码
// 此handler重写了onMethodCall(), 有600行代码, 100个case来处理来自flutter端不同的方法, 通过这些方法对webview进行控制
public class InAppWebViewMethodHandler implements MethodChannel.MethodCallHandler {
// flutterWebView的构造函数中初始化了一个InAppWebView, 调用其prepare()
// InAppWebView 继承 InputAwareWebview, InputAwareWebview 继承原生的webview
final public class InAppWebView extends InputAwareWebView implements InAppWebViewInterface {
// prepare()中设置了webview的webChromeClient, webViewClient, websettings
// 其中还设置了 userContentController
public void prepare() {
httpClient = new OkHttpClient().newBuilder().build();
// 这里添加了一个java对象映射到webview中
javaScriptBridgeInterface = new JavaScriptBridgeInterface(this);
/*
对象在js中的名字 JAVASCRIPT_BRIDGE_NAME = "flutter_inappwebview";
对象的关键方法 : public void _callHandler(final String handlerName, final String _callHandlerID, final String args)
虽然不返回值, 但是在内部会根据handlername 去调用不同的函数或者通过方法通道传给flutter去hanle
然后callAsyncJavaScriptCallbacks()返回需要返回的值
*/
addJavascriptInterface(javaScriptBridgeInterface, JavaScriptBridgeJS.JAVASCRIPT_BRIDGE_NAME);
inAppWebViewChromeClient = new InAppWebViewChromeClient(plugin, channel, inAppBrowserDelegate);
setWebChromeClient(inAppWebViewChromeClient);
inAppWebViewClient = new InAppWebViewClient(channel, inAppBrowserDelegate);
setWebViewClient(inAppWebViewClient);
具体功能实现的源码
IAwebview如何加载url
// 仍然是通过InAppWebViewController控制
InAppWebViewController? webViewController;
// ----分割线-------------
webViewController?.loadUrl(urlRequest: urlRequest);
// ----分割线-------------
Future
{
required URLRequest urlRequest,
@Deprecated('Use `allowingReadAccessTo` instead')
Uri? iosAllowingReadAccessTo,
Uri? allowingReadAccessTo}) async {
Map
};
await _channel.invokeMethod('loadUrl', args);
// 原生端methodHandler的方法处理
case "loadUrl":
if (webView != null) {
Map
webView.loadUrl(URLRequest.fromMap(urlRequest));
}
// IAwebview根据urlRequest的不同调用原生webview不同的loadurl()
public void loadUrl(URLRequest urlRequest) {
String url = urlRequest.getUrl();
String method = urlRequest.getMethod();
// request的方法为post
if (method != null && method.equals("POST")) {
byte[] postData = urlRequest.getBody();
postUrl(url, postData);
return;
}
//
Map
if (headers != null) {
loadUrl(url, headers);
return;
精彩文章
发表评论