OkHttp源码解析


image.png

序言

上一篇文章介绍了Retrofit的原理,今天,我们就来探究一下OkHttp的原理。Retrofit是对OkHttp做封装,真正发送网络请求的就是OkHttp框架。
使用Retrofit框架过程中,对OkHttp最直观的认识就是OkHttpClient,这篇文章会按如下目录展开介绍:

  • OkHttp的用法
  • OkHttp在Retrofit中的使用
  • OkHttp的原理
  • 对比OkHttp与其他网络库
  • 总结

OkHttp的用法

本文基于OkHttp3.10.0版本进行介绍。

  1. 首先添加OkHttp依赖
implementation("com.squareup.okhttp3:okhttp:3.10.0")

如果项目中添加了Retrofit2依赖,就不用再添加OkHttp3的依赖,因为Retrofit2库使用到了OkHttp3,并且已经添加了OkHttp3的依赖,所以不用我们重复添加。

  1. 使用方法
//创建OkHttpClient对象
OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
   //创建Request请求对象
  Request request = new Request.Builder()
      .url(url)
      .build();

   //创建Call对象,并执行同步获取网络数据
  Response response = client.newCall(request).execute();
  return response.body().string();
}

总结一下,OkHttp的使用步骤如下:

  • 创建OkHttpClient对象
  • 创建Request对象
  • 创建Call对象
  • 通过Call对象发起请求,同步请求调用call.execute方法,异步请求调用call.enqueue方法

OkHttp在Retrofit中的使用

结合之前的一篇文章Retrofit源码解析,我们来看一下在Retrofit中怎样使用OkHttp。

  1. 创建OkHttpClient对象
    在初始化Retrofit对象的时候,我们会传入一个OkHttpClient对象:
DeviceRetrofit() {
    OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .connectTimeout(Config.HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
            .retryOnConnectionFailure(true)
            .addInterceptor(new DeviceInterceptor())
            .addInterceptor(OkHttpUtils.getHttpInterceptor(TAG))
            .build();

    Retrofit retrofit = new Retrofit.Builder()
            .client(okHttpClient)
            .baseUrl(Config.DEVICE_HOST)
            .addConverterFactory(JacksonConverterFactory.create())
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build();
    mService = retrofit.create(DeviceService.class);
}

这里完成了第一步,创建OkHttpClient对象。

  1. 创建Request对象
    当我们通过Retrofit发起一个请求的时候,在ServiceMethod中会创建一个okhttp3.Request对象:
/**
 * 创建Call对象
 */
private okhttp3.Call createRawCall() throws IOException {
  Request request = serviceMethod.toRequest(args);
  okhttp3.Call call = serviceMethod.callFactory.newCall(request);
  if (call == null) {
    throw new NullPointerException("Call.Factory returned null.");
  }
  return call;
}

/**
 * 创建Request对象
 */
Request toRequest(Object... args) throws IOException {
  RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
      contentType, hasBody, isFormEncoded, isMultipart);

  @SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
  ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;

  int argumentCount = args != null ? args.length : 0;
  if (argumentCount != handlers.length) {
    throw new IllegalArgumentException("Argument count (" + argumentCount
        + ") doesn't match expected count (" + handlers.length + ")");
  }

  for (int p = 0; p < argumentCount; p++) {
    handlers[p].apply(requestBuilder, args[p]);
  }

  return requestBuilder.build();
}

这里完成了第二步,创建了一个Request对象。

  1. 创建Call对象
    第二步的代码中,有一个createRawCall方法,它会从callFactory工厂中创建一个Call对象,之前说过,callFactory就是OkHttpClient,所以看OkHttpClient的newCall方法:
@Override public Call newCall(Request request) {
  return RealCall.newRealCall(this, request, false /* for web socket */);
}

这里完成了第三步,创建了一个RealCall对象。

  1. 调用Call的execute方法
    在RxJavaCallAdapterFactory.CallOnSubscribe.call方法中,会调用call.execute()发送同步请求:
static final class CallOnSubscribe<T> implements Observable.OnSubscribe<Response<T>> {
  private final Call<T> originalCall;

  CallOnSubscribe(Call<T> originalCall) {
    this.originalCall = originalCall;
  }

  @Override public void call(final Subscriber<? super Response<T>> subscriber) {
    // Since Call is a one-shot type, clone it for each new subscriber.
    final Call<T> call = originalCall.clone();

    // Attempt to cancel the call if it is still in-flight on unsubscription.
    subscriber.add(Subscriptions.create(new Action0() {
      @Override public void call() {
        call.cancel();
      }
    }));

    try {
      Response<T> response = call.execute();
      if (!subscriber.isUnsubscribed()) {
        subscriber.onNext(response);
      }
    } catch (Throwable t) {
      Exceptions.throwIfFatal(t);
      if (!subscriber.isUnsubscribed()) {
        subscriber.onError(t);
      }
      return;
    }

    if (!subscriber.isUnsubscribed()) {
      subscriber.onCompleted();
    }
  }
}

这里就完成了第四步,调用call.execute方法。

可见,Retrofit中确实是按照OkHttp3的四个步骤进行使用。

OkHttp的原理

上面介绍了OkHttp的使用方法,以及Retrofit中怎样使用OkHttp。那么下面我们就来分析一下OkHttp的原理,OkHttp是怎样发送Http请求的?我们还是按照这四个步骤进行分析。

  1. 创建OkHttpClient对象
    我们看一下OkHttpClient的实现:
public class OkHttpClient implements Cloneable, Call.Factory, WebSocket.Factory {
  static final List<Protocol> DEFAULT_PROTOCOLS = Util.immutableList(
      Protocol.HTTP_2, Protocol.HTTP_1_1);

  static final List<ConnectionSpec> DEFAULT_CONNECTION_SPECS = Util.immutableList(
      ConnectionSpec.MODERN_TLS, ConnectionSpec.CLEARTEXT);

  final Dispatcher dispatcher; //请求分发器
  final @Nullable Proxy proxy;
  final List<Protocol> protocols; //支持的协议,默认http1.1和http2
  final List<ConnectionSpec> connectionSpecs;
  final List<Interceptor> interceptors; //用户设置的拦截器
  final List<Interceptor> networkInterceptors;
  final EventListener.Factory eventListenerFactory; //请求事件监听器工厂
  final ProxySelector proxySelector;
  final CookieJar cookieJar; //可设置往请求头中加入cookie信息
  final @Nullable Cache cache; //可设置是否缓存请求Response策略
  final @Nullable InternalCache internalCache; //OkHttp3.0之后使用cache,不再使用此成员变量
  final SocketFactory socketFactory;
  final @Nullable SSLSocketFactory sslSocketFactory;
  final @Nullable CertificateChainCleaner certificateChainCleaner;
  final HostnameVerifier hostnameVerifier;
  final CertificatePinner certificatePinner;
  final Authenticator proxyAuthenticator;
  final Authenticator authenticator;
  final ConnectionPool connectionPool;
  final Dns dns;
  final boolean followSslRedirects;
  final boolean followRedirects;
  final boolean retryOnConnectionFailure;
  final int connectTimeout; //默认连接超时时间10s
  final int readTimeout; //默认读取超时时间10s
  final int writeTimeout; //默认写入超时时间10s
  final int pingInterval; 
    
  //省略其他代码
}

部分关键的成员标了注释,各位同学可以看一下。着重关注一下Dispatcher类,它是网络请求分发器,同步请求和异步请求会做不同的分发处理,后面会详细介绍该类。

  1. 创建Request对象
    看一下Request的实现:
/**
 * An HTTP request. Instances of this class are immutable if their {@link #body} is null or itself
 * immutable.
 */
public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final @Nullable RequestBody body;
  final Object tag;

  private volatile CacheControl cacheControl; // Lazily initialized.

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }
  
  //省略其他代码
}

从注释中可以知道,这个类代表Http请求,其中包含了Http请求所需的信息。

  • url:请求地址
  • methdo:请求类型,如GET、POST、DELETE、PATCH、PUT等
  • headers:请求头信息
  • body:请求体
  • tag:标签

我们在看一下Header类的结构:

public final class Headers {
  private final String[] namesAndValues;

  Headers(Builder builder) {
    this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]);
  }

  //省略其他代码
}

可以看到Headers类中通过一个字符串数组保存头信息,保存方式如下{key1, value1, key2, value2, ...}

再看一下RequestBody类的结构:

public abstract class RequestBody {
  /** Returns the Content-Type header for this body. */
  public abstract @Nullable MediaType contentType();

  /**
   * Returns the number of bytes that will be written to {@code sink} in a call to {@link #writeTo},
   * or -1 if that count is unknown.
   */
  public long contentLength() throws IOException {
    return -1;
  }

  /** Writes the content of this request to {@code sink}. */
  public abstract void writeTo(BufferedSink sink) throws IOException;

  //省略其他代码
}

RequestBody是一个抽象类,其中有两个抽象方法:

  • writeTo方法:用于把请求体的内容写入到sink(发送请求到服务器的对象)
  • contentType方法:标志请求体内容的类型。

Request相关内容介绍完毕,接下来看一下创建Call部分。

  1. 创建Call对象
    Call对象,我们可以理解为一次网络请求的封装,一个Call对象只能被执行一次。那么,是如果保证只能被执行一次的特性呢?
    上面提到过,Retrofit会为一次请求创建一个RealCall对象:
final class RealCall implements Call {
  final OkHttpClient client;
  final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;

  /**
   * There is a cycle between the {@link Call} and {@link EventListener} that makes this awkward.
   * This will be set after we create the call instance then create the event listener instance.
   */
  private EventListener eventListener;

  /** The application's original request unadulterated by redirects or auth headers. */
  final Request originalRequest;
  final boolean forWebSocket;

  // Guarded by this.
  private boolean executed;

  private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }

  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } catch (IOException e) {
      eventListener.callFailed(this, e);
      throw e;
    } finally {
      client.dispatcher().finished(this);
    }
  }
  
  @Override public void enqueue(Callback responseCallback) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    eventListener.callStart(this);
    client.dispatcher().enqueue(new AsyncCall(responseCallback));
  }

  //省略其他代码
}

我们可以看到,RealCall中有个布尔型的变量executed,标志该Call是否已经执行了。
在execute和enqueue两个方法中都会校验executed,这就保证了一个Call只能执行一次

  1. 调用call.execute或call.enqueue
    再看execute和enqueue两个方法,都会调用Dispatcher对应的方法。那就看看Dispatcher的实现:
public final class Dispatcher {
  private int maxRequests = 64;
  private int maxRequestsPerHost = 5;
  private @Nullable Runnable idleCallback;

  /** Executes calls. Created lazily. */
  private @Nullable ExecutorService executorService;

  /** Ready async calls in the order they'll be run. */
  private final Deque<AsyncCall> readyAsyncCalls = new ArrayDeque<>();

  /** Running asynchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<AsyncCall> runningAsyncCalls = new ArrayDeque<>();

  /** Running synchronous calls. Includes canceled calls that haven't finished yet. */
  private final Deque<RealCall> runningSyncCalls = new ArrayDeque<>();

  public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  public Dispatcher() {
  }

  /** Used by {@code Call#execute} to signal it is in-flight. */
  synchronized void executed(RealCall call) {
    runningSyncCalls.add(call);
  }

  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

  //省略其他代码
}

Dispatcher中有几个成员变量,依次介绍一下:

  • maxRequests:表示异步请求支持的最大并发请求数
  • maxRequestsPerHost:表示每个Host支持的最大并发请求数,Host可以理解为baseUrl
  • idleCallback:分发器闲置回调,即分发器中没有正在执行的请求和准备执行的请求,就会回调idleCallback.run方法
  • executorService:线程池,用于管理异步请求的线程
  • readyAsyncCalls:处于准备状态的异步请求队列
  • runningAsyncCalls:正在执行的异步请求队列
  • runningSyncCalls:正在执行的同步请求队列

着重关注两个异步请求队列,什么时候会进入异步准备队列,什么时候又会进入异步执行队列呢?

runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost

满足上面条件,即异步线程数量已经超过最大请求数,或者单个Host的异步请求数超过最大请求数,就会进入异步准备队列,否则直接进入异步执行队列

我们再看Dispatcher的executed和enqueue方法,发现executed方法很简单:

/** Used by {@code Call#execute} to signal it is in-flight. */
synchronized void executed(RealCall call) {
  runningSyncCalls.add(call);
}

这里只是把同步请求加入同步执行队列中,并没有具体的请求执行操作。那么这个执行操作在哪里呢?
我们看回RealCall的execute方法:

@Override public Response execute() throws IOException {
  synchronized (this) {
    if (executed) throw new IllegalStateException("Already Executed");
    executed = true;
  }
  captureCallStackTrace();
  eventListener.callStart(this);
  try {
    client.dispatcher().executed(this);
    Response result = getResponseWithInterceptorChain();
    if (result == null) throw new IOException("Canceled");
    return result;
  } catch (IOException e) {
    eventListener.callFailed(this, e);
    throw e;
  } finally {
    client.dispatcher().finished(this);
  }
}

在调用了Dispatcher.executed方法后,会调用getResponseWithInterceptorChain()方法,那么可以肯定,这个方法就是真正执行请求的地方。
再看一下异步请求,RealCall的enqueue方法中,调用Dispatcher.enqueue方法时,会创建一个AsyncCall对象作为参数传入。那我们看一下AsyncCall的实现:

final class AsyncCall extends NamedRunnable {
  private final Callback responseCallback;

  AsyncCall(Callback responseCallback) {
    super("OkHttp %s", redactedUrl());
    this.responseCallback = responseCallback;
  }

  String host() {
    return originalRequest.url().host();
  }

  Request request() {
    return originalRequest;
  }

  RealCall get() {
    return RealCall.this;
  }

  @Override protected void execute() {
    boolean signalledCallback = false;
    try {
      Response response = getResponseWithInterceptorChain();
      if (retryAndFollowUpInterceptor.isCanceled()) {
        signalledCallback = true;
        responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
      } else {
        signalledCallback = true;
        responseCallback.onResponse(RealCall.this, response);
      }
    } catch (IOException e) {
      if (signalledCallback) {
        // Do not signal the callback twice!
        Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
      } else {
        eventListener.callFailed(RealCall.this, e);
        responseCallback.onFailure(RealCall.this, e);
      }
    } finally {
      client.dispatcher().finished(this);
    }
  }
}

在AsyncCall的execute方法中,也会调用getResponseWithInterceptorChain()方法。

也就是说,不管是同步还是异步请求,都是通过getResponseWithInterceptorChain()方法真正执行请求的

接下来,当然是看getResponseWithInterceptorChain方法的实现:

Response getResponseWithInterceptorChain() throws IOException {
  // Build a full stack of interceptors.
  List<Interceptor> interceptors = new ArrayList<>();
  interceptors.addAll(client.interceptors());
  interceptors.add(retryAndFollowUpInterceptor);
  interceptors.add(new BridgeInterceptor(client.cookieJar()));
  interceptors.add(new CacheInterceptor(client.internalCache()));
  interceptors.add(new ConnectInterceptor(client));
  if (!forWebSocket) {
    interceptors.addAll(client.networkInterceptors());
  }
  interceptors.add(new CallServerInterceptor(forWebSocket));

  Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
      originalRequest, this, eventListener, client.connectTimeoutMillis(),
      client.readTimeoutMillis(), client.writeTimeoutMillis());

  return chain.proceed(originalRequest);
}

这个方法中,先得到一个interceptors列表,其中包含的元素有:

  1. 用户设置的interceptor列表
  2. RetryAndFollowUpInterceptor:错误重试和重定向拦截器
  3. BridgeInterceptor:把用户的request转换为Http的request,把Http的response转换为用户需要response的转换桥拦截器
  4. CacheInterceptor:处理Response缓存的拦截器
  5. ConnectInterceptor:建立服务器连接的拦截器
  6. 用户设置的networkInterceptors列表
  7. CallServerInterceptor:真正向服务器发送请求并且得到响应的拦截器

然后创建一个RealInterceptorChain对象,继而调用其proceed方法。
RealInterceptorChain实现了Interceptor.Chain接口,先看一下接口定义:

/**
 * Observes, modifies, and potentially short-circuits requests going out and the corresponding
 * responses coming back in. Typically interceptors add, remove, or transform headers on the request
 * or response.
 */
public interface Interceptor {
  Response intercept(Chain chain) throws IOException;

  interface Chain {
    Request request();

    Response proceed(Request request) throws IOException;

    /**
     * Returns the connection the request will be executed on. This is only available in the chains
     * of network interceptors; for application interceptors this is always null.
     */
    @Nullable Connection connection();

    Call call();

    int connectTimeoutMillis();

    Chain withConnectTimeout(int timeout, TimeUnit unit);

    int readTimeoutMillis();

    Chain withReadTimeout(int timeout, TimeUnit unit);

    int writeTimeoutMillis();

    Chain withWriteTimeout(int timeout, TimeUnit unit);
  }
}

再看RealInterceptorChain的定义:

public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  private final StreamAllocation streamAllocation;
  private final HttpCodec httpCodec;
  private final RealConnection connection;
  private final int index;
  private final Request request;
  private final Call call;
  private final EventListener eventListener;
  private final int connectTimeout;
  private final int readTimeout;
  private final int writeTimeout;
  private int calls;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
      EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
    this.call = call;
    this.eventListener = eventListener;
    this.connectTimeout = connectTimeout;
    this.readTimeout = readTimeout;
    this.writeTimeout = writeTimeout;
  }

  //省略部分代码
  
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();

    calls++;

    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
        connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
        writeTimeout);
    Interceptor interceptor = interceptors.get(index);
    Response response = interceptor.intercept(next);

    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    if (response.body() == null) {
      throw new IllegalStateException(
          "interceptor " + interceptor + " returned a response with no body");
    }

    return response;
  }
}

我们着重看proceed方法,proceed方法我用一张流程图来总结:


image.png

通过这张图,可以很清晰的看到执行的流程:
通过RealInterceptorChain一层一层调用各interceptor的intercept方法,并且在此过程中可以处理request,比如往其中添加参数,或者做一些其他操作。一直到CallServerInterceptor,它是最后一个拦截器,真正用来执行网络请求,从服务器获取Response。然后把Response一层一层网上传递,在此过程中,可以对Response做一定的处理
这个过程用到了责任链模式,以链式调用,每一个节点可以执行不同的逻辑,各节点之间解耦,扩展性强,可以随意添加节点。

想知道怎样执行网络请求的同学,可以自行研究各个Interceptor,尤其是CallServerInterceptor,它是真正执行网络请求的地方。主要是调用了Http1Codec.flushRequest方法,继而调用BufferedSink.flush方法,之后的就留给各位同学自己去看了。

最后得到的Response对象交给Retrofit进行转换适配,到这里OkHttp的原理分析完毕。

对比OkHttp与其他网络库

这里主要对比几个常用的网络扩,包括android-async-http、volley、OkHttp和Retrofit。

  1. android-async-http(loopj)
  • 基于HttpClient
  • 自动请求重试
  • 持久化cookie,保存在sp中
  • 作者已经停止对项目维护,因此不推荐在项目中使用
  1. volley(Google)
  • 基于HttpURLConnection
  • 封装图片框架,支持图片加载
  • 与Activity生命周期的联动,Activit结束时取消所有的网络请求
  • 适合数据量小,频率高的请求,不适合上传或下载大文件,原因:线程池默认大小是4,Request.getBody()返回的是字节数组,也就是对于Post或Put请求,会把传输的数据一股脑读到内存中,很容易出现oom
  1. OkHttp(Square)
  • 不基于HttpClient和HttpURLConnection,本身可以理解为一个封装之后的HttpURLConnection
  • 集各种优点与一身
  • Android4.4源码可以看到HttpURLConnection已经替换为OkHttp实现,可见其强大
  1. Retrofit(Square)
  • 基于OkHttp
  • RESTful Api设计风格
  • 通过注解配置请求
  • 支持同步、异步
  • 易与其他框架配合使用,如Rxjava
  • 使用方法较多,原理比较复杂,存在一定的门槛
  • 高度解耦,扩展性极好,堪称代码设计的典范

总结

相信看到这里,各位同学对OkHttp有了一个深刻的认识,再结合Retrofit那篇文章看,就更能前后贯通理解整个Retrofit+OkHttp的流程。

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

推荐阅读更多精彩内容