Flutter与Python交互插件python_channel的使用

Flutter与Python交互插件python_channel的使用

通过此插件,开发者可以在Windows上使用Python编写Flutter应用程序。

要求

  • Python +3.7
  • flutter_channel Python包,可从PyPi安装。

如何工作?

该插件依赖于Unix套接字(TCP)协议。

如何使用?

1. 绑定主机

首先,你需要调用PythonChannelPlugin.bindHost()方法绑定主机。此方法接受四个参数:namedebugPyPathdebugExePathreleasePath。你可以绑定多个主机。

  • name 是主机的唯一名称。
  • debugPyPath 是主Python文件的绝对路径。如果未设置debugExePath,插件将使用它来启动调试模式。
  • debugExePath 是主可执行文件的绝对路径。你需要使用它来调试编译后的Python文件。
  • releasePath 是主可执行文件的相对路径。在发布模式下,你必须将编译后的可执行文件放在与应用文件相同的目录中。
主机示例
PythonChannelPlugin.bindHost(
  name: 'host',
  debugExePath: 'E:\\projects\\python_channel\\flutter_channel\\dist\\example.exe',
  debugPyPath: 'E:\\projects\\python_channel\\flutter_channel\\example.py',
  releasePath: 'main.exe',
);
发布模式下可执行文件的放置位置
release-app/
  data/
  app.exe # Flutter应用的可执行文件
  main.exe # 从Python编译的可执行文件
  ...

2. 创建通道并绑定到主机

有几种内置的通道类型可以使用,如BytesChannelJsonChannelStringChannelMethodChannel。创建通道后,你需要使用PythonChannelPlugin.bindChannel()方法将其绑定到主机。此方法接受两个参数:name(主机名称)和channel(通道)。

通道示例
BytesChannel bytesChannel = BytesChannel(name: 'channel1');
StringChannel stringChannel = StringChannel(name: 'channel2');
JsonChannel jsonChannel = JsonChannel(name: 'channel3');
MethodChannel methodChannel = MethodChannel(name: 'channel4');

// 绑定通道
PythonChannelPlugin.bindChannel('host', bytesChannel);
PythonChannelPlugin.bindChannel('host', stringChannel);
PythonChannelPlugin.bindChannel('host', jsonChannel);
PythonChannelPlugin.bindChannel('host', methodChannel);

3. 设置通道处理器

通道处理器是一个函数,用于接收发送到通道的消息。每个处理器应接受两个参数:messagereply

  • message 是接收到的消息,其类型取决于通道类型。
  • reply 是一个Reply对象,你应该使用它来回复接收到的消息。你可以回复另一个消息或回复null
处理器示例
stringChannel.setHandler(
  (message, reply) {
    print(message);
    reply.reply('hi python');
  },
);

4. 发送消息

你可以使用通道对象的send方法发送消息。此方法接受一个参数msg,其类型取决于通道类型。

  • BytesChannel 接受Uint8List
  • StringChannel 接受String
  • JsonChannel 接受Object
  • MethodChannel 接受MethodCall

此方法是一个Future,会一直等待直到回复到来。

发送示例
stringChannel.send('hello world').then((reply) => print(reply));

methodChannel.invokeMethod('sayHello', {"name": 'ghale'}).then((reply) => print(reply));

5. 获取通道

你可以使用PythonChannelPlugin.getChannel()方法从任何地方访问已绑定的通道。此方法接受两个参数:hostNamechannelName

获取通道示例
MethodChannel helloChannel = PythonChannelPlugin.getChannel('sayHello', 'sayHi') as MethodChannel;

6. 解绑主机

你可以通过调用PythonChannelPlugin.unbindHost()方法解绑主机。此方法接受一个参数,即主机名称。当解绑主机时,主机进程及其所有通道将被终止。

解绑主机示例
PythonChannelPlugin.unbindHost('sayHello');

MethodChannel 注意事项

1. 捕获异常

如果Python端的通道处理器抛出PythonChannelMethodException,你可以在Dart端捕获它。

methodChannel.invokeMethod('sayHello', {"name": 'ghale'})
  .then((reply) => print(reply))
  .catchError((e) {});

// 或者
try {
  await methodChannel.invokeMethod('sayHello', {"name": 'ghale'});
} catch (e) {}

2. 在处理器中抛出异常

你可以在处理器中抛出PythonChannelMethodException。此异常将通过通道发送,并传递给Python端的回调函数的第二个参数。

自定义通道类型

你可以通过写一个继承自Channel泛型类的类来创建自己的通道类型。你需要实现以下四个方法:

  • encodeInput 将通道输入从Uint8List转换为其他格式
  • encodeOutput 将通道输出从Uint8List转换为其他格式
  • decodeInput 将通道输入从其他格式转换为Uint8List
  • decodeOutput 将通道输出从其他格式转换为Uint8List

输入是通道发送的数据,输出是通道接收的数据。

发布模式

在发布模式下,你需要将主Python文件编译为可执行文件。我们推荐使用PyInstaller。注意,你需要以控制台方式构建可执行文件,否则包将无法正常工作。

完整示例Demo

import 'package:flutter/material.dart';
import 'package:python_channel/python_channel.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  PythonChannelPlugin.bindHost(
      name: 'calculator',
      // debugExePath:
      //     'E:\\projects\\python_channel\\flutter_channel\\dist\\calculator.exe',
      debugPyPath:
          'E:\\projects\\python_channel\\flutter_channel\\calculator-example.py',
      releasePath: 'calculator.exe');
  PythonChannelPlugin.bindHost(
      name: 'sayHello',
      // debugExePath:
      // 'E:\\projects\\python_channel\\flutter_channel\\dist\\sayHello.exe',
      debugPyPath:
          'E:\\projects\\python_channel\\flutter_channel\\sayHello-example.py',
      releasePath: 'sayHello.exe');
  MethodChannel calculatorChannel = MethodChannel(name: 'ch');
  PythonChannelPlugin.bindChannel('calculator', calculatorChannel);
  MethodChannel helloChannel = MethodChannel(name: 'sayHi');
  PythonChannelPlugin.bindChannel('sayHello', helloChannel);
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Home(),
    );
  }
}

class Home extends StatefulWidget {
  const Home({super.key});

  [@override](/user/override)
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  late final MethodChannel calculatorChannel;
  late final MethodChannel helloChannel;
  String res = '';
  String hi = '';
  bool binded = true;
  final TextEditingController controller1 = TextEditingController(),
      controller2 = TextEditingController(),
      controller3 = TextEditingController();

  [@override](/user/override)
  void initState() {
    super.initState();

    calculatorChannel =
        PythonChannelPlugin.getChannel('calculator', 'ch') as MethodChannel;
    helloChannel =
        PythonChannelPlugin.getChannel('sayHello', 'sayHi') as MethodChannel;
  }

  void getResault(String op) async {
    double res = await calculatorChannel.invokeMethod(op, [
      double.parse(controller1.text),
      double.parse(controller2.text)
    ]) as double;
    setState(() {
      this.res = res.toString();
    });
  }

  void getHello() async {
    var msg =
        await helloChannel.invokeMethod('sayHello', {'name': controller3.text});
    setState(() {
      hi = msg.toString();
    });
  }

  void unbind() {
    PythonChannelPlugin.unbindHost('sayHello');

    setState(() {
      binded = false;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          width: 400,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              TextField(
                controller: controller1,
                decoration: const InputDecoration(hintText: 'number 1'),
              ),
              TextField(
                controller: controller2,
                decoration: const InputDecoration(hintText: 'number 1'),
              ),
              Text(
                res,
                style: const TextStyle(fontSize: 27),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  TextButton(
                      onPressed: () => getResault('add'),
                      child: const Text(
                        '+',
                        style: TextStyle(fontSize: 25),
                      )),
                  TextButton(
                      onPressed: () => getResault('sub'),
                      child: const Text(
                        '-',
                        style: TextStyle(fontSize: 25),
                      )),
                  TextButton(
                      onPressed: () => getResault('mul'),
                      child: const Text(
                        '×',
                        style: TextStyle(fontSize: 25),
                      )),
                  TextButton(
                      onPressed: () => getResault('div'),
                      child: const Text(
                        '÷',
                        style: TextStyle(fontSize: 25),
                      )),
                ],
              ),
              binded
                  ? Column(
                      children: [
                        TextField(
                          controller: controller3,
                          decoration:
                              const InputDecoration(hintText: 'your name'),
                        ),
                        Text(
                          hi,
                          style: const TextStyle(fontSize: 27),
                        ),
                        TextButton(
                            onPressed: getHello,
                            child: const Text(
                              'say hi',
                              style: TextStyle(fontSize: 25),
                            )),
                        const SizedBox(
                          height: 16,
                        ),
                        ElevatedButton(
                            onPressed: unbind, child: const Text('unbind'))
                      ],
                    )
                  : const SizedBox()
            ],
          ),
        ),
      ),
    );
  }
}

更多关于Flutter与Python交互插件python_channel的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter与Python交互插件python_channel的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter应用中与Python进行交互的示例,使用python_channel插件。需要注意的是,python_channel并非一个官方或广泛认知的Flutter插件,因此这里的示例将基于假设的插件机制和原理来展示如何设置这种交互。在真实场景中,你可能需要找到一个合适的第三方插件或自行实现一个类似的机制。

假设我们有一个python_channel插件,它允许Flutter与Python脚本进行通信。以下是一个简化的示例,展示如何在Flutter端发送消息到Python,并接收Python的响应。

1. 设置Flutter项目

首先,确保你的Flutter环境已经设置好,然后创建一个新的Flutter项目:

flutter create flutter_python_interaction
cd flutter_python_interaction

2. 添加依赖(假设的python_channel插件)

pubspec.yaml中添加对python_channel插件的依赖(注意:这个插件是假设的,你需要找到一个真实可用的插件或自行实现):

dependencies:
  flutter:
    sdk: flutter
  python_channel: ^0.1.0  # 假设的版本号

然后运行flutter pub get来安装依赖。

3. 在Flutter中配置PythonChannel

打开lib/main.dart文件,并配置PythonChannel以与Python脚本进行通信:

import 'package:flutter/material.dart';
import 'package:python_channel/python_channel.dart';  // 假设的导入路径

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  PythonChannel? _pythonChannel;
  String _responseFromPython = '';

  @override
  void initState() {
    super.initState();
    // 初始化PythonChannel
    _pythonChannel = PythonChannel();
    _pythonChannel!.setMethodCallHandler((call) async {
      if (call.method == 'sendMessageFromPython') {
        setState(() {
          _responseFromPython = call.arguments as String;
        });
      }
    });

    // 发送消息到Python
    _sendMessageToPython();
  }

  Future<void> _sendMessageToPython() async {
    try {
      await _pythonChannel!.invokeMethod('sendMessageFromFlutter', 'Hello from Flutter!');
    } catch (e) {
      print('Failed to invoke: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Python Interaction'),
        ),
        body: Center(
          child: Text('Response from Python: $_responseFromPython'),
        ),
      ),
    );
  }
}

4. 配置Python端

假设你有一个Python脚本server.py,它监听来自Flutter的消息并发送响应:

import json
import socket

# 假设Flutter通过TCP与Python通信
HOST = '127.0.0.1'  # 本地主机
PORT = 65432        # 任意非特权端口

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print('Connected by', addr)
        while True:
            data = conn.recv(1024)
            if not data:
                break
            message = json.loads(data.decode('utf-8'))
            print(f'Received message from Flutter: {message}')
            
            # 发送响应回Flutter
            response = {'message': 'Hello from Python!'}
            conn.sendall(json.dumps(response).encode('utf-8'))

5. 启动Python服务器

在命令行中运行Python脚本以启动服务器:

python server.py

6. 运行Flutter应用

确保Python服务器正在运行,然后在Flutter项目中运行应用:

flutter run

注意

  • 上面的示例假设了一个TCP通信机制,而python_channel插件可能通过其他机制(如HTTP、WebSocket、FFI等)进行通信。
  • 你需要找到或实现一个真正能够与Flutter通信的Python插件或机制。
  • 本示例中的代码是为了说明目的而简化的,实际生产环境中需要更多的错误处理和健壮性考虑。

希望这个示例能帮助你理解如何在Flutter中与Python进行交互。如果有具体的插件或机制,请查阅其官方文档以获取更详细的实现指南。

回到顶部