在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開發之按鍵消息分發機制(下)會詳細介紹。