开头
Flutter官方的WebView插件webview_flutter
,提供了WebView控件,使得我们能够像原生一样嵌入网页。那问题来了,Flutter是如何实现的WebView功能的,是纯Dart代码硬撸了一套,还是借用了原生的WebView控件?如果是重新写一套,那实在是令人钦佩,代码量还是很巨大的。实际上,Flutter也不傻,有现成的原生控件,干嘛不用呢,Flutter端的WebView本质上还是使用的原生WebView控件。可仔细想想,原生控件如何能嵌入到Flutter Widget Tree里呢?顿时,感觉小脑瓜嗡嗡的,想不通,只能去翻翻源码了。
WebView的Flutter端
首先,WebView继承自StatefulWidget,主要看_WebViewState的build方法:
1
2
3
4
5
6
7
8
9
Widget build(BuildContext context) {
return WebView.platform.build(
context: context,
onWebViewPlatformCreated: _onWebViewPlatformCreated,
webViewPlatformCallbacksHandler: _platformCallbacksHandler,
gestureRecognizers: widget.gestureRecognizers,
creationParams: _creationParamsfromWidget(widget),
);
}
而WebView.platform
的实现是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static WebViewPlatform get platform {
if (_platform == null) {
switch (defaultTargetPlatform) {
case TargetPlatform.android:
_platform = AndroidWebView();
break;
case TargetPlatform.iOS:
_platform = CupertinoWebView();
break;
default:
throw UnsupportedError(
"Trying to use the default webview implementation for $defaultTargetPlatform but there isn't a default one");
}
}
return _platform;
}
在不同的平台上,有各自的实现。这也不难理解,android/iOS端的WebView控件肯定是有差异的。下面,主要以AndroidWebView来展开。
作为所有平台WebView具体实现的基类WebViewPlatform
,它的主要要求是:
1
2
3
4
5
6
7
8
9
10
11
abstract class WebViewPlatform {
Widget build({
BuildContext context,
CreationParams creationParams,
@required WebViewPlatformCallbacksHandler webViewPlatformCallbacksHandler,
WebViewPlatformCreatedCallback onWebViewPlatformCreated,
Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
});
....
}
意思就是:我呢,不管你是啥啥平台,都给我build一个widget出来,其他的我不管。。。
android二话不说,立马给整了一个AndroidWebView:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class AndroidWebView implements WebViewPlatform {
@override
Widget build({
...
}) {
return GestureDetector(
onLongPress: () {},
excludeFromSemantics: true,
child: AndroidView(
viewType: 'plugins.flutter.io/webview',
...
),
);
}
...
}
省略不相干的细节后,AndroidWebView build出来的,主要是AndroidView,它有个viewType的参数(这可是个关键参数),看看AndroidView:
它也是继承自StatefulWidget,主要看_AndroidViewState的build方法:
1
2
3
4
5
6
7
8
9
10
11
@override
Widget build(BuildContext context) {
return Focus(
...
child: _AndroidPlatformView(
controller: _controller,
hitTestBehavior: widget.hitTestBehavior,
gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
),
);
}
这里面涉及到一个重要的角色AndroidViewController _controller
,先跳过,后面在具体讲。
看_AndroidPlatformView:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class _AndroidPlatformView extends LeafRenderObjectWidget {
...
@override
RenderObject createRenderObject(BuildContext context) =>
RenderAndroidView(
viewController: controller,
hitTestBehavior: hitTestBehavior,
gestureRecognizers: gestureRecognizers,
);
...
}
_AndroidPlatformView本质上是RenderObjectWidget,它的核心是RenderAndroidView这个RenderObject。来看看这个RenderAndroidView:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class RenderAndroidView extends RenderBox with _PlatformViewGestureMixin {
RenderAndroidView({
@required AndroidViewController viewController,
@required PlatformViewHitTestBehavior hitTestBehavior,
@required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
}) : assert(viewController != null),
assert(hitTestBehavior != null),
assert(gestureRecognizers != null),
_viewController = viewController {
_viewController.pointTransformer = (Offset offset) => globalToLocal(offset);
updateGestureRecognizers(gestureRecognizers);
_viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated);
this.hitTestBehavior = hitTestBehavior;
}
...
@override
void performResize() {
size = constraints.biggest;
_sizePlatformView();
}
Future<void> _sizePlatformView() async {
...
do {
targetSize = size;
await _viewController.setSize(targetSize);
} while (size != targetSize);
...
}
void _paintTexture(PaintingContext context, Offset offset) {
context.addLayer(TextureLayer(
rect: offset & _currentAndroidViewSize,
textureId: _viewController.textureId,
freeze: _state == _PlatformViewState.resizing,
));
}
}
这里面,主要关注三个点:
- 在实例化时,传入了
AndroidViewController
。这个controller就是上面_AndroidViewState中创建的controller。 - 在
performReSize
时,设置了_viewController.setSize(targetSize)
- 在绘制时,绘制的是
_viewController.textureId
纹理。
着重关注一下第三点,RenderAndroidView最后是绘制的AndroidViewController中的纹理。大胆猜测一下,这个纹理中的内容就是原生的WebView控件,后面通过插件的原生端源码来一探究竟。
现在回过头来看看这个AndroidViewController, 它是在_AndroidViewState中出现的,来看看它是如何产生的?
下面整理了一个_AndroidViewState的部分代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class _AndroidViewState extends State<AndroidView> {
int _id;
AndroidViewController _controller;
bool _initialized = false;
@override
Widget build(BuildContext context) {
return Focus(
focusNode: _focusNode,
onFocusChange: _onFocusChange,
child: _AndroidPlatformView(
controller: _controller,
hitTestBehavior: widget.hitTestBehavior,
gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet,
),
);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
...
_initializeOnce();
...
}
void _initializeOnce() {
if (_initialized) {
return;
}
_initialized = true;
_createNewAndroidView();
...
}
void _createNewAndroidView() {
_id = platformViewsRegistry.getNextPlatformViewId();
_controller = PlatformViewsService.initAndroidView(
id: _id,
viewType: widget.viewType,
layoutDirection: _layoutDirection,
creationParams: widget.creationParams,
creationParamsCodec: widget.creationParamsCodec,
onFocus: () {
_focusNode.requestFocus();
},
);
...
}
}
从上面的源码,可以看出:
- AndroidViewController实例是在页面刚加载时创建的,didChangeDependencies ->_initializeOnce ->_createNewAndroidView.
- AndroidViewController有两个重要参数:id, viewType.
- id是通过
platformViewsRegistry.getNextPlatformViewId()
创建的,它是一个 全局唯一的递增的值。 - viewType是AndroidWebView在build AndroidView时传入的,具体值:
plugins.flutter.io/webview
- id是通过
- PlatformViewsService.initAndroidView创建的是TextureAndroidViewController,是AndroidViewController的子类。
而TextureAndroidViewController这个类里面也隐藏了一些信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class TextureAndroidViewController extends AndroidViewController {
...
@override
Future<void> setSize(Size size) async {
...
if (_state == _AndroidViewState.waitingForSize) {
_size = size;
return create();
}
...
}
@override
Future<void> create() => super.create();
@override
Future<void> _sendCreateMessage() async {
assert(_size != null && !_size.isEmpty,
'trying to create $TextureAndroidViewController without setting a valid size.');
final Map<String, dynamic> args = <String, dynamic>{
'id': viewId,
'viewType': _viewType,
'width': _size.width,
'height': _size.height,
'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
};
if (_creationParams != null) {
final ByteData paramsByteData = _creationParamsCodec.encodeMessage(_creationParams);
args['params'] = Uint8List.view(
paramsByteData.buffer,
0,
paramsByteData.lengthInBytes,
);
}
_textureId = await SystemChannels.platform_views.invokeMethod<int>('create', args);
}
}
abstract class AndroidViewController {
...
Future<void> create() async {
assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view');
await _sendCreateMessage();
_state = _AndroidViewState.created;
for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
callback(viewId);
}
}
...
}
稍微整理一下调用链:
setSize -> create -> _sendCreateMessage -> 获得了textureId。
着重看下这段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
final Map<String, dynamic> args = <String, dynamic>{
'id': viewId,
'viewType': _viewType,
'width': _size.width,
'height': _size.height,
'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
};
_textureId = await SystemChannels.platform_views.invokeMethod<int>('create', args);
///这个SystemChannels.platform_views,其实就是个MethodChannel,名字是flutter/platform_views
static const MethodChannel platform_views = MethodChannel(
'flutter/platform_views',
StandardMethodCodec(),
);
这里,它也是通过MethodChannel与Native端通讯的,主要就是告诉Native端:按着viewType的尺寸,给我造一个。
看到这里,脑袋瓜更加嗡嗡的了。不过没关系,我来简单的总结一下:
WebView(StatefulWidget) -> AndroidWebView(WebViewPlatform) -> AndroidView(StatefulWidget) [didChangeDependencies ->_initializeOnce ->_createNewAndroidView-> 创建了TextureAndroidViewController] -> _AndroidPlatformView(LeafRenderObjectWidget)
-> RenderAndroidView(RenderObject) [perfprmResize -> AndroidViewController.setSize -> AndroidViewController.create -> AndroidViewController._sendCreateMessage (通过MehthodChannel通知Native端创建返回textureId) -> paint AndroidViewController.textureId]。
需要注意的是,只有WebView、AndroidWebView是属于WebView插件中的,后面的都是Flutter SDK中的控件。所以,AndroidView如何知道要显示什么的关键,就看它自己的viewType了 。
WebView的Native端
因为上面的Flutter端代码涉及到两个方面:Flutter SDK 以及WebView plugin。所以Native端也得从这两个方面看。
从与Native端有交集的地方开始寻找蛛丝马迹,上面唯一跟Native有联系的就是这个:
1
_textureId = await SystemChannels.platform_views.invokeMethod<int>('create', args);
这个MethodChannel名字是flutter/platform_views
, 它是系统级的一个Channel。在Flutter.jar的io.flutter.embedding.engine.systemchannels
下面,可以找到PlatformViewsChannel
, 从名字上可以看出这个应该就是跟上面进行交互的Native channel了。
看一下它的部分源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class PlatformViewsChannel {
private final MethodCallHandler parsingHandler = new MethodCallHandler() {
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
if (PlatformViewsChannel.this.handler != null) {
String var3 = call.method;
byte var4 = -1;
switch(var3.hashCode()) {
case -1352294148:
if (var3.equals("create")) {
var4 = 0;
}
break;
....
}
switch(var4) {
case 0:
this.create(call, result);
break;
...
}
}
}
private void create(@NonNull MethodCall call, @NonNull Result result) {
//跳过其他逻辑代码
...
//主要看这句
long textureId = PlatformViewsChannel.this.handler.createVirtualDisplayForPlatformView(request);
result.success(textureId);
}
};
public void setPlatformViewsHandler(@Nullable PlatformViewsChannel.PlatformViewsHandler handler) {
this.handler = handler;
}
}
PlatformViewsChannel的MethodCallHandler中,当method=’create’时,会调用create方法,然后,调用PlatformViewsChannel.this.handler.createVirtualDisplayForPlatformView
方法创建生成textureId
, 并返回的。这里跟上面的dart代码就对上了。
那PlatformViewsChannel是何时实例化的以及PlatformViewsHandler是如何设置进来的?
在io.flutter.embedding.engine
下面的FlutterEngine
这个类里,所有系统的channel都在这里实例化的,唯独没有找到PlatformViewsChannel,不过看过一个名字就很像的变量PlatformViewsController platformViewsController
, 它是在FlutterEngine
实例化的时候被创建出来的:
1
2
3
4
5
6
7
8
9
10
public class FlutterEngine {
//忽略很多的Method Channel...
@NonNull
private final PlatformViewsController platformViewsController;
public FlutterEngine(@NonNull Context context, @NonNull FlutterLoader flutterLoader, @NonNull FlutterJNI flutterJNI, @Nullable String[] dartVmArgs, boolean automaticallyRegisterPlugins) {
this(context, flutterLoader, flutterJNI, new PlatformViewsController(), dartVmArgs, automaticallyRegisterPlugins);
}
}
这个PlatformViewsController在io.flutter.plugin.platform
下面,它的部分源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class PlatformViewsController implements PlatformViewsAccessibilityDelegate {
...
public void attach(Context context, TextureRegistry textureRegistry, @NonNull DartExecutor dartExecutor) {
if (this.context != null) {
throw new AssertionError("A PlatformViewsController can only be attached to a single output target.\nattach was called while the PlatformViewsController was already attached.");
} else {
this.context = context;
this.textureRegistry = textureRegistry;
this.platformViewsChannel = new PlatformViewsChannel(dartExecutor);
this.platformViewsChannel.setPlatformViewsHandler(this.channelHandler);
}
}
@UiThread
public void detach() {
this.platformViewsChannel.setPlatformViewsHandler((PlatformViewsHandler)null);
this.platformViewsChannel = null;
this.context = null;
this.textureRegistry = null;
}
}
在attach时,会创建PlatformViewsChannel,并设置了PlatformViewsHandler:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private final PlatformViewsHandler channelHandler = new PlatformViewsHandler() {
...
//简化了一下逻辑
@TargetApi(17)
public long createVirtualDisplayForPlatformView(@NonNull PlatformViewCreationRequest request) {
//根据viewType拿出相应的PlatformViewFactory,在WebView plugin注册的时候,会注册WebViewFactory
PlatformViewFactory viewFactory = PlatformViewsController.this.registry.getFactory(request.viewType);
Object createParams = null;
if (request.params != null) {
createParams = viewFactory.getCreateArgsCodec().decodeMessage(request.params);
}
int physicalWidth = PlatformViewsController.this.toPhysicalPixels(request.logicalWidth);
int physicalHeight = PlatformViewsController.this.toPhysicalPixels(request.logicalHeight);
PlatformViewsController.this.validateVirtualDisplayDimensions(physicalWidth, physicalHeight);
//创建纹理
SurfaceTextureEntry textureEntry = PlatformViewsController.this.textureRegistry.createSurfaceTexture();
//通过多屏显示技术,把WebViewFactory中的view绘制在纹理上
VirtualDisplayController vdController = VirtualDisplayController.create(PlatformViewsController.this.context, PlatformViewsController.this.accessibilityEventsDelegate, viewFactory, textureEntry, physicalWidth, physicalHeight, request.viewId, createParams, (view, hasFocus) -> {
if (hasFocus) {
PlatformViewsController.this.platformViewsChannel.invokeViewFocused(request.viewId);
}
});
if (vdController == null) {
throw new IllegalStateException("Failed creating virtual display for a " + request.viewType + " with id: " + request.viewId);
} else {
if (PlatformViewsController.this.flutterView != null) {
vdController.onFlutterViewAttached(PlatformViewsController.this.flutterView);
}
//通过viewId记录vdController, 这里的viewId是Flutter端传过来的,是全局唯一的。
PlatformViewsController.this.vdControllers.put(request.viewId, vdController);
View platformView = vdController.getView();
platformView.setLayoutDirection(request.direction);
PlatformViewsController.this.contextToPlatformView.put(platformView.getContext(), platformView);
return textureEntry.id();
}
}
};
通过viewType获取到特定的PlatformViewFactory,然后创建纹理基于此纹理创建一个虚拟的屏幕,通过双屏显示技术把WebView显示在虚拟屏幕上。
关于双屏显示的后续逻辑,就先不展开了。
接下来,将注意力放到插件的Native端实现上,看看WebView插件是如何写的。
插件类在io.flutter.plugins.webviewflutter
下的WebViewFlutterPlugin:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class WebViewFlutterPlugin implements FlutterPlugin {
private FlutterCookieManager flutterCookieManager;
public WebViewFlutterPlugin() {}
@Override
public void onAttachedToEngine(FlutterPluginBinding binding) {
BinaryMessenger messenger = binding.getBinaryMessenger();
binding
.getFlutterEngine()
.getPlatformViewsController()
.getRegistry()
.registerViewFactory(
"plugins.flutter.io/webview", new WebViewFactory(messenger, /*containerView=*/ null));
flutterCookieManager = new FlutterCookieManager(messenger);
}
@Override
public void onDetachedFromEngine(FlutterPluginBinding binding) {
if (flutterCookieManager == null) {
return;
}
flutterCookieManager.dispose();
flutterCookieManager = null;
}
}
当插件onAttachedToEngine时,就会注册名为plugins.flutter.io/webview
的WebViewFactory。这里有两个恍然大悟的点:
plugins.flutter.io/webview
与Flutter 端 viewType的内容是一致的。系统也是通过它来找到WebViewFactory的。- 这里注册的WebViewFactory,正是
PlatformViewsHandler.createVirtualDisplayForPlatformView
方法中PlatformViewFactory viewFactory = PlatformViewsController.this.registry.getFactory(request.viewType);
获取到的PlatformViewFactory。
WebViewFactory的实现源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public final class WebViewFactory extends PlatformViewFactory {
private final BinaryMessenger messenger;
private final View containerView;
WebViewFactory(BinaryMessenger messenger, View containerView) {
super(StandardMessageCodec.INSTANCE);
this.messenger = messenger;
this.containerView = containerView;
}
@SuppressWarnings("unchecked")
@Override
public PlatformView create(Context context, int id, Object args) {
Map<String, Object> params = (Map<String, Object>) args;
return new FlutterWebView(context, messenger, id, params, containerView);
}
}
它的create方法会返回一个FlutterWebView:
1
2
3
4
5
6
7
8
public class FlutterWebView implements PlatformView, MethodCallHandler {
private final InputAwareWebView webView;
@Override
public View getView() {
return webView;s
}
}
getView返回的实际View是InputAwareWebView,而InputAwareWebView是WebView的子类:
1
2
3
final class InputAwareWebView extends WebView {
...
}
现在终于真相大白了,WebViewFactory里面确实藏着个WebView。
总结一下Native端的流程:
- FlutterPlugin 实例化 -> PlatformViewsController 实例化 -> PlatformViewsChannel() 实例化
WebViewPlugin在注册时,添加了name=”plugins.flutter.io/webview”,value为WebViewFactory的内容
- Flutter端通过MethodChannel调用create方法-> PlatformViewsHandler.createVirtualDisplayForPlatformView -> 根据viewType==”plugins.flutter.io/webview” 拿到WebViewFactory -> 创建纹理 -> 基于纹理创建虚拟屏幕 -> 通过双屏显示技术将WebViewFactory中的WebView
整体结构总结
//TODO