Android 原生与 Flutter 通信完整实现 (Kotlin 版)

1. 项目配置

pubspec.yaml 添加依赖

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.5

2. Flutter 端实现

状态管理类

dart 复制代码
// settings_provider.dart
import 'package:flutter/foundation.dart';

class SettingsProvider with ChangeNotifier {
  String _themeColor = 'blue';
  bool _darkMode = false;
  
  String get themeColor => _themeColor;
  bool get darkMode => _darkMode;
  
  void updateSettings(String color, bool dark) {
    _themeColor = color;
    _darkMode = dark;
    notifyListeners();
  }
}

主界面

dart 复制代码
// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => SettingsProvider(),
      child: const MyApp(),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: const MainPage(),
    );
  }
}

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

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  static const platform = MethodChannel('com.example.app/settings');
  
  @override
  void initState() {
    super.initState();
    _initMethodChannel();
  }

  void _initMethodChannel() {
    platform.setMethodCallHandler((call) async {
      if (call.method == 'updateSettings') {
        final args = call.arguments as Map<dynamic, dynamic>;
        Provider.of<SettingsProvider>(context, listen: false).updateSettings(
          args['color'] as String,
          args['darkMode'] as bool,
        );
      }
      return null;
    });
  }

  @override
  Widget build(BuildContext context) {
    final settings = Provider.of<SettingsProvider>(context);
    
    return Scaffold(
      appBar: AppBar(
        title: const Text('主界面'),
        backgroundColor: _getColor(settings.themeColor),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              '当前主题色: ${settings.themeColor}',
              style: TextStyle(
                color: settings.darkMode ? Colors.white : Colors.black,
              ),
            ),
            const SizedBox(height: 20),
            Text(
              '暗黑模式: ${settings.darkMode ? "开启" : "关闭"}',
              style: TextStyle(
                color: settings.darkMode ? Colors.white : Colors.black,
              ),
            ),
            const SizedBox(height: 40),
            ElevatedButton(
              onPressed: _openNativeSettings,
              child: const Text('打开原生设置页面'),
            ),
          ],
        ),
      ),
      backgroundColor: settings.darkMode ? Colors.grey[800] : Colors.white,
    );
  }

  Color _getColor(String colorName) {
    return switch (colorName) {
      'red' => Colors.red,
      'green' => Colors.green,
      _ => Colors.blue,
    };
  }

  Future<void> _openNativeSettings() async {
    try {
      final settings = Provider.of<SettingsProvider>(context, listen: false);
      await platform.invokeMethod('openSettings', {
        'color': settings.themeColor,
        'darkMode': settings.darkMode,
      });
    } on PlatformException catch (e) {
      debugPrint("打开设置失败: ${e.message}");
    }
  }
}

3. Android 原生端实现 (Kotlin)

MainActivity.kt

kotlin 复制代码
package com.example.myapp

import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity : FlutterActivity() {
    private val CHANNEL = "com.example.app/settings"
    private var latestSettings: Map<String, Any>? = null

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        
        MethodChannel(flutterEngine.dartExecutor, CHANNEL).setMethodCallHandler { call, result ->
            when (call.method) {
                "openSettings" -> {
                    val color = call.argument<String>("color") ?: "blue"
                    val darkMode = call.argument<Boolean>("darkMode") ?: false
                    
                    Intent(this, SettingsActivity::class.java).apply {
                        putExtra("color", color)
                        putExtra("darkMode", darkMode)
                        startActivityForResult(this, 101)
                    }
                    result.success(null)
                }
                else -> result.notImplemented()
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 101 && resultCode == RESULT_OK) {
            data?.extras?.let { extras ->
                val settings = mapOf(
                    "color" to extras.getString("color", "blue"),
                    "darkMode" to extras.getBoolean("darkMode", false)
                )
                latestSettings = settings
                
                Handler(Looper.getMainLooper()).post {
                    MethodChannel(flutterEngine?.dartExecutor, CHANNEL).invokeMethod(
                        "updateSettings", 
                        settings
                    )
                }
            }
        }
    }
}

SettingsActivity.kt

kotlin 复制代码
package com.example.myapp

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.myapp.databinding.ActivitySettingsBinding

class SettingsActivity : AppCompatActivity() {
    private lateinit var binding: ActivitySettingsBinding
    private var selectedColor = "blue"
    private var darkMode = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivitySettingsBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // 获取初始值
        selectedColor = intent.getStringExtra("color") ?: "blue"
        darkMode = intent.getBooleanExtra("darkMode", false)

        // 初始化UI状态
        when (selectedColor) {
            "red" -> binding.colorGroup.check(R.id.red)
            "green" -> binding.colorGroup.check(R.id.green)
            else -> binding.colorGroup.check(R.id.blue)
        }
        binding.darkModeSwitch.isChecked = darkMode

        // 保存按钮点击
        binding.saveButton.setOnClickListener {
            selectedColor = when (binding.colorGroup.checkedRadioButtonId) {
                R.id.red -> "red"
                R.id.green -> "green"
                else -> "blue"
            }
            darkMode = binding.darkModeSwitch.isChecked

            Intent().apply {
                putExtra("color", selectedColor)
                putExtra("darkMode", darkMode)
                setResult(RESULT_OK, this)
            }
            finish()
        }
    }
}

activity_settings.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="主题颜色"
        android:textSize="18sp"/>

    <RadioGroup
        android:id="@+id/colorGroup"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <RadioButton
            android:id="@+id/red"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="红色"/>

        <RadioButton
            android:id="@+id/green"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="绿色"
            android:layout_marginStart="16dp"/>

        <RadioButton
            android:id="@+id/blue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="蓝色"
            android:layout_marginStart="16dp"/>
    </RadioGroup>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginTop="24dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="暗黑模式"
            android:textSize="18sp"/>

        <Switch
            android:id="@+id/darkModeSwitch"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"/>
    </LinearLayout>

    <Button
        android:id="@+id/saveButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="保存设置"
        android:layout_marginTop="32dp"/>
</LinearLayout>

AndroidManifest.xml

xml 复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">

    <application
        android:label="MyApp"
        android:icon="@mipmap/ic_launcher">

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name=".SettingsActivity"
            android:theme="@style/Theme.AppCompat.Light.DialogWhenLarge"
            android:exported="false" />
    </application>
</manifest>

4. 关键点总结

  1. 通信流程:

    • Flutter → 通过 MethodChannel 调用原生方法
    • Android → 通过 startActivityForResult 启动设置页
    • 设置页 → 返回结果通过 MethodChannel 传回Flutter
  2. Kotlin特性利用:

    • 使用 lateinit 延迟初始化绑定
    • 使用 apply 简化对象配置
    • 使用 when 表达式替代 switch-case
  3. 类型安全:

    • 使用Kotlin的空安全操作符 ?
    • 明确指定泛型类型 Map<String, Any>
  4. 线程安全:

    • 通过 Handler(Looper.getMainLooper()) 确保在主线程更新UI
  5. 资源管理:

    • 使用 View Binding (ActivitySettingsBinding)
    • 合理处理 Activity 生命周期

这个实现完整展示了 Flutter 与 Android 原生页面之间的双向通信,所有代码均采用 Kotlin 编写,符合现代 Android 开发最佳实践。