在Android开发中,咱们知道用户消息分为按键消息和触摸消息,对于TV应用,咱们只考虑按键消息。javascript
分析源码能够看出,Android是将按键的数据获取和消息处理放在Native层,并提供回调接口给应用层。因为Flutter框架也是Google团队写的,因此对于按键消息的处理方式,原理上是同样的,只不过为了实现跨平台,原先android native层扮演的角色变成了各个平台应用层按键消息回调接口,在此基础上,又作了一层消息封装,并将按键事件回调接口提供给Flutter UI层。html
以android平台为例,首先咱们从MainActivity开始分析,这个是应用的主界面。在flutter中,该类继承FlutterActivity,FlutterActivity存在于flutter专门为android系统打包的库里面,这个库叫flutter.jar,负责flutter与android创建联系。java
这个库还有一个重要的类:FlutterView,flutter跨平台跨的就是界面,UI绘制不依赖系统组件,那么这个FlutterView就是将dart编写的界面封装成android平台可使用的控件,通常状况下,这个控件完成了android应用界面的全部绘制工做,ios也有相同的一套机制,从而实现了不一样平台共用一套绘制界面的代码。android
继续往下看,FlutterActivity继承于Activity,同时实现了Provider, PluginRegistry, ViewFactory这几个接口,其中Provider接口有一个抽象方法getFlutterView(),返回一个FlutterView对象,FlutterActivity实现了这个方法,具体过程为:ios
//实例化一个FlutterActivityDelegate对象并定义一个Provider对象viewProvider
private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this);
private final Provider viewProvider;
//在空构造函数里面对viewProvider初始化
public FlutterActivity() {
this.eventDelegate = this.delegate;
this.viewProvider = this.delegate;
this.pluginRegistry = this.delegate;
}
// 实现getFlutterView()接口
public FlutterView getFlutterView() {
return this.viewProvider.getFlutterView();
}
经过getFlutterView()方法,Android平台应用就拿到了Flutter绘制的界面。接下来,咱们看看这个特殊的控件FlutterView具体怎么建立的。在FlutterActivityDelegate类里面,定义了一个ViewFactory接口,里面有两个抽象方法:web
public interface ViewFactory {
FlutterView createFlutterView(Context var1);
FlutterNativeView createFlutterNativeView();
}
咱们看到FlutterActivity虽然implements ViewFactory,可是并无真正实现接口,两个方法都返回空:设计模式
public FlutterView createFlutterView(Context context) {
return null;
}
public FlutterNativeView createFlutterNativeView() {
return null;
}
那么具体实如今哪里呢?往回看,咱们发现Provider和PluginRegistry这两个接口对象实例化都是直接将建立的FlutterActivityDelegate对象赋值,查阅FlutterActivityDelegate这个类,咱们发现该类同时实现了FlutterActivityEvents, Provider, PluginRegistry接口,默认构造函数以下:api
public FlutterActivityDelegate(Activity activity, FlutterActivityDelegate.ViewFactory viewFactory) {
this.activity = (Activity)Preconditions.checkNotNull(activity);
this.viewFactory = (FlutterActivityDelegate.ViewFactory)Preconditions.checkNotNull(viewFactory);
}
再结合FlutterActivity建立FlutterActivityDelegate对象过程发现,FlutterActivityDelegate类里面的activity和viewFactory对象都是指向FlutterActivity对象,并进行了非空判断。app
FlutterActivity实现了PluginRegistry这个插件注册接口,打通了Android平台和flutter界面的通信通道,FlutterActivityDelegate实现的FlutterActivityEvents接口里面定义了和Android 系统Activity类似的生命周期方法:框架
public interface FlutterActivityEvents extends ComponentCallbacks2, ActivityResultListener, RequestPermissionResultListener {
void onCreate(Bundle var1);
void onNewIntent(Intent var1);
void onPause();
void onResume();
void onPostResume();
void onDestroy();
boolean onBackPressed();
void onUserLeaveHint();
}
追踪代码,咱们会看到,在FlutterView类里面,flutter经过发消息的形式将Android平台应用的生命周期通知给dart绘制的flutter界面。flutter对不一样平台的应用生命周期数据交互专门定义了一个消息系统:
private final BasicMessageChannel<String> mFlutterLifecycleChannel;
由于此处是讲按键消息机制,对于生命周期相关的,就再也不细述。
继续往下分析,咱们发如今FlutterActivityDelegate类重写的onCreate()方法里,进行了初始化和建立FlutterView的操做:
public void onCreate(Bundle savedInstanceState) {
if(VERSION.SDK_INT >= 21) {
Window window = this.activity.getWindow();
window.addFlags(-2147483648);
window.setStatusBarColor(1073741824);
window.getDecorView().setSystemUiVisibility(1280);
}
String[] args = getArgsFromIntent(this.activity.getIntent());
FlutterMain.ensureInitializationComplete(this.activity.getApplicationContext(), args);
this.flutterView = this.viewFactory.createFlutterView(this.activity);
if(this.flutterView == null) {
FlutterNativeView nativeView = this.viewFactory.createFlutterNativeView();
this.flutterView = new FlutterView(this.activity, (AttributeSet)null, nativeView);
this.flutterView.setLayoutParams(matchParent);
this.activity.setContentView(this.flutterView);
this.launchView = this.createLaunchView();
if(this.launchView != null) {
this.addLaunchView();
}
}
boolean reuseIsolate = true;
if(!this.loadIntent(this.activity.getIntent(), true)) {
String appBundlePath = FlutterMain.findAppBundlePath(this.activity.getApplicationContext());
if(appBundlePath != null) {
this.flutterView.runFromBundle(appBundlePath, (String)null, "main", true);
}
}
}
首先,系统调用了viewFactory的createFlutterView()接口,若是建立失败,再调用viewFactory的createFlutterNativeView()方法实例化一个FlutterNativeView对象,而后将这个对象做为参数调用FlutterView的构造函数进行实例化。
打开FlutterView这个类,咱们看到,若是传入的FlutterNativeView对象为空,系统会去调用带一个Context对象的FlutterView构造函数对其持有的FlutterNativeView对象进行初始化。
终于重量级人物登场:FlutterView,这个类是实现Android应用界面交给flutter框架绘制的核心类,界面能作到平台无关,是由于FlutterView继承SurfaceView。
咱们知道Android的View是绘制在”表层”上面,对于SurfaceView来讲,它本身就是充当表层自己。SurfaceView就是在窗口上挖一个洞,它本身显示在这个洞里,其余的View是显示在窗口上,因此View能够显示在 SurfaceView之上,你也能够添加一些层在SurfaceView之上。
从API中能够看出SurfaceView属于View的子类, 它是专门为制做游戏而产生的,它的功能很是强大,最重要的是它支持OpenGL ES库,2D和3D的效果均可以实现。这就是为何咱们说Flutter应用跨平台机制能够看作是一个游戏App,实现了一个相似的代码引擎。
既然flutter界面是直接继承于SurfaceView的,它的绘制过程就再也不依赖于系统平台,解耦了系统控件的调用,flutter编写的界面就能够在Android、IOS等平台上运行。
而且,SurfaceView实现了双缓冲机制。咱们知道Android系统提供了View进行绘图处理,咱们经过自定义的View能够知足大部分的绘图需求。可是咱们一般自定义的View是用于主动更新状况的,用户没法控制其绘制的速度。
因为View是经过invalidate方法通知系统去调用view.onDraw方法进行重绘,而Android系统是经过发出VSYNC信号来进行屏幕的重绘,刷新的时间是16ms,若是在16ms内View完成不了执行的操做,用户就会看着卡顿。
好比当draw方法里执行的逻辑过多,须要频繁刷新的界面上,例如游戏界面,那么就会不断的阻塞主线程,从而致使画面卡顿。而SurfaceView至关因而另外一个绘图线程,它是不会阻碍主线程的。
简单介绍完SurfaceView,咱们继续往下走。flutter跨平台开发,除了关注界面的绘制,更为关心的是怎么跟不一样的平台进行通信,主要是数据和部分业务逻辑的通讯。
说到这里,咱们不得不提flutter的消息系统,这个消息系统目前分为7个模块管理:多语言、路由导航、按键消息、生命周期、系统信息、用户设置和平台插件,这几乎涵盖了不一样平台全部差别化最大的功能,这几个模块很是依赖原生系统。
咱们看下代码中的定义:
private final MethodChannel mFlutterLocalizationChannel;
private final MethodChannel mFlutterNavigationChannel;
private final BasicMessageChannel<Object> mFlutterKeyEventChannel;
private final BasicMessageChannel<String> mFlutterLifecycleChannel;
private final BasicMessageChannel<Object> mFlutterSystemChannel;
private final BasicMessageChannel<Object> mFlutterSettingsChannel;
//这个插件消息管理对象被定义为局部的变量,上面几个都是在不少地方使用的
MethodChannel flutterPlatformChannel = new MethodChannel(this, "flutter/platform", JSONMethodCodec.INSTANCE);
严格来讲,这7个消息接口还要分为两类:一类是经过反射原生系统的api进行数据通信,一类是真正意义上的将数据打包成特殊格式以消息的形式和使用dart编写的flutter控件交互。消息数据流都是以二进制形式传输的,对于不一样语言编写的平台系统,二进制格式想必都认识。
再看下这7个消息管理对象的初始化:
this.mFlutterLocalizationChannel = new MethodChannel(this, "flutter/localization", JSONMethodCodec.INSTANCE);
this.mFlutterNavigationChannel = new MethodChannel(this, "flutter/navigation", JSONMethodCodec.INSTANCE);
this.mFlutterKeyEventChannel = new BasicMessageChannel(this, "flutter/keyevent", JSONMessageCodec.INSTANCE);
this.mFlutterLifecycleChannel = new BasicMessageChannel(this, "flutter/lifecycle", StringCodec.INSTANCE);
this.mFlutterSystemChannel = new BasicMessageChannel(this, "flutter/system", JSONMessageCodec.INSTANCE);
this.mFlutterSettingsChannel = new BasicMessageChannel(this, "flutter/settings", JSONMessageCodec.INSTANCE);
//platformPlugin负责处理平台插件消息,并提供回调接口
PlatformPlugin platformPlugin = new PlatformPlugin(activity);
MethodChannel flutterPlatformChannel = new MethodChannel(this, "flutter/platform", JSONMethodCodec.INSTANCE);
//监听平台插件消息回调接口
flutterPlatformChannel.setMethodCallHandler(platformPlugin);
这里,咱们只看按键消息处理流程。
FlutterView类有两个处理按键事件的接口,一个是onKeyUp(),一个是onKeyDown(),分别对应按键松开和按下事件,两个方法流程同样,在tv交互中,咱们通常只关心遥控按键按下事件,因此咱们这里只分析onKeyDown()。
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(!this.isAttached()) {
return super.onKeyDown(keyCode, event);
} else {
Map<String, Object> message = new HashMap();
message.put("type", "keydown");
message.put("keymap", "android");
this.encodeKeyEvent(event, message);
this.mFlutterKeyEventChannel.send(message);
return super.onKeyDown(keyCode, event);
}
}
分析代码可知,当FlutterView没有绑定到Activity时,直接返回父类View的onKeyDown()方法返回值,不然会建立一个Map对象,key为String类型,value为Object,由于消息发送时类型是被定义为泛型的,而Object类是全部类的父类,因此value没有具体指定某一个子类对象类型。
这个map存放了按键处理须要的数据,第一组键值对key是type,这个value有两个可取值,若是是onKeyDown(), 值为”keydown”,同理,onKeyUp(),值为”keyup”。第二组键值对key字段是keymap, 用于区分按键消息是哪一个平台发来的,若是应用是运行在Android系统,那么传的值为”android”,若是运行在Fuchsia系统,那么传的值为”fuchsia”,目前flutter api只处理了这两个系统的按键消息,至于IOS,因为不太熟悉这个系统框架,或许按键处理有另外一套机制?
其他的几个字段由于对于onKeyUp()和onKeyDown()是同样的,因此系统封了一个方法encodeKeyEvent():
private void encodeKeyEvent(KeyEvent event, Map<String, Object> message) {
message.put("flags", Integer.valueOf(event.getFlags()));
message.put("codePoint", Integer.valueOf(event.getUnicodeChar()));
message.put("keyCode", Integer.valueOf(event.getKeyCode()));
message.put("scanCode", Integer.valueOf(event.getScanCode()));
message.put("metaState", Integer.valueOf(event.getMetaState()));
}
这几个KeyEvent字段是否是很熟悉?咱们看flutter的源码注释:
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getFlags()>;
final int flags;
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getUnicodeChar()>;
final int codePoint;
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getKeyCode()>;
final int keyCode;
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getScanCode()>;
final int scanCode;
/// See <https://developer.android.com/reference/android/view/KeyEvent.html#getMetaState()>;
final int metaState;
意思很明确,这几个字段就是对应Android的KeyEvent属性,若是不知道是干啥的,让咱们本身去Android开发官网查阅相关api文档。
到这里,咱们熟悉了Flutter跟Android平台按键消息桥接的过程,顺便了解了Flutter跟Android原生API交互的原理。最后,总结下Flutter在Android平台下按键消息分发流程从底层到UI层几个关键类调用:
Android平台处理按键消息
Goldfish_event.c
处于Linux内核层
负责驱动按键消息
EventHub.cpp
硬件抽象层
用来读取设备文件中的RawEvent
com_android_server_KeyInputQueue.cpp
JNI本地方法
向Java框架层提供了函数android_server_KeyInputQueue_readEvent,用于读取输入设备事件
WindowManagerService.java
Java框架层
窗口管理服务,经过InputManager提供的接口开启一个线程驱动InputReader不断地从/dev/input/目录下面的设备文件读取事件,而后经过InputDispatcher分发给链接到WindowManagerService服务的客户端。
KeyInputQueue.java
Java框架层
建立一个线程,循环读取事件,并把事件放入事件队列里
InputManager.java
Java框架层
监控按键事件
ViewRootImpl.java
Android UI 层
Android的Activity经过该类的setView()接口来注册和销毁键盘消息接收通道
DecorView.java
Android UI层
分发按键事件,重要方法 dispatchKeyEvent()
FlutterView.java
Android和Flutter UI桥接层
封装按键消息
Flutter将平台的按键事件以消息方式拦截
system_channels.dart
dart框架层
flutter消息系统,在按键事件中负责按键消息的传递
raw_keyboard.dart
dart框架层
里面定义了按键事件相关的几个类,包括2个抽象类RawKeyEvent、RawKeyEventData及其余们的子类实现,1个监听按键事件的RawKeyBoard类。这个dart语言喜欢将几个相关类弄在一个文件里,跟Java语言一个类一个文件的规则相悖,须要适应。
RawKeyEvent有一个工厂方法,这个方法返回一个子类实现的对象,也就是继承RawKeyEvent的RawKeyUpEvent或者RawKeyDownEvent对象。具体过程是,先将接收到的不一样平台按键数据生成具体某个平台的RawKeyEventData,根据消息中的keymap字段来区分平台,好比Android平台,是将data实例化成RawKeyEventDataAndroid子类对象。同时,会根据消息中的type字段来决定返回RawKeyUpEvent对象仍是RawKeyDownEvent对象。
/// Creates a concrete [RawKeyEvent] class from a message in the form received
/// on the [SystemChannels.keyEvent] channel.
factory RawKeyEvent.fromMessage(Map<String, dynamic> message) {
RawKeyEventData data;
final String keymap = message['keymap'];
switch (keymap) {
case 'android':
data = new RawKeyEventDataAndroid(
flags: message['flags'] ?? 0,
codePoint: message['codePoint'] ?? 0,
keyCode: message['keyCode'] ?? 0,
scanCode: message['scanCode'] ?? 0,
metaState: message['metaState'] ?? 0,
);
break;
case 'fuchsia':
data = new RawKeyEventDataFuchsia(
hidUsage: message['hidUsage'] ?? 0,
codePoint: message['codePoint'] ?? 0,
modifiers: message['modifiers'] ?? 0,
);
break;
default:
// We don't yet implement raw key events on iOS, but we don't hit this
// exception because the engine never sends us these messages.
throw new FlutterError('Unknown keymap for key events: $keymap');
}
final String type = message['type'];
switch (type) {
case 'keydown':
return new RawKeyDownEvent(data: data);
case 'keyup':
return new RawKeyUpEvent(data: data);
default:
throw new FlutterError('Unknown key event type: $type');
}
}
RawKeyBoard类负责监听平台发送的按键消息,并实现消息回调,在消息回调里调用RawKeyEvent的fromMessage()方法实例化一个RawKeyEvent对象,并将该对象做为参数以回调的方式供响应按键事件的控件使用。
class RawKeyboard {
RawKeyboard._() {
SystemChannels.keyEvent.setMessageHandler(_handleKeyEvent);
}
/// The shared instance of [RawKeyboard].
static final RawKeyboard instance = new RawKeyboard._();
final List<ValueChanged<RawKeyEvent>> _listeners = <ValueChanged<RawKeyEvent>>[];
/// Calls the listener every time the user presses or releases a key.
///
/// Listeners can be removed with [removeListener].
void addListener(ValueChanged<RawKeyEvent> listener) {
_listeners.add(listener);
}
/// Stop calling the listener every time the user presses or releases a key.
///
/// Listeners can be added with [addListener].
void removeListener(ValueChanged<RawKeyEvent> listener) {
_listeners.remove(listener);
}
Future<dynamic> _handleKeyEvent(dynamic message) async {
if (_listeners.isEmpty)
return;
final RawKeyEvent event = new RawKeyEvent.fromMessage(message);
if (event == null)
return;
for (ValueChanged<RawKeyEvent> listener in new List<ValueChanged<RawKeyEvent>>.from(_listeners))
if (_listeners.contains(listener))
listener(event);
}
}
raw_keyboard_listener.dart
flutter ui层
负责定义响应按键的控件容器,监听焦点变化事件、提供按键回调接口
该文件定义了一个RawKeyboardListener类,该类继承StatefulWidget,因此是带状态的控件,在状态初始化时,会监听控件焦点变化:
@override
void initState() {
super.initState();
widget.focusNode.addListener(_handleFocusChanged);
}
void _handleFocusChanged() {
if (widget.focusNode.hasFocus)
_attachKeyboardIfDetached();
else
_detachKeyboardIfAttached();
}
只有控件得到焦点时,才会响应按键事件,具体作法是,当控件有焦点时,监听按键事件并实现回调:
void _attachKeyboardIfDetached() {
if (_listening)
return;
RawKeyboard.instance.addListener(_handleRawKeyEvent);
_listening = true;
}
当失去焦点时,移除事件监听:
void _detachKeyboardIfAttached() {
if (!_listening)
return;
RawKeyboard.instance.removeListener(_handleRawKeyEvent);
_listening = false;
}
咱们再看这个_handleRawKeyEvent()方法,其实是经过用户的按键操做回调按键事件接口:
void _handleRawKeyEvent(RawKeyEvent event) {
if (widget.onKey != null)
widget.onKey(event);
}
如何使用该控件呢?实际上dart这门语言最大的特点是万物皆控件,因此一个基础控件想要扩展功能,就要被拥有该功能的控件包裹一层,也就是做为别人的孩子节点,在设计模式中,咱们称为装饰模式。具体到这里,那就是,你想让某个控件响应按键事件,你就要让该控件的父节点为RawKeyboardListener。
关于Flutter界面接收系统平台的按键消息后如何实现UI交互,下一篇flutter tv开发之按键消息分发机制(下)会详细介绍。