Flutter - 编写 Android 特定代码
Flutter - 编写 Android 平台特定代码
Section titled “Flutter - 编写 Android 平台特定代码”虽然 Flutter 允许您从单一代码库构建跨平台 UI,但有时您需要访问原生平台功能(如设备传感器、特定的操作系统服务或现有的原生库)。Flutter 为此提供了平台通道 (Platform Channels)。
平台通道架构
Section titled “平台通道架构”平台通道实现了您的 Dart 代码 (Client) 与原生平台代码 (Host - Android/Java/Kotlin 或 iOS/ObjC/Swift) 之间的通信。
- Message Channel (消息通道): 在 Dart 端和原生端之间建立的命名通道。
- Method Calls (方法调用): Dart 代码在通道上调用方法,并可选择传递参数。
- Message Codec (消息编解码器): 使用标准编解码器 (
StandardMethodCodec是常用的,支持数字、字符串、布尔值、列表、映射等基本类型) 自动对数据(参数和结果)进行序列化/反序列化。 - Host Implementation (宿主实现): 原生代码监听同一通道名称上的方法调用,执行所需的平台特定逻辑,然后将结果(成功或错误)发送回 Dart。
- Async Communication (异步通信): 通信是异步的,使用 Dart 的
Future对象,防止在等待原生代码执行时 UI 冻结。
可以将其想象成通过一条由唯一名称标识的特定通信线路来回发送消息。
示例:在原生浏览器中打开 URL (Android)
Section titled “示例:在原生浏览器中打开 URL (Android)”让我们创建一个带按钮的应用程序,该按钮使用 Android 的原生 Intent 系统打开一个网页 URL。(注意:对于此特定任务,url_launcher package 是推荐的更简单的方法。此示例是为了演示手动实现平台通道的机制。)
- 创建项目: 创建一个新的 Flutter 应用程序:
flutter create android_platform_channel_app。 - Dart 代码 (
lib/main.dart):
import 'package:flutter/material.dart';import 'package:flutter/services.dart'; // 平台通道所需import 'dart:async';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key);
@override Widget build(BuildContext context) { return MaterialApp( title: 'Platform Channel Demo', home: const MyHomePage(title: 'Android Platform Channel'), ); }}
class MyHomePage extends StatelessWidget { const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
// 1. 定义 MethodChannel // 使用唯一的名称,通常采用反向域名表示法 static const platform = MethodChannel('com.example.android_platform_channel_app/browser');
// 2. 调用原生代码的方法 Future<void> _openBrowser(String url) async { try { // 使用 invokeMethod 调用原生端期望的方法名 // 以 Map 形式传递参数 final bool result = await platform.invokeMethod('openBrowser', {'url': url}); if (result) { print('Browser opened successfully (Android response)'); } else { print('Failed to open browser (Android response)'); } } on PlatformException catch (e) { // 处理来自原生端的错误 print("Failed to open browser: '${e.message}'."); } catch (e) { // 处理其他潜在错误 print("An error occurred: $e"); } }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( // 用 ElevatedButton 替换已废弃的 RaisedButton child: ElevatedButton( child: const Text('Open Flutter Dev'), onPressed: () { // 3. 按钮按下时调用方法 _openBrowser('https://flutter.dev'); }, ), ), ); }}- Android 原生代码 (Kotlin - 推荐): 打开
android/app/src/main/kotlin/com/your_org/your_app/MainActivity.kt(路径可能略有不同)。
package com.example.android_platform_channel_app // 使用你的包名
import androidx.annotation.NonNullimport io.flutter.embedding.android.FlutterActivityimport io.flutter.embedding.engine.FlutterEngineimport io.flutter.plugin.common.MethodChannelimport android.content.Intentimport android.net.Uriimport android.os.Bundleimport android.util.Log
class MainActivity: FlutterActivity() { // 1. 定义与 Dart 中相同的通道名称 private val CHANNEL = "com.example.android_platform_channel_app/browser"
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine)
// 2. 设置 MethodChannel MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { // 当 Dart 调用 invokeMethod 时会调用此 lambda 函数 call, result ->
// 3. 检查哪个方法被调用了 if (call.method == "openBrowser") { // 4. 提取参数 val url = call.argument<String>("url") if (url != null) { // 5. 调用原生函数 val success = openBrowser(url) // 6. 将结果发送回 Dart result.success(success) } else { result.error("INVALID_ARGUMENT", "URL cannot be null.", null) } } else { // 表示原生端没有实现该方法 result.notImplemented() } } }
// 使用 Intent 打开浏览器的原生函数 private fun openBrowser(url: String): Boolean { return try { val intent = Intent(Intent.ACTION_VIEW) intent.data = Uri.parse(url) // 确保 context 不为空 - activity 应该可用 activity?.startActivity(intent) true // 表示成功 } catch (e: Exception) { Log.e("MainActivity", "Error opening browser", e) false // 表示失败 } }}- (备选) Android 原生代码 (Java): 如果使用 Java,打开
android/app/src/main/java/com/your_org/your_app/MainActivity.java。
package com.example.android_platform_channel_app; // 使用你的包名
import androidx.annotation.NonNull;import io.flutter.embedding.android.FlutterActivity;import io.flutter.embedding.engine.FlutterEngine;import io.flutter.plugin.common.MethodChannel;import android.content.Intent;import android.net.Uri;import android.os.Bundle;import android.util.Log;
public class MainActivity extends FlutterActivity { // 1. 定义与 Dart 中相同的通道名称 private static final String CHANNEL = "com.example.android_platform_channel_app/browser";
@Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine);
// 2. 设置 MethodChannel new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL) .setMethodCallHandler( (call, result) -> { // 当 Dart 调用 invokeMethod 时会调用此 lambda 函数 // 3. 检查哪个方法被调用了 if (call.method.equals("openBrowser")) { // 4. 提取参数 String url = call.argument("url"); if (url != null) { // 5. 调用原生函数 boolean success = openBrowser(url); // 6. 将结果发送回 Dart result.success(success); } else { result.error("INVALID_ARGUMENT", "URL cannot be null.", null); } } else { // 表示原生端没有实现该方法 result.notImplemented(); } } ); }
// 使用 Intent 打开浏览器的原生函数 private boolean openBrowser(String url) { try { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(url)); // 使用 getActivity() 获取 context getActivity().startActivity(intent); return true; // 表示成功 } catch (Exception e) { Log.e("MainActivity", "Error opening browser", e); return false; // 表示失败 } }}- 运行应用程序: 停止并重新启动应用程序(热重载可能不会加载原生代码更改)。点击 ‘Open Flutter Dev’ 按钮。它应该会在您的 Android 设备/模拟器上启动默认浏览器并导航到
https://flutter.dev。
重要注意事项
Section titled “重要注意事项”- 错误处理 (Error Handling): 始终将 Dart 的
invokeMethod调用放在try-catch块中,捕获PlatformException,并在原生端处理潜在错误。 - 唯一性 (Uniqueness): 通道名称在您的应用程序中必须是唯一的。
- 数据类型 (Data Types): 确保您传递和接收的数据类型受所选的
MethodCodec(通常是StandardMethodCodec)支持。 - 线程安全 (Thread Safety - Android): 平台通道方法默认在 Android 主线程上执行。长时间运行的任务应在后台线程上执行,以避免阻塞 UI。
- 软件包 (Packages): 在编写自定义平台通道之前,请检查 pub.dev (例如,查找
url_launcher、camera、battery_plus),因为可能已经存在提供所需功能的软件包。