1. 程式人生 > >Android 使用Google Protocol buffer協議

Android 使用Google Protocol buffer協議

什麼是 Protocol buffer

它是用於對結構化資料進行序列化的一種靈活、高效、自動化的機制——類似XML,但是更小、更快、更簡單。您可以定義資料的結構化方式,然後使用特殊生成的原始碼輕鬆地在各種資料流和使用的各種語言之間讀寫結構化資料。

對於使用 Java, C++, 或 Python 等多種語言 的開發者,想要進行結構化資料的序列化,並用於通訊或儲存的場景時,可以採用。

使用平臺無關的特定語法建立 ".proto"檔案,來描述結構化資料;通過相應工具生成相關語言平臺的結構化資料檔案,在通訊時可以呼叫相關函式,轉成二進位制資料進行傳遞。

參考

官網

下載

定義.proto 檔案

建立一個名為 test.proto 的檔案:

syntax = "proto3"; //使用3.x 版語法

package com.stone.bean; //生成檔案將在這個包名下,包名目錄不存在將自動建立

//常量
enum Version {
    VERSION_NAME = 0;
    VERSION_CODE = 1;
}

message VersionCheck {
    Version curVersion = 1;
    Version newVersion = 2;
}

message User {
    int32 uid = 1;
    string uname = 2;
    bool active = 3;
    float weight = 4;
    repeated Role roles = 5;
    map<int32, string> subUser = 6;
}
message Role {
    int32 rid = 1;
    string rname = 2;
}

enum 型別,會轉成 java的 enum 型別;定義的資料標號需要從0開始。
message 型別,會轉成 java 的 class;定義的資料標號需要從1開始。

message 內部定義

  • 關於上文中的 repeated 型別,對應一個List集合。
    repeated Role roles = 3; ===> List<Role> roles_;
    map<int32, string> subUser ===> Map<int, String> subUser_;
  • 內部,還可以定義 enum,即在 java 中是一個內部類的形式。
    當然,也可以巢狀 message。

option 選項

  • java_outer_classname 指定輸出
    option java_outer_classname = "MyTest"; 指定輸出檔名;若不指定,則以.proto 檔名為類名。
  • java_multiple_files 是否生成多檔案
    option java_multiple_files = true; 預設為 false,即將只生成一個類檔案,所有定義的類、列舉等都以內部類形式定義; 為 true,則生成多個 java 檔案
  • optimize_for 優化方式
    option optimize_for = SPEED; 有三種可選值:SPEED, CODE_SIZE, LITE_RUNTIME

SPEED 是預設值
CODE_SIZE 生成程式碼比 SPEED 少,但操作效率低
LITE_RUNTIME 生成的程式碼也少,且生成的類不再繼承 GeneratedMessageV3,而是繼承一個更輕量級的類 GeneratedMessageLite; 這個非常適合移動端;但在將來,程式碼生成工具protoc將忽略它,建議使用Java Lite plugin : `` ,

轉成 java 檔案

轉換命令:
protoc --proto_path=. --java_out=. test.proto

protoc -I. --java_out=. test.proto
第一條命令中 --proto_path= 可以簡寫成-I
--proto_path= 指定 .proto檔案目錄。 "."表示當前目錄下(即執行命令的目錄)。
--java_out= 指定輸出目錄。
最後 跟上 proto 檔名。

若要轉多語言平臺的檔案,命令為:
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto

轉換後,java 類中的常量用方法

  • enum
    getNumber(); 返回當前列舉物件對應的 int 值
    forNumber(int value);返回列舉物件
  • message
    每個 message 對應的 java類中,都生成了一個靜態內部類 Builder;
    有一個靜態方法 newBuilder()來建立 Builder 物件。
    Builder 物件有getter/setter方法,建立物件時呼叫 setter 方法,最後呼叫 builder.build(); 返回 message 對應的類的例項物件。
    message 類,公開了 getter 方法;還有如下兩個重要的方法:
    parseFrom(byte[] data)該方法有一系過載,通常用含有這個引數的就可以了;表示從位元組陣列中返回 message 對應類的物件;
    toByteArray(); 將物件轉成位元組陣列;
    示例:
/*建立 user 物件*/
MyTest.User user = MyTest.User.newBuilder()
       .setActive(false)
//     .set  ... 其它 set 方法
       .build();
//轉 byte 陣列
byte[] bytes1 = user.toByteArray();
//ByteString是 protobuf 中定義的,通常不使用它;
//ByteString bytes2 = user.toByteString();

//從 byte 陣列,轉成 user 物件;並獲取user 物件屬性
try {
    MyTest.User user1 = MyTest.User.parseFrom(bytes1);
//            MyTest.User user2 = MyTest.User.parseFrom(bytes2);
     boolean active = user.getActive();
 } catch (InvalidProtocolBufferException e) {
     e.printStackTrace();
 }

自定義 java bean 封裝 message 類

實際場景中,protocol buffer 中的 java bean,可能不能直接使用;
因為,可能我們有些本地的屬性需要定義;可能使用資料庫框架,或其它框架需要對 java bean 的類或屬性添加註解等等。
所以,需要自行定義java bean

例:

public class UserBean {
    boolean active;

    public boolean isActive() {
        return active;
    }

    public void setActive(boolean active) {
        this.active = active;
    }

    public UserBean() {

    }

    public byte[] toByteArray() {
        return MyTest.User.newBuilder()
                .setActive(active)
                .build()
                .toByteArray();
    }

    public UserBean parseFrom(byte[] data) throws InvalidProtocolBufferException {
        MyTest.User user = MyTest.User.parseFrom(data);
        UserBean userBean = new UserBean();
        userBean.setActive(user.getActive());
        return userBean;
    }
}

android 使用 gradle 外掛實現 protocol buffer lite

前文說了,可以使用protoc 工具,手動轉換 ".proto"檔案。
這裡使用 gradle 的外掛進行轉換,且轉出的是 對應 option optimize_for = LITE_RUNTIME的 java 檔案。
gradle外掛地址

gradle 配置

  • project-path/build.gradle
buildscript {
	dependencies {
		...
		classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.7'
	}
}

新增在這裡對所有 module 都有效;

  • app-path/build.gradle
apply plugin: 'com.android.application'
apply plugin: 'com.google.protobuf'

//若不在 project-path/build.gradle下配置 classpath,需要新增如下
buildscript {
    repositories {
        maven { url "https://plugins.gradle.org/m2/" }
    }
    dependencies {//只會從上面定義的倉庫中去查詢 classpath
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.7'
    }
}

android {
	...
	sourceSets {
		main {
			proto {//配置.proto 檔案目錄
                srcDir 'src/main/protobuf'
		}
	}
}

dependencies {
	...
	//僅新增這個 lite 版即可
    implementation 'com.google.protobuf:protobuf-lite:3.0.1'
}

protobuf {
    protoc {
        // The artifact spec for the Protobuf Compiler. Download from repositories
        artifact = 'com.google.protobuf:protoc:3.6.1'

        //or specify a local path
//        path = '/usr/local/bin/protoc'
    }
    plugins {
        javalite {
            // The codegen for lite comes as a separate artifact
            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
        }
    }
    generateProtoTasks {
        all().each { task ->
            task.builtins {
                // In most cases you don't need the full Java output
                // if you use the lite output.
                remove java
            }
            task.plugins {
                javalite { }
            }

            task.generateDescriptorSet = true
            task.descriptorSetOptions.path =
                    "${projectDir}/build/descriptors/${task.name}.dsc"
            task.descriptorSetOptions.includeSourceInfo = true
            task.descriptorSetOptions.includeImports = true
        }
        // (Android-only selectors)  即何種條件,產生 protobuf 對應的原始碼檔案
        // Returns tasks for a flavor
        ofFlavor('demo')
        // Returns tasks for a buildType
        ofBuildType('release')
        ofBuildType('debug')
        // Returns tasks for a variant
        ofVariant('demoRelease')
        // Returns non-androidTest tasks
        ofNonTest()
        // Return androidTest tasks
        ofTest()
    }
}

編譯生成java檔案

編譯後,生成的 java 檔案在:app/build/generated/source/proto 下。