本文首發於公眾號「劉望舒」
ReactNative入門系列
React Native元件
Flutter基礎系列
前言
本來這篇文章應該講一下Flutter的外掛開發,但是在外掛開發的基礎是PlatformChannel,也就是Flutter與Android/iOS Native的通訊,理解了這一個知識點,Flutter的外掛開發也就不在話下。
1.PlatformChannel概述
Flutter不能完成所有Native的功能,因此需要Flutter與Native的通訊,Flutter提供了一套Platform Channel的機制,來滿足Flutter與Native通訊的需求。 下面是PlatformChannel架構。
圖中可以看到,Flutter是Client端,Native是Host,Client和host通訊是通過PlatformChannel,Client通過PlatformChannel向Host傳送訊息,Host監聽PlatformChannel並接收訊息,然後將響應結果傳送給Client。訊息和響應以非同步方式傳遞,以確保UI不阻塞。另外,PlatformChannel是雙工的,這意味著Flutter和Native可以交替做Client和Host。Flutter定義了三種不同型別的PlatformChannel,它們分別是:
- MethodChannel:用於傳遞方法呼叫,是比較常用的PlatformChannel。
- EventChannel: 用於傳遞事件。
- BasicMessageChannel:用於傳遞資料。
這幾個PlatformChannel的用法都不難,本文會以比較常用的MethodChannel來進行舉例。在此之前我們先要了解BinaryMessenger、Codec、Handler的概念。
BinaryMessenger BinaryMessenger是PlatformChannel與Flutter端的通訊的工具,其通訊使用的訊息格式為二進位制格式資料,BinaryMessenger在Android中是一個介面,它的實現類為FlutterNativeView。
Codec Codec是訊息編解碼器,主要用於將二進位制格式的資料轉化為Handler能夠識別的資料,Flutter定義了兩種Codec:MessageCodec和MethodCodec。MessageCodec用於二進位制格式資料與基礎資料之間的編解碼,BasicMessageChannel所使用的編解碼器是MessageCodec。MethodChannel和EventChannel所使用的編解碼均為MethodCodec。
Handler Flutter定義了三種型別的Handler,它們與PlatformChannel型別一一對應,分別是MessageHandler、MethodHandler、StreamHandler。在使用PlatformChannel時,會為它註冊一個Handler,PlatformChannel會將該二進位制資料通過Codec解碼為轉化為Handler能夠識別的資料,並交給Handler處理。當Handler處理完訊息之後,會通過回撥函式返回result,將result通過編解碼器編碼為二進位制格式資料,通過BinaryMessenger傳送回Flutter端。
MethodChannel可以實現Flutter呼叫Android,也可以實現Android呼叫Flutter,這裡分別來進行舉例。
2.Flutter呼叫Android
這裡實現一個Android的簡單的功能:彈出一個AlertDialog,然後在Flutter中呼叫這一功能。
Android端實現 先在MainActivity中實現功能,如下所示。
package com.example.platform_channel;
import android.app.AlertDialog;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);//1
MethodChannel methodChannel = new MethodChannel(getFlutterView(), "com.example.platform_channel/dialog");//2
methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {//3
@Override
public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
if ("dialog".equals(methodCall.method)) {
if (methodCall.hasArgument("content")) {
showAlertDialog();
result.success("彈出成功");
} else {
result.error("error", "彈出失敗", "content is null");
}
} else {
result.notImplemented();
}
}
private void showAlertDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setPositiveButton("確定", null);
builder.setTitle("Flutter呼叫Android");
builder.show();
}
});
}
}
複製程式碼
註釋1處用於註冊外掛,這個是建立Flutter工程時MainActivity自帶的。 註釋2處建立一個MethodChannel,它有兩個引數,一個是getFlutterView方法,用於獲取FlutterView,FlutterView實現了BinaryMessenger介面。一個是MethodChannel的Name,這個Name要保證是唯一的,後面Flutter端實現中會用到這個Name。 註釋3處為methodChannel註冊一個MethodCallHandler,用於監聽回撥的資料。 onMethodCall方法中的result是Flutter端傳來的資料,我們需要對資料進行判斷,然後向Flutter端傳送資料。 向Flutter端傳送資料有以下方法:
result.success(Object result) 結果成功,將result返回給Flutter端。
result.error(String errorCode,String errorMsg,Object errorDetails) 結果失敗,將errorCode、errorMsg、errorDetails返回給Flutter端。
result.notImplemented() Android端沒有實現Flutter端需要的方法,會將notImplemented返回給Flutter端。
Flutter端實現 在main.dart中加入如下程式碼。
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
static const platformChannel =
const MethodChannel('com.example.platform_channel/dialog');//1
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
home: Scaffold(
appBar: AppBar(
title: Text("Flutter呼叫Android"),
),
body: Padding(
padding: EdgeInsets.all(40.0),
child: RaisedButton(
child: Text("呼叫Dialog"),
onPressed: () {
showDialog("Flutter呼叫AlertDialog");
},
),
),
),
);
}
void showDialog(String content) async {
var arguments = Map();
arguments['content'] = content;
try {
String result = await platformChannel.invokeMethod('dialog', arguments);//2
print('showDialog ' + result);
} on PlatformException catch (e) {
print('showDialog ' + e.code + e.message + e.details);
} on MissingPluginException catch (e) {
print('showDialog ' + e.message);
}
}
}
複製程式碼
註釋1處建立了MethodChannel,它需要傳入MethodChannel的Name,這個Name要保證和Android端設定的Name是一樣的。當點選按鈕時會觸發showDialog方法。註釋2處用於呼叫Android中的方法,第一個引數是方法的名稱,第二個引數arguments只能是Map或者JSON型別的,是我們需要傳遞給Android端的資料。 執行程式,當我們點選"呼叫Dialog"按鈕時,效果如下所示。
3.Android呼叫Flutter
有的時候Flutter呼叫Android後,Android還會將結果返回給Flutter,雖然有時可以用result來實現,但Android端的處理可能是非同步的,result物件也不能長期的持有,這時就需要Android來呼叫Flutter。 因為頁面UI是Flutter端繪製的,我們很難在頁面中控制Android端,要實現Android呼叫Flutter,可以利用Android的Activty的生命週期,如果將應用切到後臺再切回前臺,這樣Activty的onResume方法就會被呼叫,我們在onResume方法中實現呼叫Flutter的功能就可以了。
Android端的實現
package com.example.platform_channel;
import android.os.Bundle;
import android.util.Log;
import java.util.HashMap;
import java.util.Map;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
public static final String MAIN_ACTIVITY = "MainActivity";
MethodChannel methodChannel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
methodChannel = new MethodChannel(getFlutterView(),"com.example.platform_channel/text");//1
}
@Override
protected void onResume() {
super.onResume();
Map map = new HashMap();
map.put("content","Android進階三部曲");
methodChannel.invokeMethod("showText", map, new MethodChannel.Result() {//2
@Override
public void success(Object o) {
Log.d(MAIN_ACTIVITY,(String)o);
}
@Override
public void error(String errorCode, String errorMsg, Object errorDetail) {
Log.d(MAIN_ACTIVITY,"errorCode:"+errorCode+" errorMsg:"+errorMsg+" errorDetail:"+(String)errorDetail);
}
@Override
public void notImplemented() {
Log.d(MAIN_ACTIVITY,"notImplemented");
}
});
}
}
複製程式碼
和Flutter呼叫Android的程式碼是類似的,在註釋1處建立MethodChannel,然後在註釋2處呼叫Flutter端的showText方法,並將資料以Map的形式傳遞過去。MethodChannel.Result() 的回撥裡有三個方法,通過這三個方法可以得到Android呼叫Flutter的結果。
Flutter端的實現
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
static const platformChannel =
const MethodChannel('com.example.platform_channel/text');
String textContent = 'Flutter端初始文字';
@override
void initState() {
// TODO: implement initState
super.initState();
platformChannel.setMethodCallHandler((methodCall) async {
switch (methodCall.method) {
case 'showText':
String content = await methodCall.arguments['content'];
if (content != null && content.isNotEmpty) {
setState(() {
textContent = content;
});
return 'success';
} else {
throw PlatformException(
code: 'error', message: '失敗', details: 'content is null');
}
break;
default:
throw MissingPluginException();
}
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter Demo",
home: Scaffold(
appBar: AppBar(
title: Text('Android呼叫Flutter'),
),
body: Padding(
padding: EdgeInsets.all(40.0),
child: Text(textContent),
),
),
);
}
}
複製程式碼
因為要實現Flutter頁面的改變,就需要在initState方法中為MethodChannel新增回撥,如果Android端傳遞過來的方法名稱為showText,就獲取Android端傳來的content的值,賦值給Text來改變頁面的狀態。 執行程式後,將程式切到後臺再切回前臺,效果如下圖所示。
Flutter基礎系列
Flutter基礎(一)移動開發的跨平臺技術演進
Flutter基礎(二)Flutter開發環境搭建和Hello World
Flutter基礎(三)Dart快速入門
Flutter基礎(四)開發Flutter應用前需要掌握的Basic Widget
Flutter基礎(五)Material元件之MaterialApp、Scaffold、AppBar
Flutter基礎(六)Material元件之BottomNavigationBar、TabBar、Drawer
Flutter基礎(七)Scrolling Widget之ListView、GridView、PageView
Flutter基礎(八)手勢相關Widget:GestureDetector和Dismissible
Flutter基礎(九)資源和圖片
Flutter基礎(十)佈局Widget快速入門
Flutter基礎(十一)網路請求(Dio)與JSON資料解析
Flutter基礎(十二)路由(頁面跳轉)與資料傳遞
Flutter基礎(十三)Flutter與Android的相互通訊