如何将 Android 原生功能封装成插件及使用方法

如何将 Android 原生功能封装成插件及使用方法

随着 Flutter 的普及,越来越多的开发者希望在 Flutter 中调用原生平台的功能。虽然 Flutter 提供了大量的插件,但有时我们可能需要封装自己的原生功能,例如相机、定位、支付等。本文将详细介绍如何将 Android 原生功能(如相机)封装成 Flutter 插件,并在 Flutter 中使用它。

什么是 Flutter 插件?

Flutter 插件允许你在 Flutter 应用中调用原生平台的功能。插件通常包含两部分:

  • Dart 层代码:用于在 Flutter 中调用原生功能。
  • 原生平台代码(Android 或 iOS):实现具体的原生功能,并通过平台通道(Platform Channels)与 Dart 层进行交互。

本文将演示如何将 Android 原生相机功能封装成一个 Flutter 插件,并在 Flutter 项目中使用。

步骤 1: 创建 Android 原生插件

首先,我们需要在 Flutter 项目中创建一个 Android 原生插件。

创建 Android Library 模块

  1. 在 Flutter 项目的根目录下,使用 Android Studio 创建一个新的 Android Library 模块。
  2. 在创建过程中,选择 Android Library 类型。
  3. 创建完成后,插件目录结构如下:
    • camera-plugin/
      • src/
      • build.gradle
      • AndroidManifest.xml

配置 Android 插件

build.gradle 文件中,我们将插件配置为 Android 库,便于生成 .aar 文件以供 Flutter 使用。

apply plugin: 'com.android.library'

android {
    compileSdkVersion 30

    defaultConfig {
        minSdkVersion 16
        targetSdkVersion 30
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

步骤 2: 编写相机功能的 Android 原生代码

接下来,我们将封装 Android 相机功能,并通过平台通道与 Flutter 进行通信。

编写 CameraPlugin

CameraPlugin 类将处理相机启动、权限检查和拍照操作。我们使用 Intent 启动相机应用,并通过 onActivityResult 获取返回的图像数据。

import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.provider.MediaStore;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

public class CameraPlugin {
    private static final int REQUEST_IMAGE_CAPTURE = 1;
    private static final int CAMERA_PERMISSION_REQUEST = 101;

    private final Activity activity;
    private CameraResultCallback callback;

    public interface CameraResultCallback {
        void onSuccess(Bitmap bitmap);
        void onError(Exception exception);
    }

    public CameraPlugin(Activity activity) {
        this.activity = activity;
    }

    public void takePicture(CameraResultCallback callback) {
        this.callback = callback;

        // 检查相机权限
        if (ContextCompat.checkSelfPermission(activity, android.Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(
                    activity,
                    new String[]{android.Manifest.permission.CAMERA},
                    CAMERA_PERMISSION_REQUEST
            );
        } else {
            dispatchTakePictureIntent();
        }
    }

    private void dispatchTakePictureIntent() {
        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        if (takePictureIntent.resolveActivity(activity.getPackageManager()) != null) {
            activity.startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        } else {
            if (callback != null) {
                callback.onError(new Exception("No camera app available"));
            }
        }
    }

    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == CAMERA_PERMISSION_REQUEST) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                dispatchTakePictureIntent();
            } else {
                if (callback != null) {
                    callback.onError(new Exception("Camera permission denied"));
                }
            }
        }
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
            Bundle extras = data.getExtras();
            Bitmap imageBitmap = (Bitmap) extras.get("data");
            if (imageBitmap != null && callback != null) {
                callback.onSuccess(imageBitmap);
            } else if (callback != null) {
                callback.onError(new Exception("Failed to capture image"));
            }
        }
    }

    // 保存图片到文件的辅助方法
    public static String saveBitmapToFile(Activity activity, Bitmap bitmap, String filename) {
        java.io.File file = new java.io.File(activity.getExternalFilesDir(null), filename);
        try {
            java.io.FileOutputStream out = new java.io.FileOutputStream(file);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
            out.close();
            return file.getAbsolutePath();
        } catch (java.io.IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

AndroidManifest.xml 中添加权限

在插件的 AndroidManifest.xml 中,我们需要声明使用相机的权限。

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />

步骤 3: 使用 Gradle 构建 .aar 文件

为了将插件集成到 Flutter 项目中,我们需要生成 .aar 文件。

在终端中运行以下命令构建插件:

./gradlew :camera-plugin:assembleRelease

这将生成 camera-plugin-release.aar 文件,保存在 camera-plugin/build/outputs/aar 目录下。

步骤 4: 将 .aar 文件集成到 Flutter 项目中

camera-plugin-release.aar 文件复制到 Flutter 项目的 android/libs 目录下,并在 android/app/build.gradle 文件中添加对 .aar 文件的依赖。

dependencies {
    implementation(files("../libs/camera-plugin-release.aar"))
}

步骤 5: 编写 Flutter 到 Android 的桥接代码

为了让 Flutter 调用 Android 相机功能,我们需要通过平台通道(MethodChannel)建立 Flutter 与原生 Android 代码的通信。

在 Flutter 项目的 MainActivity.kt 中,设置平台通道并调用 CameraPlugin 类的功能。

class MainActivity : FlutterActivity() {
    private val CHANNEL = "camera_plugin"
    private var cameraPlugin: CameraPlugin? = null

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        cameraPlugin = CameraPlugin(this)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == "takePicture") {
                cameraPlugin?.takePicture(object : CameraPlugin.CameraResultCallback {
                    override fun onSuccess(bitmap: Bitmap) {
                        val path = CameraPlugin.saveBitmapToFile(this@MainActivity, bitmap, "photo.jpg")
                        result.success(path)
                    }

                    override fun onError(exception: Exception) {
                        result.error("CAMERA_ERROR", exception.message, null)
                    }
                })
            } else {
                result.notImplemented()
            }
        }
    }
}

步骤 6: 在 Flutter 中调用原生方法

现在,我们已经完成了原生 Android 插件的封装和桥接。接下来,我们在 Flutter 中调用相机功能。

在 Flutter 中,我们通过 MethodChannel 调用 takePicture 方法。

dart复制编辑class CameraServicePlugin {
  static const _channel = MethodChannel('camera_plugin');

  static Future<String?> takePicture() async {
    try {
      final result = await _channel.invokeMethod<String>('takePicture') ?? "";
      return result;
    } catch (e) {
      print("拍照失败: $e");
      return null;
    }
  }
}

在 Flutter 中调用相机插件并获取图片路径:

String? path = await CameraServicePlugin.takePicture();
print("图片路径:$path");

总结

通过本文,我们学习了如何将 Android 原生相机功能封装成 Flutter 插件,并通过平台通道在 Flutter 中调用原生功能。通过封装原生功能为插件,我们可以更好地复用和共享代码,并将 Android 功能无缝集成到 Flutter 应用中。

posted @ 2025-04-14 12:22  疾风不问归途  阅读(107)  评论(0)    收藏  举报