Flutter即學即用系列博客—8MethodChannel實現Flutter與原生通信

背景

前面我們講了很多 Flutter 相關的知識點,但是我們並沒有介紹怎樣實現 Flutter 與原生的通信。

比如我在 Flutter UI 上面點擊了一個按鈕,我希望原生做一些處理,那麼原生怎麼知道?

比如我在原生有些變化需要告知 Flutter,Flutter 又如何獲知?

本篇我們先解決第一個問題。即 Flutter-> 原生的通信。

路由回顧

之前我們一直在講 Flutter 相關的知識點,而且基本上都是在 main.dart 文件上面折騰,為了避免很多小夥伴覺得我們跨度過大。

因此我們這裡補充一下之前第三篇 Flutter 即學即用系列博客——03 在舊有項目引入 Flutter 的知識點。

在 Flutter Module 的 main.dart 文件裡面,對於存在多個頁面的情況,我們可以寫下面的模板代碼:

import 'dart:ui';
import 'package:flutter/material.dart';
void main() => runApp(_widgetForRoute(window.defaultRouteName));
Widget _widgetForRoute(String route) {
switch (route) {
case 'route1':
return SomeWidget(...);
case 'route2':
return SomeOtherWidget(...);

default:
return Center(
child: Text('Unknown route: $route', textDirection: TextDirection.ltr),
);
}
}

這段代碼我們可以重點關注 switch 那一塊代碼。這裡會根據不同的路由,返回不同的頁面。

下面我們會用到這種寫法。

實際案例

接下來我們通過實際案例來說明如何實現 Flutter 向原生髮送消息?

我們的案例是假設我要獲取 Android 設備的當前電量,我希望點擊按鈕之後電量會顯示出來。

當然這裡的按鈕和顯示電量的文本都是 Flutter 界面的。

那麼步驟是怎樣的呢?

1. 搭建 Flutter 界面

我們將界面寫成一個單獨的 battery_widget.dart 文件:

import 'package:flutter/material.dart';
class BatteryWidget extends StatefulWidget {

@override
_BatteryWidgetState createState() => _BatteryWidgetState();
}
class _BatteryWidgetState extends State<batterywidget> {
String _batteryLevel = 'Battery level: unknown.';
void _getBatteryLevel() {}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <widget>[
Text(_batteryLevel),
RaisedButton(
child: const Text('Refresh'),
onPressed: _getBatteryLevel,
),
],
),
);
}
}
/<widget>/<batterywidget>

很簡單的界面,就是一個文本和一個按鈕,排成一列。

然後我們 main.dart 修改如下:

import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:my_flutter/battery_widget.dart';
void main() => runApp(_widgetForRoute(window.defaultRouteName));
Widget _widgetForRoute(String route) {
switch (route) {
case 'battery':
return MaterialApp(
home: Scaffold(
body: BatteryWidget(),
),
);
default:
return MaterialApp(
home: Scaffold(
body: Container(),
),
);
}

}

這裡的關鍵點是指定 route 名字為 battery 時,返回我們剛剛新建的 battery_widget 界面。

2. 原生調用 Flutter 界面

在 MainActivity.java 裡面,我們寫出下面代碼:

package com.nesger.flutterdemo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import io.flutter.facade.Flutter;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View flutterView = Flutter.createView(
MainActivity.this,
getLifecycle(),
"battery"
);
FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
addContentView(flutterView, layout);
}
}

可以看到 battery 指定了要加載的 Flutter 界面。

運行後效果如下:

Flutter即學即用系列博客—8MethodChannel實現Flutter與原生通信

接下來就是關鍵的在點擊按鈕的時候如何獲取原生設備電量。

根據上面的代碼,我們知道點擊按鈕會執行 _getBatteryLevel 方法。因此我們要在這裡做一些修改。

3. Flutter 定義 MethodChannel

我們在 _BatteryWidgetState 裡面加入下面變量:

static const MethodChannel methodChannel = MethodChannel('samples.flutter.io/battery');

samples.flutter.io/battery 可以自己指定,一般保證唯一,所以 samples 實際使用可以替換為包名。主要是要跟原生對應即可。

4. Flutter 調用 methodChannel API invokeMethod 調用原生某個方法並獲取對應的值。

final int result = await methodChannel.invokeMethod('getBatteryLevel');

比如我們這裡要通過原生的 getBatteryLevel 方法獲取到對應的電量,並將返回值用 result 保存。

這裡的 await 是因為這個操作是異步的。同時 _getBatteryLevel 也要改為對應的異步方法,因此最終方法代碼如下:

Future<void> _getBatteryLevel() async {
String batteryLevel;
try {
final int result = await methodChannel.invokeMethod('getBatteryLevel');
batteryLevel = 'Battery level: $result%.';
} on PlatformException {
batteryLevel = 'Failed to get battery level.';
}
setState(() {
_batteryLevel = batteryLevel;
});
}
/<void>

可以看到通過異步方法獲取到電量之後通過 setState 方法更新界面。

5. 原生定義 MethodChannel

private static final String BATTERY_CHANNEL = "samples.flutter.io/battery";

注意需要跟 Flutter 的一一對應。

6. 原生調用創建 MethodChannel 並通過 MethodCallHandler 接收 Flutter 的方法調用

new MethodChannel((FlutterView)flutterView, BATTERY_CHANNEL).setMethodCallHandler(
new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(MethodCall call, MethodChannel.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();
}
}
}

);

可以看到我們是通過 call.method 來區分 Flutter 的不同方法調用。

這裡 result.success 返回成功回調。 result.error 返回錯誤回調。result.notImplemented 表明沒有對應實現。

最後我們實現原生 getBatteryLevel 方法即可。

如下:

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

運行點擊按鈕,效果如下:

Flutter即學即用系列博客—8MethodChannel實現Flutter與原生通信

到此我們 Flutter 調用原生並獲取返回值的方法就介紹完了。

這裡我們總結如下:

Flutter 準備工作:

  • 定義 MethodChannel
  • 通過異步方法調用 methodChannel 的 invokeMethod 指定這個 methodChannel 具體要調用的方法名

原生準備工作:

  • 定義 CHANNEL(與 Flutter 對應)
  • 創建 MethodChannel 並通過 setMethodCallHandler 方法來區分 Flutter 的不同調用方法名和返回對應的回調

源碼位置:

https://github.com/nesger/FlutterSample/tree/feature/method_channel

參考鏈接:

https://flutter.dev/docs/development/platform-integration/platform-channels

https://github.com/flutter/flutter/tree/master/examples/platform_channel


分享到:


相關文章: