Android 架构之Glide源码解读(上)

前言

我们在编写Android程序时,Glide图片加载框架已成为大多数App必不可少的部分。我这准备分为上、中、下三篇文章解读Glide源码。接下来我将从如下几点解读Glide源码的上部分。

  • Glide 网络请求
  • Glide 生命周期举例
  • Glide 生命周期管理
  • Glide 为什么能监听网络判断

Android开发Glide原理解析/面试题解析_哔哩哔哩_bilibili

1、Glide 网络请求

在讲Glide 网络请求之前,先看看最原始的网络图片请求加载方式。

   public void loadImageUrl(View view) {
        //最原始的网络图片加载
        //1、请求网络,子线程请求网络,HttpURLConnection
        //2、渲染UI,主线
        //3、切换到主线程
        //4、将流转成Bitmap
        //5、bitmap设置到imageview
        final String url = "https://img0.baidu.com/it/u=3736037748,233424948&fm=26&fmt=auto&gp=0.jpg";

        //非主线程操作的网络请求
        new Thread(new Runnable() {
            @Override
            public void run() {
              final Bitmap bitmap =  getImageBitmap(url);
               runOnUiThread(new Runnable() {
                   @Override
                   public void run() {
                       iv_image1.setImageBitmap(bitmap);
                   }
               });

            }
        }).start();

    }

    //http
    private Bitmap  getImageBitmap(String url){
        Bitmap bitmap=null;
        try {
            URL imageUrl = new URL(url);
            //要用到HttpURLConnection
            HttpURLConnection  conn = (HttpURLConnection) imageUrl.openConnection();
            conn.connect();
            InputStream is = conn.getInputStream();
            //Bitmap工厂类,流转化成Bitmap
            bitmap = BitmapFactory.decodeStream(is);
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bitmap;
    }

非常原始的方式,开启一个子线程,通过 HttpURLConnection 得到连接流,最后再通过BitmapFactory 转为 Bitmap。

那么我们来看看Glide是怎样进行网络请求的。

定位到 HttpUrlFetcher.class

public class HttpUrlFetcher implements DataFetcher<InputStream> {

  ...略
  
  @Override
  public void loadData(@NonNull Priority priority,
      @NonNull DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    try {
      InputStream result = loadDataWithRedirects(glideUrl.toURL(), 0, null, glideUrl.getHeaders());
      callback.onDataReady(result);
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
    } finally {
      if (Log.isLoggable(TAG, Log.VERBOSE)) {
        Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime));
      }
    }
  }

...略
}

源码解析

很明显这里这调用了 loadDataWithRedirects 方法得到对应的 InputStream 。

loadDataWithRedirects

private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,

    ...略
    
    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    urlConnection.setConnectTimeout(timeout);
    urlConnection.setReadTimeout(timeout);
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true);
    urlConnection.setInstanceFollowRedirects(false);
    urlConnection.connect();
    stream = urlConnection.getInputStream();
    if (isCancelled) {
      return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (isHttpOk(statusCode)) {
      return getStreamForSuccessfulRequest(urlConnection);
    } else if (isHttpRedirect(statusCode)) {
      String redirectUrlString = urlConnection.getHeaderField("Location");
      if (TextUtils.isEmpty(redirectUrlString)) {
        throw new HttpException("Received empty or null redirect url");
      }
      URL redirectUrl = new URL(url, redirectUrlString);
      cleanup();
      return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
    } else if (statusCode == INVALID_STATUS_CODE) {
      throw new HttpException(statusCode);
    } else {
      throw new HttpException(urlConnection.getResponseMessage(), statusCode);
    }
  }

源码解析

通过这段代码可得出,Glide 框架内部依然通过 HttpURLConnection 连接的网络。

2、Glide 生命周期举例

在正式讲解 Glide生命周期之前,先以大家熟悉的Activity与Fragment之间关系举例。

MainActivity

public class MainActivity extends Activity {

    private static final String TAG = "1111--MainActivity";
    ImageView iv_image1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"MainActivity--onCreate");
        setContentView(R.layout.activity_main);
        iv_image1 = findViewById(R.id.iv_image1);
        FragmentManager fragmentManager = getFragmentManager();
        //开启一个事物
        FragmentTransaction beginTransaction = fragmentManager.beginTransaction();
        beginTransaction.replace(android.R.id.content,new Fragment1());
        beginTransaction.commit();
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG,"MainActivity--onStart");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG,"MainActivity--onRestart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG,"MainActivity--onResume");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG,"MainActivity--onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG,"MainActivity--onStop");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"MainActivity--onDestroy");
    }
  
}


Fragment

public class Fragment1 extends Fragment {
    private static String TAG = "1111--Fragment1";
    //他的功能只是给我们进行一个生命周期的监听

    public void onAttach(Context context) {
        super.onAttach(context);
        Log.d(TAG,"Fragment1--onAttach");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG,"Fragment1--onCreate");
    }

    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(TAG,"Fragment1--onCreateView");
        View root = inflater.inflate(R.layout.fragment, container, false);
        return root;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.d(TAG,"Fragment1--onActivityCreated");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.d(TAG,"Fragment1--onStart");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG,"Fragment1--onResume");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG,"Fragment1--onPause");
    }

    @Override
    public void onStop() {
        super.onStop();
        Log.d(TAG,"Fragment1--onStop");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d(TAG,"Fragment1--onDestroyView");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG,"Fragment1--onDestroy");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.d(TAG,"Fragment1--onDetach");
    }
}

这没啥可说的,直接运行看效果。

MainActivity: MainActivity--onCreate
Fragment1: Fragment1--onAttach
Fragment1: Fragment1--onCreate
Fragment1: Fragment1--onCreateView
Fragment1: Fragment1--onActivityCreated
MainActivity: MainActivity--onStart
Fragment1: Fragment1--onStart
MainActivity: MainActivity--onResume
Fragment1: Fragment1--onResume
Fragment1: Fragment1--onPause
MainActivity: MainActivity--onPause
Fragment1: Fragment1--onStop
MainActivity: MainActivity--onStop
Fragment1: Fragment1--onDestroyView
Fragment1: Fragment1--onDestroy
Fragment1: Fragment1--onDetach
MainActivity: MainActivity--onDestroy

这一系列的生命周期,最后再来张图总结下。

3、Glide 生命周期管理

说到生命周期,我们第一时间想到的都是Activity与Fragment之间的关系,现在我们来看看Glide生命周期是什么样的。

Glide.with(this).load(url).into(iv_image1);

这简短的一句话,就完成了整个网络图片加载。我们先进with看看。

  @NonNull
  public static RequestManager with(@NonNull Activity activity) {
    return getRetriever(activity).get(activity);
  }

在进get。

  @NonNull
  public RequestManager get(@NonNull Activity activity) {
    //当前图片加载页面是否在后台运行
    if (Util.isOnBackgroundThread()) {
      //如果加载的图片对应的页面在后台运行,那么进入该逻辑
      return get(activity.getApplicationContext());
    } else {
      //判断当前显示的activity是否被销毁。
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();
      return fragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }

这里有个逻辑判断,表示当前加载的图片是否在后台页面展示。现在我们分别以在后台、不在后台两个方向解读源码。

3.1、当前图片加载页面在后台运行

那么直接会调用对应get方法,继续追进看看

  @NonNull
  public RequestManager get(@NonNull Context context) {
    if (context == null) {
      throw new IllegalArgumentException("You cannot start a load on a null Context");
    } else if (Util.isOnMainThread() && !(context instanceof Application)) {
      if (context instanceof FragmentActivity) {
        return get((FragmentActivity) context);
      } else if (context instanceof Activity) {
        return get((Activity) context);
      } else if (context instanceof ContextWrapper) {
        return get(((ContextWrapper) context).getBaseContext());
      }
    }
    return getApplicationManager(context);
  }

源码解读

因为上面传参为:activity.getApplicationContext() ,那么就不可能进入if条件判断,将会直接进 getApplicationManager 方法。进去看看。

 @NonNull
  private RequestManager getApplicationManager(@NonNull Context context) {
    // Either an application context or we're on a background thread.
    if (applicationManager == null) {
      synchronized (this) {
        if (applicationManager == null) {
          Glide glide = Glide.get(context.getApplicationContext());
          applicationManager =
              factory.build(
                  glide,
                  new ApplicationLifecycle(),
                  new EmptyRequestManagerTreeNode(),
                  context.getApplicationContext());
        }
      }
    }

    return applicationManager;
  }

源码解析

仔细看源码中用到 ApplicationLifecycle ,也就是说,当我们加载的图片对应的界面在后台运行时,对应的生命周期就和应用生命周期相互绑定。

现在我们就该分析图片加载在前台运行的源码了。

3.2、当前图片加载页面在前台运行

  @NonNull
  public RequestManager get(@NonNull Activity activity) {
    //当前图片加载页面是否在后台运行
    if (Util.isOnBackgroundThread()) {
      //如果加载的图片对应的页面在后台运行,那么进入该逻辑
      return get(activity.getApplicationContext());
    } else {
      //判断当前显示的activity是否被销毁。
      assertNotDestroyed(activity);
      android.app.FragmentManager fm = activity.getFragmentManager();
      return fragmentGet(
          activity, fm, /*parentHint=*/ null, isActivityVisible(activity));
    }
  }

源码解析

继续回到这里,刚刚已经分析了if逻辑里面的源码,现在该分析else里面了。首先调用了assertNotDestroyed进行 activity是否销毁的处理。其次核心逻辑在 fragmentGet方法里,进去看看。

  @NonNull
  private RequestManager fragmentGet(@NonNull Context context,
      @NonNull android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    RequestManagerFragment current = getRequestManagerFragment(fm, parentHint, isParentVisible);
    RequestManager requestManager = current.getRequestManager();
    if (requestManager == null) {
      // TODO(b/27524013): Factor out this Glide.get() call.
      Glide glide = Glide.get(context);
      requestManager =
          factory.build(
              glide, current.getGlideLifecycle(), current.getRequestManagerTreeNode(), context);
      current.setRequestManager(requestManager);
    }
    return requestManager;
  }

源码解析

这里隐隐约约好像看到了 Fragment,通过 getRequestManagerFragment 方法获取的。继续看看 getRequestManagerFragment 方法。

  @NonNull
  private RequestManagerFragment getRequestManagerFragment(
      @NonNull final android.app.FragmentManager fm,
      @Nullable android.app.Fragment parentHint,
      boolean isParentVisible) {
    RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
    if (current == null) {
    //如果第一层判断为空,就去缓存里面获取对应的 Fragment 
      current = pendingRequestManagerFragments.get(fm);
      if (current == null) {
        //如果缓存里面还为空,那么就创建一个新的 Fragment 
        current = new RequestManagerFragment();
        current.setParentFragmentHint(parentHint);
        if (isParentVisible) {
          current.getGlideLifecycle().onStart();
        }
        //向缓存里面添加新创建的 Fragment 
        pendingRequestManagerFragments.put(fm, current);
        fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
        handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
      }
    }
    return current;
  }

源码解析

到这里可以看出,这 getRequestManagerFragment 方法返回的就是 Fragment,并和当前显示的activity相互绑定 ,难道说Glide的生命周期和 Fragment 有关系?带着这样的疑问进入RequestManagerFragment看看。

RequestManagerFragment.class

public class RequestManagerFragment extends Fragment {
  private static final String TAG = "RMFragment";
  private final ActivityFragmentLifecycle lifecycle;
  
  ...略

  @VisibleForTesting
  @SuppressLint("ValidFragment")
  RequestManagerFragment(@NonNull ActivityFragmentLifecycle lifecycle) {
    this.lifecycle = lifecycle;
  }

  ...略

  @NonNull
  ActivityFragmentLifecycle getGlideLifecycle() {
    return lifecycle;
  }

 ...略
 
  @Override
  public void onDetach() {
    super.onDetach();
    unregisterFragmentWithRoot();
  }

  @Override
  public void onStart() {
    super.onStart();
    lifecycle.onStart();
  }

  @Override
  public void onStop() {
    super.onStop();
    lifecycle.onStop();
  }

  @Override
  public void onDestroy() {
    super.onDestroy();
    lifecycle.onDestroy();
    unregisterFragmentWithRoot();
  }

  @Override
  public String toString() {
    return super.toString() + "{parent=" + getParentFragmentUsingHint() + "}";
  }

  ...略
}


源码解析

到这里,我们大致差不多能明白了,Glide加载的图片在当前显示activity时,将会创建或者获取已创建过对应空页面的Fragment,接着将对应Fragment生命周期里面的 onStart、onStop、onDestroy这三个方法通过 ActivityFragmentLifecycle 与Glide进行生命周期的绑定。也就是说,当Fragment进行这三个生命周期方法时,对应的Glide也会处理对应生命周期的逻辑。

现在我们举例 onDestroy 来验证一下,上面说的是否正确。

  @Override
  public void onDestroy() {
    super.onDestroy();
    lifecycle.onDestroy();
    unregisterFragmentWithRoot();
  }

当执行到 onDestroy 方法时,也会执行 lifecycle 的 onDestroy。现在进入 lifecycle 的 onDestroy看看。

所图所示

进入 ActivityFragmentLifecycle 实现类 RequestManager

public class RequestManager implements LifecycleListener,
    ModelTypes<RequestBuilder<Drawable>> {

   ...略

  @Override
  public void onStart() {
    resumeRequests();
    targetTracker.onStart();
  }

  @Override
  public void onStop() {
    pauseRequests();
    targetTracker.onStop();
  }

  @Override
  public void onDestroy() {
    targetTracker.onDestroy();
    for (Target<?> target : targetTracker.getAll()) {
      clear(target);
    }
    targetTracker.clear();
    requestTracker.clearRequests();
    lifecycle.removeListener(this);
    lifecycle.removeListener(connectivityMonitor);
    mainHandler.removeCallbacks(addSelfToLifecycle);
    glide.unregisterRequestManager(this);
  }

...略
}

源码解析

看到这里,能够完全确认了,当activity执行对应的生命周期时,Glide对应的Fragment也会执行相应的生命周期,同时Glide内部的lifecycle 也会执行相应的逻辑处理。

4、Glide 为什么能监听网络判断

在我们使用Glide的切换网络(WIFI/流量相互切换)的时候,Glide依然能够正常的加载网络图片。它在里面做了些什么处理呢?我们带着这样的疑问进去看看。

public class Glide implements ComponentCallbacks2 {
  ...略
  @NonNull
  public static RequestManager with(@NonNull Activity activity) {
    return getRetriever(activity).get(activity);
  }
  ...略

}

源码解析

这里with方法返回 RequestManager ,核心逻辑就在这里面,进去看看。

public class RequestManager implements LifecycleListener,
    ModelTypes<RequestBuilder<Drawable>> {
    
 ...略
 
  RequestManager(
      Glide glide,
      Lifecycle lifecycle,
      RequestManagerTreeNode treeNode,
      RequestTracker requestTracker,
      ConnectivityMonitorFactory factory,
      Context context) {
    this.glide = glide;
    this.lifecycle = lifecycle;
    this.treeNode = treeNode;
    this.requestTracker = requestTracker;
    this.context = context;

    connectivityMonitor =
        factory.build(
            context.getApplicationContext(),
            new RequestManagerConnectivityListener(requestTracker));

    if (Util.isOnBackgroundThread()) {
      mainHandler.post(addSelfToLifecycle);
    } else {
      lifecycle.addListener(this);
    }
    lifecycle.addListener(connectivityMonitor);

    setRequestOptions(glide.getGlideContext().getDefaultRequestOptions());

    glide.registerRequestManager(this);
  }

...略

  private static class RequestManagerConnectivityListener implements ConnectivityMonitor
      .ConnectivityListener {
    private final RequestTracker requestTracker;

    RequestManagerConnectivityListener(@NonNull RequestTracker requestTracker) {
      this.requestTracker = requestTracker;
    }

    @Override
    public void onConnectivityChanged(boolean isConnected) {
      if (isConnected) {
        requestTracker.restartRequests();
      }
    }
  }
}

源码解析

从这段代码可以看出,在构造方法里面注册了RequestManagerConnectivityListener 事件,而这个 RequestManagerConnectivityListener 类实现了 ConnectivityMonitor里面的ConnectivityMonitor 接口。进去看看。

如图所示

进入DefaultConnectivityMonitor 里面看看。

final class DefaultConnectivityMonitor implements ConnectivityMonitor {
...略
  private final BroadcastReceiver connectivityReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(@NonNull Context context, Intent intent) {
      boolean wasConnected = isConnected;
      isConnected = isConnected(context);
      if (wasConnected != isConnected) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "connectivity changed, isConnected: " + isConnected);
        }

        listener.onConnectivityChanged(isConnected);
      }
    }
  };
...略
}

源码解析

这里核心逻辑定义了一个广播,里面调用了 isConnected 方法。继续跟进。

  @Synthetic
  // Permissions are checked in the factory instead.
  @SuppressLint("MissingPermission")
  boolean isConnected(@NonNull Context context) {
    ConnectivityManager connectivityManager =
        Preconditions.checkNotNull(
            (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE));
    NetworkInfo networkInfo;
    try {
      networkInfo = connectivityManager.getActiveNetworkInfo();
    } catch (RuntimeException e) {
      if (Log.isLoggable(TAG, Log.WARN)) {
        Log.w(TAG, "Failed to determine connectivity status when connectivity changed", e);
      }
      return true;
    }
    return networkInfo != null && networkInfo.isConnected();
  }

源码解析

这里已 Context.CONNECTIVITY_SERVICE注册了 网络连接的广播,当有正常网络连接的时候,将会实时获取。我们再回到上一步。

final class DefaultConnectivityMonitor implements ConnectivityMonitor {
...略
  private final BroadcastReceiver connectivityReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(@NonNull Context context, Intent intent) {
      boolean wasConnected = isConnected;
      isConnected = isConnected(context);
      if (wasConnected != isConnected) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
          Log.d(TAG, "connectivity changed, isConnected: " + isConnected);
        }

        listener.onConnectivityChanged(isConnected);
      }
    }
  };
...略
}

源码解析

这里的 if (wasConnected != isConnected) 判断,也就是说,当网络发生改变时,就会调用下面onConnectivityChanged 方法。继续追进。

public class RequestManager implements LifecycleListener,
    ModelTypes<RequestBuilder<Drawable>> {
...略
  private static class RequestManagerConnectivityListener implements ConnectivityMonitor
      .ConnectivityListener {

    ...略

    @Override
    public void onConnectivityChanged(boolean isConnected) {
      if (isConnected) {
        requestTracker.restartRequests();
      }
    }
  }
...略
}

源码解析

这里如果说网络状态为可用,那么将会调用 restartRequests 方法。就会走到。

  public void restartRequests() {
    for (Request request : Util.getSnapshot(requests)) {
      if (!request.isComplete() && !request.isCleared()) {
        request.clear();
        if (!isPaused) {
          request.begin();
        } else {
          // Ensure the request will be restarted in onResume.
          pendingRequests.add(request);
        }
      }
    }
  }

源码解析

这里就会遍历所有网络图片加载情况,先判断单个图片是否加载完成,那就删除对应图片加载,其次判断加载过程中是否暂停,如果暂停那就重新开始,最后就是如果还没开始加载,如果没加载那就添加至队列中。

5、总结

本篇文章,主要讲解了 Glide网络请求、生命周期、以及对应的网络监听处理。

在下一篇中,主要解读 Glide 的 with、load、into 三部曲。

相关推荐

本文转自 https://juejin.cn/post/7016611778767355935,如有侵权,请联系删除。

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

推荐阅读更多精彩内容