Home Flutter WebView的实现原理深入
Post
Cancel

Flutter WebView的实现原理深入

开头

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
  • 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

This post is licensed under CC BY 4.0 by the author.