前言
flutter是一个UI框架,有许多方法和功能只能靠原生自己来调用,但是我们怎么通过flutter去间接调用呢?官方给出了两种方法
一、通过平台通道(channel)传递消息
1、单向调用方法
在平台通道之间进行消息传递:
注:消息和响应以异步的形式进行传递,以确保用户界面能够保持响应。
平台数据类型对应
flutter端:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel('com.tdsss.get/battery');
String _batteryLevel = "Unknown battery level";
Future<void> _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;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(onPressed: _getBatteryLevel,
child: const Text('get Battery'),
),
Text(_batteryLevel),
],
),
),// This trailing comma makes auto-formatting nicer for build methods.
);
}
}
然后找到android工程,打开MainActivity,点击右上角的Open for Editing in Android Studio,新打开一个窗口在Andorid的环境中编辑
Android端:
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build;
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.tdsss.get/battery";
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(),CHANNEL)
.setMethodCallHandler(
(call,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();
}
}
);
}
private int getBatteryLevel() {
int batteryLevel = -1;
BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
return batteryLevel;
}
}
注意!注意!注意!这里的平台通道名必须是两边保持一致的,即"com.tdsss.get/battery"
这个通道名是自定义的
然后回到flutter工程中,在手机上运行如下图所示:
2、通过EventChannel传递事件流,持续数据通信
在业务逻辑中,有时候并不仅仅是调用一次方法就能解决的,有一些功能需要我们持续监听原生的一些数据,这时我们可以用Stream来发送事件,只要原生端触发了需要的数据,flutter就能监听到。
比如在flutter端持续监听Android端的计步传感器及其点击音量键的事件,这里两种通道都用到了,methodChannel只是用来检查安卓10以上的计步权限
dart端 :
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class CountStepPage extends StatefulWidget {
const CountStepPage({Key? key}) : super(key: key);
@override
State<CountStepPage> createState() => _CountStepPageState();
}
class _CountStepPageState extends State<CountStepPage> {
int steps = 0;
int time = 0;
int count = 0;
var nowVoice;
//method通信
static const _permissionsChannel = MethodChannel('com.tdsss.permissions');
//event通信
static const _eventChannel = EventChannel('com.tdsss.step');
@override
void initState() {
// TODO: implement initState
super.initState();
//初始化通信
try {
//请求权限
_permissionsChannel.invokeMethod('getStepPermission');
} on PlatformException catch (e) {
print(e);
}
_eventChannel.receiveBroadcastStream().listen((event) {
print('event : $event');
print('event type: ${event is Map}');
final Map<dynamic,dynamic> map = event;
for(String key in map.keys){
switch(key){
case 'changeVoice':
setState(() {
nowVoice = map["changeVoice"];
});
break;
case 'step':
setState(() {
steps = map["step"];
});
break;
case 'time':
setState(() {
time = map["time"];
});
break;
}
}
},onError: onError,onDone:onDone,);
}
void onError(error){
print('event error: $error');
}
void onDone(){
print('event done:');
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
Text('times : $time'),
Text('steps : $steps'),
Text('voice : $nowVoice'),
],
),
),
);
}
}
Android端:
import android.Manifest;
import android.app.Service;
import android.content.pm.PackageManager;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class MainActivity extends FlutterActivity {
public static final String[] STEP_PERMISSION = {Manifest.permission.ACTIVITY_RECOGNITION};
//事件派发对象
private EventChannel.EventSink eventSink = null;
//事件派发流
private EventChannel.StreamHandler streamHandler = new EventChannel.StreamHandler() {
@Override
public void onListen(Object arguments, EventChannel.EventSink events) {
eventSink = events;
}
@Override
public void onCancel(Object arguments) {
eventSink = null;
}
};
SensorManager sensorManager;
Sensor stepSensor;
boolean isRegistered = false;
int steps;
int time = 0;
AudioManager audioManager;
int mediaVoice;
Map map = new HashMap();
String key = "changeVoice";
String stepKey = "step";
String timeKey = "time";
String TAG = "flutter";
MethodChannel permissionChannel;
MethodChannel.MethodCallHandler permissionHandler = new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
if (call.method.equals(Config.getPerMethodName)){
checkPermission(result);
}
}
};
EventChannel eventChannel;
SensorEventListener sensorEventListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
steps = (int) event.values[0];
map.put(stepKey,steps);
sendEventData(map);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
permissionChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(),"com.tdsss.permissions");
permissionChannel.setMethodCallHandler(permissionHandler);
eventChannel = new EventChannel(flutterEngine.getDartExecutor().getBinaryMessenger(),"com.tdsss.step");
eventChannel.setStreamHandler(streamHandler);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode){
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_DOWN:
mediaVoice = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
map.put(key,mediaVoice);
sendEventData(map);
Log.e("flutter", "onKeyDown mediaVoice: "+mediaVoice );
return false;
}
return super.onKeyDown(keyCode, event);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
audioManager = (AudioManager) getSystemService(Service.AUDIO_SERVICE);
mediaVoice = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
map.put("changeVoice",mediaVoice);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
time++;
map.put(timeKey,time);
//sendEventData(map);
}
},0,1000);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (isRegistered){
sensorManager.unregisterListener(sensorEventListener);
}
}
private void sendEventData(Object data){
if (eventSink != null){
Log.i(TAG, "sendEventData: ");
eventSink.success(data);
}
}
private void checkPermission(@NonNull MethodChannel.Result result){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// 检查该权限是否已经获取
int get = ContextCompat.checkSelfPermission(getApplicationContext(), STEP_PERMISSION[0]);
// 权限是否已经 授权 GRANTED---授权 DINIED---拒绝
if (get != PackageManager.PERMISSION_GRANTED) {
requestPermissions(STEP_PERMISSION,101);
result.success(1);
}else{
result.success(0);
initSensor();
//result.error("STEP_PERMISSION","isGranted",0);
}
}
}
private void initSensor(){
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
stepSensor =sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
if (stepSensor != null) {
isRegistered = sensorManager.registerListener(sensorEventListener,stepSensor,SensorManager.SENSOR_DELAY_FASTEST);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case 101:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
initSensor();
Log.i(TAG, "onRequestPermissionsResult: allowed");
}else{
Log.i(TAG, "onRequestPermissionsResult: false");
}
return;
}
}
}
最后结果如下:
二、利用Pigeon插件
官方推荐这种方法,因为利用这个插件是类型安全的,在在 Pigeon 中,消息接口在 Dart 中进行定义,然后它将生成对应的 Android 以及 iOS 的代码。
首先在项目依赖文件pubspec.yaml中加入插件依赖:
dependencies: #调用双平台API插件 pigeon: ^9.0.1
这个方法步骤比较繁琐,而且官方给的例子不是太明确,这里我用flutter简单获取android中的一段字符串来举例。
首先,在flutter工程中新写一个抽象类,并标明注解为@HostApi,说明这是从flutter去调用android的方法,要在android端实现。
pigeon_get.dart:
import 'package:pigeon/pigeon.dart';
@HostApi()
abstract class AndroidApi {
String getString();
}
然后在工程根目录下打开控制台,或是在IDE中打开终端,运行指令,自动生成代码。
input的文件是刚刚我们新建的那个dart文件,里面包含了这个AndroidApi构造方法
dart_out就是自动生成dart端的代码生成到哪里并命名
java_out 就是在Android端用java语言生成这个构造方法的位置并命名
java_package就是应用的包名
flutter pub run pigeon --input lib/pigeon_get.dart --dart_out lib/pigeon_g.dart --java_out android/app/src/main/java/com/tdsss/found/food/found_food/StepPigeon.java --java_package "com.tdsss.found.food.found_food"
工程目录如下:
可以看到我们生成的2个文件都已经出现了,打开StepPigeon.java文件可以看到,我们在dart端创建的抽象类现在变成了一个接口。
现在我们先去Android端把这个AndroidApi实现
MainActivity.java:
public class MainActivity extends FlutterActivity {
private class MyApi implements StepPigeon.AndroidApi{
@NonNull
@Override
public String getString() {
return "this is Android String!!";
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
StepPigeon.AndroidApi.setup(
getFlutterEngine().getDartExecutor().getBinaryMessenger(),new MyApi()
);
}
}
最后回到flutter端,找一个页面调用这个方法。
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:found_food/pigeon_g.dart';
class CountStepPage extends StatefulWidget {
const CountStepPage({Key? key}) : super(key: key);
@override
State<CountStepPage> createState() => _CountStepPageState();
}
class _CountStepPageState extends State<CountStepPage> {
String androidS = '';
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
ElevatedButton(onPressed: _getString, child: Text('string : $androidS')),
],
),
),
);
}
_getString() async{
AndroidApi api = AndroidApi();
String s = await api.getString();
setState(() {
androidS = s;
});
print(androidS);
}
}
运行结果如下:
至此!我们就完成了flutter调用原生的两种方法。