Protocol Buffer V3.3.0 在Andoroid中的使用介紹

前面翻譯了谷歌的Proto3 語言指南

這一篇講實戰(zhàn)應(yīng)用
先總結(jié)一下 Protocol Buffer

一、什么是Protocol Buffer

Google 出品 的一種結(jié)構(gòu)化數(shù)據(jù) 的數(shù)據(jù)存儲格式浦辨,類似于 XML醋安、Json抖剿。但更小,更快,更簡單片吊。
它可以通過將 結(jié)構(gòu)化的數(shù)據(jù) 進(jìn)行 串行化(序列化),從而實現(xiàn) 數(shù)據(jù)存儲 / RPC 數(shù)據(jù)交換的功能协屡。

  • 序列化: 將 數(shù)據(jù)結(jié)構(gòu)或?qū)ο?轉(zhuǎn)換成 二進(jìn)制串 的過程
  • 反序列化:將在序列化過程中所生成的二進(jìn)制串 轉(zhuǎn)換成 數(shù)據(jù)結(jié)構(gòu)或者對象 的過程
1 使用場景

傳輸數(shù)據(jù)量大 & 網(wǎng)絡(luò)環(huán)境不穩(wěn)定 的數(shù)據(jù)存儲俏脊、RPC 數(shù)據(jù)交換 的需求場景(如即時IM )

2 相對于其他數(shù)據(jù)存儲格式 Protocol Buffer的優(yōu)勢與缺點
二、Window下 Protocol Buffer的安裝環(huán)境

要使用Protocol Buffer肤晓,先要配好環(huán)境

1 下載Protocol Buffers v3.3.0

官方下載Protocol Buffers v3.3.0
需翻墻(翻墻都不會爷贫?H先弧!還好我有百度云)
百度云下載

2 下載protoc-3.3.0-win32.zip

protoc-3.3.0-win32.zip

3 將protoc-3.3.0-win32.zip解壓后的bin/protoc.exe文件拷貝到解壓后的protobuf-3.3目錄中漫萄,并配置好環(huán)境變量卷员。

執(zhí)行命令檢查是否安裝成功

protoc --version

這樣環(huán)境算是搞好了

三、Protocol Buffer在JAVA中的應(yīng)用

如果你對Proto的基本語法以及結(jié)構(gòu)還不熟悉腾务,那么請你先快速看一下這篇 Proto3 語言指南

1毕骡、創(chuàng)建 .proto 文件

我們先通過官方的Demo來感知一下 .proto 文件是怎么樣的,然后根據(jù)它的格式創(chuàng)建你想要的結(jié)構(gòu)文件岩瘦。

addressbook.proto

// [START declaration]
syntax = "proto3";
package tutorial;
// [END declaration]

// [START java_declaration]
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
// [END java_declaration]

// [START csharp_declaration]
option csharp_namespace = "Google.Protobuf.Examples.AddressBook";
// [END csharp_declaration]

// [START messages]
message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;
}

// Our address book file is just one of these.
message AddressBook {
  repeated Person people = 1;
}
// [END messages]

解釋一下上面部分關(guān)鍵字的定義

  • required 字段必須提供未巫,否則消息將被認(rèn)為是 "未初始化的 (uninitialized)"。嘗試構(gòu)建一個未初始化的消息將拋出一個 RuntimeException启昧。解析一個未初始化的消息將拋出一個 IOException叙凡。
  • optional 字段可以設(shè)置也可以不設(shè)置。如果可選的字段值沒有設(shè)置密末,則將使用默認(rèn)值握爷。默認(rèn)值你可以自己定義,也可以用系統(tǒng)默認(rèn)值:數(shù)字類型為0苏遥,字符串類型為空字符串饼拍,bools值為false。
  • repeated 字段可以重復(fù)任意多次 (包括0)(相當(dāng)于JAVA中的list)
  • default 默認(rèn)值
2田炭、編譯 .proto 文件

通過以下命令來編譯 .proto 文件:

protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto

-I师抄,--java_out 分別用于指定源目錄 (放置應(yīng)用程序源代碼的地方 —— 如果沒有提供則使用當(dāng)前目錄),目的目錄 (希望放置生成的代碼的位置)教硫,最后的參數(shù)為 .proto 文件的路徑叨吮。
protoc會按照標(biāo)準(zhǔn)Java風(fēng)格,生成Java類及目錄結(jié)構(gòu)瞬矩。如對于上面的例子茶鉴,會生成 com/example/tutorial/ 目錄結(jié)構(gòu),及 AddressBookProtos.java 文件景用。

如我的addressbook.proto文件放在E盤下涵叮,執(zhí)行:

E:\pro>protoc -I=E:/pro --java_out=E:/pro E:/pro/addressbook.proto

//或者
E:\pro>protoc -I=./ --java_out=./ ./addressbook.proto

最后生成此文件

3、但是當(dāng)你把生成的文件放進(jìn)項目中的時候伞插,你會發(fā)現(xiàn)報錯了
...
Error:(9, 26) 錯誤: 程序包com.google.protobuf不存在
...

解決方法一:利用eclipse程序進(jìn)行編譯 前面下載protoc-3.3.0-win32.zip
右鍵點擊java文件夾下面的pom.xml文件


生成protobuf-java-3.3.0.jar

解決方法二:網(wǎng)上下一個protobuf-java-3.3.0.jar割粮,如果找不到留言聯(lián)系我

關(guān)于 Protocol Buffers 編譯文件后的細(xì)節(jié)
1、簡單分析通過protoc編譯后的文件到底是有什么東西

這么簡單的一個結(jié)構(gòu)文件居然生成了快2700行的代碼媚污,看著就頭痛

抽取一些對象以及它們相關(guān)的代碼發(fā)現(xiàn)共同點:

  • 每個類都有它自己的 Builder 類舀瓢,你可以用來創(chuàng)建那個類的實例。
  • 消息只有g(shù)etters耗美,而builders則同時具有g(shù)etters和setters京髓。
  • 每個字段都會有四個常用方法(hasX航缀,getX,setX堰怨,clearX)
  • 定義了 repeated 之后會多( getXList()芥玉、getXCount、addXPhone)這幾個方法备图。
  • accessor方法是如何以駝峰形式命名的飞傀,所以在命名.proto 文件字段時,盡量使用小寫字母加下劃線诬烹。
// required string name = 1;
public boolean hasName();
public java.lang.String getName();
public Builder setName(String value);
public Builder clearName();

// required int32 id = 2;
public boolean hasId();
public int getId();
public Builder setId(int value);
public Builder clearId();

// optional string email = 3;
public boolean hasEmail();
public String getEmail();
public Builder setEmail(String value);
public Builder clearEmail();

// repeated .tutorial.Person.PhoneNumber phone = 4;
public List<PhoneNumber> getPhoneList();
public int getPhoneCount();
public PhoneNumber getPhone(int index);
public Builder setPhone(int index, PhoneNumber value);
public Builder addPhone(PhoneNumber value);
public Builder addAllPhone(Iterable<PhoneNumber> value);
public Builder clearPhone();

2 每個消息和builder類還包含大量的其它方法,來讓你檢查或管理整個消息
  • isInitialized() : 檢查是否所有的required字段都已經(jīng)被設(shè)置了弃鸦。
  • toString() : 返回一個人類可讀的消息表示绞吁,對調(diào)試特別有用。
  • mergeFrom(Message other): (只有builder可用) 將 other 的內(nèi)容合并到這個消息中唬格,覆寫單數(shù)的字段家破,附接重復(fù)的。
  • clear(): (只有builder可用) 清空所有的元素為空狀態(tài)购岗。

更多信息汰聋,請參考 Message的完整API文檔

3 解析和序列化

每個protocol buffer類都有使用protocol buffer 二進(jìn)制格式寫和讀你所選擇類型的消息的方法喊积。這些方法包括:

  • byte[] toByteArray();: 序列化消息并返回一個包含它的原始字節(jié)的字節(jié)數(shù)組烹困。
  • static Person parseFrom(byte[] data);: 從給定的字節(jié)數(shù)組解析一個消息。
  • void writeTo(OutputStream output);: 序列化消息并將消息寫入 OutputStream乾吻。
  • static Person parseFrom(InputStream input);: 從一個 InputStream 讀取并解析消息髓梅。
4 寫消息

你要想把你個人信息寫進(jìn)AddressBook,流程如下:

  • 程序通過文件讀取一個AddressBook绎签。
  • 添加一個Person枯饿,并將新的AddressBook寫回文件。
import com.example.tutorial.AddressBookProtos.AddressBook;
import com.example.tutorial.AddressBookProtos.Person;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PrintStream;

class AddPerson {
  // This function fills in a Person message based on user input.
  static Person PromptForAddress(BufferedReader stdin,
                                 PrintStream stdout) throws IOException {
    Person.Builder person = Person.newBuilder();

    stdout.print("Enter person ID: ");
    person.setId(Integer.valueOf(stdin.readLine()));

    stdout.print("Enter name: ");
    person.setName(stdin.readLine());

    stdout.print("Enter email address (blank for none): ");
    String email = stdin.readLine();
    if (email.length() > 0) {
      person.setEmail(email);
    }

    while (true) {
      stdout.print("Enter a phone number (or leave blank to finish): ");
      String number = stdin.readLine();
      if (number.length() == 0) {
        break;
      }

      Person.PhoneNumber.Builder phoneNumber =
        Person.PhoneNumber.newBuilder().setNumber(number);

      stdout.print("Is this a mobile, home, or work phone? ");
      String type = stdin.readLine();
      if (type.equals("mobile")) {
        phoneNumber.setType(Person.PhoneType.MOBILE);
      } else if (type.equals("home")) {
        phoneNumber.setType(Person.PhoneType.HOME);
      } else if (type.equals("work")) {
        phoneNumber.setType(Person.PhoneType.WORK);
      } else {
        stdout.println("Unknown phone type.  Using default.");
      }

      person.addPhone(phoneNumber);
    }

    return person.build();
  }

  // Main function:  Reads the entire address book from a file,
  //   adds one person based on user input, then writes it back out to the same
  //   file.
  public static void main(String[] args) throws Exception {
    if (args.length != 1) {
      System.err.println("Usage:  AddPerson ADDRESS_BOOK_FILE");
      System.exit(-1);
    }

    AddressBook.Builder addressBook = AddressBook.newBuilder();

    // Read the existing address book.
    try {
      addressBook.mergeFrom(new FileInputStream(args[0]));
    } catch (FileNotFoundException e) {
      System.out.println(args[0] + ": File not found.  Creating a new file.");
    }

    // Add an address.
    addressBook.addPerson(
      PromptForAddress(new BufferedReader(new InputStreamReader(System.in)),
                       System.out));

    // Write the new address book back to disk.
    FileOutputStream output = new FileOutputStream(args[0]);
    addressBook.build().writeTo(output);
    output.close();
  }
}

四诡必、 Protocol Buffers 在Android中的使用
1 添加依賴
dependencies {
    ...
    compile 'com.google.protobuf:protobuf-java:3.3.0'
}

2奢方、將剛編譯好的文件對應(yīng)包名放在你的項目目錄下

放好之后,你可以通過如下方式去Builder 你的對象:

Person haiJia =
  Person.newBuilder()
    .setId(888)
    .setName("黃海佳")
    .setEmail("haijia@qq.com")
    .addPhone(
      Person.PhoneNumber.newBuilder()
        .setNumber("110-119")
        .setType(Person.PhoneType.HOME))
    .build();
3爸舒、寫消息
public class AddPerson {


    // This function fills in a Person message based on user input.
    static Person PromptForAddress(BufferedReader stdin) throws IOException {
        Person.Builder person = Person.newBuilder();

        LogUtils.log("Enter person ID: ");
        person.setId(Integer.valueOf(stdin.readLine()));

        LogUtils.log("Enter name: ");
        person.setName(stdin.readLine());

        LogUtils.log("Enter email address (blank for none): ");
        String email = stdin.readLine();
        if (email.length() > 0) {
            person.setEmail(email);
        }

        while (true) {
            LogUtils.log("Enter a phone number (or leave blank to finish): ");
            String number = stdin.readLine();
            if (number.length() == 0) {
                break;
            }

            Person.PhoneNumber.Builder phoneNumber =
                    Person.PhoneNumber.newBuilder().setNumber(number);

            LogUtils.log("Is this a mobile, home, or work phone? ");
            String type = stdin.readLine();
            if (type.equals("mobile")) {
                phoneNumber.setType(Person.PhoneType.MOBILE);
            } else if (type.equals("home")) {
                phoneNumber.setType(Person.PhoneType.HOME);
            } else if (type.equals("work")) {
                phoneNumber.setType(Person.PhoneType.WORK);
            } else {
                LogUtils.log("Unknown phone type.  Using default.");
            }

            person.addPhone(phoneNumber);
        }

        return person.build();
    }

    // Main function:  Reads the entire address book from a file,
    //   adds one person based on user input, then writes it back out to the same
    //   file.
    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            System.err.println("Usage:  AddPerson ADDRESS_BOOK_FILE");
            System.exit(-1);
        }

        AddressBook.Builder addressBook = AddressBook.newBuilder();

        // Read the existing address book.
        try {
            addressBook.mergeFrom(new FileInputStream(args[0]));
        } catch (FileNotFoundException e) {
            System.out.println(args[0] + ": File not found.  Creating a new file.");
        }

        // Add an address.
        addressBook.addPerson(PromptForAddress(new BufferedReader(new InputStreamReader(System.in))));

        // Write the new address book back to disk.
        FileOutputStream output = new FileOutputStream(args[0]);
        addressBook.build().writeTo(output);
        output.close();
    }


}
4 讀消息
class ListPeople {
  // Iterates though all people in the AddressBook and prints info about them.
  static void Print(AddressBook addressBook) {
    for (Person person: addressBook.getPersonList()) {
      System.out.println("Person ID: " + person.getId());
      System.out.println("  Name: " + person.getName());
      if (person.hasEmail()) {
        System.out.println("  E-mail address: " + person.getEmail());
      }

      for (Person.PhoneNumber phoneNumber : person.getPhoneList()) {
        switch (phoneNumber.getType()) {
          case MOBILE:
            System.out.print("  Mobile phone #: ");
            break;
          case HOME:
            System.out.print("  Home phone #: ");
            break;
          case WORK:
            System.out.print("  Work phone #: ");
            break;
        }
        System.out.println(phoneNumber.getNumber());
      }
    }
  }

  // Main function:  Reads the entire address book from a file and prints all
  //   the information inside.
  public static void main(String[] args) throws Exception {
    if (args.length != 1) {
      System.err.println("Usage:  ListPeople ADDRESS_BOOK_FILE");
      System.exit(-1);
    }

    // Read the existing address book.
    AddressBook addressBook =
      AddressBook.parseFrom(new FileInputStream(args[0]));

    Print(addressBook);
  }
}


五蟋字、使用protobuf-gradle-plugin

每次單獨執(zhí)行protoc編譯 .proto 文件總是太麻煩,通過protobuf-gradle-plugin可以在編譯我們的app時自動地編譯 .proto 文件碳抄,這樣就大大降低了我們在Android項目中使用 Protocol Buffers 的難度愉老。

1 首先我們需要將 .proto 文件添加進(jìn)我們的項目中,如:
2 然后修改 app/build.gradle 對protobuf gradle插件做配置:
buildscript {
 repositories {
     jcenter()
     mavenCentral()
 }
 dependencies {
     classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
 }
}


3 添加protobuf塊剖效,對protobuf-gradle-plugin的執(zhí)行做配置:
protobuf {
 protoc {
     path = '/usr/local/bin/protoc'
 }

 generateProtoTasks {
     all().each { task ->
         task.builtins {
             remove java
         }
         task.builtins {
             java { }
             // Add cpp output without any option.
             // DO NOT omit the braces if you want this builtin to be added.
             cpp { }
         }
     }
 }
}

protoc塊用于配置Protocol Buffers編譯器嫉入,這里我們指定用我們之前手動編譯的編譯器焰盗。
task.builtins的塊必不可少,這個塊用于指定我們要為那些編程語言生成代碼咒林,這里我們?yōu)镃++和Java生成代碼熬拒。缺少這個塊的話,在編譯時會報出如下的錯誤:

Information:Gradle tasks [:app:generateDebugSources, :app:mockableAndroidJar, :app:prepareDebugUnitTestDependencies, :app:generateDebugAndroidTestSources, :netlib:generateDebugSources, :netlib:mockableAndroidJar, :netlib:prepareDebugUnitTestDependencies, :netlib:generateDebugAndroidTestSources]
Error:Execution failed for task ':app:generateDebugProto'.
> protoc: stdout: . stderr: /media/data/CorpProjects/netlibdemo/app/build/extracted-protos/main: warning: directory does not exist.
/media/data/CorpProjects/netlibdemo/app/src/debug/proto: warning: directory does not exist.
/media/data/CorpProjects/netlibdemo/app/build/extracted-protos/debug: warning: directory does not exist.
/media/data/CorpProjects/netlibdemo/app/build/extracted-include-protos/debug: warning: directory does not exist.
/media/data/CorpProjects/netlibdemo/app/src/debug/proto: warning: directory does not exist.
/media/data/CorpProjects/netlibdemo/app/build/extracted-protos/debug: warning: directory does not exist.
/media/data/CorpProjects/netlibdemo/app/build/extracted-include-protos/debug: warning: directory does not exist.
Missing output directives.

提示說沒有指定輸出目錄的路徑垫竞。
這是由于 protobuf-gradle-plugin 執(zhí)行的protobuf編譯器命令的參數(shù)是在protobuf-gradle-plugin/src/main/groovy/com/google/protobuf/gradle/GenerateProtoTask.groovy中構(gòu)造的:

def cmd = [ tools.protoc.path ]
 cmd.addAll(dirs)

 // Handle code generation built-ins
 builtins.each { builtin ->
   String outPrefix = makeOptionsPrefix(builtin.options)
   cmd += "--${builtin.name}_out=${outPrefix}${getOutputDir(builtin)}"
 }

 // Handle code generation plugins
 plugins.each { plugin ->
   String name = plugin.name
   ExecutableLocator locator = tools.plugins.findByName(name)
   if (locator == null) {
     throw new GradleException("Codegen plugin ${name} not defined")
   }
   String pluginOutPrefix = makeOptionsPrefix(plugin.options)
   cmd += "--${name}_out=${pluginOutPrefix}${getOutputDir(plugin)}"
   cmd += "--plugin=protoc-gen-${name}=${locator.path}"
 }

 if (generateDescriptorSet) {
   def path = getDescriptorPath()
   // Ensure that the folder for the descriptor exists;
   // the user may have set it to point outside an existing tree
   def folder = new File(path).parentFile
   if (!folder.exists()) {
     folder.mkdirs()
   }
   cmd += "--descriptor_set_out=${path}"
   if (descriptorSetOptions.includeImports) {
     cmd += "--include_imports"
   }
   if (descriptorSetOptions.includeSourceInfo) {
     cmd += "--include_source_info"
   }
 }

 cmd.addAll protoFiles
 logger.log(LogLevel.INFO, cmd.toString())
 def stdout = new StringBuffer()
 def stderr = new StringBuffer()
 Process result = cmd.execute()
 result.waitForProcessOutput(stdout, stderr)
 def output = "protoc: stdout: ${stdout}. stderr: ${stderr}"
 logger.log(LogLevel.INFO, cmd)
 if (result.exitValue() == 0) {
   logger.log(LogLevel.INFO, output)
 } else {
   throw new GradleException(output)
 }

可以看到澎粟,輸出目錄是由builtins構(gòu)造的。

4 指定 .proto 文件的路徑
sourceSets {
     main {
         java {
             srcDir 'src/main/java'
         }
         proto {
             srcDir 'src/main/proto'
         }
     }
 }

這樣我們就不用那么麻煩每次手動執(zhí)行protoc了欢瞪。
對前面的protobuf塊做一點點修改活烙,我們甚至來編譯protobuf編譯器都不需要了。修改如下:

protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.0.0'
    }

    generateProtoTasks {
        all().each { task ->
            task.builtins {
                remove java
            }
            task.builtins {
                java { }
                cpp { }
            }
        }
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末遣鼓,一起剝皮案震驚了整個濱河市啸盏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌骑祟,老刑警劉巖回懦,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異次企,居然都是意外死亡怯晕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進(jìn)店門缸棵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舟茶,“玉大人焙贷,你說我怎么就攤上這事附井。” “怎么了雅采?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵型诚,是天一觀的道長客燕。 經(jīng)常有香客問我,道長狰贯,這世上最難降的妖魔是什么也搓? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮涵紊,結(jié)果婚禮上傍妒,老公的妹妹穿的比我還像新娘。我一直安慰自己摸柄,他們只是感情好颤练,可當(dāng)我...
    茶點故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著驱负,像睡著了一般嗦玖。 火紅的嫁衣襯著肌膚如雪患雇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天宇挫,我揣著相機(jī)與錄音苛吱,去河邊找鬼。 笑死器瘪,一個胖子當(dāng)著我的面吹牛翠储,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播橡疼,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼援所,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了欣除?” 一聲冷哼從身側(cè)響起任斋,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耻涛,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瘟檩,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡抹缕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了墨辛。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卓研。...
    茶點故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖睹簇,靈堂內(nèi)的尸體忽然破棺而出奏赘,到底是詐尸還是另有隱情,我是刑警寧澤太惠,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布磨淌,位于F島的核電站,受9級特大地震影響凿渊,放射性物質(zhì)發(fā)生泄漏梁只。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一埃脏、第九天 我趴在偏房一處隱蔽的房頂上張望搪锣。 院中可真熱鬧,春花似錦彩掐、人聲如沸构舟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狗超。三九已至弹澎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抡谐,已是汗流浹背裁奇。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留麦撵,地道東北人刽肠。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像免胃,于是被迫代替她去往敵國和親音五。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容