Flutter 49: 圖解 Flutter 與 Android 原生交互

??????小菜上一篇簡(jiǎn)單學(xué)習(xí)了一下 Android 原生接入 Flutter Module辛藻,現(xiàn)在學(xué)習(xí)一下兩者之間的數(shù)據(jù)交互耍鬓;

??????FlutterAndroid/iOS 之間信息交互通過(guò) Platform Channel 進(jìn)行橋接;Flutter 定義了三種不同的 Channel骇钦;但無(wú)論是傳遞方法還是傳遞事件宛渐,其本質(zhì)上都是數(shù)據(jù)的傳遞;

1. MethodChannel:用于傳遞方法調(diào)用眯搭;
2. EventChannel:用于數(shù)據(jù)流信息通信;
3. BasicMessageChannel:用于傳遞字符串和半結(jié)構(gòu)化的信息业岁;

??????每種 Channel 均包含三個(gè)成員變量鳞仙;

  1. name:代表 Channel 唯一標(biāo)識(shí)符,Channel 可以包含多個(gè)笔时,但 name 為唯一的棍好;
  2. messager:代表消息發(fā)送與接收的工具 BinaryMessenger
  3. codec:代表消息的編解碼器允耿;

??????小菜以上一節(jié) Android 原生集成 Flutter Module 為基礎(chǔ)借笙,針對(duì)不同的 Channel 進(jìn)行學(xué)習(xí)嘗試粘昨;且小菜通過(guò) View / Fragment / Activity 三種原生加載方式進(jìn)行測(cè)試炼列;

MethodChannel

??????小菜在 Flutter 頁(yè)面,點(diǎn)擊右下角按鈕横漏,將消息傳遞給 Android蚂蕴;MethodChannel 通過(guò) invokeMethod 進(jìn)行消息發(fā)送低散,固定的第一個(gè) name 參數(shù)是必須存在且唯一的,與 Android 原生中匹配骡楼;第二個(gè)參數(shù)為傳送的數(shù)據(jù)熔号,類(lèi)似于 Intent 中的 ExtraData,只是支持的數(shù)據(jù)類(lèi)型偏少鸟整;第三個(gè)可隱藏的參數(shù)為編解碼器引镊;

class _MyHomePageState extends State<MyHomePage> {
  static const methodChannel = const MethodChannel('ace_demo_android_flutter');
  String _result = '';

  Future<Null> _getInvokeResult() async {
    try {
      _result = await methodChannel
          .invokeMethod('ace_demo_user', {'name': '我不是老豬', 'gender': 1});
    } on PlatformException catch (e) {
      _result = "Failed: '${e.message}'.";
    }
    setState(() {});
  }

  void _incrementCounter() {
    setState(() {
      _getInvokeResult();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text(widget.title)),
        body: Center(
            child: Text('${_result}', style: TextStyle(color: Colors.blueAccent, fontSize: 18.0))),
        floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter, child: Icon(Icons.arrow_back)));
  }
}

1. FlutterView

??????在 Android 集成 Flutter Module 中時(shí),官方建議使用 View / Fragment 方式篮条,在使用 View 時(shí)弟头,建議 Activity 繼承 FlutterActivity 或 FlutterFragmentActivity,通過(guò) FlutterView 進(jìn)行 MethodChannel 綁定監(jiān)聽(tīng)兑燥;

public class MyFlutterViewActivity extends FlutterFragmentActivity {

    private static final String CHANNEL = "ace_demo_android_flutter";
    private static final String TAG = "MyFlutterViewActivity";
    private static final int REQUEST_CODE = 1000;
    FlutterView flutterView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_flutter);

        DisplayMetrics outMetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
        int widthPixels = outMetrics.widthPixels;
        int heightPixels = outMetrics.heightPixels;

        flutterView = Flutter.createView(MyFlutterViewActivity.this, getLifecycle(), "/");
        FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(widthPixels, heightPixels);
        addContentView(flutterView, layout);
        new MethodChannel(flutterView, CHANNEL).setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall call, MethodChannel.Result result) {
                if (call.method.equals("ace_demo_user")) {
                    if (call.arguments != null) {
                        Log.e(TAG, "Flutter -> Android 回調(diào)內(nèi)容:" + call.arguments.toString());
                    } else {
                        Log.e(TAG, "Flutter -> Android 回調(diào)參數(shù)為空亮瓷!");
                    }
                    result.success("Android -> Flutter 接收回調(diào)后返回值:" + TAG);
                    Intent intent = new Intent();
                    intent.putExtra("data", call.arguments!=null?call.arguments.toString():"");
                    setResult(REQUEST_CODE, intent);
                    MyFlutterViewActivity.this.finish();
                } else {
                    result.notImplemented();
                }
            }
        });
    }
}

2. FlutterFragment

??????使用 Fragment 方式時(shí)與 View 方式均需要獲取 FlutterView 進(jìn)行綁定,此時(shí) Fragment 繼承 FlutterFragment 較易獲冉低嘱支;

public class MyFlutterFragment extends FlutterFragment {

    private static final String CHANNEL = "ace_demo_android_flutter";
    private static final String TAG = "MyFlutterFragment";

    @SuppressWarnings("unchecked")
    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        new MethodChannel((FlutterView) getView(), CHANNEL).setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall call, final MethodChannel.Result result) {
                if (call.method.equals("ace_demo_user")) {
                    if (call.arguments != null) {
                        Log.e(TAG, "Flutter -> Android 回調(diào)內(nèi)容:" + call.arguments.toString());
                    } else {
                        Log.e(TAG, "Flutter -> Android 回調(diào)參數(shù)為空蚓胸!");
                    }
                    result.success("Android -> Flutter 接收回調(diào)后返回值:" + TAG);
                    Toast.makeText(getActivity(), (call.arguments != null) ? "回調(diào)內(nèi)容為:" + call.arguments.toString() : "回調(diào)參數(shù)為空!", Toast.LENGTH_SHORT).show();
                } else {
                    result.notImplemented();
                }
            }
        });
    }
}

3. FlutterActivity

??????使用 Activity 方式同樣需要獲取 FlutterView 此時(shí)直接繼承 FlutterActivity 或 FlutterFragmentActivity 即可除师;

public class MyFlutterActivity extends FlutterActivity {

    private static final String CHANNEL = "ace_demo_android_flutter";
    private static final String TAG = "MyFlutterActivity";
    private static final int REQUEST_CODE = 1000;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        GeneratedPluginRegistrant.registerWith(this);
        new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall call, MethodChannel.Result result) {
                if (call.method.equals("ace_demo_user")) {
                    if (call.arguments != null) {
                        Log.e(TAG, "Flutter -> Android 回調(diào)內(nèi)容:" + call.arguments.toString());
                    } else {
                        Log.e(TAG, "Flutter -> Android 回調(diào)參數(shù)為空沛膳!");
                    }
                    result.success("Android -> Flutter 接收回調(diào)后返回值:" + TAG);
                    Intent intent = new Intent();
                    intent.putExtra("data", call.arguments!=null?call.arguments.toString():"");
                    setResult(REQUEST_CODE, intent);
                    MyFlutterActivity.this.finish();
                } else {
                    result.notImplemented();
                }
            }
        });
    }
}

??????我們分析 FlutterFragmentFlutterActivity 時(shí)會(huì)發(fā)現(xiàn),依舊是一層層封裝的 FlutterView汛聚;

??????小菜測(cè)試 onMethodCall 中若有與 Flutter 中傳遞的相同 method name 時(shí)可以嘗試獲取傳遞參數(shù)锹安;若此時(shí)需要向 Flutter 返回傳遞參數(shù)可以通過(guò) result.success() 方法進(jìn)行數(shù)據(jù)傳遞,若無(wú)需傳遞則可不設(shè)置當(dāng)前方法倚舀;

??????小菜理解叹哭,MethodChannel 主要是由 Flutter 主動(dòng)向 Android 原生發(fā)起交互請(qǐng)求,小菜理解相對(duì)于于原生為被動(dòng)式交互較多痕貌;

EventChannel

??????EventChannel 可以由 Android 原生主動(dòng)向 Flutter 發(fā)起交互請(qǐng)求风罩,小菜理解相對(duì)于原生為主動(dòng)式交互,類(lèi)似于 Android 發(fā)送一個(gè)廣播在 Flutter 端進(jìn)行接收舵稠;其使用方式與 MethodChannel 類(lèi)似超升,根據(jù) FlutterView 進(jìn)行綁定監(jiān)聽(tīng),與上述相似哺徊,小菜不分開(kāi)寫(xiě)了室琢;

??????EventChannel 是對(duì) Stream 流的監(jiān)聽(tīng),通過(guò) onListener 進(jìn)行消息發(fā)送落追,通過(guò) onCancel 對(duì)消息取消盈滴;

new EventChannel(flutterView, CHANNEL).setStreamHandler(new EventChannel.StreamHandler() {
    @Override
    public void onListen(Object arguments, final EventChannel.EventSink events) {
        events.success("我來(lái)自 " + TAG +" !! 使用的是 EventChannel 方式");
    }

    @Override
    public void onCancel(Object arguments) {
    }
});

??????Flutter 端通過(guò) receiveBroadcastStream 進(jìn)行數(shù)據(jù)流監(jiān)聽(tīng);分析源碼得知淋硝,其內(nèi)部同樣是通過(guò) invokeMethod 方法進(jìn)行發(fā)送雹熬;listen 方法中,onData 為必須參數(shù)用作收到 Android 端發(fā)送數(shù)據(jù)的回調(diào)谣膳;onError 為數(shù)據(jù)接收失敗回調(diào)竿报;onDone 為接收數(shù)據(jù)結(jié)束回調(diào);

StreamSubscription<T> listen(void onData(T event),
      {Function onError, void onDone(), bool cancelOnError});
class _MyHomePageState extends State<MyHomePage> {
  static const eventChannel = const EventChannel('ace_demo_android_flutter');
  String _result = '';
  StreamSubscription _streamSubscription;

  @override
  void initState() {
    super.initState();
    _getEventResult();
  }

  @override
  void dispose() {
    super.dispose();
    if (_streamSubscription != null) {
      _streamSubscription.cancel();
    }
  }

  _getEventResult() async {
    try {
      _streamSubscription =
          eventChannel.receiveBroadcastStream().listen((data) {
        setState(() {
          _result = data;
        });
      });
    } on PlatformException catch (e) {
      setState(() {
        _result = "event get data err: '${e.message}'.";
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text(widget.title)),
        body: Center(
            child: Text('${_result}',
                style: TextStyle(color: Colors.blueAccent, fontSize: 18.0))),
        floatingActionButton: FloatingActionButton(
            onPressed: _incrementCounter, child: Icon(Icons.arrow_back)));
  }
}

BasicMessageChannel

??????BasicMessageChannel 主要傳遞字符串和半結(jié)構(gòu)化的數(shù)據(jù)交互继谚;其編解碼有多種類(lèi)型烈菌,在使用時(shí)建議 AndroidFlutter 兩端一致;

  1. BinaryCodec:基本二進(jìn)制編碼類(lèi)型花履;
  2. StringCodec:字符串與二進(jìn)制之間的編碼類(lèi)型芽世;
  3. JSONMessageCodecJson 與二進(jìn)制之間的編碼類(lèi)型;
  4. StandardMessageCodec:默認(rèn)編碼類(lèi)型诡壁,包括基礎(chǔ)數(shù)據(jù)類(lèi)型济瓢、二進(jìn)制數(shù)據(jù)、列表妹卿、字典等與二進(jìn)制之間等編碼類(lèi)型旺矾;

Flutter -> Android

??????Flutter 端向 Android 端發(fā)送 send 數(shù)據(jù)請(qǐng)求蔑鹦,Android 端接收到后通過(guò) replayFlutter 端發(fā)送消息,從而完成一次消息交互箕宙;

// Flutter 端
static const basicChannel = BasicMessageChannel<String>('ace_demo_android_flutter', StringCodec());

@override
void initState() {
  super.initState();
  _getBasicResult();
}
  
_getBasicResult() async {
  final String reply = await basicChannel.send('ace_demo_user');
  setState(() {
    _result = reply;
  });
}

// Android 端
final BasicMessageChannel channel = new BasicMessageChannel<String> (flutterView, CHANNEL, StringCodec.INSTANCE);
channel.setMessageHandler(new BasicMessageChannel.MessageHandler() {
    @Override
    public void onMessage(Object o, BasicMessageChannel.Reply reply) {
        reply.reply("我來(lái)自 " + TAG +" !! 使用的是 BasicMessageChannel 方式");
    }
});

Android -> Flutter

??????根據(jù)上述繼續(xù)由 Android 端主動(dòng)向 Flutter 端發(fā)送數(shù)據(jù)嚎朽,Android 通過(guò) sendFlutter 發(fā)送數(shù)據(jù)請(qǐng)求,Flutter 通過(guò) setMessageHandler 接收后向 Androidreturn 返回結(jié)果柬帕,再由 Android 回調(diào)接收哟忍,從而完成一次數(shù)據(jù)交互;

public void send(T message) {
    this.send(message, (BasicMessageChannel.Reply)null);
}

public void send(T message, BasicMessageChannel.Reply<T> callback) {
    this.messenger.send(this.name, this.codec.encodeMessage(message), callback == null ? null : new BasicMessageChannel.IncomingReplyHandler(callback));
}

??????分析源碼 send 有兩個(gè)構(gòu)造函數(shù)陷寝,有兩個(gè)參數(shù)的構(gòu)造方法用來(lái)接收 Flutter 回調(diào)的數(shù)據(jù)锅很;

// Flutter 端
static const basicChannel = BasicMessageChannel<String>('ace_demo_android_flutter', StringCodec());

@override
void initState() {
  super.initState();
  _getBasicResult();
}

_getBasicResult() async {
  final String reply = await
  channel.setMessageHandler((String message) async {
    print('Flutter Received: ${message}');
    setState(() {
      _result = message;
    });
    return "{'name': '我不是老豬', 'gender': 1}";
  });
}

// Android 端
channel.setMessageHandler(new BasicMessageChannel.MessageHandler() {
    @Override
    public void onMessage(Object o, BasicMessageChannel.Reply reply) {
        reply.reply("我來(lái)自 " + TAG +" !! 使用的是 BasicMessageChannel 方式");
        channel.send("ace_demo_user");
        //channel.send("ace_demo_user", new BasicMessageChannel.Reply() {
        //    @Override
        //    public void reply(Object o) {
        //        Intent intent = new Intent();
        //        intent.putExtra("data", o!=null?o.toString():"");
        //        setResult(REQUEST_CODE, intent);
        //        MyFlutterViewActivity.this.finish();
        //    }
        //});
    }
});

注意事項(xiàng)

1. ensureInitializationComplete must be called after startInitialization

??????小菜在從 AndroidFlutter 交互過(guò)程時(shí),崩潰提示如下問(wèn)題凤跑;

??????小菜發(fā)現(xiàn)在 Application 中需要使用 FlutterApplication粗蔚,FlutterApplication 的作用就是通過(guò)調(diào)用 FlutterMainstartInitialization 方法進(jìn)行初始化;

import io.flutter.app.FlutterApplication;

public class MyApplication extends FlutterApplication {
}
2. 注意交互返回中內(nèi)容是否為空

??????小菜在測(cè)試 MethodChannel 時(shí)饶火,invokeMethod 時(shí)嘗試了一個(gè)參數(shù)和兩個(gè)參數(shù)的構(gòu)造,只有一個(gè)參數(shù)的 invokeMethod 是沒(méi)有回調(diào)內(nèi)容的致扯,而小菜在 Android 端未判空肤寝,雖然沒(méi)有報(bào)異常,但是后面的代碼都沒(méi)有執(zhí)行抖僵,很基本的問(wèn)題卻困擾小菜很久鲤看,希望大家可以避免;

3. 多種 Platform Channel 共同使用

??????小菜測(cè)試過(guò)程中耍群,多種 Platform Channel 可以共同使用义桂,可以根據(jù)業(yè)務(wù)或場(chǎng)景的不同配合使用,提高效率蹈垢;


??????小菜對(duì) AndroidFlutter 交互還不夠深入慷吊,仍需進(jìn)一步學(xué)習(xí);如有錯(cuò)誤請(qǐng)多多指導(dǎo)曹抬!

來(lái)源:阿策小和尚
歡迎閑來(lái)吐槽~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末溉瓶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子谤民,更是在濱河造成了極大的恐慌堰酿,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件张足,死亡現(xiàn)場(chǎng)離奇詭異触创,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)为牍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)哼绑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)岩馍,“玉大人,你說(shuō)我怎么就攤上這事凌那〖嫘郏” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵帽蝶,是天一觀的道長(zhǎng)赦肋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)励稳,這世上最難降的妖魔是什么佃乘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮驹尼,結(jié)果婚禮上趣避,老公的妹妹穿的比我還像新娘。我一直安慰自己新翎,他們只是感情好程帕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著地啰,像睡著了一般愁拭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上亏吝,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天岭埠,我揣著相機(jī)與錄音,去河邊找鬼蔚鸥。 笑死惜论,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的止喷。 我是一名探鬼主播馆类,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼启盛!你這毒婦竟也來(lái)了蹦掐?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤僵闯,失蹤者是張志新(化名)和其女友劉穎卧抗,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鳖粟,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡社裆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了向图。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泳秀。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡标沪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嗜傅,到底是詐尸還是另有隱情金句,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布吕嘀,位于F島的核電站违寞,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏偶房。R本人自食惡果不足惜趁曼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望棕洋。 院中可真熱鬧挡闰,春花似錦、人聲如沸掰盘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)愧捕。三九已至射众,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間晃财,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工典蜕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留断盛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓愉舔,卻偏偏與公主長(zhǎng)得像钢猛,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子轩缤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容