Jetpack使用(三)DataBinding核心原理

寫在前面:Jetpack的更新速度非诚延溃快尤溜,可能你一個(gè)月前看WorkManager是這樣用的,下個(gè)月這個(gè)使用方法就有可能被廢棄了(我看源碼的時(shí)候是遇到過的逃片,而且源碼也變了,但核心原理是不變的)只酥,所以我們這一系列文章偏重講原理褥实,使用就一帶而過(因?yàn)橹v了也沒用啊,會(huì)變的层皱。性锭。。叫胖。草冈。,讀者使用最好看官方文檔官方文檔
,當(dāng)然我這里講的也是截止到目前的最新用法)怎棱。

Jetpack使用(一)Lifecycles核心原理
Jetpack使用(二)LiveData核心原理
Jetpack使用(三)DataBinding核心原理
Jetpack使用(四)ViewModel核心原理
Jetpack使用(五)Navigation核心原理
Jetpack使用(六) WorkManager的4種用法

DataBinding

是 Google 在 Jetpack 中推出的一款數(shù)據(jù)綁定的支持庫哩俭,利用該庫可以實(shí)現(xiàn)在頁面組件和數(shù)據(jù)的雙向綁定,類似與MVVM拳恋。

具體使用

  • 在build.gradle里配置
 dataBinding{
            enabled true
 }
  • 新建一個(gè)實(shí)體類繼承BaseObservable凡资,并且在對(duì)應(yīng)的get方法上加上注解 @Bindable,在set方法里加 notifyPropertyChanged(BR.name);
public class User extends BaseObservable {
    @Bindable
    private String name;
    @Bindable
    private String password;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }

    public String getName() {
        return name;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
  • 在布局文件里導(dǎo)入要用的實(shí)體類
  <?xml version="1.0" encoding="utf-8"?>
<layout 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">
    <data>
        <variable
            name="user"
            type="com.example.databinding.User" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <TextView
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.name}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <TextView
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.password}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </LinearLayout>
</layout>
  • MainActivity里具體去使用
public class MainActivity extends AppCompatActivity {

    ActivityMainBinding binding;

    User user;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        user = new User("小三爺", "123");

        user.setName(user.getName() + "1");
        binding.setVariable(BR.user, user);

    }
}

在你想要更新的地方谬运,調(diào)用binding.setVariable(BR.user, user)就ok隙赁。

核心原理分析

當(dāng)我們的代碼在編譯的時(shí)候,系統(tǒng)會(huì)給我們的xml文件分離成兩個(gè)文件
1梆暖、app/build/imtermediates/data_binding_layout_info_type_merge/
debug/activity_main-layout.xml伞访,相當(dāng)于把我們的xml文件用另一種xml方式翻譯了一下

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="/Users/wudengyao/android-projects/DataBinding_Demo_20200329/app/src/main/res/layout/activity_main.xml"
    isMerge="false"
    layout="activity_main" modulePackage="com.example.databinding_demo_20200329">
    <Variables name="user" declared="true" type="com.example.databinding_demo_20200329.User">
        <location endLine="7" endOffset="63" startLine="5" startOffset="8" />
    </Variables>
    <Targets>
        <Target tag="layout/activity_main_0" view="LinearLayout">
            <Expressions />
            <location endLine="35" endOffset="18" startLine="10" startOffset="4" />
        </Target>
        <Target id="@+id/tv1" tag="binding_1" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="user.name">
                    <Location endLine="20" endOffset="38" startLine="20" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="20" endOffset="36" startLine="20" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="24" endOffset="55" startLine="16" startOffset="8" />
        </Target>
        <Target id="@+id/tv2" tag="binding_2" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="user.password">
                    <Location endLine="29" endOffset="42" startLine="29" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="29" endOffset="40" startLine="29" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="33" endOffset="55" startLine="25" startOffset="8" />
        </Target>
    </Targets>
</Layout>

2、app/build/imtermediates/incremental/mergeDebugResources/stripped.dir/
layout/activity_main.xml轰驳,把我們的xml去掉了<data></data>包含的代碼塊

<?xml version="1.0" encoding="utf-8"?>

                                                       
                                                   
    
                 
                       
                                                                
           

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity" android:tag="layout/activity_main_0" 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">

        <TextView
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="binding_1"    
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <TextView
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="binding_2"        
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </LinearLayout>
         

還有會(huì)通過apt生成三個(gè)類文件ActivityMainBindingImpl厚掷、BR、DataBinderMapperImpl
BR就相當(dāng)于R文件级解,里面存放xml布局里控件的id

public class BR {
  public static final int _all = 0;

  public static final int password = 1;

  public static final int name = 2;

  public static final int user = 3;
}
image.png

知道了這些我們現(xiàn)在真正進(jìn)入源碼分析階段

流程一冒黑、binding.setVariable(設(shè)置數(shù)據(jù))

我們?cè)谠贛ainActicty里調(diào)用setVariable來設(shè)置數(shù)據(jù)的時(shí)候,會(huì)進(jìn)入到ViewDataBinding類

public abstract boolean setVariable(int variableId, @Nullable Object value);

這是一個(gè)抽象類勤哗,具體實(shí)現(xiàn)會(huì)在ActivityMainBingdingImpl里抡爹,

    @Override
    public boolean setVariable(int variableId, @Nullable Object variable)  {
        boolean variableSet = true;
        if (BR.user == variableId) {
            setUser((com.example.databinding_demo.User) variable);
        }
        else {
            variableSet = false;
        }
            return variableSet;
    }

setVariable里會(huì)調(diào)用到ActivityMainBingdingImpl里的另一個(gè)方法setUser,我們?cè)冱c(diǎn)進(jìn)去

 public void setUser(@Nullable com.example.databinding_demo.User User) {
        updateRegistration(0, User);
        this.mUser = User;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.user);
        super.requestRebind();
    }

setUser里先調(diào)用注冊(cè)u(píng)pdateRegistration方法俺陋,然后把User設(shè)置給成員變量mUser
初始化了mDirtyFlags標(biāo)志豁延,最后是調(diào)用notifyPropertyChanged通知更新。
我們先進(jìn)入注冊(cè)u(píng)pdateRegistration方法

 protected boolean updateRegistration(int localFieldId, Observable observable) {
        return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
    }

CREATE_PROPERTY_LISTENER實(shí)際是返回了WeakPropertyListener對(duì)象

    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;
    }

最后會(huì)走到這個(gè)方法里來腊状,我們?cè)冱c(diǎn)到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);
    }

這個(gè)方法實(shí)會(huì)判斷mLocalFieldObservers里是否存在WeakListener listener诱咏,如果不存在就創(chuàng)建一個(gè)存進(jìn)去,mLocalFieldObservers里面保存的就是我們控件在BR文件里的id缴挖,每個(gè)id就對(duì)應(yīng)一個(gè)監(jiān)聽器袋狞,然后通過listener.setLifecycleOwner(mLifecycleOwner);把lifecycle關(guān)聯(lián)起來,再通過listener.setTarget(observable);把User對(duì)象關(guān)聯(lián)起來映屋,所以這個(gè)注冊(cè)方法簡單的說就是:把我們的實(shí)體對(duì)象User(觀察者)苟鸯、DataBinding、Lifecycle(被觀察者這里就是activity)建立綁定關(guān)系棚点,還有把控件的BR文件里的id放到一個(gè)數(shù)組里早处。

流程二、setContentView(讀取XML信息)

我們?cè)贛ainActivty里調(diào)用DataBindingUtil.setContentView(this,R.layout.activity_main)實(shí)際會(huì)調(diào)用到
DataBinderMapperImpl的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_ACTIVITYMAIN: {
          if ("layout/activity_main_0".equals(tag)) {
            return new ActivityMainBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for activity_main is invalid. Received: " + tag);
        }
      }
    }
    return null;
  }

ActivityMainBindingImpl再點(diǎn)進(jìn)去砌梆,最后會(huì)調(diào)到ViewDataBinding的mapBindings的方法

private static void mapBindings(DataBindingComponent bindingComponent, View view,
            Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
            boolean isRoot) {
        final int indexInIncludes;
        final ViewDataBinding existingBinding = getBinding(view);
        if (existingBinding != null) {
            return;
        }
        Object objTag = view.getTag();
        final String tag = (objTag instanceof String) ? (String) objTag : null;
        boolean isBound = false;
        if (isRoot && tag != null && tag.startsWith("layout")) {
            final int underscoreIndex = tag.lastIndexOf('_');
            if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
                final int index = parseTagInt(tag, underscoreIndex + 1);
                if (bindings[index] == null) {
                    bindings[index] = view;
                }
                indexInIncludes = includes == null ? -1 : index;
                isBound = true;
            } else {
                indexInIncludes = -1;
            }
        } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
            int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
            if (bindings[tagIndex] == null) {
                bindings[tagIndex] = view;
            }
            isBound = true;
            indexInIncludes = includes == null ? -1 : tagIndex;
        } else {
            // Not a bound view
            indexInIncludes = -1;
        }
        if (!isBound) {
            final int id = view.getId();
            if (id > 0) {
                int index;
                if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
                        bindings[index] == null) {
                    bindings[index] = view;
                }
            }
        }

        if (view instanceof  ViewGroup) {
            final ViewGroup viewGroup = (ViewGroup) view;
            final int count = viewGroup.getChildCount();
            int minInclude = 0;
            for (int i = 0; i < count; i++) {
                final View child = viewGroup.getChildAt(i);
                boolean isInclude = false;
                if (indexInIncludes >= 0 && child.getTag() instanceof String) {
                    String childTag = (String) child.getTag();
                    if (childTag.endsWith("_0") &&
                            childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
                        // This *could* be an include. Test against the expected includes.
                        int includeIndex = findIncludeIndex(childTag, minInclude,
                                includes, indexInIncludes);
                        if (includeIndex >= 0) {
                            isInclude = true;
                            minInclude = includeIndex + 1;
                            final int index = includes.indexes[indexInIncludes][includeIndex];
                            final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
                            int lastMatchingIndex = findLastMatching(viewGroup, i);
                            if (lastMatchingIndex == i) {
                                bindings[index] = DataBindingUtil.bind(bindingComponent, child,
                                        layoutId);
                            } else {
                                final int includeCount =  lastMatchingIndex - i + 1;
                                final View[] included = new View[includeCount];
                                for (int j = 0; j < includeCount; j++) {
                                    included[j] = viewGroup.getChildAt(i + j);
                                }
                                bindings[index] = DataBindingUtil.bind(bindingComponent, included,
                                        layoutId);
                                i += includeCount - 1;
                            }
                        }
                    }
                }
                if (!isInclude) {
                    mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
                }
            }
        }
    }

mapBindings里就是解析xml文件信息默责,并存入bingdings數(shù)組,

流程三咸包、notifyPropertyChanged(更新數(shù)據(jù))

我們知道在流程一setVariable方法之后會(huì)調(diào)用notifyPropertyChanged去更新數(shù)據(jù)桃序,我們點(diǎn)進(jìn)去最終會(huì)跑到ViewDataBingding的 onPropertyChanged里


@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);
        }

一層層點(diǎn)進(jìn)去發(fā)現(xiàn)是一個(gè)抽象方法

    protected abstract boolean onFieldChange(int localFieldId, Object object, int fieldId);

最終它的實(shí)現(xiàn)類在ActivityMainBingdingImpl的onFieldChange里


 @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
            case 0 :
                return onChangeUser((com.example.databinding_demo_20200329.User) object, fieldId);
        }
        return false;
    }
  private boolean onChangeUser(com.example.databinding_demo_20200329.User User, int fieldId) {
        if (fieldId == BR._all) {
            synchronized(this) {
                    mDirtyFlags |= 0x1L;
            }
            return true;
        }
        else if (fieldId == BR.name) {
            synchronized(this) {
                    mDirtyFlags |= 0x2L;
            }
            return true;
        }
        else if (fieldId == BR.password) {
            synchronized(this) {
                    mDirtyFlags |= 0x4L;
            }
            return true;
        }
        return false;
    }

這個(gè)方法去通過位或操作設(shè)置mDirtyFlags的初始值的,我們?cè)倏吹絍iewDataBinding里的一個(gè)靜態(tài)代碼塊

   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) {
                }
            };
        }
    }

這里有個(gè)監(jiān)聽器烂瘫,在監(jiān)聽executeBindings媒熊,這個(gè)方法最終的實(shí)現(xiàn)類在ActivityMainBindingImpl里

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

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


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

                    if (user != null) {
                        // read user.name
                        userName = user.getName();
                    }
            }
            if ((dirtyFlags & 0xdL) != 0) {

                    if (user != null) {
                        // read user.password
                        userPassword = user.getPassword();
                    }
            }
        }
        // batch finished
        if ((dirtyFlags & 0xbL) != 0) {
            // api target 1

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

            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tv2, userPassword);
        }
    }

我們前面通過onFieldChange設(shè)置的標(biāo)志就是在這里通過位與操作來進(jìn)行控件的更新的。到此坟比,DataBingding核心原理就一目了然了

總結(jié):1芦鳍、我們一開始使用DataBinding的時(shí)候,在布局文件里將實(shí)體對(duì)象類通過data標(biāo)簽寫在layout布局文件里了葛账。然后build.gradle里開啟DataBinding的使用權(quán)限怜校,所以在編譯的系統(tǒng)會(huì)默認(rèn)給我們生成兩個(gè)layout文件,一個(gè)是把data標(biāo)簽?zāi)玫舻膌ayout注竿,layout里存的是通過另一種xml方式翻譯了下我們的layout,里面就是一些什么target標(biāo)簽把我們的textview和LinearLayout這些控件重新包裝了下魂贬,然后還有一個(gè)BR文件來存儲(chǔ)我們控件的id(和R文件類似)巩割,

2、所以我們?cè)谕ㄟ^DataBindingUtil初始化DataBinding的時(shí)候付燥,就會(huì)把我們的前面生成的xml文件讀取到一個(gè)數(shù)組里宣谈,

3、DataBinding在set數(shù)據(jù)的時(shí)候键科,就是通過registerTo注冊(cè)方法把我們的實(shí)體對(duì)象(觀察者)闻丑、DataBinding、Lifecycle(被觀察者這里就是activity)建立綁定關(guān)系勋颖,還有把控件的BR文件里的id放到一個(gè)數(shù)組里嗦嗡,

4、再調(diào)用更新數(shù)據(jù)的notifyPropertyChanged方法饭玲,獲取前面存放控件數(shù)組里當(dāng)前這個(gè)控件所對(duì)應(yīng)的元素侥祭,去更新數(shù)據(jù)。它這里更新數(shù)據(jù)是先通過位或操作茄厘,初始化更新的標(biāo)志位矮冬,再通過位與具體去判斷這個(gè)控件是否需要更新

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市次哈,隨后出現(xiàn)的幾起案子胎署,更是在濱河造成了極大的恐慌,老刑警劉巖窑滞,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件琼牧,死亡現(xiàn)場離奇詭異恢筝,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)障陶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門滋恬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抱究,你說我怎么就攤上這事恢氯。” “怎么了鼓寺?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵勋拟,是天一觀的道長。 經(jīng)常有香客問我妈候,道長敢靡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任苦银,我火速辦了婚禮啸胧,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘幔虏。我一直安慰自己纺念,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布想括。 她就那樣靜靜地躺著陷谱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瑟蜈。 梳的紋絲不亂的頭發(fā)上烟逊,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音铺根,去河邊找鬼宪躯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛夷都,可吹牛的內(nèi)容都是我干的眷唉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼囤官,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼冬阳!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起党饮,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤肝陪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后刑顺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氯窍,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饲常,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狼讨。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贝淤。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡范咨,死狀恐怖灼芭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情昭伸,我是刑警寧澤布隔,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布离陶,位于F島的核電站,受9級(jí)特大地震影響衅檀,放射性物質(zhì)發(fā)生泄漏招刨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一哀军、第九天 我趴在偏房一處隱蔽的房頂上張望沉眶。 院中可真熱鬧,春花似錦杉适、人聲如沸沦寂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至腻暮,卻和暖如春彤守,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哭靖。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國打工具垫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人试幽。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓筝蚕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親铺坞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子起宽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

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