Flutter官方提供了一系列的外掛的外掛
來為Flutter提供眾多原生系統級API
呼叫,包括感測器、檔案讀寫、資料庫、輕量儲存等等,這些外掛大都是以原生、Dart間通過MethodChannel
、EventChannel
相互通訊實現的。但google還提拱了一個Video_Player,專門用於視訊播放的外掛。外掛原理是通過Native提供播放器能力,dart層通過Channel呼叫,以及雙方通訊控制播放,但畫面是如何渲染到FlutterView(Android繼承自SurfaceView)上的呢?
在此之前我們先回顧下Android上的檢視都是如何渲染到螢幕上的。
在Android 中負責繪製UI的服務是SurfaceFlinger
,SurfaceFlinger
服務執行在Android系統的System程式中,它負責管理Android系統的幀緩衝區(Frame Buffer)。Android應用程式為了能夠將自己的UI繪製在系統的幀緩衝區上,它們就必須要與SurfaceFlinger服務進行通訊,由於Android應用程式與SurfaceFlinger服務是執行在不同的程式中的,因此,它們採用Binder程式間通訊機制來進行通訊。
Android應用程式在通知SurfaceFlinger服務來繪製自己的UI的時候,需要將大量的UI相關資料傳遞給SurfaceFlinger服務(如要繪製UI的區域、位置等資訊)。一個Android應用程式可能會有很多個Window,而每一個Window都有自己的UI後設資料,為了解決客戶端與服務端通訊效率問題,每一個客戶端利用Android系統的匿名共享記憶體機制(Anonymous Shared Memory)與SurfaceFlinger服務程式開闢一塊共享記憶體。
並對該共享記憶體進行封裝,成為一個ShareClient
。ShareClient中儲存著ShareBufferStack
,可以簡單的理解為對應的Surface。
Android 應用程式通過Canvas 繪製其UI並儲存進FramBuffer中,而Canvas是其Surface 的成員,所以SurfaceFlinger可以讀取FramBuffer資料並繪製渲染UI。
簡而言之,Android是通過SurfaceFlinger服務進行繪製UI,而FlutterApplication中通過FlutterView(SurfaceView)將渲染UI的工作交給了Flutter ,dart->組裝LayerTree->Skia(SurfaceFlinger服務底層繪製二維影象同樣使用開源的Skia)
其實我們可以發現Android的繪製流程和Flutter的回執流程大體相當,但是否可以在Dart層將繪製能力返還給Android呢(Java),由於Surface持有Canvas,我們只要能夠得到Surface就可以將Surface交由播放器繪製了,事實上FlutterSDK也提供了相關的API。下面我們來用一個簡單的例子看下如何實現。
首先建立一個FlutterPlugin:MySurfaceTestPlugin
package com.example.darttest;
import android.annotation.TargetApi;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Build;
import android.view.Surface;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry.Registrar;
import io.flutter.view.TextureRegistry;
public class MySurfaceTestPlugin implements MethodChannel.MethodCallHandler {
private final Registrar flutterRegistrar;
private TextureRegistry textureRegistry;
private TextureRegistry.SurfaceTextureEntry surfaceTextureEntry;
private Surface surface;
Random random;
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
String method = methodCall.method;
switch (method) {
case "init":
//flutterRegistrar的textures方法獲取textureRegistry,textureRegistry是包含了當前Flutter應用所有SurfaceTexture的登記表
//同時也可以建立一個新的surfaceTexture
textureRegistry = flutterRegistrar.textures();
surfaceTextureEntry = textureRegistry.createSurfaceTexture();
//通過一個剛剛建立的SurfaceTexure作為引數建立一個Surface
surface = new Surface(surfaceTextureEntry.surfaceTexture());
Map<String, Long> reply = new HashMap<>();
long textureId = surfaceTextureEntry.id();
//textureId回傳給Flutter 用來建立Texure控制元件
reply.put("textureId", textureId);
result.success(reply);
break;
case "render":
Canvas canvas = surface.lockCanvas(null);
//這裡的canvas 寬和高只有1個畫素 因為surface得建立不是surfaceView拖管的,所以不能夠draw實際內容,但是仍然可以繪製背景色
//int height = canvas.getHeight();
//int width = canvas.getWidth();
canvas.drawColor(Color.argb(255, random.nextInt(255), random.nextInt(255), random.nextInt(255)));
surface.unlockCanvasAndPost(canvas);
result.success(null);
break;
default:
break;
}
}
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "flutter.io/SurfaceTest");
channel.setMethodCallHandler(new MySurfaceTestPlugin(registrar));
}
private MySurfaceTestPlugin(Registrar registrar) {
this.flutterRegistrar = registrar;
random = new Random();
}
}
複製程式碼
可以看到通過FlutterSdk提供的方法獲取了一個SurfaceTexure,通過SurfaceTexure我們new了一個Surface,官方文件對於Surface有如下描述:
從
SurfaceTexture
物件中建立的Surface
類物件可被用作下述類的輸出目標: android.hardware.camera2, MediaCodec, MediaPlayer, 和 Allocation APIs。
也就是說Surface可以直接交由MediaPlayer、或者更低一級的MediaCodec作為輸出物件使用。通過**new Surface(SurfaceTexture texture) 方式建立的Surface.LockCanvas(DirtyRect rect)**獲取到的畫布只有1px,顯然私自建立的Surface官方意圖並非讓我們拿來繪製,而是交由MediaCodec, MediaPlayer
輸出,但我們仍然可以drawColor背景色來確認我們的java層與dart建立了關聯。
接下來我們註冊該外掛
MySurfaceTestPlugin.registerWith(getFlutterView().getPluginRegistry().registrarFor("flutter.io/SurfaceTest"));
複製程式碼
註冊完畢我們就可以在Dart中使用該外掛了,我們建立一個簡單的DartApp 上面是通過java層渲染過的"播放視窗",下面是一個控制渲染的按鈕。
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
main() async {
await _initVideoPlugin();
runApp(new MyApp());
}
final MethodChannel _channel = const MethodChannel('flutter.io/SurfaceTest');
int _textureId;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test',
home: new Scaffold(
body: Column(
children: <Widget>[
AspectRatio(child: new VideoView(), aspectRatio: 16 / 9),
Expanded(
child: Center(
child: new FlatButton(
padding: EdgeInsets.only(
left: 25.0, right: 25.0, top: 15.0, bottom: 15.0),
onPressed: _render,
child: new Text("render"))))
],
)
),
);
}
}
Future<void> _render() async {
_channel.invokeMethod(
'render',
<String, dynamic>{},
);
}
class VideoView extends StatefulWidget {
@override
State createState() {
return new VideoState();
}
}
_initVideoPlugin() async {
final Map<dynamic, dynamic> response = await _channel.invokeMethod('init');
_textureId = response['textureId'];
}
class VideoState extends State<VideoView> {
@override
Widget build(BuildContext context) {
//這裡注意Texure是dart提供的控制元件 引數必須是java Plugin傳過來的texureId
return new Texture(textureId: _textureId);
}
}
複製程式碼
需要注意的是播放視窗實際上是一個Dart控制元件Texture,這個Texure就是Plugin裡通過flutterRegistrar.textures()
獲取到的textureRegistry
所管理的SurfaceTexture 一一對應的。執行看一下效果
可見由java
層繪製背景已經可以渲染到螢幕上,接下來我們只要將Surface交於播放器即可實現視訊播放。如官方的Video_Player
使用的ExoPlayer,我們也用ExoPlayer快速實現視訊播放。
case "init":
//flutterRegistrar的textures方法獲取textureRegistry,textureRegistry是包含了當前Flutter應用所有SurfaceTexture的登記表
//同時也可以建立一個新的surfaceTexture
textureRegistry = flutterRegistrar.textures();
surfaceTextureEntry = textureRegistry.createSurfaceTexture();
//通過一個剛剛建立的SurfaceTexure作為引數建立一個Surface
surface = new Surface(surfaceTextureEntry.surfaceTexture());
initPlayer();
Map<String, Long> reply = new HashMap<>();
long textureId = surfaceTextureEntry.id();
//textureId回傳給Flutter 用來建立Texure控制元件
reply.put("textureId", textureId);
result.success(reply);
break;
case "render":
// Canvas canvas = surface.lockCanvas(null);
// canvas.drawColor(Color.argb(255, random.nextInt(255), random.nextInt(255), random.nextInt(255)));
// surface.unlockCanvasAndPost(canvas);
exoPlayer.setPlayWhenReady(true);
exoPlayer.setRepeatMode(REPEAT_MODE_ALL);
result.success(null);
break;
複製程式碼
這裡 ,我們在init中執行initPlayer()
其中將Surface直接交給播放器,render中執行播放
private void initPlayer() {
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(flutterRegistrar.activeContext(), "ExoPlayer");
MediaSource mediaSource = new ExtractorMediaSource(Uri.parse("asset:///flutter_assets/assets/Butterfly.mp4"), dataSourceFactory, new DefaultExtractorsFactory(), null, null);
exoPlayer = ExoPlayerFactory.newSimpleInstance(flutterRegistrar.activeContext(), new DefaultTrackSelector());
exoPlayer.setVideoSurface(surface);
exoPlayer.prepare(mediaSource);
}
複製程式碼
ok 現在我們開下效果
參考: