天天看點

【Flutter 專題】49 圖解 Flutter 與 Android 原生互動 #yyds幹貨盤點#

      小菜上一篇簡單學習了一下 Android 原生接入 Flutter Module,現在學習一下兩者之間的資料互動;

      Flutter 與 Android/iOS 之間資訊互動通過 Platform Channel 進行橋接;Flutter 定義了三種不同的 Channel;但無論是傳遞方法還是傳遞事件,其本質上都是資料的傳遞;

1. MethodChannel:用于傳遞方法調用;
2. EventChannel:用于資料流資訊通信;
3. BasicMessageChannel:用于傳遞字元串和半結構化的資訊;

      每種 Channel 均包含三個成員變量;

  1. name:代表 Channel 唯一辨別符,Channel 可以包含多個,但 name 為唯一的;
  2. messager:代表消息發送與接收的工具 BinaryMessenger;
  3. codec:代表消息的編解碼器;
    【Flutter 專題】49 圖解 Flutter 與 Android 原生互動 #yyds幹貨盤點#
          小菜以上一節 Android 原生內建 Flutter Module 為基礎,針對不同的 Channel 進行學習嘗試;且小菜通過 View / Fragment / Activity 三種原生加載方式進行測試;

MethodChannel

      小菜在 Flutter 頁面,點選右下角按鈕,将消息傳遞給 Android;MethodChannel 通過 invokeMethod 進行消息發送,固定的第一個 name 參數是必須存在且唯一的,與 Android 原生中比對;第二個參數為傳送的資料,類似于 Intent 中的 ExtraData,隻是支援的資料類型偏少;第三個可隐藏的參數為編解碼器;

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 中時,官方建議使用 View / Fragment 方式,在使用 View 時,建議 Activity 繼承 FlutterActivity 或 FlutterFragmentActivity,通過 FlutterView 進行 MethodChannel 綁定監聽;

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 回調内容:" + call.arguments.toString());
                    } else {
                        Log.e(TAG, "Flutter -> Android 回調參數為空!");
                    }
                    result.success("Android -> Flutter 接收回調後傳回值:" + TAG);
                    Intent intent = new Intent();
                    intent.putExtra("data", call.arguments!=null?call.arguments.toString():"");
                    setResult(REQUEST_CODE, intent);
                    MyFlutterViewActivity.this.finish();
                } else {
                    result.notImplemented();
                }
            }
        });
    }
}
           
【Flutter 專題】49 圖解 Flutter 與 Android 原生互動 #yyds幹貨盤點#

2. FlutterFragment

      使用 Fragment 方式時與 View 方式均需要擷取 FlutterView 進行綁定,此時 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 回調内容:" + call.arguments.toString());
                    } else {
                        Log.e(TAG, "Flutter -> Android 回調參數為空!");
                    }
                    result.success("Android -> Flutter 接收回調後傳回值:" + TAG);
                    Toast.makeText(getActivity(), (call.arguments != null) ? "回調内容為:" + call.arguments.toString() : "回調參數為空!", Toast.LENGTH_SHORT).show();
                } else {
                    result.notImplemented();
                }
            }
        });
    }
}
           
【Flutter 專題】49 圖解 Flutter 與 Android 原生互動 #yyds幹貨盤點#

3. FlutterActivity

      使用 Activity 方式同樣需要擷取 FlutterView 此時直接繼承 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 回調内容:" + call.arguments.toString());
                    } else {
                        Log.e(TAG, "Flutter -> Android 回調參數為空!");
                    }
                    result.success("Android -> Flutter 接收回調後傳回值:" + TAG);
                    Intent intent = new Intent();
                    intent.putExtra("data", call.arguments!=null?call.arguments.toString():"");
                    setResult(REQUEST_CODE, intent);
                    MyFlutterActivity.this.finish();
                } else {
                    result.notImplemented();
                }
            }
        });
    }
}
           
【Flutter 專題】49 圖解 Flutter 與 Android 原生互動 #yyds幹貨盤點#

      我們分析 FlutterFragment 和 FlutterActivity 時會發現,依舊是一層層封裝的 FlutterView;

      小菜測試 onMethodCall 中若有與 Flutter 中傳遞的相同 method name 時可以嘗試擷取傳遞參數;若此時需要向 Flutter 傳回傳遞參數可以通過 result.success() 方法進行資料傳遞,若無需傳遞則可不設定目前方法;

      小菜了解,MethodChannel 主要是由 Flutter 主動向 Android 原生發起互動請求,小菜了解相對于于原生為被動式互動較多;

EventChannel

      EventChannel 可以由 Android 原生主動向 Flutter 發起互動請求,小菜了解相對于原生為主動式互動,類似于 Android 發送一個廣播在 Flutter 端進行接收;其使用方式與 MethodChannel 類似,根據 FlutterView 進行綁定監聽,與上述相似,小菜不分開寫了;

      EventChannel 是對 Stream 流的監聽,通過 onListener 進行消息發送,通過 onCancel 對消息取消;

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

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

      Flutter 端通過 receiveBroadcastStream 進行資料流監聽;分析源碼得知,其内部同樣是通過 invokeMethod 方法進行發送;listen 方法中,onData 為必須參數用作收到 Android 端發送資料的回調;onError 為資料接收失敗回調;onDone 為接收資料結束回調;

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)));
  }
}
           
【Flutter 專題】49 圖解 Flutter 與 Android 原生互動 #yyds幹貨盤點#

BasicMessageChannel

      BasicMessageChannel 主要傳遞字元串和半結構化的資料互動;其編解碼有多種類型,在使用時建議 Android 與 Flutter 兩端一緻;

  1. BinaryCodec:基本二進制編碼類型;
  2. StringCodec:字元串與二進制之間的編碼類型;
  3. JSONMessageCodec:Json 與二進制之間的編碼類型;
  4. StandardMessageCodec:預設編碼類型,包括基礎資料類型、二進制資料、清單、字典等與二進制之間等編碼類型;

Flutter -> Android

      Flutter 端向 Android 端發送 send 資料請求,Android 端接收到後通過 replay 向 Flutter 端發送消息,進而完成一次消息互動;

// 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("我來自 " + TAG +" !! 使用的是 BasicMessageChannel 方式");
    }
});
           
【Flutter 專題】49 圖解 Flutter 與 Android 原生互動 #yyds幹貨盤點#

Android -> Flutter

      根據上述繼續由 Android 端主動向 Flutter 端發送資料,Android 通過 send 向 Flutter 發送資料請求,Flutter 通過 setMessageHandler 接收後向 Android 端 return 傳回結果,再由 Android 回調接收,進而完成一次資料互動;

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 有兩個構造函數,有兩個參數的構造方法用來接收 Flutter 回調的資料;

// 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("我來自 " + 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();
        //    }
        //});
    }
});
           
【Flutter 專題】49 圖解 Flutter 與 Android 原生互動 #yyds幹貨盤點#

注意事項

1. ensureInitializationComplete must be called after startInitialization

      小菜在從 Android 到 Flutter 互動過程時,崩潰提示如下問題;

【Flutter 專題】49 圖解 Flutter 與 Android 原生互動 #yyds幹貨盤點#

      小菜發現在 Application 中需要使用 FlutterApplication,FlutterApplication 的作用就是通過調用 FlutterMain 的 startInitialization 方法進行初始化;

import io.flutter.app.FlutterApplication;

public class MyApplication extends FlutterApplication {
}
           
2. 注意互動傳回中内容是否為空

      小菜在測試 MethodChannel 時,invokeMethod 時嘗試了一個參數和兩個參數的構造,隻有一個參數的 invokeMethod 是沒有回調内容的,而小菜在 Android 端未判空,雖然沒有報異常,但是後面的代碼都沒有執行,很基本的問題卻困擾小菜很久,希望大家可以避免;

3. 多種 Platform Channel 共同使用

繼續閱讀