安卓学习 - Fragment的懒加载

转载请附上原博客连接 https://www.jianshu.com/p/837cdd4cf371

懒加载的应用场景和作用:

在我们开发APP的过程中,经常会用到ViewPager+Fragment的UI结构(微信主界面就是这样组成的)。通过左右滑动,ViewPager加载不同的Fragment进行显示。ViewPager在加载当前应当显示的Fragment的时候,会同时将该Fragment前后相邻的Fragment也加载到内存中。这样,当几个Fragment中都有很多耗费资源的初始化操作时,可能会因网络阻塞造成当前页面初始化缓慢的问题。因此我们需要只在Fragment用户可见的时候才加载数据,这就是懒加载。

首先我们来证实一下在使用Fragment+ViewPager的时候。ViewPager会提前加载当前Fragment左右相邻的Fragment。

我们像往常一样,在主界面中使用ViewPager,如下面代码所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.tinymonster.lazyloadtest.MainActivity">

    <android.support.v4.view.ViewPager
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/viewPager"
        />
</LinearLayout>

自定义Fragment,在生命周期内打印信息。

public class MyFragment extends Fragment{
    private String data;

    public MyFragment(){
    }

    public MyFragment(String data){
        this.data=data;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.e(data,"onAttach");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e(data,"onCreate");
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.e(data,"onCreateView");
        View view=inflater.inflate(R.layout.my_fragment,null);
        TextView textView=(TextView)view.findViewById(R.id.text);
        textView.setText(data);
        return view;
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.e(data,"onStart");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.e(data,"onResume");
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        Log.e(data,"setUserVisibleHint:"+isVisibleToUser);
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.e(data,"onDestroyView");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(data,"onDestroy");
    }

}

在主界面代码中给ViewPager添加Fragment,并且设置首先显示第三个Fragment。

public class MainActivity extends AppCompatActivity {
    private ViewPager viewPager;
    private MyAdapter myAdapter;
    List<Fragment> list=new ArrayList<>();
    @Override

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        viewPager=(ViewPager)findViewById(R.id.viewPager);
        list.add(new MyFragment("Fragment_1"));
        list.add(new MyFragment("Fragment_2"));
        list.add(new MyFragment("Fragment_3"));
        list.add(new MyFragment("Fragment_4"));
        list.add(new MyFragment("Fragment_5"));
        myAdapter=new MyAdapter(getSupportFragmentManager(),list);
        viewPager.setAdapter(myAdapter);
        viewPager.setCurrentItem(2);
    }
}

运行程序,打印信息如下。


打印生命周期信息

我们从打印信息中可以看出,ViewPager在加载Fragment_3的同时,也调用的Fragment_2和Fragment_4的生命周期(onAttach,onCreate,onCreateView,onStart,onResume)。因此,如果Fragment_2,Fragment_3和Fragment_4的初始化中有很多耗费网络资源的操作时,可能造成网络阻塞,使得需要显示的Fragment_3迟迟不能初始化。

ViewPager源码分析

为什么会在加载当前Fragment的时候也把他左右两个Fragment也加载到内存中呢?下面我们看一下ViewPager的源码。
首先我们看ViewPager中的onMeasure()。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 没有指定的情况下宽高为0 ;
        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
                getDefaultSize(0, heightMeasureSpec));

        final int measuredWidth = getMeasuredWidth();
        final int maxGutterSize = measuredWidth / 10;
        mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);

        // 得到可用空间
        int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
        int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();

        //计算每一个孩子所用空间
        int size = getChildCount();
        for (int i = 0; i < size; ++i) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (lp != null && lp.isDecor) {
                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
                    int widthMode = MeasureSpec.AT_MOST;
                    int heightMode = MeasureSpec.AT_MOST;
                    boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
                    boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;

                    if (consumeVertical) {
                        widthMode = MeasureSpec.EXACTLY;
                    } else if (consumeHorizontal) {
                        heightMode = MeasureSpec.EXACTLY;
                    }

                    int widthSize = childWidthSize;
                    int heightSize = childHeightSize;
                    if (lp.width != LayoutParams.WRAP_CONTENT) {
                        widthMode = MeasureSpec.EXACTLY;
                        if (lp.width != LayoutParams.MATCH_PARENT) {
                            widthSize = lp.width;
                        }
                    }
                    if (lp.height != LayoutParams.WRAP_CONTENT) {
                        heightMode = MeasureSpec.EXACTLY;
                        if (lp.height != LayoutParams.MATCH_PARENT) {
                            heightSize = lp.height;
                        }
                    }
                    final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
                    final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
                    child.measure(widthSpec, heightSpec);

                    if (consumeVertical) {
                        childHeightSize -= child.getMeasuredHeight();
                    } else if (consumeHorizontal) {
                        childWidthSize -= child.getMeasuredWidth();
                    }
                }
            }
        }
// 合成测量规格
        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);

        // Make sure we have created all fragments that we need to have shown.
        mInLayout = true;
        //填充
        populate();
        mInLayout = false;

        // 测量调用每一个孩子的measure()方法
        size = getChildCount();
        for (int i = 0; i < size; ++i) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                if (DEBUG) {
                    Log.v(TAG, "Measuring #" + i + " " + child + ": " + mChildWidthMeasureSpec);
                }

                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (lp == null || !lp.isDecor) {
                    final int widthSpec = MeasureSpec.makeMeasureSpec(
                            (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
                    child.measure(widthSpec, mChildHeightMeasureSpec);
                }
            }
        }
    }

ViewPager的onMeasure()方法进行了如下操作:1.确定了自身可用空间,2.遍历每一个装饰view,测量他们的大小和计算剩余空间,3.调用populate()方法根据页面缓存显示进行页面销毁与重建,4.测量每一个孩子。

下面我们再看一下populate()方法。

void populate() {
        // 将当前选中position放入
        populate(mCurItem);
    }
    void populate(int newCurrentItem) {
        // 旧的选中的 ItemInfo
        ItemInfo oldCurInfo = null;
        if (mCurItem != newCurrentItem) {
            //infoForPosition方法得到 旧 ItemInfo
            oldCurInfo = infoForPosition(mCurItem);
            mCurItem = newCurrentItem;
        }

        if (mAdapter == null) {
            //排序绘制view的顺序
            sortChildDrawingOrder();
            return;
        }

        if (mPopulatePending) {
            if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
            sortChildDrawingOrder();
            return;
        }

        // 不在window中时
        if (getWindowToken() == null) {
            return;
        }
// 调用 startUpdate 方法
        mAdapter.startUpdate(this);
      //页的限制
        final int pageLimit = mOffscreenPageLimit;
        //开始页
        final int startPos = Math.max(0, mCurItem - pageLimit);
        //总数
        final int N = mAdapter.getCount();
        // 结束页
        final int endPos = Math.min(N - 1, mCurItem + pageLimit);
         // 如果预期数量不一致
        if (N != mExpectedAdapterCount) {
            String resName;
            try {
                //得到资源名称
                resName = getResources().getResourceName(getId());
            } catch (Resources.NotFoundException e) {
                resName = Integer.toHexString(getId());
            }
            throw new IllegalStateException("The application's PagerAdapter changed the adapter's"
                    + " contents without calling PagerAdapter#notifyDataSetChanged!"
                    + " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N
                    + " Pager id: " + resName
                    + " Pager class: " + getClass()
                    + " Problematic adapter: " + mAdapter.getClass());
        }

        // Locate the currently focused item or add it if needed.
        int curIndex = -1;
        ItemInfo curItem = null;
        for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
             //得到当前 ItemInfo
            final ItemInfo ii = mItems.get(curIndex);
            //如果 position 大于 mCurItem
            if (ii.position >= mCurItem) {
                // cutlItem=ii
                if (ii.position == mCurItem) curItem = ii;
                break;
            }
        }
// 如果等于空  添加
        if (curItem == null && N > 0) {
            curItem = addNewItem(mCurItem, curIndex);
        }
// 如果 curItem 不为空
        if (curItem != null) {
            float extraWidthLeft = 0.f;
            //-1
            int itemIndex = curIndex - 1;
            //ii  如果itemIndex 大于 0 取出,否则null
            ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
            //得到可用宽度
            final int clientWidth = getClientWidth();
           // 计算左边需要的宽度
            final float leftWidthNeeded = clientWidth <= 0 ? 0 :
                    2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
            // 从'选中的下标-1' --> 0  遍历
            for (int pos = mCurItem - 1; pos >= 0; pos--) {
                //如果额外的 大于 需要的   和  pos 小于 startPos
                if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
                    if (ii == null) {
                        break;
                    }
                    // 移除
                    if (pos == ii.position && !ii.scrolling) {
                        mItems.remove(itemIndex);
                        mAdapter.destroyItem(this, pos, ii.object);
                        if (DEBUG) {
                            Log.i(TAG, "populate() - destroyItem() with pos: " + pos
                                    + " view: " + ((View) ii.object));
                        }
                        itemIndex--;
                        curIndex--;
                        ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                    }
                } else if (ii != null && pos == ii.position) {    // 如果选中的上一个有 infoItem 
                    // extraWidthLeft 加上  ii.widthFactor
                    extraWidthLeft += ii.widthFactor;
                    itemIndex--;
                    //拿上一个 infoItem
                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                } else {
                    // 新增一个 INfoItem
                    ii = addNewItem(pos, itemIndex + 1);
                    // 将占用的宽度比例加进来
                    extraWidthLeft += ii.widthFactor;
                    curIndex++;
                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                }
            }
            // 右边消耗的比例
            float extraWidthRight = curItem.widthFactor;
            //右边index
            itemIndex = curIndex + 1;
            if (extraWidthRight < 2.f) {
                ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                // 計算右边需要的宽度
                final float rightWidthNeeded = clientWidth <= 0 ? 0 :
                        (float) getPaddingRight() / (float) clientWidth + 2.f;
                for (int pos = mCurItem + 1; pos < N; pos++) {
                    // 如果 pos大于 endPos
                    if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
                        if (ii == null) {
                            break;
                        }
                        //移除
                        if (pos == ii.position && !ii.scrolling) {
                            mItems.remove(itemIndex);
                            mAdapter.destroyItem(this, pos, ii.object);
                            if (DEBUG) {
                                Log.i(TAG, "populate() - destroyItem() with pos: " + pos
                                        + " view: " + ((View) ii.object));
                            }
                            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                        }
                    } else if (ii != null && pos == ii.position) {
                        // 相等 那么取出数据
                        extraWidthRight += ii.widthFactor;
                        itemIndex++;
                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    } else {
                        // 否则 新建 infoItem
                        ii = addNewItem(pos, itemIndex);
                        itemIndex++;
                        extraWidthRight += ii.widthFactor;
                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    }
                }
            }
            // 计算偏移量
            calculatePageOffsets(curItem, curIndex, oldCurInfo);
        }

        if (DEBUG) {
            Log.i(TAG, "Current page list:");
            for (int i = 0; i < mItems.size(); i++) {
                Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
            }
        }
        // 通知适配器 哪个个是主页
        mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
       // 结束更新
        mAdapter.finishUpdate(this);

        // Check width measurement of current pages and drawing sort order.
        // Update LayoutParams as needed.
        final int childCount = getChildCount();
        // 遍历view
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            lp.childIndex = i;
            if (!lp.isDecor && lp.widthFactor == 0.f) {
                // 0 means requery the adapter for this, it doesn't have a valid width.
                final ItemInfo ii = infoForChild(child);
                if (ii != null) {
                    // 设置属性
                    lp.widthFactor = ii.widthFactor;
                    lp.position = ii.position;
                }
            }
        }
        //排序
        sortChildDrawingOrder();

        if (hasFocus()) {
            View currentFocused = findFocus();
            ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
            if (ii == null || ii.position != mCurItem) {
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    ii = infoForChild(child);
                   //如果拿到当前view
                    if (ii != null && ii.position == mCurItem) {
                         //设置聚焦
                        if (child.requestFocus(View.FOCUS_FORWARD)) {
                            break;
                        }
                    }
                }
            }
        }
    }

上面一段代码很长,主要内容就是首先根据mOffscreenPageLimit(页面限制,用于确定缓存Fragment的数量)和mCurItem(当前应显示页码)计算出了应加载的Fragment的页码数,

//页的限制
        final int pageLimit = mOffscreenPageLimit;
        //开始页
        final int startPos = Math.max(0, mCurItem - pageLimit);
        //总数
        final int N = mAdapter.getCount();
        // 结束页
        final int endPos = Math.min(N - 1, mCurItem + pageLimit);

然后根据计算得到的开始页和结束页调用Adapter移除或者加载Fragment。
原来ViewPager中有一个变量(mOffscreenPageLimit),用来确定加载的Fragment数量,那么我们修改这个变量不久行了吗?
继续往下看,ViewPager中有提供设置mOffscreenPageLimit的函数。

//默认的缓存页面数量(常量)
private static final int DEFAULT_OFFSCREEN_PAGES = 1;

//缓存页面数量(变量)
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;

public void setOffscreenPageLimit(int limit) {
    //当我们手动设置的limit数小于默认值1时,limit值会自动被赋值为默认值1(即DEFAULT_OFFSCREEN_PAGES)
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
        Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "+ DEFAULT_OFFSCREEN_PAGES);
        limit = DEFAULT_OFFSCREEN_PAGES;
    }

    if (limit != mOffscreenPageLimit) {
        //经过前面的拦截判断后,将limit的值设置给mOffscreenPageLimit,用于
        mOffscreenPageLimit = limit;
        populate();
    }
}

根据上面代码我们可知,如果我们设置的mOffscreenPageLimit小于DEFAULT_OFFSCREEN_PAGE,就强制给mOffscreenPageLimit赋值为DEFAULT_OFFSCREEN_PAGE。这个DEFAULT_OFFSCREEN_PAGE的值就是1。
现在终于真相大白了,ViewPager通过一定的逻辑判断来确保至少会预加载左右两侧相邻的1个页面,我们不能通过简单设置mOffscreenPageLimit来达到懒加载的目的。

懒加载实现原理

还记得我们前面再Fragment中打印的生命周期吗?我还在这个函数setUserVisibleHint(boolean isVisibleToUser)中打印了信息。根据函数名我们可以猜出来这个函数与用户可见有关。查看这个函数的源码

    /**
     * Set a hint to the system about whether this fragment's UI is currently visible
     * to the user. This hint defaults to true and is persistent across fragment instance
     * state save and restore.
     *
     * <p>An app may set this to false to indicate that the fragment's UI is
     * scrolled out of visibility or is otherwise not directly visible to the user.
     * This may be used by the system to prioritize operations such as fragment lifecycle updates
     * or loader ordering behavior.</p>
     *
     * <p><strong>Note:</strong> This method may be called outside of the fragment lifecycle.
     * and thus has no ordering guarantees with regard to fragment lifecycle method calls.</p>
     *
     * @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
     *                        false if it is not.
     */
    public void setUserVisibleHint(boolean isVisibleToUser) {
        if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
                && mFragmentManager != null && isAdded()) {
            mFragmentManager.performPendingDeferredStart(this);
        }
        mUserVisibleHint = isVisibleToUser;
        mDeferStart = mState < STARTED && !isVisibleToUser;
    }

翻译上面一段注释:设置一个提示用来表示这个Fragment是否被用户可见。应用程序可以将此设置为false,以指示Fragment的UI被滚动到不可见的位置,或者用户无法直接看到。系统可以使用它来对诸如片段生命周期更新或加载器排序行为等操作进行优先级排序。这个函数可以在Fragment的生命周期以外调用。

总之,当Fragment从可见变为不可见是或者从不可见变为可见时都会自动调用这个函数,可见是输入值为TRUE,不可见时输入值为FALSE。因此我们可以根据这个函数的输入值来判断Fragment是否可见,从而进行懒加载。

因为Fragment在由可见变为不可见,和由不可见变为可见的时候都会调用setUserVisibleHint()函数,因此我们可以在这个函数实现懒加载的逻辑。在这个函数中,我们判断只有当Fragment可见 & Fragment的View已经加载完成 & Fragment没有加载过数据 的条件下才进行数据加载。

根据这样的思路,我们设置了几个标记位:

    private boolean isViewInit; //view是否已经加载完成
    private boolean isVisible; //是否可见
    private boolean isFirstLoad = true;//是否第一次加载数据,默认为TURE,既默认为第一次加载数据

设置了一个函数来模拟从网络加载数据

    /**
     * 模拟加载数据
     */
    protected void loadData(){
        Log.e(data,"加载数据!");
    }

然后将懒加载的逻辑写成下面一个函数

    /**
     * 懒加载逻辑
     */
    private void lazyLoad() {
        if (!isViewInit || !isVisible || !isFirstLoad) {   //view加载完成&可见&第一次加载时才加载数据
            return;
        }
        loadData();   //加载数据
        isFirstLoad = false;   //更新标志位
    }

注意,别忘记了在onActivityCreated()中调用懒加载函数,因为第一个要显示的Fragment在由不可见变为可见调用setUserVisibleHint()函数时,该Fragment的View还没有初始化完成,导致第一个显示的Fragment无法第一次出现时无法加载数据,必须重新滑动一下才能加载数据。

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        isViewInit=true;    //设置标志位,view表示初始化完成
        lazyLoad();    //懒加载
    }

setUserVisibleHint()函数中调用懒加载。

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        Log.e(data,"setUserVisibleHint:"+isVisibleToUser);
        if(isVisibleToUser){
            isVisible=true;
            lazyLoad();
        }else {
            isVisible=false;
        }
    }

以上就是我对懒加载的理解。如果你觉得我写的有什么不对或者有什么补充的请留言。
代码已上传至GitHub https://github.com/Tiny-Monster/LazyLoadTest
参考博客
https://blog.csdn.net/chengkun_123/article/details/73694936
https://www.jianshu.com/p/b8fe093a9d4b

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

推荐阅读更多精彩内容