Flutter 调用原生(Android)方法以及数据传输

前言

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调用原生的两种方法。

参考

撰写双端平台代码(插件编写实现) - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter

 flutter插件基础之调用EventChannel的简单使用(三)_XiaoBaiGeYHS的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/TDSSS/article/details/129248396