Wire-Lite項(xiàng)目介紹

Protocol Buffers(以下簡(jiǎn)稱PB) 是google 的一種數(shù)據(jù)交換的格式。PB獨(dú)立于語(yǔ)言恒序,獨(dú)立于平臺(tái)三圆,相比于json,xml等基于字符的數(shù)據(jù)封裝格式污呼,PB是一種效率和兼容性都很優(yōu)秀的二進(jìn)制數(shù)據(jù)傳輸格式裕坊,可以用于諸如網(wǎng)絡(luò)傳輸、配置文件燕酷、數(shù)據(jù)存儲(chǔ)等諸多領(lǐng)域籍凝。在數(shù)據(jù)包體積方面,PB的優(yōu)勢(shì)尤為明顯苗缩,使用PB封裝的數(shù)量包體積要遠(yuǎn)小于json或xml(根據(jù)一些網(wǎng)上公開(kāi)的測(cè)試結(jié)果饵蒂,封裝同樣的數(shù)據(jù),PB的數(shù)據(jù)包大小是json的三分之一左右)酱讶,所以PB非常適合網(wǎng)絡(luò)數(shù)據(jù)的傳輸退盯,特別對(duì)于數(shù)據(jù)量和網(wǎng)速都受限的移動(dòng)網(wǎng)絡(luò),使用PB對(duì)提升用戶的體驗(yàn)?zāi)芷鸬椒浅7e極的作用。 但是PB的缺點(diǎn)也是明顯的渊迁,一個(gè)簡(jiǎn)單的message經(jīng)過(guò)compile后會(huì)生成一個(gè)復(fù)雜的源碼文件慰照,即使只定義了一個(gè)field,源碼文件里也會(huì)有數(shù)十個(gè)方法琉朽,這些方法都是在PB的runtime庫(kù)需要用到的毒租,在服務(wù)端使用倒沒(méi)什么,但是用到Android端就麻煩了箱叁,隨著網(wǎng)絡(luò)協(xié)議的豐富墅垮,與之對(duì)應(yīng)的PB數(shù)據(jù)結(jié)構(gòu)包的方法數(shù)會(huì)急劇膨脹,大大加快應(yīng)用觸碰到64K方法數(shù)超界這塊天花板的時(shí)間耕漱。
Wire-Lite作為Android平臺(tái)的PB庫(kù)噩斟,在開(kāi)源庫(kù)[Wire]的基礎(chǔ)上再次做了大幅度的精簡(jiǎn),相比于另一個(gè)著名的PB庫(kù)protobuf孤个,可將方法數(shù)精簡(jiǎn)70%以上剃允,相比于其前身Wire,也可精簡(jiǎn)接近50%齐鲤,非常適合在移動(dòng)端使用斥废。

Wire-Lite功能及演示

通過(guò)一個(gè)例子對(duì)比不同PB庫(kù)的區(qū)別:

例子proto:
package com.example;

option java_package = "com.example";
option java_outer_classname = "PersonProtos";

message Person {
  // The customer's full name.
  required string name = 1;
  // The customer's ID number.
  required int32 id = 2;
  // Email address for the customer.
  optional string email = 3;
}

這個(gè)proto定義了名為Person的message,很簡(jiǎn)單给郊,只有name, id, email共3個(gè)field牡肉,讓我們看看四個(gè)庫(kù)編譯后的結(jié)果:

使用google的protobuf庫(kù)編譯后為:
package com.example;

public final class PersonProtos {
  private PersonProtos() {}
  public static void registerAllExtensions(
      com.google.protobuf.ExtensionRegistry registry) {
  }
  public interface PersonOrBuilder
      extends com.google.protobuf.MessageOrBuilder {

    // required string name = 1;
    /**
     * <code>required string name = 1;</code>
     *
     * <pre>
     * The customer's full name.
     * </pre>
     */
    boolean hasName();
    /**
     * <code>required string name = 1;</code>
     *
     * <pre>
     * The customer's full name.
     * </pre>
     */
    java.lang.String getName();
    /**
     * <code>required string name = 1;</code>
     *
     * <pre>
     * The customer's full name.
     * </pre>
     */
    com.google.protobuf.ByteString
        getNameBytes();

    // required int32 id = 2;
    /**
     * <code>required int32 id = 2;</code>
     *
     * <pre>
     * The customer's ID number.
     * </pre>
     */
    boolean hasId();
    /**
     * <code>required int32 id = 2;</code>
     *
     * <pre>
     * The customer's ID number.
     * </pre>
     */
    int getId();
    .........

因?yàn)樯傻奈募?02行,太多了淆九,所以只粘一小部分示意一下统锤,對(duì)于只有3個(gè)field的message卻生成了如此多的代碼,過(guò)于冗余了炭庙。

使用protostuff編譯后為:
// Generated by http://code.google.com/p/protostuff/ ... DO NOT EDIT!
// Generated from example.proto

package com.example;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

import com.dyuproject.protostuff.GraphIOUtil;
import com.dyuproject.protostuff.Input;
import com.dyuproject.protostuff.Message;
import com.dyuproject.protostuff.Output;
import com.dyuproject.protostuff.Schema;
import com.dyuproject.protostuff.UninitializedMessageException;

public final class Person implements Externalizable, Message<Person>, Schema<Person>
{

    public static Schema<Person> getSchema()
    {
        return DEFAULT_INSTANCE;
    }

    public static Person getDefaultInstance()
    {
        return DEFAULT_INSTANCE;
    }

    static final Person DEFAULT_INSTANCE = new Person();

    private String name;
    private Integer id;
    private String email;

    public Person()
    {

    }

    public Person(
        String name,
        Integer id
    )
    {
        this.name = name;
        this.id = id;
    }

    // getters and setters

    // name

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    // id

    public Integer getId()
    {
        return id;
    }

    public void setId(Integer id)
    {
        this.id = id;
    }

    // email

    public String getEmail()
    {
        return email;
    }

    public void setEmail(String email)
    {
        this.email = email;
    }

    // java serialization

    public void readExternal(ObjectInput in) throws IOException
    {
        GraphIOUtil.mergeDelimitedFrom(in, this, this);
    }

    public void writeExternal(ObjectOutput out) throws IOException
    {
        GraphIOUtil.writeDelimitedTo(out, this, this);
    }

    // message method

    public Schema<Person> cachedSchema()
    {
        return DEFAULT_INSTANCE;
    }

    // schema methods

    public Person newMessage()
    {
        return new Person();
    }

    public Class<Person> typeClass()
    {
        return Person.class;
    }

    public String messageName()
    {
        return Person.class.getSimpleName();
    }

    public String messageFullName()
    {
        return Person.class.getName();
    }

    public boolean isInitialized(Person message)
    {
        return 
            message.name != null 
            && message.id != null;
    }

    public void mergeFrom(Input input, Person message) throws IOException
    {
        for(int number = input.readFieldNumber(this);; number = input.readFieldNumber(this))
        {
            switch(number)
            {
                case 0:
                    return;
                case 1:
                    message.name = input.readString();
                    break;
                case 2:
                    message.id = input.readInt32();
                    break;
                case 3:
                    message.email = input.readString();
                    break;
                default:
                    input.handleUnknownField(number, this);
            }   
        }
    }

    public void writeTo(Output output, Person message) throws IOException
    {
        if(message.name == null)
            throw new UninitializedMessageException(message);
        output.writeString(1, message.name, false);

        if(message.id == null)
            throw new UninitializedMessageException(message);
        output.writeInt32(2, message.id, false);

        if(message.email != null)
            output.writeString(3, message.email, false);
    }

    public String getFieldName(int number)
    {
        return Integer.toString(number);
    }

    public int getFieldNumber(String name)
    {
        return Integer.parseInt(name);
    }

}

protostuff編譯后只有184行饲窿,但方法數(shù)還是多,有22個(gè)焕蹄,除了get, set方法外逾雄,還有很多輔助方法,性價(jià)比不高腻脏。

使用wire編譯后為:
// Code generated by Wire protocol buffer compiler, do not edit.
// Source file: /Users/aoxiao/Develop/protostuff/example.proto
package com.example;

import com.squareup.wire.Message;
import com.squareup.wire.ProtoField;

import static com.squareup.wire.Message.Datatype.INT32;
import static com.squareup.wire.Message.Datatype.STRING;
import static com.squareup.wire.Message.Label.REQUIRED;

public final class Person extends Message {

  public static final String DEFAULT_NAME = "";
  public static final Integer DEFAULT_ID = 0;
  public static final String DEFAULT_EMAIL = "";

  /**
   * The customer's full name.
   */
  @ProtoField(tag = 1, type = STRING, label = REQUIRED)
  public final String name;

  /**
   * The customer's ID number.
   */
  @ProtoField(tag = 2, type = INT32, label = REQUIRED)
  public final Integer id;

  /**
   * Email address for the customer.
   */
  @ProtoField(tag = 3, type = STRING)
  public final String email;

  public Person(String name, Integer id, String email) {
    this.name = name;
    this.id = id;
    this.email = email;
  }

  private Person(Builder builder) {
    this(builder.name, builder.id, builder.email);
    setBuilder(builder);
  }

  @Override
  public boolean equals(Object other) {
    if (other == this) return true;
    if (!(other instanceof Person)) return false;
    Person o = (Person) other;
    return equals(name, o.name)
        && equals(id, o.id)
        && equals(email, o.email);
  }

  @Override
  public int hashCode() {
    int result = hashCode;
    if (result == 0) {
      result = name != null ? name.hashCode() : 0;
      result = result * 37 + (id != null ? id.hashCode() : 0);
      result = result * 37 + (email != null ? email.hashCode() : 0);
      hashCode = result;
    }
    return result;
  }

  public static final class Builder extends Message.Builder<Person> {

    public String name;
    public Integer id;
    public String email;

    public Builder() {
    }

    public Builder(Person message) {
      super(message);
      if (message == null) return;
      this.name = message.name;
      this.id = message.id;
      this.email = message.email;
    }

    /**
     * The customer's full name.
     */
    public Builder name(String name) {
      this.name = name;
      return this;
    }

    /**
     * The customer's ID number.
     */
    public Builder id(Integer id) {
      this.id = id;
      return this;
    }

    /**
     * Email address for the customer.
     */
    public Builder email(String email) {
      this.email = email;
      return this;
    }

    @Override
    public Person build() {
      checkRequiredFields();
      return new Person(this);
    }
  }
}

wire編譯后為116行鸦泳,方法數(shù)減少到10個(gè),減少的原因一方面是輔助方法數(shù)少了永品,另一方面是其使用了annotation做鹰,精簡(jiǎn)了get, set方法。 不過(guò)為了創(chuàng)建一個(gè)Person實(shí)例鼎姐,wire又為每個(gè)message提供了一個(gè)Builder钾麸,里面為各field設(shè)置值的方法相當(dāng)于把set方法又加了回來(lái)掉弛,當(dāng)然Builder在build時(shí)會(huì)做很多有效性檢查的工作,但是仍有精簡(jiǎn)的余地喂走。

使用wire-lite編譯后為:
// Code generated by Wire-Lite protocol buffer compiler, do not edit.
// Source file: /Users/aoxiao/Develop/protostuff/example.proto
package com.example;

import com.squareup.wire.Message;
import com.squareup.wire.ProtoField;

import static com.squareup.wire.Message.Datatype.INT32;
import static com.squareup.wire.Message.Datatype.STRING;
import static com.squareup.wire.Message.Label.REQUIRED;

public final class Person extends Message {

  public static final int TAG_NAME = 1;
  public static final int TAG_ID = 2;
  public static final int TAG_EMAIL = 3;

  public static final String DEFAULT_NAME = "";
  public static final Integer DEFAULT_ID = 0;
  public static final String DEFAULT_EMAIL = "";

  /**
   * The customer's full name.
   */
  @ProtoField(tag = 1, type = STRING, label = REQUIRED)
  public String name;

  /**
   * The customer's ID number.
   */
  @ProtoField(tag = 2, type = INT32, label = REQUIRED)
  public Integer id;

  /**
   * Email address for the customer.
   */
  @ProtoField(tag = 3, type = STRING)
  public String email;

  public Person(Person message) {
    super(message);
    if (message == null) return;
    this.name = message.name;
    this.id = message.id;
    this.email = message.email;
  }

  public Person() {
  }

  public Person fillTagValue(int tag, Object value) {
    switch(tag) {
        case TAG_NAME:
        this.name = (String)value;
        break;
        case TAG_ID:
        this.id = (Integer)value;
        break;
        case TAG_EMAIL:
        this.email = (String)value;
        break;
        default: break;
        };
    return this;
  }

  @Override
  public boolean equals(Object other) {
    if (other == this) return true;
    if (!(other instanceof Person)) return false;
    Person o = (Person) other;
    return equals(name, o.name)
        && equals(id, o.id)
        && equals(email, o.email);
  }

  @Override
  public int hashCode() {
    int result = hashCode;
    if (result == 0) {
      result = name != null ? name.hashCode() : 0;
      result = result * 37 + (id != null ? id.hashCode() : 0);
      result = result * 37 + (email != null ? email.hashCode() : 0);
      hashCode = result;
    }
    return result;
  }
}

wire-lite編譯后的類行數(shù)減少到88行, 方法數(shù)減少到5個(gè)谋作,與wire生成的代碼對(duì)比可見(jiàn)其精簡(jiǎn)了Builder類芋肠,改為直接創(chuàng)建message實(shí)例并可對(duì)field賦值 (wire中的field是final的,生成后不可更改)遵蚜, 原Builder中對(duì)數(shù)據(jù)有效性的檢查則放到序列化或反序列化之前做帖池。通過(guò)這些改造,使得生成代碼的方法數(shù)基本等于甚至小于field數(shù)吭净,這在編譯有很多屬性的proto文件時(shí)效果尤為明顯睡汹。

wire-lite的另一個(gè)較大變動(dòng)是增加了fillTagValue方法,這樣可以通過(guò)key/value的形式鏈?zhǔn)皆O(shè)置field的值寂殉,這個(gè)方法的目的是取代Builder的鏈?zhǔn)秸{(diào)用囚巴,達(dá)到同樣可以快速創(chuàng)建實(shí)例的目的,同時(shí)又把方法數(shù)收縮到了一個(gè)友扰,這在復(fù)雜proto結(jié)構(gòu)的生成中是比較常見(jiàn)的彤叉,例如下面這個(gè)proto設(shè)置了多個(gè)自定義的option:

extend google.protobuf.FieldOptions {
  optional int32 my_field_option_one = 60001;
  optional float my_field_option_two = 60002;
  optional FooBar.FooBarBazEnum my_field_option_three = 60003;
  optional FooBar my_field_option_four = 60004;
}

message FooBar {
  extensions 100 to 200;

  optional int32 foo = 1 [my_field_option_one = 17];
  optional string bar = 2 [my_field_option_two = 33.5];
  optional Nested baz = 3 [my_field_option_three = BAR];
  optional uint64 qux = 4 [my_field_option_one = 18, my_field_option_two = 34.5];
  repeated float fred = 5 [my_field_option_four = {
      foo: 11, bar: "22", baz: { value: BAR }, fred : [444.0, 555.0],
      nested: { foo: 33, fred: [100.0, 200.0] }
  }, my_field_option_two = 99.9];
  optional double daisy = 6 [my_field_option_four.baz.value = FOO];
......

用wire生成的代碼為:

public static final MessageOptions MESSAGE_OPTIONS = new MessageOptions.Builder()
      .setExtension(Ext_custom_options.my_message_option_one, new FooBar.Builder()
          .foo(1234)
          .bar("5678")
          .baz(new FooBar.Nested.Builder()
              .value(FooBar.FooBarBazEnum.BAZ)
              .build())
          .qux(-1L)
          .fred(java.util.Arrays.asList(
              123.0F,
              321.0F))
          .daisy(456.0D)
          .build())
......

用wire-lite生成的代碼為:

public static final MessageOptions MESSAGE_OPTIONS = new MessageOptions.Builder()
      .setExtension(Ext_custom_options.my_message_option_one, new FooBar()
          .fillTagValue(FooBar.TAG_FOO, 1234)
          .fillTagValue(FooBar.TAG_BAR, "5678")
          .fillTagValue(FooBar.TAG_BAZ, new FooBar.Nested()
              .fillTagValue(FooBar.Nested.TAG_VALUE, FooBar.FooBarBazEnum.BAZ)
      )
          .fillTagValue(FooBar.TAG_QUX, -1L)
          .fillTagValue(FooBar.TAG_FRED, java.util.Arrays.asList(
              123.0F,
              321.0F))
          .fillTagValue(FooBar.TAG_DAISY, 456.0D)
      )
......

從兩段代碼的對(duì)比中可見(jiàn),通過(guò)fillTagValue來(lái)進(jìn)行鏈?zhǔn)劫x值比wire中使用Builder稍多了一點(diǎn)代碼村怪,但是方法數(shù)大為減少秽浇,還是可以接受的,而且這種調(diào)用多在自動(dòng)生成的代碼中出現(xiàn)甚负,在日常編碼中柬焕,一般采用直接賦值的方式。

如何使用wire-lite

wire-lite的使用方法與wire基本一致梭域,可以參考wire項(xiàng)目的主頁(yè) https://github.com/square/wire 這里主要說(shuō)一下不同的地方:

  1. 編譯 編譯proto文件有兩種選擇斑举,一是下載jar包對(duì)proto文件進(jìn)行編譯,jar包地址為: http://mvnrepo.alibaba-inc.com/nexus/service/local/artifact/maven/redirect?r=snapshots&g=com.squareup.wire&a=wire-lite-compiler&v=1.5.3-SNAPSHOT&e=jar&c=jar-with-dependencies

二是使用maven插件編譯

      <plugin>
        <groupId>com.squareup.wire</groupId>
        <artifactId>wire-lite-maven-plugin</artifactId>
        <executions>
          <execution>
            <phase>generate-sources</phase>
            <goals>
              <goal>generate-sources</goal>
            </goals>
            <configuration>
              <protoFiles>
                <param>squareup/wire/exemplar.proto</param>
              </protoFiles>
              <serviceWriter>com.squareup.wire.SimpleServiceWriter</serviceWriter>
            </configuration>
          </execution>
        </executions>
      </plugin>

使用方法見(jiàn)wire項(xiàng)目的說(shuō)明

  1. 引用runtime庫(kù) 在maven配置中依賴wire-lite的runtime庫(kù)即可
<dependency>
  <groupId>com.squareup.wire</groupId>
  <artifactId>wire-lite-runtime</artifactId>
  <version>1.5.3-SNAPSHOT</version>
</dependency>
  1. 創(chuàng)建對(duì)象 wire-lite創(chuàng)建對(duì)象與賦值的方式更為簡(jiǎn)單病涨,以上文的Person為例:
Person person = new Person();   //創(chuàng)建對(duì)象
person.id = 111;    //直接對(duì)field賦值
person.email = "test@test.com";

//當(dāng)然也可以通過(guò)fillTagValue賦值懂昂,每個(gè)key都是全大寫,以field name加上TAG_前綴
person.fillTagValue(Person.TAG_NAME, "Mike");

Person newPerson = new Person(person);  //創(chuàng)建一個(gè)對(duì)象没宾,并從一個(gè)已有對(duì)象處復(fù)制所有值
  1. 序列化與反序列化
byte[] personData = person.toByteArray(); //直接轉(zhuǎn)到byte數(shù)組

//或者先創(chuàng)建數(shù)據(jù)再寫入
byte[] newPersonData = new byte[person.getSerializedSize()];
person.witeTo(newPersonData);

//反序列化
Wire wire = new Wire();
Person newPersonInstance = wire.parseFrom(personData, Person.class);
  1. 有效性檢查 在wire中凌彬,有效性檢查發(fā)生在build對(duì)象時(shí)期,主要包括對(duì)require field的檢查以及數(shù)組中null元素的檢查循衰,而在wire-lite中铲敛,有效性檢查發(fā)生在序列化與反序列化時(shí)期,也就是說(shuō)第3點(diǎn)中的代碼都會(huì)進(jìn)行檢查会钝,如果數(shù)據(jù)不合法(必填項(xiàng)為null或者數(shù)組中有null值)伐蒋,則會(huì)拋出 IllegalStateException 或 NullPointerException 工三,換句話說(shuō),就是wire-lite認(rèn)為PB對(duì)象在序列化或反序列化時(shí)都應(yīng)該是合法的先鱼,而其出錯(cuò)拋異常的方式與wire一致俭正。

目前wire-lite項(xiàng)目的進(jìn)展:

wire-lite項(xiàng)目有compiler, runtime, maven-plugin三個(gè)工程,均通過(guò)了所有的單元測(cè)試焙畔,也就是說(shuō)處于立即可用的狀態(tài)掸读。在maven倉(cāng)庫(kù)中也有對(duì)應(yīng)的SNAPSHOT包,版本為1.5.3, 這是由于wire-lite是基于wire 1.5.3版本改動(dòng)而來(lái)宏多,所以版本號(hào)上保持了一致儿惫, 如果有同學(xué)還有更好的精簡(jiǎn)方案,非常歡迎加入進(jìn)來(lái)伸但,我們一起把這個(gè)庫(kù)做的更好更穩(wěn)定.

關(guān)于Android L Preview版本的問(wèn)題:

目前wire-lite在Android L Preview版本上會(huì)出現(xiàn)崩潰的情況肾请,這其實(shí)是google的問(wèn)題,google在preview版中引入了okio等開(kāi)源庫(kù)更胖,但沒(méi)有repackage铛铁,這會(huì)與wire-lite中引用的okio庫(kù)沖突,導(dǎo)致 IllegalAccessErrorException却妨。google已經(jīng)明確表示在Android L正式版中會(huì)解決這個(gè)問(wèn)題避归,將okio等包加上com.android的前綴,所以wire-lite和wire一樣管呵,不會(huì)針對(duì)preview版本做適配性修復(fù)梳毙,如果有同學(xué)一定要在preview版本中使用的話,可以自己用jarjar等工具做repackage捐下。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末账锹,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子坷襟,更是在濱河造成了極大的恐慌奸柬,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婴程,死亡現(xiàn)場(chǎng)離奇詭異廓奕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)档叔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門桌粉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人衙四,你說(shuō)我怎么就攤上這事铃肯。” “怎么了传蹈?”我有些...
    開(kāi)封第一講書人閱讀 152,370評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵押逼,是天一觀的道長(zhǎng)步藕。 經(jīng)常有香客問(wèn)我,道長(zhǎng)挑格,這世上最難降的妖魔是什么咙冗? 我笑而不...
    開(kāi)封第一講書人閱讀 55,168評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮漂彤,結(jié)果婚禮上雾消,老公的妹妹穿的比我還像新娘。我一直安慰自己显歧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布确镊。 她就那樣靜靜地躺著士骤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蕾域。 梳的紋絲不亂的頭發(fā)上拷肌,一...
    開(kāi)封第一講書人閱讀 48,954評(píng)論 1 283
  • 那天,我揣著相機(jī)與錄音旨巷,去河邊找鬼巨缘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛采呐,可吹牛的內(nèi)容都是我干的若锁。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼斧吐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼又固!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起煤率,我...
    開(kāi)封第一講書人閱讀 36,916評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤仰冠,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蝶糯,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體洋只,經(jīng)...
    沈念sama閱讀 43,382評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評(píng)論 2 323
  • 正文 我和宋清朗相戀三年昼捍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了识虚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,989評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡妒茬,死狀恐怖舷礼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情郊闯,我是刑警寧澤妻献,帶...
    沈念sama閱讀 33,624評(píng)論 4 322
  • 正文 年R本政府宣布蛛株,位于F島的核電站,受9級(jí)特大地震影響育拨,放射性物質(zhì)發(fā)生泄漏谨履。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評(píng)論 3 307
  • 文/蒙蒙 一熬丧、第九天 我趴在偏房一處隱蔽的房頂上張望笋粟。 院中可真熱鬧,春花似錦析蝴、人聲如沸害捕。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,199評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)尝盼。三九已至,卻和暖如春佑菩,著一層夾襖步出監(jiān)牢的瞬間盾沫,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,418評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工殿漠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赴精,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,401評(píng)論 2 352
  • 正文 我出身青樓绞幌,卻偏偏與公主長(zhǎng)得像蕾哟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子莲蜘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評(píng)論 2 345

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