PS:很多情境中,80% 的已知效果源自 20% 的可能原因。
前面幾篇文章介紹了 Navigator 組件、Flex 佈局、圖片加載、Widget 生命週期、混合開發等 Flutter 開發基礎知識, 文章如下:
- Flutter 系列之 Navigator 組件使用
- Flutter 系列之 Flex 佈局詳解
- Flutter 系列之圖片加載詳解
- Flutter 系列之 Widget 生命週期
- Flutter 系列之混合開發 Android 篇
下面介紹一下 Flutter 混合開發中 Platform Channel 的使用,主要內容如下:
- 平台通道介紹
- 平台數據類型對照
- BasicMessageChannel
- MethodChannel
- EventChannel
平台通道介紹#
Platform Channel 是一個異步消息通道,消息在發送之前會編碼成二進制消息,接收到的二進制消息會解碼成 Dart 值,其傳遞的消息類型只能是對應的解編碼器支持的值,所有的解編碼器都支持空消息,其 Native 與 Flutter 通信架構如下圖所示:
Flutter 中定義了三種不同類型的 PlatformChannel,主要有三種如下:
- BasicMessageChannel:用於數據傳遞;
- MethodChannel:用於傳遞方法調用;
- EventChannel:用於傳遞事件;
其構造方法都需指定一個通道標識、解編碼器以及 BinaryMessenger,BinaryMessenger 是一個 Flutter 與平台的通信工具,用來傳遞二進制數據、設置對應的消息處理器等。
解編碼器有兩種分別是 MethodCodec 和 MessageCodec,前者對應方法後者對應消息,BasicMessageChannel 使用的是 MessageCodec,MethodChannel 和 EventChannel 使用的是 MethodCodec。
平台數據類型對照#
Platform Channel 提供不同的消息解碼機制,如 StandardMessageCodec 提供基本數據類型的解碼、JSONMessageCodec 支持 Json 的解碼等,在平台之間通信時都會自動轉換,各平台數據類型對照如下:
BasicMessageChannel#
BasicMessageChannel 主要用來數據傳遞,包括二進制數據,借助 BasicMessageChannel 可以實現 MethodChannel 和 EventChannel 的功能,這裡用 BasicMessageChannel 實現 Android 項目使用 Flutter 資源文件的案例,關鍵流程如下:
- Flutter 端獲得圖片資源對應的二進制數據,這裡使用 BinaryCodec,則數據格式為 ByteData;
- 使用 BasicMessageChannel 發送圖片對應的數據;
- 在 Android 端使用 ByteBuffer 接收,並將其轉換成 ByteArray,然後解析成 Bitmap 顯示出來。
Flutter 端關鍵代碼如下:
// 創建BasicMessageChannel
_basicMessageChannel = BasicMessageChannel<ByteData>("com.manu.image", BinaryCodec());
// 獲取assets中的圖片對應的ByteData數據
rootBundle.load('images/miao.jpg').then((value) => {
_sendStringMessage(value)
});
// 發送圖片數據
_sendStringMessage(ByteData byteData) async {
await _basicMessageChannel.send(byteData);
}
Android 端關鍵代碼如下:
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
Log.i(tag, "configureFlutterEngine")
// 設置消息處理器
BasicMessageChannel<ByteBuffer>(
flutterEngine.dartExecutor, "com.manu.image", BinaryCodec.INSTANCE
).setMessageHandler { message, reply ->
Log.i(tag, "configureFlutterEngine > message:$message")
// 數據轉換:ByteBuffer->ByteArray
val byteBuffer = message as ByteBuffer
imageByteArray = ByteArray(byteBuffer.capacity())
byteBuffer.get(imageByteArray)
}
// 用於設置Flutter跳轉Android的方法處理器
MethodChannel(flutterEngine.dartExecutor, channel).setMethodCallHandler { call, result ->
Log.i(tag, "configureFlutterEngine > method:${call.method}")
if ("startBasicMessageChannelActivity" == call.method) {
// 攜帶圖片數據
BasicMessageChannelActivity.startBasicMessageChannelActivity(this,imageByteArray)
}
}
}
// 顯示來自Flutter assets中的圖片
val imageByteArray = intent.getByteArrayExtra("key_image")
val bitmap = BitmapFactory.decodeByteArray(imageByteArray,0,imageByteArray.size)
imageView.setImageBitmap(bitmap)
另外,BasicMessageChannel 結合 BinaryCodec 是支持大內存數據塊的傳遞的。
MethodChannel#
MethodChannel 主要用來方法的傳遞,自然可以傳遞 Native 方法和 Dart 方法,即可以通過 MethodChannel 在 Flutter 中調用 Android 原生方法,在 Android 中調用 Dart 方法,互相調用都是通過 MethodChannel 的 invokeMethod 方法調用的,通信時必須使用相同的通道標識符,具體如下:
- Flutter 調用 Android 方法
下面通過 MethodChannel 實現從 Flutter 跳轉到 Android 原生界面 MainActivity,Android 端如下:
/**
* @desc FlutterActivity
* @author jzman
*/
val tag = AgentActivity::class.java.simpleName;
class AgentActivity : FlutterActivity() {
val tag = AgentActivity::class.java.simpleName;
private val channel = "com.manu.startMainActivity"
private var platform: MethodChannel? = null;
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
Log.d(tag,"configureFlutterEngine")
platform = MethodChannel(flutterEngine.dartExecutor, channel)
// 設置方法處理器
platform!!.setMethodCallHandler(StartMethodCallHandler(this@AgentActivity))
}
companion object{
/**
* 重新創建NewEngineIntentBuilder才能保證生效
*/
fun withNewEngine(): MNewEngineIntentBuilder? {
return MNewEngineIntentBuilder(AgentActivity::class.java)
}
}
/**
* 自定義NewEngineIntentBuilder
*/
class MNewEngineIntentBuilder(activityClass: Class<out FlutterActivity?>?) :
NewEngineIntentBuilder(activityClass!!)
/**
* 實現MethodCallHandler
*/
class StartMethodCallHandler(activity:Activity) : MethodChannel.MethodCallHandler{
private val context:Activity = activity
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
if ("startMainActivity" == call.method) {
Log.i(tag,"arguments:"+call.arguments)
startMainActivity(context)
// 向Flutter回調執行結果
result.success("success")
} else {
result.notImplemented()
}
}
}
}
如上還可以使用 MethodChannel.Result 對象向 Flutter 回調執行結果,Flutter 端如下:
/// State
class _PageState extends State<PageWidget> {
MethodChannel platform;
@override
void initState() {
super.initState();
platform = new MethodChannel('com.manu.startMainActivity');
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: EdgeInsets.fromLTRB(8, 8, 8, 0),
child: RaisedButton(
onPressed: () {
_startMainActivity();
},
child: Text("Flutter to Android"),
),
);
}
/// 跳轉到原生Activity
void _startMainActivity() {
platform.invokeMethod('startMainActivity', 'flutter message').then((value) {
// 接收返回的數據
print("value:$value");
}).catchError((e) {
print(e.message);
});
}
}
- Android 調用 Dart 方法
下面通過 MethodChannel 調用 Flutter 中的 Dart 方法 getName,Android 端代碼如下:
/**
* @desc MainActivity
* @author jzman
*/
class MainActivity : FlutterActivity() {
private val tag = MainActivity::class.java.simpleName;
private val channel = "com.manu.startMainActivity"
private var methodChannel: MethodChannel? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnGetDart.setOnClickListener {
getDartMethod()
}
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
Log.i(tag,"configureFlutterEngine")
methodChannel = MethodChannel(flutterEngine.dartExecutor,channel)
}
private fun getDartMethod(){
methodChannel?.invokeMethod("getName",null, object :MethodChannel.Result{
override fun success(result: Any?) {
Log.i(tag,"success: "+result.toString())
Toast.makeText(this@MainActivity,result.toString(),Toast.LENGTH_LONG).show()
}
override fun error(errorCode: String,errorMessage: String?,errorDetails: Any?) {
Log.i(tag,"error")
}
override fun notImplemented() {
Log.i(tag,"notImplemented")
}
})
}
companion object{
fun startMainActivity(context: Context) {
val intent = Intent(context, MainActivity::class.java)
context.startActivity(intent)
}
}
}
Flutter 端如下:
/// State
class _PageState extends State<PageWidget> {
MethodChannel platform;
@override
void initState() {
super.initState();
platform = new MethodChannel('com.manu.startMainActivity');
// 監聽Android調用Flutter方法
platform.setMethodCallHandler(platformCallHandler);
}
@override
Widget build(BuildContext context) {
return Container();
}
/// FLutter Method
Future<dynamic> platformCallHandler(MethodCall call) async{
switch(call.method){
case "getName":
return "name from flutter";
break;
}
}
}
EventChannel#
EventChannel 主要用於 Flutter 到原生之間的單向調用,其使用方式類似 Android 中的廣播,原生界面負責 Event 的發送,Flutter 端註冊監聽即可,不多說直接看代碼,Android 端代碼如下:
/// Android
class MFlutterFragment : FlutterFragment() {
// 這裡用Fragment,Activity也一樣
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
Log.d(tag,"configureFlutterEngine")
EventChannel(flutterEngine.dartExecutor,"com.manu.event").setStreamHandler(object:
EventChannel.StreamHandler{
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
Log.i(tag,"configureFlutterEngine > onListen")
// EventSink發送事件通知
events?.success("event message")
}
override fun onCancel(arguments: Any?) {
Log.i(tag,"configureFlutterEngine > onCancel")
}
})
}
companion object{
fun withNewEngine(): NewEngineFragmentBuilder? {
return MNewEngineIntentBuilder(
MFlutterFragment::class.java
)
}
}
class MNewEngineIntentBuilder(activityClass: Class<out FlutterFragment?>?) :
NewEngineFragmentBuilder(activityClass!!)
}
Flutter 端如下:
/// State
class EventState extends State<EventChannelPage> {
EventChannel _eventChannel;
String _stringMessage;
StreamSubscription _streamSubscription;
@override
void initState() {
super.initState();
_eventChannel = EventChannel("com.manu.event");
// 監聽Event事件
_streamSubscription =
_eventChannel.receiveBroadcastStream().listen((event) {
setState(() {
_stringMessage = event;
});
}, onError: (error) {
print("event error$error");
});
}
@override
void dispose() {
super.dispose();
if (_streamSubscription != null) {
_streamSubscription.cancel();
_streamSubscription = null;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("EventChannel"),
centerTitle: true,
),
body: Center(
child: Text(_stringMessage == null ? "default" : _stringMessage),
));
}
}
以上就是 Flutter 平台通道的使用。