Databinding基本使用和原理

一湃交、Databinding基本使用

實體類

class User(name: String) : BaseObservable() {

    private var name: String

    init {
        this.name = name
    }

    @Bindable
    fun getName(): String {
        return name
    }

    fun setName(name: String) {
        this.name = name
        notifyPropertyChanged(BR.name)
    }
}

布局文件

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="user"
            type="com.sun.jetpack.databinding.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user.name}" />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@={user.name}" />
    </LinearLayout>

</layout>

基本使用

class DataBindingTestActivity : AppCompatActivity() {
    private lateinit var binding: ActivityDatabindingTestBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_databinding_test)
        val user = User("sun")
        binding.user = user
    }
}
二、Databinding原理分析

Databinding使用了apt技術(shù),我們build項目時Databinding會生成多個文件渠抹,我們可以在build文件中查看。


databinding生成的文件.png

Databinding會將原有的activity_databinding_test.xml進行了拆分毯炮,分別是activity_databinding_test.xml和activity_databinding_test-layout.xml


activity_databinding_test.png

通過上面的代碼我們發(fā)現(xiàn):Databinding將原有的layout和data標簽去除了逼肯,并為根布局聲明了一個layout/文件名_0的tag,為其它使用到@{}或@={}的控件按順序添加了一個binding_X的tag桃煎。
activity_databinding_test-layout.png

該配置文件詳細記述了:<Variables name="user" declared="true" type="com.sun.jetpack.databinding.User"> 我們聲明的變量篮幢,變量指向的數(shù)據(jù)類型的絕對路徑。<Target tag="binding_1" view="TextView"> tag對應(yīng)的View類型为迈。 <Expression attribute="android:text" text="user.name"> 控件綁定具體屬性和Model中的具體屬性三椿。 <TwoWay>false</TwoWay> 是否是雙向的。

接下來我們從 DataBindingUtil.setContentView(this,R.layout.activity_databinding_test) 入手

public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
        int layoutId, @Nullable DataBindingComponent bindingComponent) {
    activity.setContentView(layoutId);
    View decorView = activity.getWindow().getDecorView();
    ViewGroup contentView = (ViewGroup) decorView.findViewById(android.R.id.content);
    return bindToAddedViews(bindingComponent, contentView, 0, layoutId);
}

方法中通過activity的setContentView加載布局葫辐,并通過window找到id為content的ViewGroup搜锰,它是一個FrameLayout用于加載我們添加的布局文件。接下來就是bindToAddedViews發(fā)方法耿战。

private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
        ViewGroup parent, int startChildren, int layoutId) {
    final int endChildren = parent.getChildCount();
    final int childrenAdded = endChildren - startChildren;
    if (childrenAdded == 1) {
        final View childView = parent.getChildAt(endChildren - 1);
        return bind(component, childView, layoutId);
    } else {
        final View[] children = new View[childrenAdded];
        for (int i = 0; i < childrenAdded; i++) {
            children[i] = parent.getChildAt(i + startChildren);
        }
        return bind(component, children, layoutId);
    }
}

parent中的子View就是我們布局文件中的根布局LinearLayout蛋叼,所以走if中的代碼bind。

static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
        int layoutId) {
    return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
}

bind方法又調(diào)用了DataBinderMapperImpl(apt生產(chǎn)的類)中的getDataBinder方法剂陡。

@Override
public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
  int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
  if(localizedLayoutId > 0) {
    final Object tag = view.getTag();
    if(tag == null) {
      throw new RuntimeException("view must have a tag");
    }
    switch(localizedLayoutId) {
      case  LAYOUT_ACTIVITYDATABINDINGTEST: {
        if ("layout/activity_databinding_test_0".equals(tag)) {
          return new ActivityDatabindingTestBindingImpl(component, view);
        }
        throw new IllegalArgumentException("The tag for activity_databinding_test is invalid. Received: " + tag);
      }
    }
  }
  return null;
}

通過一系列條件判斷之后返回一個ActivityDatabindingTestBindingImpl對象狈涮。

public ActivityDatabindingTestBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
    //這里的3表示3個節(jié)點
    this(bindingComponent, root, mapBindings(bindingComponent, root, 3, sIncludes, sViewsWithIds));
}

mapBindings調(diào)用了ViewDataBinding類中的mapBindings方法,在加載這個類的時候鸭栖,會先執(zhí)行靜態(tài)塊歌馍,調(diào)用onViewAttachedToWindow,所有沒調(diào)用setUser之前數(shù)據(jù)綁定就是這么做的晕鹊。
問題:沒調(diào)用setUser之前數(shù)據(jù)綁定是怎么做的松却?ViewDataBinding類中有個靜態(tài)塊,調(diào)了onViewAttachedToWindow(v)

static {
    if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
        ROOT_REATTACHED_LISTENER = null;
    } else {
        ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
            @TargetApi(VERSION_CODES.KITKAT)
            @Override
            public void onViewAttachedToWindow(View v) {
                // execute the pending bindings.
                final ViewDataBinding binding = getBinding(v);
                binding.mRebindRunnable.run();
                v.removeOnAttachStateChangeListener(this);
            }

            @Override
            public void onViewDetachedFromWindow(View v) {
            }
        };
    }
}

靜態(tài)塊中有個OnAttachStateChangeListener()監(jiān)聽事件溅话,當(dāng)控件狀態(tài)發(fā)生改變晓锻,會執(zhí)行一個Runnable,找到這個mRebindRunnable飞几。

/**
 * Runnable executed on animation heartbeat to rebind the dirty Views.
 */
private final Runnable mRebindRunnable = new Runnable() {
    @Override
    public void run() {
        synchronized (this) {
            mPendingRebind = false;
        }
        processReferenceQueue();

        if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
            // Nested so that we don't get a lint warning in IntelliJ
            if (!mRoot.isAttachedToWindow()) {
                // Don't execute the pending bindings until the View
                // is attached again.
                mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                return;
            }
        }
        executePendingBindings();
    }
};
protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
        int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
    Object[] bindings = new Object[numBindings];
    mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
    return bindings;
}

 private static void mapBindings(DataBindingComponent bindingComponent, View view,
            Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
            boolean isRoot) {
        //...
        if (isRoot && tag != null && tag.startsWith("layout")) {
           //...
                    bindings[index] = view;
           //...     
        } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
            //...
                bindings[tagIndex] = view;
           //...
        } else {
            // Not a bound view
            indexInIncludes = -1;
        }
        if (!isBound) {
           //...
             bindings[index] = view;
          //...
        }

        if (view instanceof  ViewGroup) {
            //...
            for (int i = 0; i < count; i++) {
              //...
              bindings[index] = DataBindingUtil.bind(bindingComponent, included,
                                        layoutId);
              //...
                
        }
    }

mapBindings方法匹配布局中含有databinding賦值的tag控件一一存入bindings的Object的數(shù)組中并返回带射。ViewDataBinding持有Activity或Fragment和View的引用,主要作用一次遍歷View循狰,實例化所有的子View窟社,并存儲到數(shù)組中,解決了findViewById性能問題绪钥,同時為我們省去了findViewById操作灿里。

private ActivityDatabindingTestBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
    super(bindingComponent, root, 1
        );
    this.mboundView0 = (android.widget.LinearLayout) bindings[0];
    this.mboundView0.setTag(null);
    this.mboundView1 = (android.widget.TextView) bindings[1];
    this.mboundView1.setTag(null);
    this.mboundView2 = (android.widget.EditText) bindings[2];
    this.mboundView2.setTag(null);
    setRootTag(root);
    // listeners
    invalidateAll();
}

 @Override
public void invalidateAll() {
  synchronized(this) {
    mDirtyFlags = 0x4L;
  }
  requestRebind();
}

將獲取的View數(shù)組賦值給成員變量,執(zhí)行了invalidateAll()方法程腹,接著執(zhí)行了requestRebind方法匣吊。

protected void requestRebind() {
    if (mContainingBinding != null) {
        mContainingBinding.requestRebind();
    } else {
        final LifecycleOwner owner = this.mLifecycleOwner;
        if (owner != null) {
            Lifecycle.State state = owner.getLifecycle().getCurrentState();
            if (!state.isAtLeast(Lifecycle.State.STARTED)) {
                return; // wait until lifecycle owner is started
            }
        }
        synchronized (this) {
            if (mPendingRebind) {
                return;
            }
            mPendingRebind = true;
        }
        if (USE_CHOREOGRAPHER) {
            mChoreographer.postFrameCallback(mFrameCallback);
        } else {
            mUIThreadHandler.post(mRebindRunnable);
        }
    }
}

protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
        mBindingComponent = bindingComponent;
        mLocalFieldObservers = new WeakListener[localFieldCount];
        this.mRoot = root;
        if (Looper.myLooper() == null) {
            throw new IllegalStateException("DataBinding must be created in view's UI Thread");
        }
        if (USE_CHOREOGRAPHER) {
            mChoreographer = Choreographer.getInstance();
            mFrameCallback = new Choreographer.FrameCallback() {
                @Override
                public void doFrame(long frameTimeNanos) {
                    mRebindRunnable.run();
                }
            };
        } else {
            mFrameCallback = null;
            mUIThreadHandler = new Handler(Looper.myLooper());
        }
    }

mChoreographer.postFrameCallback(mFrameCallback)實際上也執(zhí)行了mRebindRunnable.run():

private final Runnable mRebindRunnable = new Runnable() {
    @Override
    public void run() {
        synchronized (this) {
            mPendingRebind = false;
        }
        processReferenceQueue();

        if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
            // Nested so that we don't get a lint warning in IntelliJ
            if (!mRoot.isAttachedToWindow()) {
                // Don't execute the pending bindings until the View
                // is attached again.
                mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
                return;
            }
        }
        executePendingBindings();
    }
};

接著看Runnable中的executePendingBindings()方法

/**
  * Evaluates the pending bindings, updating any Views that have expressions bound to
  * modified variables. This <b>must</b> be run on the UI thread.
  */
public void executePendingBindings() {
    if (mContainingBinding == null) {
        executeBindingsInternal();
    } else {
        mContainingBinding.executePendingBindings();
    }
}

如果綁定了就不需要再綁定了,如果沒有綁定就執(zhí)行綁定寸潦。

/**
 * Evaluates the pending bindings without executing the parent bindings.
 */
private void executeBindingsInternal() {
    if (mIsExecutingPendingBindings) {
        requestRebind();
        return;
    }
    if (!hasPendingBindings()) {
        return;
    }
    mIsExecutingPendingBindings = true;
    mRebindHalted = false;
    if (mRebindCallbacks != null) {
        mRebindCallbacks.notifyCallbacks(this, REBIND, null);

        // The onRebindListeners will change mPendingHalted
        if (mRebindHalted) {
            mRebindCallbacks.notifyCallbacks(this, HALTED, null);
        }
    }
    if (!mRebindHalted) {
        executeBindings();
        if (mRebindCallbacks != null) {
            mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
        }
    }
    mIsExecutingPendingBindings = false;
}

executeBindingsInternal方法對一些狀態(tài)進行了判斷添加回調(diào)色鸳,核心是executeBindings()方法,執(zhí)行綁定操作见转。executeBindings()是一個抽象方法命雀,它在哪實現(xiàn)的呢?我們回到build生成的ActivityDatabindingTestBindingImpl中斩箫。
核心代碼

@Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String userName = null;
        com.sun.jetpack.databinding.User user = mUser;

        if ((dirtyFlags & 0x7L) != 0) { 

                if (user != null) {
                    // read user.name
                    userName = user.getName();
                }
        }
        // batch finished
        if ((dirtyFlags & 0x7L) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, userName);
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userName);
        }
        if ((dirtyFlags & 0x4L) != 0) {
            // api target 1

            androidx.databinding.adapters.TextViewBindingAdapter.setTextWatcher(this.mboundView2, (androidx.databinding.adapters.TextViewBindingAdapter.BeforeTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.OnTextChanged)null, (androidx.databinding.adapters.TextViewBindingAdapter.AfterTextChanged)null, mboundView2androidTextAttrChanged);
        }
    }

dirtyFlags用于表示哪個屬性發(fā)生變化吏砂,如果是M中的name被修改了 ,dirtyFlags & 0x7L —> 010 & 111 —> 010不為0乘客,讀取user.name,調(diào)用setText設(shè)置值狐血,這是M—>V的過程。當(dāng)(dirtyFlags & 0x4L) != 0成立易核,就是V—>M匈织,它為雙向綁定的控件添加了一個內(nèi)容變化的監(jiān)聽mboundView2androidTextAttrChanged,當(dāng)控件內(nèi)容發(fā)生變化時牡直,就會更新到Model上缀匕,這就是V—>M的過程。

private androidx.databinding.InverseBindingListener mboundView2androidTextAttrChanged = new androidx.databinding.InverseBindingListener() {
    @Override
    public void onChange() {
        // Inverse of user.name
        // is user.setName((java.lang.String) callbackArg_0)
        java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView2);
        // localize variables for thread safety
        // user.name
        java.lang.String userName = null;
        // user != null
        boolean userJavaLangObjectNull = false;
        // user
        com.sun.jetpack.databinding.User user = mUser;

        userJavaLangObjectNull = (user) != (null);
        if (userJavaLangObjectNull) {
            user.setName(((java.lang.String) (callbackArg_0)));
        }
    }
};

我們接著分析一下ViewModel上值的變化井氢,當(dāng)我們調(diào)用binding.setUser(user)時會執(zhí)行ActivityDatabindingTestBindingImpl中的setUser方法

public void setUser(@Nullable com.sun.jetpack.databinding.User User) {
    updateRegistration(0, User);
    this.mUser = User;
    synchronized(this) {
        mDirtyFlags |= 0x1L;
    }
    notifyPropertyChanged(BR.user);
    super.requestRebind();
}
protected boolean updateRegistration(int localFieldId, Observable observable) {
    return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
}
private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
    @Override
    public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
        return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
    }
};

 private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
            implements ObservableReference<Observable> {
        final WeakListener<Observable> mListener;

        public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
            mListener = new WeakListener<Observable>(binder, localFieldId, this);
        }

        @Override
        public WeakListener<Observable> getListener() {
            return mListener;
        }

        @Override
        public void addListener(Observable target) {
            target.addOnPropertyChangedCallback(this);
        }

        @Override
        public void removeListener(Observable target) {
            target.removeOnPropertyChangedCallback(this);
        }

        @Override
        public void setLifecycleOwner(LifecycleOwner lifecycleOwner) {
        }

        @Override
        public void onPropertyChanged(Observable sender, int propertyId) {
            ViewDataBinding binder = mListener.getBinder();
            if (binder == null) {
                return;
            }
            Observable obj = mListener.getTarget();
            if (obj != sender) {
                return; // notification from the wrong object?
            }
            binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
        }
    }

private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
        private final ObservableReference<T> mObservable;
        protected final int mLocalFieldId;
        private T mTarget;

        public WeakListener(ViewDataBinding binder, int localFieldId,
                ObservableReference<T> observable) {
            super(binder, sReferenceQueue);
            mLocalFieldId = localFieldId;
            mObservable = observable;
        }
  //...
}

從上面可以看到CREATE_PROPERTY_LISTENER是一個CreateWeakListener對象弦追,CreateWeakListener調(diào)用create得到WeakPropertyListener,WeakPropertyListener內(nèi)有變量WeakListener花竞,WeakListener持有ViewDataBinding和Observable(即VM User)劲件。

接著看updateRegistration

private boolean updateRegistration(int localFieldId, Object observable,
        CreateWeakListener listenerCreator) {
    if (observable == null) {
        return unregisterFrom(localFieldId);
    }
    WeakListener listener = mLocalFieldObservers[localFieldId];
    if (listener == null) {
        registerTo(localFieldId, observable, listenerCreator);
        return true;
    }
    if (listener.getTarget() == observable) {
        return false;//nothing to do, same object
    }
    unregisterFrom(localFieldId);
    registerTo(localFieldId, observable, listenerCreator);
    return true;
}

從mLocalFieldObservers中取localFieldId對應(yīng)的WeakListener,如果為null就調(diào)用registerTo進行注冊约急。與之前注冊不一致就重新注冊零远。

protected void registerTo(int localFieldId, Object observable,
        CreateWeakListener listenerCreator) {
    if (observable == null) {
        return;
    }
    WeakListener listener = mLocalFieldObservers[localFieldId];
    if (listener == null) {
        listener = listenerCreator.create(this, localFieldId);
        mLocalFieldObservers[localFieldId] = listener;
        if (mLifecycleOwner != null) {
            listener.setLifecycleOwner(mLifecycleOwner);
        }
    }
    listener.setTarget(observable);
}

 public void setTarget(T object) {
            unregister();
            mTarget = object;
            if (mTarget != null) {
               //調(diào)到上面的WeakPropertyListener中的addListener
                mObservable.addListener(mTarget);
            }
        }
private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
            implements ObservableReference<Observable> {
  //...
    @Override
    public void addListener(Observable target) {
        target.addOnPropertyChangedCallback(this);
    }
  //...
}

registerTo方法把CreateWeakListener存儲在mLocalFieldObservers里面。
listener.setTarget(observable)設(shè)置ViewModel的監(jiān)聽事件厌蔽。

三牵辣、小結(jié)

1、DataBinding的關(guān)鍵類

  • ActivityDatabindingTestBinding持有綁定類的引用奴饮。
  • ActivityDatabindingTestBindingImpl具體實現(xiàn)了綁定纬向。
  • BR存儲了VM的id择浊,功能和R文件類似,還用于確定監(jiān)聽器逾条。
  • ViewDataBinding持有Activity或者Fragment和View的引用琢岩,主要作用是遍歷View,實例化所有的子View师脂,并存儲在數(shù)組中担孔,省去了findviewbyid的操作。
  • DataBinderMapperImpl提供布局文件layoutId到ViewDataBinding類對應(yīng)的映射吃警,主要用于加載layout返回的ViewDataBinding對象
    2糕篇、VM變化如何通知到View

設(shè)置監(jiān)聽:updateRegistration—>registerTo—>listener.setTarget(observable)—>target.addOnPropertyChangedCallback(this) 設(shè)置ViewModel的監(jiān)聽事件

VM—>V:notifyPropertyChanged(BR.id)—>mCallbacks.notifyCallbacks—>executeBindings

3、View變化如何同步到VM

設(shè)置監(jiān)聽:requestRebind—>executeBindings—>setTextWatcher設(shè)置對View的監(jiān)聽

V—>VM:回調(diào)InverseBindingListener的onChange設(shè)置值

4酌心、流程圖


DataBinding.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拌消,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子谒府,更是在濱河造成了極大的恐慌拼坎,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件完疫,死亡現(xiàn)場離奇詭異泰鸡,居然都是意外死亡,警方通過查閱死者的電腦和手機壳鹤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門盛龄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人芳誓,你說我怎么就攤上這事余舶。” “怎么了锹淌?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵匿值,是天一觀的道長。 經(jīng)常有香客問我赂摆,道長挟憔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任烟号,我火速辦了婚禮绊谭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘汪拥。我一直安慰自己达传,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宪赶,像睡著了一般宗弯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逊朽,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天罕伯,我揣著相機與錄音,去河邊找鬼叽讳。 笑死,一個胖子當(dāng)著我的面吹牛坟募,可吹牛的內(nèi)容都是我干的岛蚤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼懈糯,長吁一口氣:“原來是場噩夢啊……” “哼涤妒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赚哗,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤她紫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后屿储,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贿讹,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年够掠,在試婚紗的時候發(fā)現(xiàn)自己被綠了民褂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疯潭。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡赊堪,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出竖哩,到底是詐尸還是另有隱情哭廉,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布相叁,位于F島的核電站遵绰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏钝荡。R本人自食惡果不足惜街立,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望埠通。 院中可真熱鬧赎离,春花似錦、人聲如沸端辱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至荣病,卻和暖如春码撰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背个盆。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工脖岛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人颊亮。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓柴梆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親终惑。 傳聞我的和親對象是個殘疾皇子绍在,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

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