Flutter接入ProtoBuff和原生Android通信【性能最优】

Protocol Buffers(简称Protobuf)是由 Google 开发的一种结构化数据序列化框架,旨在实现高效的数据交换与存储。其核心特性及优势如下:

一、核心特性

  1. 跨语言与跨平台
    支持多种编程语言(如 C++、Java、Python、Dart 等),生成的语言无关代码可在不同平台间无缝交互。
  2. 高性能编码
    采用二进制格式进行序列化,数据体积小且解析速度快,相较于 XML 和 JSON 可缩减数据量 3--10 倍,解析速度提升 20--100 倍。
  3. 灵活扩展性
    通过 .proto 文件定义数据结构,支持向后兼容的字段更新(如新增字段不影响旧版解析)。

二、工作原理

  1. 定义数据结构
    使用 .proto 文件声明消息类型及其字段规则,例如:
Kotlin 复制代码
message User {
  required string name = 1;
  optional int32 age = 2;
  repeated string emails = 3;
}
  1. 代码生成
    通过 Protobuf 编译器(protoc)配合语言插件(如 protoc_plugin)生成目标语言的序列化/反序列化类。
  2. 序列化与反序列化
    生成的类提供接口将对象转换为二进制流(网络传输或存储)或从二进制流重建对象。

三、典型应用场景

四、与其他序列化协议的对比

五、flutter访问原生Android图库实践

5.1 protobuff环境搭建

需要 protobuff 安装环境,以mac为例:

1. 检查protoc_plugin插件版本

Dart 复制代码
dart pub global list | grep protoc_plugin

安装protoc_plugin插件

Dart 复制代码
MacBook-Pro ~ % dart pub global activate protoc_plugin

Downloading packages... . 

+ collection 1.19.1

+ fixnum 1.1.1

+ meta 1.16.0

+ path 1.9.1

+ protobuf 4.0.0

+ protoc_plugin 22.0.1

输出(protoc_plugin 22.0.1、要求:protobuf 4.0.0):

⚠️注意:flutter项目pubspec.yaml也需要配置:

Dart 复制代码
  protobuf: ^4.0.0
  protoc_plugin: ^22.0.1

2. 检查libprotoc版本及安装

Dart 复制代码
MacBook-Pro ~ % protoc --version 
libprotoc 3.21.12

5.2 Android原生配置

  1. android/build.radle
Groovy 复制代码
    dependencies {
        classpath 'com.android.tools.build:gradle:7.4.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.4' // 增加
    }

2. android/app/build.radle

Groovy 复制代码
apply plugin: 'com.google.protobuf' // 导入插件

android {
     // 添加
    sourceSets {
        main {
            proto {
                srcDir 'src/main/proto'
            }
        }
    }
}

// 添加
protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.24.4'
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option 'lite'
                }
            }
        }
    }
}


dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.multidex:multidex:2.0.1'
    //    # Flutter 侧(确保两侧一致protoc --version)
        //    protoc --version && dart pub global list | grep protoc_plugin
    implementation 'com.google.protobuf:protobuf-javalite:3.21.12'
}

5.3 Android原生proto开发及编译

  1. 编写代码:android/app/src/main/proto/media.proto
Kotlin 复制代码
syntax = "proto3";
package com.example.test.proto;

message MediaItem {
  int64 id = 1;
  string path = 2;
  string album = 3;
  int64 size = 4;
  int64 date_added = 5;
  int64 date_modified = 6;
  int64 duration = 7; // 0表示图片
  string mime_type = 8; // 新增字段
}

message MediaList {
  repeated MediaItem items = 1;
}
  1. 编译输出产物flutter项目根目录下:lib/protos/media目录

执行终端命令:

bash 复制代码
# 创建目录
mkdir -p lib/protos/media

# 生成代码
protoc --dart_out=grpc:lib/protos/media --proto_path=android/app/src/main/proto android/app/src/main/proto/media.proto

输出:

5.4 Android原生插件通信开发

开发业务model:

Kotlin 复制代码
class PhotoMediaModel {
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @SuppressLint("Range")
    fun getPhotoMedias(context: Context): ByteArray {
        val proto = MediaList.newBuilder()

        val projection = arrayOf(
            MediaStore.MediaColumns._ID,
            MediaStore.MediaColumns.DATA,
            MediaStore.MediaColumns.BUCKET_DISPLAY_NAME,
            MediaStore.MediaColumns.SIZE,
            MediaStore.MediaColumns.DATE_ADDED,
            MediaStore.MediaColumns.DATE_MODIFIED,
            MediaStore.Video.VideoColumns.DURATION
        )

        val uri = MediaStore.Files.getContentUri("external")
        val selection = "${MediaStore.Files.FileColumns.MEDIA_TYPE} IN (?,?)"
        val args = arrayOf(
            MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE.toString(),
            MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO.toString()
        )

        context.contentResolver.query(
            uri,
            projection,
            selection,
            args,
            null
        )?.use { cursor ->
            val idCol = cursor.getColumnIndex(MediaStore.MediaColumns._ID)
            val dataCol = cursor.getColumnIndex(MediaStore.MediaColumns.DATA)
            val albumCol = cursor.getColumnIndex(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME)
            val sizeCol = cursor.getColumnIndex(MediaStore.MediaColumns.SIZE)
            val addedCol = cursor.getColumnIndex(MediaStore.MediaColumns.DATE_ADDED)
            val modifiedCol = cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED)
            val durationCol = cursor.getColumnIndex(MediaStore.Video.VideoColumns.DURATION)
            val itemBuilder = MediaItem.newBuilder()
            while (cursor.moveToNext()) {
                val item = itemBuilder.clear().apply {
                    id = cursor.getLong(idCol)
                    path = cursor.getString(dataCol)
                    album = cursor.getString(albumCol) ?: ""
                    size = cursor.getLong(sizeCol)
                    dateAdded = cursor.getLong(addedCol)
                    dateModified = cursor.getLong(modifiedCol)
                    duration = if (cursor.isNull(durationCol)) 0 else cursor.getLong(durationCol)
                }.build()
                proto.addItems(item)
            }
        }
        return proto.build().toByteArray()
    }
}

然后在Actvity注册自己的插件业务:

Kotlin 复制代码
class MainActivity : FlutterActivity() {
    private val CHANNEL_PHOTO = "com.example.test/photo_media_channel"

    private val photoMediaModel = PhotoMediaModel()
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        registerPlugins(flutterEngine, this)
    }

    private fun registerPlugins(flutterEngine: FlutterEngine, context: Context) {
        // 图库查询
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_PHOTO).setMethodCallHandler { call, result ->
            when (call.method) {
                "getPhotoMedias" -> {
                    val byteArray = photoMediaModel.getPhotoMedias(context)
                    result.success(byteArray)
                }

                else -> {
                    result.notImplemented()
                }
            }
        }
    }
}

至此原生开发完成。

5.6 Flutter访问原生插件

开发对应的MethodChannel

Dart 复制代码
class PhotoMediaOptChannel {
  static const MethodChannel _photoMediaChannel = MethodChannel("com.example.test/photo_media_channel");

  static Future<List<MediaItem>> getPhotoMedias() async {
    if (!Platform.isAndroid) {
      return [];
    }
    try {
      final byteArray = await _photoMediaChannel.invokeMethod<Uint8List>('getPhotoMedias');
      // 使用 Protobuf 生成的代码进行反序列化
      if (byteArray == null) {
        debugPrint('Performance: getPhotoMedias 错误: protoBytes 为空');
        return [];
      }
      final MediaList mediaList = MediaList.fromBuffer(byteArray);
      debugPrint('Performance: getPhotoMedias 结束: ${mediaList.items.length}');
      return mediaList.items;
    } catch (e) {
      debugPrint('Performance: getPhotoMedias 错误: $e');
      return [];
    }
  }
}

注意:flutter需要配置依赖:pubspec.yaml

Dart 复制代码
  protobuf: ^4.0.0
  protoc_plugin: ^22.0.1

运行输出:

2025-05-08 19:21:37.929 12321-12487 flutter com.example.test I Performance: getPhotoMedias 结束: 264

六、总结

由于对protobuff不太了解版本关系,一直搞不定,坑了半天,总结一下实践步骤以供借鉴。

相关推荐
肥肥呀呀呀40 分钟前
flutter 的热更新方案shorebird
flutter
鸿蒙布道师1 小时前
鸿蒙NEXT开发动画案例3
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
鸿蒙布道师1 小时前
AI原生手机:三大技术阵营的终极对决与未来展望
android·人工智能·ios·华为·智能手机·ai-native·hauwei
每次的天空2 小时前
移动应用开发:自定义 View 处理大量数据的性能与交互优化方案
android·java·学习·交互
Huang兄3 小时前
Android 项目中配置了多个 maven 仓库,但依赖还是下载失败,除了使用代理,还有其他方法吗?
android·gradle·maven
難釋懷4 小时前
Android开发-常用布局
android·gitee
程序猿阿伟4 小时前
《让歌声跨越山海:Flutter借助Agora SDK实现高质量连麦合唱》
flutter
墨菲斯托8885 小时前
fakebook
android
s11show_1635 小时前
hz2新建Keyword页面
android