Flutter基礎(十三)Flutter與Android的相互通訊

本文首發於公衆號「後廠村碼農」html

ReactNative入門系列
React Native組件
Flutter基礎系列前端

前言

原本這篇文章應該講一下Flutter的插件開發,可是在插件開發的基礎是PlatformChannel,也就是Flutter與Android/iOS Native的通訊,理解了這一個知識點,Flutter的插件開發也就不在話下。java

1.PlatformChannel概述

Flutter不能完成全部Native的功能,所以須要Flutter與Native的通訊,Flutter提供了一套Platform Channel的機制,來知足Flutter與Native通訊的需求。 下面是PlatformChannel架構。 android

ZLyOw4.png
圖中能夠看到,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的概念。json

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端。app

MethodChannel能夠實現Flutter調用Android,也能夠實現Android調用Flutter,這裏分別來進行舉例。less

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"按鈕時,效果以下所示。

ZLhtVf.png

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來改變頁面的狀態。 運行程序後,將程序切到後臺再切回前臺,效果以下圖所示。

ZLy7lV.png

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的相互通訊


這裏不只分享大前端、Android、Java等技術,還有程序員成長類文章。
相關文章
相關標籤/搜索