Android Studio使用ProtocolBuffers

protobuf的java库比较大,为满足android移动设备在内存、性能等各方面的要求,google也推出了android定制版protobuf-lite库。

使用步骤

  1. 在 .proto 文件中定义消息格式。
    语法学习可参考官方文档
    语法有syntax2 syntax3的区分,可在书写schema的时候声明用哪个版本,3相对2来说有更好的压缩特性。
  2. 使用 Protocol Buffer 编译器编译生成所需的java文件。
    编译器生成及用法参见http://www.jianshu.com/p/e8712962f0e9
    每次手动执行 Protocol Buffers 编译器将 .proto 文件转换为Java文件显然有点太麻烦,因此google提供了一个Android Studio gradle插件 protobuf-gradle-plugin ,以便于在我们项目的编译期间自动地执行 Protocol Buffers 编译器。
  3. 使用Java Protocol Buffer API读写消息。
    整个使用过程如下图



    下面就以Demo展示protobuf-gradle-plugin+protobuf-lite库实现消息序列化和反序列化的过程。

gradle集成protobuf-gradle-plugin

app module的gradle脚本如下:

apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf' //在gradle脚本开始处声明依赖的插件

buildscript {
    repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.3'
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'//配置plugin的版本信息
    }
}
android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.dy.testprotocolbuffer"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
//编写编译任务,调用plugin编译生成java文件
protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.0.0'//编译器版本
    }
    plugins {
        javalite {
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'//指定当前工程使用的protobuf版本为javalite版,以生成javalite版的java类
        }
    }
    generateProtoTasks.generatedFilesBaseDir = "$projectDir/src/main/java" //指定编译生成java类的存放位置
    generateProtoTasks {
        all().each { task ->
            task.plugins {
                javalite {
                    outputSubDir = '' //指定存放位置的二级目录,这里未指定
                }
            }
        }
    }
}
//指定原始.proto文件的位置
android {
    sourceSets {
        main {
            java {
                srcDirs 'src/main/java'
            }
            proto {
                srcDirs 'src/main/proto'
            }
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.1.0'
    compile 'com.google.protobuf:protobuf-lite:3.0.0' //依赖protobuf-lite库
}

工程目录如下:

pb工程目录.png

关于protobuf-gradle-plugin的更多用法可参考官方文档https://github.com/google/protobuf-gradle-plugin

schema编写(.proto文件)

我们将以下Json示例转为pb格式:

{
    "data":[
      {
        "datatype":1,
        "itemdata":
            {//共有字段45个
              "sname":"\u5fae\u533b",
              "packageid":"330611",
              …
              "tabs":[
                        {
                          "type":1,
                          "f":"abc"
                        },
                        …
              ]
            }
      },
      …
    ],
    "hasNextPage":true,
    "dirtag":"soft"
  }

schema编写如下:

syntax = "proto2";
package com.dy.messagepackdemo.protobuffer.model;

option java_package = "com.dy.messagepackdemo.protobuffer.model";
option java_outer_classname = "ResponsePB";

message Tab {
  required int32 type = 1;
  optional string f = 2;
}
message ItemData {
    required string sname = 1;
    required string packageid = 2;
    ...
    repeated Tab tabs = 45;
}
message DataItem {
    required int32 datatype = 1;
    required ItemData itemdata = 2;
}
message ResponsePB {
    repeated DataItem data = 1;
    required bool hasNextPage = 2;
    required string dirtag = 3;
}

这个schema已经将上面json示例的层次结构体现的很明显了。简单解释一下各参数的意义及用法:

  • syntax指定用哪个版本的语法,proto3比2有更好的压缩特性
  • package指定编译生成java类的包名
  • java_outer_classname指定java类的类名
  • 修饰符:required表示必填,optional可选,repeated表示重复的list
  • 每个层级内的数据要按顺序编号,也就是上一篇文中讲的field_number
  • 每个结构体是一个message,message之间可以互相引用。

编译

build工程,可以看到在java包下生成了debug目录(由于当前是debug模式),再下面就是我们想要的包及schema编译后的java文件。



使用编译生成的java类,就可以进行数据的序列化和反序列化了。

序列化操作

构造一个response数据并写入文件

public static void writeResponseToPbFile(String pbfilepath, ResponseJson responseJson) {
        File fproto = new File(pbfilepath);
        if (!fproto.exists()) {
            try {
                fproto.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //build response
        //构造builder
        ResponsePB.Response.Builder responseBuilder = ResponsePB.Response.newBuilder();
        //填充数据
        responseBuilder.setHasNextPage(resultJson.hasNextPage);
        responseBuilder.setDirtag(resultJson.dirtag);
        ...//此处省略若干行
        //结束 build
        ResponsePB.Response response = responseBuilder.build();
        //写文件
        try {
            FileOutputStream foProto = new FileOutputStream(fproto);
            response.writeTo(foProto);
            foProto.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

用法很简单,生成对象的构造器builder,用提供的各种Set方法填充数据,最后build,二进制数据就生成了。用法跟普通的pojo类没有区别。

反序列化

将文件中的数据解析到Response对象中

public static void parseXinruiPb(byte[] bytes) {
    ResponsePB.Response response = ResponsePB.Response.parseFrom(bytes);
    boolean hasNextPage = response.getHasNextPage();
    String dirtag = response.getDirtag();
    ...
}

用起来也非常简单,parseFrom搞定。 值得一提的是,由于protobuf的存储结构决定了它在进行数据解析的时候必须将整个数据完整解析一遍才能得到你想要的数据,也就是数据传输过程中所谓的封包-解析过程,这与json解析的过程类似,区别在于它对key键的特殊编码,省去了字符匹配的过程。

更高级的用法--动态编译

Protobuf 提供了 google::protobuf::compiler 包来完成动态编译的功能。感兴趣的同学可以自行研究。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 不想再假装坚强 也不坚强 其实我并不是外表看上去的不在乎无所谓 哪个女生不期望爱情 不想要爱人的陪伴 所说的庆幸自...
    小沐oc阅读 143评论 0 1
  • 1. 编辑相关快捷键 2. 查看和定位快捷键 3. 调试快捷键 Eclipse中有如下一些和运行调试相关的快捷键。...
    dongbingliu阅读 394评论 4 3
  • 我在社区医院上班,要接触不少慢性病患者,给他们配药是我工作的一部分。这种患者买药的特点是定期配相同的药,除非有特殊...
    迎风小叶子阅读 371评论 1 1
  • 想念是无法控制的情绪。 无论在安静或喧嚣的环境里,不能控制自己,我好想你。 我想每天能看着你,每天能抱抱你 ...
    知秋業阅读 201评论 1 0