Flutter如何和Native通信-Android視角

前言

我們都知道Flutter開發(fā)的app是可以同時(shí)在iOS和Android系統(tǒng)上運(yùn)行的。顯然Flutter需要有和Native通信的能力。比如說,你的Flutter app要顯示手機(jī)的電量,而電量只能通過平臺(tái)的系統(tǒng)Api獲取。這時(shí)就需要有個(gè)機(jī)制使得Flutter可以通過某種方式來調(diào)用這個(gè)系統(tǒng)Api并且獲得返回值。那么Flutter是如何做到的呢?答案是Platform Channels。

Platform Channels

先來看張圖

PlatformChannels.png

上圖來自Flutter官網(wǎng),表明了Platform Channels的架構(gòu)示意圖。有細(xì)心的同學(xué)就要問了,你不是說Flutter和Native通信是通過Platform Channels嗎?怎么架構(gòu)圖里面連接他們的是MethodChannel? 其實(shí)呢,MethodChannel是Platform Channels中的一種,顧名思義,MethodChannel用起來應(yīng)該和方法調(diào)用差不多。那么還有別的channel?有的,還有EventChannel,BasicMessageChannel等。如果你需要把數(shù)據(jù)從Native平臺(tái)發(fā)送給Flutter,推薦你使用EventChannel。Flutter framework也是在用這些通道和Native通信,具體可以參考一下FlutterView.java,在這里能看到Platform Channels的更多用法。

這里需要注意一點(diǎn),為了保證UI的響應(yīng),通過Platform Channels傳遞的消息都是異步的。

在Platform Channels上傳遞的消息都是經(jīng)過編碼的,編碼的方式也有幾種,默認(rèn)的是用StandardMethodCodec。其他的還有BinaryCodec(二進(jìn)制的編碼,其實(shí)啥也沒干,直接把入?yún)⒔o返回了), JSONMessageCodec(JSON格式的編碼),StringCodec(String格式的編碼)。這些編解碼器允許的只能是以下這些類型:

MessageCodec接受的類型

所以如果你想把你自己定義的com.yourmodule.YourObject類型的一個(gè)實(shí)例直接扔給Platform Channels傳送是不行滴。

Platform Channels 怎么用

前面大概介紹了Flutter和Native通信的Platform Channels。那么我們用具體的例子來說說Platform Channels的使用。這里使用Flutter官方出的獲取手機(jī)電量的Demo。相關(guān)源代碼可以從Github下載。

Platform Channels是連接Flutter和Native的通道,那么我們?nèi)绻⑦@樣的通道顯然要在兩端都要寫代碼嘍。

MethodChannel

先看Native 端怎么寫

MethodChannel-Native 端

為簡單起見,本例的Android端代碼都直接寫在MainActivity中。Android平臺(tái)下獲取電量是通過調(diào)用BatteryManager來獲取的,所以我們先在MainActivity中增加一個(gè)獲取電量的函數(shù):

private int getBatteryLevel() {
  int batteryLevel = -1;
  if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
    BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
    batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
  } else {
    Intent intent = new ContextWrapper(getApplicationContext()).
        registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
    batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
        intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
  }

  return batteryLevel;
}

這個(gè)函數(shù)需要能被Flutter app調(diào)用,此時(shí)就需要通過MethodChannel來建立這個(gè)通道了。
首先在MainActivityonCreate函數(shù)中加入以下代碼來新建一個(gè)MethodChannel

public class MainActivity extends FlutterActivity {
    //channel的名稱,由于app中可能會(huì)有多個(gè)channel,這個(gè)名稱需要在app內(nèi)是唯一的。
    private static final String CHANNEL = "samples.flutter.io/battery";

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
        
        // 直接 new MethodChannel,然后設(shè)置一個(gè)Callback來處理Flutter端調(diào)用
        new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
                new MethodCallHandler() {
                    @Override
                    public void onMethodCall(MethodCall call, Result result) {
                        // 在這個(gè)回調(diào)里處理從Flutter來的調(diào)用
                    }
                });
    }
}

注意,每個(gè)MethodChannel需要有唯一的字符串作為標(biāo)識(shí),用以互相區(qū)分,這個(gè)名稱建議使用package.module...這樣的模式來命名。因?yàn)樗械?code>MethodChannel都是保存在以通道名為Key的Map中。所以你要是設(shè)了兩個(gè)名字一樣的channel,只有后設(shè)置的那個(gè)會(huì)生效。

接下來我們來填充onMethodCall。

@Override
public void onMethodCall(MethodCall call, Result result) {
    if (call.method.equals("getBatteryLevel")) {
        int batteryLevel = getBatteryLevel();

        if (batteryLevel != -1) {
            result.success(batteryLevel);
        } else {
            result.error("UNAVAILABLE", "Battery level not available.", null);
        }
    } else {
        result.notImplemented();
    }
}               

onMethodCall有兩個(gè)入?yún)ⅲ?code>MethodCall里包含要調(diào)用的方法名稱和參數(shù)。Result是給Flutter的返回值。方法名是兩端協(xié)商好的。通過if語句判斷MethodCall.method來區(qū)分不同的方法,在我們的例子里面我們只會(huì)處理名為“getBatteryLevel”的調(diào)用。在調(diào)用本地方法獲取到電量以后通過result.success(batteryLevel)調(diào)用把電量值返回給Flutter。
Native端的代碼就完成了。是不是很簡單?

MethodChannel-Flutter 端

接下來看Flutter端代碼怎么寫:
首先在 State中創(chuàng)建Flutter端的MethodChannel

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
...
class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel('samples.flutter.io/battery');

  // Get battery level.
}

channel的名稱要和Native端的一致。
然后是通過MethodChannel調(diào)用的代碼

String _batteryLevel = 'Unknown battery level.';

  Future<Null> _getBatteryLevel() async {
    String batteryLevel;
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }

final int result = await platform.invokeMethod('getBatteryLevel');這行代碼就是通過通道來調(diào)用Native方法了。注意這里的await關(guān)鍵字。前面我們說過MethodChannel是異步的,所以這里必須要使用await關(guān)鍵字。
在上面Native代碼中我們把獲取到的電量通過result.success(batteryLevel);返回給Flutter。這里await表達(dá)式執(zhí)行完成以后電量就直接賦值給result變量了。剩下的就是怎么展示的問題了,就不再細(xì)說了,具體可以去看代碼。

需要注意的是,這里我們只介紹了從Flutter調(diào)用Native方法,其實(shí)通過MethodChannel,Native也能調(diào)用Flutter的方法,這是一個(gè)雙向的通道。

舉個(gè)例子,我們想從Native端請(qǐng)求Flutter端的一個(gè)getName方法獲取一個(gè)字符串。在Flutter端你需要給MethodChannel設(shè)置一個(gè)MethodCallHandler

_channel.setMethodCallHandler(platformCallHandler);

Future<dynamic> platformCallHandler(MethodCall call) async {
       switch (call.method) {
             case "getName":
             return "Hello from Flutter";
             break;
       }
}

在Native端,只需要讓對(duì)應(yīng)的的channel調(diào)用invokeMethod就行了

channel.invokeMethod("getName", null, new MethodChannel.Result() {
          @Override
          public void success(Object o) {
            // 這里就會(huì)輸出 "Hello from Flutter"
            Log.i("debug", o.toString());
          }
          @Override
          public void error(String s, String s1, Object o) {
          }
          @Override
          public void notImplemented() {
          }
        });

至此,MethodChannel的用法就介紹完了??梢园l(fā)現(xiàn),通過MethodChannelNative和Flutter方法互相調(diào)用還是蠻直接的。這里只是做了個(gè)大概的介紹,具體細(xì)節(jié)和一些復(fù)雜用法還有待大家的探索。

MethodChannel提供了方法調(diào)用的通道,那如果Native有數(shù)據(jù)流需要傳送給Flutter該怎么辦呢?這時(shí)候就要用到EventChannel了。

EventChannel

EventChannel的使用我們也以官方獲取電池電量的demo為例,手機(jī)的電池狀態(tài)是不停變化的。我們要把這樣的電池狀態(tài)變化由Native及時(shí)通過EventChannel來告訴Flutter。這種情況用之前講的MethodChannel辦法是不行的,這意味著Flutter需要用輪詢的方式不停調(diào)用getBatteryLevel來獲取當(dāng)前電量,顯然是不正確的做法。而用EventChannel的方式,則是將當(dāng)前電池狀態(tài)"推送"給Flutter.

EventChannel - Native端

先看我們熟悉的Native端怎么來創(chuàng)建EventChannel, 還是在MainActivity.onCreate中,我們加入如下代碼:

new EventChannel(getFlutterView(), "samples.flutter.io/charging").setStreamHandler(
        new StreamHandler() {
          // 接收電池廣播的BroadcastReceiver。
          private BroadcastReceiver chargingStateChangeReceiver;
          @Override
         // 這個(gè)onListen是Flutter端開始監(jiān)聽這個(gè)channel時(shí)的回調(diào),第二個(gè)參數(shù) EventSink是用來傳數(shù)據(jù)的載體。
          public void onListen(Object arguments, EventSink events) {
            chargingStateChangeReceiver = createChargingStateChangeReceiver(events);
            registerReceiver(
                chargingStateChangeReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
          }

          @Override
          public void onCancel(Object arguments) {
            // 對(duì)面不再接收
            unregisterReceiver(chargingStateChangeReceiver);
            chargingStateChangeReceiver = null;
          }
        }
    );

MethodChannel類似,我們也是直接new一個(gè)EventChannel實(shí)例,并給它設(shè)置了一個(gè)StreamHandler類型的回調(diào)。其中onCancel代表對(duì)面不再接收,這里我們應(yīng)該做一些clean up的事情。而 onListen則代表通道已經(jīng)建好,Native可以發(fā)送數(shù)據(jù)了。注意onListen里帶的EventSink這個(gè)參數(shù),后續(xù)Native發(fā)送數(shù)據(jù)都是經(jīng)過EventSink的??创a:

private BroadcastReceiver createChargingStateChangeReceiver(final EventSink events) {
    return new BroadcastReceiver() {
      @Override
      public void onReceive(Context context, Intent intent) {
        int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);

        if (status == BatteryManager.BATTERY_STATUS_UNKNOWN) {
          events.error("UNAVAILABLE", "Charging status unavailable", null);
        } else {
          boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                               status == BatteryManager.BATTERY_STATUS_FULL;
          // 把電池狀態(tài)發(fā)給Flutter
          events.success(isCharging ? "charging" : "discharging");
        }
      }
    };
  }

onReceive函數(shù)內(nèi),系統(tǒng)發(fā)來電池狀態(tài)廣播以后,在Native這里轉(zhuǎn)化為約定好的字符串,然后通過調(diào)用events.success();發(fā)送給Flutter。Native端的代碼就是這樣,接下來看Flutter端。

EventChannel - Flutter端

首先還是在State內(nèi)創(chuàng)建EventChannel

static const EventChannel eventChannel =
      const EventChannel('samples.flutter.io/charging');

然后在initState的時(shí)候打開這個(gè)channel:

@override
  void initState() {
    super.initState();
    eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
  }

收到event以后的處理是在_onEvent函數(shù)里:

void _onEvent(Object event) {
    setState(() {
      _chargingStatus =
          "Battery status: ${event == 'charging' ? '' : 'dis'}charging.";
    });
  }

  void _onError(Object error) {
    setState(() {
      _chargingStatus = 'Battery status: unknown.';
    });
  }

從Native端傳過來的"charging"/"discharging"字符串直接就是入?yún)?code>event。好了,F(xiàn)lutter端的代碼也貼完了,是不是感覺EventChannel用起來也很簡單?

收尾

至此,本文對(duì)Flutter和Native之間互相通信的方式的講解也要告一段落了。Flutter的出發(fā)點(diǎn)就是跨平臺(tái),而真正要做到跨平臺(tái)則取決于Flutter是否能通過簡單的方式與Native高效通信。Platform Channels能否實(shí)現(xiàn)這個(gè)目標(biāo)還有待大規(guī)模應(yīng)用的檢驗(yàn)。對(duì)于Flutter開發(fā)者來講,由于眾多的Native平臺(tái)API需要暴露給Flutter,還有很多用Native實(shí)現(xiàn)的組件/業(yè)務(wù)邏輯也可能需要暴露給Flutter。這需要寫大量的通道代碼,也就是說我們必須掌握使用Platform Channels的技能,才能體會(huì)到Flutter真正的跨平臺(tái)能力。本文中對(duì)Platform Channels的應(yīng)用只是非常簡單的demo。在大型app中還存在兩大挑戰(zhàn),一個(gè)是大量的通道我們?nèi)绾谓M織,如何維護(hù)。另一個(gè)是通道協(xié)議如何設(shè)計(jì)才能抹平Android和iOS之間的平臺(tái)差異,這就需要開發(fā)這對(duì)兩個(gè)平臺(tái)都非常熟悉,這個(gè)貌似更加困難。

當(dāng)然了,如果你做出來了完美的通道,將平臺(tái)的某個(gè)功能(比如藍(lán)牙,GPS什么的)包裝成了優(yōu)美的Flutter API,并且希望世界上其他Flutter開發(fā)者也能使用。那么你可以把你智慧的結(jié)晶通過發(fā)布Flutter插件(plugin)的方式開放給別人。下篇文章我會(huì)介紹一下如何來開發(fā)一個(gè)Flutter插件,敬請(qǐng)期待。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,733評(píng)論 25 709
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • 本文列舉了項(xiàng)目開發(fā)使用Flutter會(huì)遇到的問題,以及如何使用Flutter module在現(xiàn)有項(xiàng)目中集成Flut...
    Q吹個(gè)大氣球Q閱讀 4,800評(píng)論 7 15
  • 第一百一十七首 我愛你的不完美 秋憶濃 我愛你的不完美, 就像蝸牛愛著貝殼,...
    山丘qiu閱讀 322評(píng)論 0 2
  • (PS你說:陪你走過人生中的點(diǎn)點(diǎn)滴滴。)這是你們,看著你和你愛人的照片,她的臉上溢出了幸福和滿足感,就像一只剛剛吃...
    有你我便不再是摩西閱讀 233評(píng)論 0 0

友情鏈接更多精彩內(nèi)容