Android DataBinding原理分析

一苟径、DataBinding使用

本文著重講解DataBinding原理,使用的例子比較簡單,若讀者想要了解更多的DataBinding的使用方法介紹茅姜,可以自尋相關(guān)資料,本文純屬個(gè)人理解月匣,若有錯(cuò)誤钻洒,還望指出(抱拳)

在app模塊的build.gradle中加入如下配置

android {
    ...
    dataBinding {
        enabled = true
    }
}

現(xiàn)在你就可以在代碼中使用DataBinding了,這里我們舉個(gè)簡單例子锄开,給一個(gè)TextView設(shè)置單向綁定一個(gè)ObservableField< String>類型的name素标,給一個(gè)EditText設(shè)置雙向綁定一個(gè)類型為ObservableField< String>的nickName,點(diǎn)擊一個(gè)Button可以獲取nickName里面的值萍悴,nickName首先顯示“美女”头遭,在代碼中延遲三秒后將nickName的值改為“延遲三秒”,三秒后然后觀察到EditText上文本變?yōu)椤把舆t三秒”癣诱,然后再將EditText上文本刪除计维,輸入“beauty”,點(diǎn)擊button獲取nickName的值狡刘,發(fā)現(xiàn)也是“beauty”享潜,這里的name是單項(xiàng)綁定到TextView上,nickName是雙向綁定到EditText上嗅蔬。

image.png

image.png

image.png

來看下布局文件

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

    <data>
        <variable
            name="userInfo"
            type="com.jokerwan.databinding.UserInfo" />

        <variable
            name="listener"
            type="android.view.View.OnClickListener" />
    </data>

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

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{userInfo.name}"
            android:textSize="16sp"
            tools:text="姓名"/>

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@={userInfo.nickName}"
            tools:text="昵稱"
            android:layout_marginTop="10dp"/>

        <Button
            android:id="@+id/btn_get_nick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="獲取model里面的昵稱"
            android:layout_marginTop="20dp"
            android:onClick="@{listener}"/>

    </LinearLayout>
</layout>

注意這里給TextView和EditText綁定數(shù)據(jù)的區(qū)別:
給TextView是設(shè)置單項(xiàng)綁定
android:text="@{userInfo.name}",
給EditText是設(shè)置雙向綁定
android:text="@={userInfo.nickName}"
可以看到單項(xiàng)綁定和雙向綁定的區(qū)別就是“@”和“{}”之間多了個(gè)“=”剑按。
xml綁定了一個(gè)UserInfo對(duì)象和一個(gè)listener疾就,listener是Button的點(diǎn)擊監(jiān)聽,我們來看下UserInfo的代碼

public class UserInfo {

    private ObservableField<String> name = new ObservableField<>();
    private ObservableField<String> nickName = new ObservableField<>();

    public ObservableField<String> getName() {
        return name;
    }

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

    public ObservableField<String> getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName.set(nickName);
    }
}

再看下MainActivity中的代碼艺蝴,主要就是構(gòu)造UserInfo并給binding的屬性賦值

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private UserInfo userInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

        userInfo = new UserInfo();
        userInfo.setName("王昭君");
        userInfo.setNickName("美人");
        binding.setUserInfo(userInfo);
        binding.setListener(this);


        binding.getRoot().postDelayed(new Runnable() {
            @Override
            public void run() {
                userInfo.setNickName("延遲三秒");
            }
        }, 3000);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.btn_get_nick) {
            Toast.makeText(this, userInfo.getNickName().get(), Toast.LENGTH_SHORT).show();
        }
    }
}

可以看到猬腰,使用了數(shù)據(jù)綁定,我們的代碼邏輯結(jié)構(gòu)變得清晰猜敢,由數(shù)據(jù)綁定框架替我們生成findViewById和給View設(shè)置數(shù)據(jù)的代碼姑荷,數(shù)據(jù)綁定框架幫我們做了控件的數(shù)據(jù)變化監(jiān)聽,并將數(shù)據(jù)同步更新到控件上缩擂。

二鼠冕、DataBinding原理分析

數(shù)據(jù)綁定的運(yùn)行機(jī)制是怎樣的呢?胯盯,為什么我們改變nickName的值UI上可以直接更新懈费,我們操作UI,對(duì)應(yīng)的nickName的值也會(huì)更新呢博脑,下面我們一探DataBinding的究竟憎乙。

首先我們要先找到一個(gè)切入點(diǎn),就是MainActivity中的
DataBindingUtil.setContentView(this, R.layout.activity_main);

public static <T extends ViewDataBinding> T setContentView(@NonNull Activity activity,
            int layoutId) {
        return setContentView(activity, layoutId, sDefaultComponent);
    }
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);
    }
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);
        }
    }
static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
    }

DataBindingUtil.setContentView()一路方法調(diào)用跟下來叉趣,這里的parent是布局id為R.id.content的跟布局泞边,一般跟布局里面就是我們自己的布局,最外層是一個(gè)容器疗杉,所以childrenAdded == 1阵谚,并調(diào)用bind(component, childView, layoutId)方法,跟進(jìn)bind()方法發(fā)現(xiàn)調(diào)用sMapper.getDataBinder(bindingComponent, root, layoutId)乡数,
這里的sMapper是DataBinderMapper類椭蹄,該類是抽象類,找到它的實(shí)現(xiàn)類DataBinderMapperImpl净赴,路徑是:

app/build/generated/ap_generated_sources/debug/out/com/jokerwan/databinding/DataBinderMapperImpl.java

DataBinderMapperImpl#getDataBinder(bindingComponent, root, layoutId)

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

判斷view的tag是不是與layout/activity_main_0相等绳矩,如果相等,就new ActivityMainBindingImpl(component, view)玖翅,這個(gè)ActivityMainBindingImpl就是DataBinding框架根據(jù)我們的activity_main.xml通過APT在編譯時(shí)生成的類翼馆,ActivityMainBindingImpl的路徑為:

app/build/generated/ap_generated_sources/debug/out/com/jokerwan/databinding/databinding/ActivityMainBindingImpl.java

有的小伙伴就有疑問了,為什么是layout/activity_main_0金度,view的tag又是在哪里set進(jìn)去的呢?

原來应媚,數(shù)據(jù)綁定在處理布局的時(shí)候生成了兩個(gè)xml文件

  • activity_main-layout.xml(DataBinding需要的布局控件信息)
  • activity_main.xml(Android OS 渲染的布局文件)

activity_main-layout.xml

路徑:

app/build/intermediates/data_binding_layout_info_type_merge/debug/mergeDebugResources/out/activity_main-layout.xml

文件內(nèi)容:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout"
    filePath="/Users/jokerwan/AndroidStudioProjects/DataBinding/app/src/main/res/layout/activity_main.xml"
    isMerge="false" layout="activity_main" modulePackage="com.jokerwan.databinding">
    <Variables name="userInfo" declared="true" type="com.jokerwan.databinding.UserInfo">
        <location endLine="8" endOffset="54" startLine="6" startOffset="8" />
    </Variables>
    <Variables name="listener" declared="true" type="android.view.View.OnClickListener">
        <location endLine="12" endOffset="54" startLine="10" startOffset="8" />
    </Variables>
    <Targets>
        <Target tag="layout/activity_main_0" view="LinearLayout">
            <Expressions />
            <location endLine="44" endOffset="18" startLine="15" startOffset="4" />
        </Target>
        <Target tag="binding_1" view="TextView">
            <Expressions>
                <Expression attribute="android:text" text="userInfo.name">
                    <Location endLine="25" endOffset="42" startLine="25" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="25" endOffset="40" startLine="25" startOffset="28" />
                </Expression>
            </Expressions>
            <location endLine="27" endOffset="28" startLine="22" startOffset="8" />
        </Target>
        <Target tag="binding_2" view="EditText">
            <Expressions>
                <Expression attribute="android:text" text="userInfo.nickName">
                    <Location endLine="32" endOffset="47" startLine="32" startOffset="12" />
                    <TwoWay>true</TwoWay>
                    <ValueLocation endLine="32" endOffset="45" startLine="32" startOffset="29" />
                </Expression>
            </Expressions>
            <location endLine="34" endOffset="44" startLine="29" startOffset="8" />
        </Target>
        <Target id="@+id/btn_get_nick" tag="binding_3" view="Button">
            <Expressions>
                <Expression attribute="android:onClick" text="listener">
                    <Location endLine="42" endOffset="40" startLine="42" startOffset="12" />
                    <TwoWay>false</TwoWay>
                    <ValueLocation endLine="42" endOffset="38" startLine="42" startOffset="31" />
                </Expression>
            </Expressions>
            <location endLine="42" endOffset="42" startLine="36" startOffset="8" />
        </Target>
    </Targets>
</Layout>

activity_main.xml

路徑:

app/build/intermediates/incremental/mergeDebugResources/stripped.dir/layout/activity_main.xml

文件內(nèi)容:

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        android:orientation="vertical"
        android:gravity="center" 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:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="binding_1"        
            android:textSize="16sp"
            tools:text="姓名"/>

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:tag="binding_2"             
            tools:text="昵稱"
            android:layout_marginTop="10dp"/>

        <Button
            android:id="@+id/btn_get_nick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="獲取model里面的昵稱"
            android:layout_marginTop="20dp"
            android:tag="binding_3"      />

    </LinearLayout>     

可以看到在activity_main.xml生成了輔助信息tag,每個(gè)容器和view都對(duì)應(yīng)一個(gè)tag猜极,而布局的第一個(gè)容器LinearLayout的tag就為layout/activity_main_0中姜,這些tag在我們build工程時(shí)會(huì)隨著activity_main.xml的生成而存在。

接著看生成的ActivityMainBindingImpl#構(gòu)造器

    public ActivityMainBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 4, sIncludes, sViewsWithIds));
    }
    private ActivityMainBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        super(bindingComponent, root, 1
            , (android.widget.Button) bindings[3]
            );
        this.btnGetNick.setTag(null);
        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();
    }

構(gòu)造函數(shù)內(nèi)首先調(diào)用mapBindings()遞歸把root中所有的view找出來,數(shù)字4是指布局中總共有4個(gè)View丢胚,然后還傳入sIncludessViewsWithIds,前者是布局中include進(jìn)來的布局的索引翩瓜,后者是布局中包含id的索引。

再回到構(gòu)造函數(shù)携龟,mapBindings()查找到的View都放置在bindings這個(gè)數(shù)組中氏义,并通過生成代碼的方式虚青,將它們一一取出來龄减,轉(zhuǎn)化為對(duì)應(yīng)的數(shù)據(jù)類型璃氢,有設(shè)置id的控件,會(huì)以id作為變量名蕊蝗,沒有設(shè)置id的控件仅乓,則以mboundView + 數(shù)字的方式依次賦值。然后通過setRootTag(root)方法通過setTag的方式將這個(gè)Binding和root關(guān)聯(lián)起來

    protected void setRootTag(View view) {
        view.setTag(R.id.dataBinding, this);
    }

ViewDataBinding#mapBindings()

    protected static Object[] mapBindings(DataBindingComponent bindingComponent, View[] roots, int numBindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds) {
        Object[] bindings = new Object[numBindings];

        for(int i = 0; i < roots.length; ++i) {
            mapBindings(bindingComponent, roots[i], bindings, includes, viewsWithIds, true);
        }

        return bindings;
    }

    private static void mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, ViewDataBinding.IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot) {
        ViewDataBinding existingBinding = getBinding(view);
        if (existingBinding == null) {
            Object objTag = view.getTag();
            String tag = objTag instanceof String ? (String)objTag : null;
            boolean isBound = false;
            int indexInIncludes;
            int id;
            int count;
            if (isRoot && tag != null && tag.startsWith("layout")) {
                id = tag.lastIndexOf(95);
                if (id > 0 && isNumeric(tag, id + 1)) {
                    count = parseTagInt(tag, id + 1);
                    if (bindings[count] == null) {
                        bindings[count] = view;
                    }

                    indexInIncludes = includes == null ? -1 : count;
                    isBound = true;
                } else {
                    indexInIncludes = -1;
                }
            } else if (tag != null && tag.startsWith("binding_")) {
                id = parseTagInt(tag, BINDING_NUMBER_START);
                if (bindings[id] == null) {
                    bindings[id] = view;
                }

                isBound = true;
                indexInIncludes = includes == null ? -1 : id;
            } else {
                indexInIncludes = -1;
            }

            if (!isBound) {
                id = view.getId();
                if (id > 0 && viewsWithIds != null && (count = viewsWithIds.get(id, -1)) >= 0 && bindings[count] == null) {
                    bindings[count] = view;
                }
            }

            if (view instanceof ViewGroup) {
                ViewGroup viewGroup = (ViewGroup)view;
                count = viewGroup.getChildCount();
                int minInclude = 0;

                for(int i = 0; i < count; ++i) {
                    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(47) > 0) {
                            int includeIndex = findIncludeIndex(childTag, minInclude, includes, indexInIncludes);
                            if (includeIndex >= 0) {
                                isInclude = true;
                                minInclude = includeIndex + 1;
                                int index = includes.indexes[indexInIncludes][includeIndex];
                                int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
                                int lastMatchingIndex = findLastMatching(viewGroup, i);
                                if (lastMatchingIndex == i) {
                                    bindings[index] = DataBindingUtil.bind(bindingComponent, child, layoutId);
                                } else {
                                    int includeCount = lastMatchingIndex - i + 1;
                                    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()方法主要是把root內(nèi)所有的view給查找出來匿又,并放置到bindings對(duì)應(yīng)的索引內(nèi)方灾,這個(gè)索引如何確定呢?上面我們分析過碌更,DataBinding在加載我們的布局activity_main.xml時(shí)會(huì)生成兩個(gè)xml文件,一個(gè)用來關(guān)聯(lián)布局控件信息洞慎,一個(gè)是布局文件并對(duì)每個(gè)ViewGroup和View都打了一個(gè)tag痛单,通過解析這個(gè)tag,就能知道對(duì)應(yīng)的索引了劲腿。所以旭绒,為了避免自己inflate布局文件后,不小心操作了view的tag對(duì)解析產(chǎn)生干擾焦人,盡量使用數(shù)據(jù)綁定來得到inflate之后的view挥吵。

通過代碼我們可以發(fā)現(xiàn)mapBindings()通過自身遞歸調(diào)用把root中所有的view找出來,這里雖然用到了遞歸花椭,但實(shí)際上是通過這種方式實(shí)現(xiàn)對(duì)root下所有的控件的遍歷忽匈,因此整個(gè)方法的時(shí)間復(fù)雜度是O(n),通過一次遍歷矿辽,找到所有的控件丹允,整體性能比使用findViewById還優(yōu)秀,因?yàn)閒indViewById每次都要從DecorView開始循環(huán)遍歷找到對(duì)應(yīng)的View袋倔。

回到ActivityMainBindingImpl實(shí)現(xiàn)類的構(gòu)造器雕蔽,可以看到最后調(diào)用了invalidateAll()

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

invalidateAll()方法實(shí)現(xiàn)很簡單,將臟標(biāo)記位mDirtyFlags標(biāo)記為0x8L宾娜,即在二進(jìn)制表示上批狐,第4位的值為1,這個(gè)臟標(biāo)記位是一個(gè)long的值前塔,也就是最多有64個(gè)位可供使用嚣艇。由于mDirtyFlags這個(gè)變量是成員變量缘眶,且多處會(huì)對(duì)其進(jìn)行寫操作,所以對(duì)它的寫操作加了同步鎖髓废。接著調(diào)用了requestRebind()巷懈,ViewDataBinding#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);
            }
        }
    }

mContainingBinding表示當(dāng)前ViewDataBinding所包含的另外一個(gè)ViewDataBinding,如果當(dāng)前ViewDataBinding包含另一個(gè)ViewDataBinding慌洪,則子ViewDataBinding會(huì)優(yōu)先執(zhí)行rebind操作顶燕,從if (mContainingBinding != null)則執(zhí)行mContainingBinding.requestRebind()代碼中可以看出,若當(dāng)前ViewDataBinding內(nèi)部沒有包含另一個(gè)ViewDataBinding冈爹,則程序繼續(xù)走else邏輯涌攻,獲取到此ViewDataBinding綁定的LifecycleOwner,如果LifecycleOwner的生命周期狀態(tài)不是Lifecycle.State.STARTED频伤,則直接return恳谎,不進(jìn)行綁定數(shù)據(jù)的相關(guān)操作,否則繼續(xù)執(zhí)行下面的程序代碼憋肖。

如果此前沒請(qǐng)求執(zhí)行rebind操作因痛,那么會(huì)將mPendingRebind置為true,API等級(jí)16及以上岸更,會(huì)往mChoreographer發(fā)一個(gè)mFrameCallback鸵膏,在系統(tǒng)刷新界面(doFrame)的時(shí)候執(zhí)行rebind操作,API等級(jí)16以下怎炊,則是往UI線程post一個(gè)mRebindRunnable任務(wù)谭企。mFrameCallback的內(nèi)部實(shí)際上調(diào)用的是mRebindRunnable的run方法,因此這兩個(gè)任務(wù)僅僅是調(diào)用時(shí)機(jī)不同评肆。

    mFrameCallback = new Choreographer.FrameCallback() {
         @Override
         public void doFrame(long frameTimeNanos) {
               mRebindRunnable.run();
         }
    };

而如果此前請(qǐng)求過執(zhí)行rebind操作债查,即已經(jīng)post了一個(gè)任務(wù)到隊(duì)列去,而且這個(gè)任務(wù)還未獲得執(zhí)行瓜挽,此時(shí)mPendingRebind的值為true盹廷,那么requestRebind將直接返回,避免重復(fù)秸抚、頻繁執(zhí)行rebind操作帶來的性能損耗速和。接著我們來看下mRebindRunnable里面究竟干了什么不為人知的事情:

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

當(dāng)mRebindRunnablerun()方法執(zhí)行時(shí),先同步吧mPendingRebind賦值為false剥汤,以便后續(xù)其他requestRebind能往主線程發(fā)起rebind的任務(wù)颠放。在 API 19及以上的版本,檢查下UI控件是否附加到了窗口上吭敢,如果沒有附到窗口上碰凶,則設(shè)置監(jiān)聽器,以便在UI附加到窗口上的時(shí)候立即執(zhí)行rebind操作,然后返回欲低。當(dāng) API 19 以下或UI控件已經(jīng)附加到窗口上辕宏,,則調(diào)用executePendingBindings()執(zhí)行binding邏輯砾莱。繼續(xù)跟進(jìn)ViewDataBinding#executePendingBindings()

    public void executePendingBindings() {
        if (mContainingBinding == null) {
            executeBindingsInternal();
        } else {
            mContainingBinding.executePendingBindings();
        }
    }

在這里依舊先判斷若當(dāng)前ViewDataBinding內(nèi)部有沒有包含另一個(gè)ViewDataBinding瑞筐,若有包含,則先執(zhí)行子ViewDataBinding的executePendingBindings()腊瑟,否則執(zhí)行自己的executePendingBindings()

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

通過以上代碼可以看出聚假,此處進(jìn)行了binding操作之前的一些判定,如果已經(jīng)開始執(zhí)行綁定操作了闰非,即這段代碼正在執(zhí)行膘格,那么調(diào)用一次requestRebind,然后返回财松。接著調(diào)用hasPendingBindings()判斷是否需要刷新UI瘪贱,若返回true表示需要刷新,繼續(xù)執(zhí)行代碼辆毡,否則直接return掉該方法菜秦。

    Override
    public boolean hasPendingBindings() {
        synchronized(this) {
            if (mDirtyFlags != 0) {
                return true;
            }
        }
        return false;
    }

這里mDirtyFlags != 0表示需要刷新UI,還記得之前invalidateAll()中的mDirtyFlags = 0x8L嗎胚迫?

接下來在執(zhí)行具體的executeBindings()操作前喷户,調(diào)用下mRebindCallbacks.notifyCallbacks通知所有回調(diào)將開始rebind操作,回調(diào)可以在執(zhí)行的過程中访锻,將mRebindHalted置為true,阻止executeBindings()方法的執(zhí)行闹获,攔截成功同樣通過回調(diào)進(jìn)行通知期犬。如果沒有被攔截,executeBindings()方法會(huì)被執(zhí)行避诽,運(yùn)行結(jié)束后龟虎,同樣通過回調(diào)進(jìn)行通知。executeBindings()是ViewDataBinding的一個(gè)抽象方法沙庐,具體實(shí)現(xiàn)在ActivityMainBindingImpl#executeBindings()

    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        android.view.View.OnClickListener listener = mListener;
        androidx.databinding.ObservableField<java.lang.String> userInfoNickName = null;
        java.lang.String userInfoNickNameGet = null;
        java.lang.String userInfoName = null;
        com.jokerwan.databinding.UserInfo userInfo = mUserInfo;

        if ((dirtyFlags & 0xaL) != 0) {
        }
        if ((dirtyFlags & 0xdL) != 0) {
            
                if (userInfo != null) {
                    // read userInfo.nickName
                    userInfoNickName = userInfo.getNickName();
                }
                updateRegistration(0, userInfoNickName);
                if (userInfoNickName != null) {
                    // read userInfo.nickName.get()
                    userInfoNickNameGet = userInfoNickName.get();
                }
            if ((dirtyFlags & 0xcL) != 0) {

                    if (userInfo != null) {
                        // read userInfo.name
                        userInfoName = userInfo.getName();
                    }
            }
        }
        // batch finished
        if ((dirtyFlags & 0xaL) != 0) {
            // api target 1
            this.btnGetNick.setOnClickListener(listener);
        }
        if ((dirtyFlags & 0xcL) != 0) {
            // api target 1
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, userInfoName);
        }
        if ((dirtyFlags & 0xdL) != 0) {
            // api target 1
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView2, userInfoNickNameGet);
        }
        if ((dirtyFlags & 0x8L) != 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);
        }
    }

首先將臟標(biāo)記為存儲(chǔ)到局部變量dirtyFlags中鲤妥,然后將臟數(shù)據(jù)標(biāo)記位mDirtyFlags置0,然后對(duì)綁定到布局文件的model對(duì)象進(jìn)行判空拱雏,并取到對(duì)應(yīng)的值賦值給對(duì)應(yīng)的控件棉安,這里又體現(xiàn)出了數(shù)據(jù)綁定的一個(gè)優(yōu)勢(shì):在進(jìn)行數(shù)據(jù)相關(guān)的操作前,會(huì)檢查變量是否為空铸抑,倘若沒有傳入對(duì)應(yīng)的變量贡耽,或者傳入null,在布局上進(jìn)行的操作并不會(huì)執(zhí)行,因此蒲赂,假如上述例子中阱冶,我們沒有傳入對(duì)應(yīng)的userInfo對(duì)象也不會(huì)引發(fā)Crash。

接著根據(jù)臟標(biāo)記位和相關(guān)的值進(jìn)行位與運(yùn)算來判斷滥嘴,上面我們已經(jīng)知道木蹬,在ActivityMainBindingImpl構(gòu)造函數(shù)調(diào)用了invalidateAll()mDirtyFlags0x8L,轉(zhuǎn)為二進(jìn)制第四位為1若皱,上述四個(gè)if條件經(jīng)過位與運(yùn)算后與0比較都為真镊叁,即這種情況下上述條件里面的代碼都會(huì)執(zhí)行。這里主要執(zhí)行的操作:

  1. 獲取userInfoName是尖,將userInfoName綁定到mboundView1意系,獲取userInfoNickNameGet,并將其綁定到mboundView2饺汹,通過代碼可以看到蛔添,每一個(gè)被綁定到View上的通過variable標(biāo)簽定義的變量都會(huì)有一個(gè)專屬的標(biāo)記位,當(dāng)該變量的值被更新時(shí)兜辞,對(duì)應(yīng)的臟標(biāo)記位就會(huì)置為1迎瞧,executeBindings()執(zhí)行的時(shí)候就會(huì)將變動(dòng)的數(shù)據(jù)更新到對(duì)應(yīng)的UI控件上。

  2. 在設(shè)置了雙向綁定的控件上逸吵,為其添加對(duì)應(yīng)的監(jiān)聽器凶硅,監(jiān)聽其變動(dòng),如:EditText上設(shè)置TextWatcher扫皱,具體的設(shè)置邏輯放置到了TextViewBindingAdapter.setTextWatcher里足绅。源碼如下:

    @BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",
             "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)
     public static void setTextWatcher(TextView view, final BeforeTextChanged before,
             final OnTextChanged on, final AfterTextChanged after,
             final InverseBindingListener textAttrChanged) {
         final TextWatcher newValue;
         if (before == null && after == null && on == null && textAttrChanged == null) {
             newValue = null;
         } else {
             newValue = new TextWatcher() {
                 @Override
                 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                     if (before != null) {
                         before.beforeTextChanged(s, start, count, after);
                     }
                 }
    
                 @Override
                 public void onTextChanged(CharSequence s, int start, int before, int count) {
                     if (on != null) {
                         on.onTextChanged(s, start, before, count);
                     }
                     if (textAttrChanged != null) {
                         textAttrChanged.onChange();
                     }
                 }
    
                 @Override
                 public void afterTextChanged(Editable s) {
                     if (after != null) {
                         after.afterTextChanged(s);
                     }
                 }
             };
         }
         final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
         if (oldValue != null) {
             view.removeTextChangedListener(oldValue);
         }
         if (newValue != null) {
             view.addTextChangedListener(newValue);
         }
     }
    

    代碼中創(chuàng)建了一個(gè)新的TextWatcher,并將傳進(jìn)來的mboundView2androidTextAttrChanged監(jiān)聽器包裹在里面韩脑。
    當(dāng)數(shù)據(jù)發(fā)生變化的時(shí)候氢妈,TextWatcher在回調(diào)onTextChanged()的最后,會(huì)通過textAttrChanged.onChange()回調(diào)到傳入的mboundView2androidTextAttrChangedonChange()段多。

    在這里我們也看到了熟悉的@BindingAdapter注解首量,這個(gè)注解實(shí)現(xiàn)了控件屬性和代碼內(nèi)的方法調(diào)用的映射,編譯期进苍,數(shù)據(jù)綁定框架通過這種方式加缘,為對(duì)應(yīng)的控件生成對(duì)應(yīng)的方法調(diào)用。

    接著我們來看下傳進(jìn)來的監(jiān)聽器mboundView2androidTextAttrChanged的代碼

        private androidx.databinding.InverseBindingListener mboundView2androidTextAttrChanged = new androidx.databinding.InverseBindingListener() {
         @Override
         public void onChange() {
             // Inverse of userInfo.nickName.get()
             //         is userInfo.nickName.set((java.lang.String) callbackArg_0)
             java.lang.String callbackArg_0 = androidx.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView2);
             // localize variables for thread safety
             // userInfo != null
             boolean userInfoJavaLangObjectNull = false;
             // userInfo.nickName
             androidx.databinding.ObservableField<java.lang.String> userInfoNickName = null;
             // userInfo.nickName.get()
             java.lang.String userInfoNickNameGet = null;
             // userInfo
             com.jokerwan.databinding.UserInfo userInfo = mUserInfo;
             // userInfo.nickName != null
             boolean userInfoNickNameJavaLangObjectNull = false;
             
             userInfoJavaLangObjectNull = (userInfo) != (null);
             if (userInfoJavaLangObjectNull) {
                 userInfoNickName = userInfo.getNickName();
                 userInfoNickNameJavaLangObjectNull = (userInfoNickName) != (null);
                 if (userInfoNickNameJavaLangObjectNull) {
                     userInfoNickName.set(((java.lang.String) (callbackArg_0)));
                 }
             }
         }
     };
    

    這段代碼會(huì)去對(duì)應(yīng)的View中取得控件中最新的值觉啊,然后一系列地判斷布局文件中綁定的userInfo以及對(duì)應(yīng)的屬性是否為null拣宏,不為null時(shí)將值更新為從控件中取出的最新值。

    上面講到的情況是當(dāng)UI控件上EditText顯示內(nèi)容改變時(shí)通知綁定到布局文件的ObservableField更新柄延,那當(dāng)ObservableField更新后是如何通知UI更新的呢蚀浆?我們繼續(xù)回到ActivityMainBindingImpl#executeBindings()

         ...
         if ((dirtyFlags & 0xdL) != 0) {
             
                 if (userInfo != null) {
                     // read userInfo.nickName
                     userInfoNickName = userInfo.getNickName();
                 }
                 updateRegistration(0, userInfoNickName);
                 ...
         }
         ...
    

    注意我們綁定到布局中的userInfo中的nickName的類型是ObservableField<String>缀程,通過注釋我們也可以看到此處的userInfoNickName就是讀取的userInfo.nickName,接著調(diào)用updateRegistration(0, userInfoNickName)市俊,ObservableField最終是繼承Observable杨凑,我們不妨猜想一下,updateRegistration()方法傳入一個(gè)Observable對(duì)象應(yīng)該是要觀察這個(gè)對(duì)象的變化摆昧,當(dāng)被觀察者發(fā)生變化時(shí)去重新執(zhí)行rebind操作撩满,第一個(gè)參數(shù)0表示的是userInfoNickName的id,看代碼ViewDataBinding#updateRegistration()

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

    這個(gè)方法中接著調(diào)用updateRegistration方法并傳入一個(gè)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();
         }
     };
    

    通過代碼發(fā)現(xiàn)CREATE_PROPERTY_LISTENER是一個(gè)屬性監(jiān)聽器創(chuàng)建類绅你,其中new WeakPropertyListener(viewDataBinding, localFieldId)并返回其監(jiān)聽器伺帘,我們先繼續(xù)看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;
     }
    

    當(dāng)傳進(jìn)來的observable為null時(shí),解注冊(cè)該被觀察者忌锯,后面的邏輯就是先判斷傳進(jìn)來的observable的監(jiān)聽器在mLocalFieldObservers中是否存在伪嫁,存在就根據(jù)localFieldId拿出來先重新綁定監(jiān)聽器,不存在就新創(chuàng)建一個(gè)監(jiān)聽器并綁定該observable根據(jù)localFieldId存進(jìn)mLocalFieldObservers中偶垮,mLocalFieldObservers是一個(gè)數(shù)組张咳,我們繼續(xù)看registerTo(localFieldId, observable, listenerCreator)

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

    當(dāng)listener為null時(shí),調(diào)用傳進(jìn)來的監(jiān)聽器CreateWeakListenercreate(this, localFieldId)方法創(chuàng)建listener似舵,并且給listener設(shè)置當(dāng)前ViewDataBinding的LifecycleOwner和對(duì)應(yīng)的Target為observable脚猾,這里的this就是指當(dāng)前的ViewDataBinding,CreateWeakListener是一個(gè)抽象類砚哗,我們之前new出來的WeakPropertyListener是它的實(shí)現(xiàn)類龙助,接下來我們看WeakPropertyListener

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

    首先看其構(gòu)造方法mListener = new WeakListener<Observable>(binder, localFieldId, this);這里new一個(gè)WeakListener并且將ViewDataBinding類型的binder對(duì)象和localFieldId傳進(jìn)去,好蛛芥,繼續(xù)往下看提鸟,注意看onPropertyChanged()方法,見名知意仅淑,這個(gè)方法應(yīng)該是當(dāng)屬性變化時(shí)會(huì)調(diào)用沽一,從mListener中取出binder,當(dāng)binder為null時(shí)直接退出漓糙,從mListener取出之前存入的Observable對(duì)象,并校驗(yàn)下當(dāng)前獲取到的Observable對(duì)象是否是發(fā)出更新消息的Observable對(duì)象烘嘱,如果不是昆禽,則不作處理,如果是蝇庭,則調(diào)用binder的handleFieldChange()方法醉鳖,跟進(jìn)ViewDataBinding#handleFieldChange()

    private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
         if (mInLiveDataRegisterObserver) {
             // We're in LiveData registration, which always results in a field change
             // that we can ignore. The value will be read immediately after anyway, so
             // there is no need to be dirty.
             return;
         }
         boolean result = onFieldChange(mLocalFieldId, object, fieldId);
         if (result) {
             requestRebind();
         }
     }
    

    這里面會(huì)調(diào)用onFieldChange()方法來判斷需要更新的數(shù)據(jù)是否有變化,這個(gè)方法是ViewDataBinding的抽象方法哮内,具體實(shí)現(xiàn)在ActivityMainBindingImpl#onFieldChange()

    @Override
     protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
         switch (localFieldId) {
             case 0 :
                 return onChangeUserInfoNickName((androidx.databinding.ObservableField<java.lang.String>) object, fieldId);
         }
         return false;
     }
     private boolean onChangeUserInfoNickName(androidx.databinding.ObservableField<java.lang.String> UserInfoNickName, int fieldId) {
         if (fieldId == BR._all) {
             synchronized(this) {
                     mDirtyFlags |= 0x1L;
             }
             return true;
         }
         return false;
     }
    

    還記得調(diào)用updateRegistration(0, userInfoNickName)方法嗎盗棵,第一個(gè)參數(shù)就是nickName的localFieldId壮韭,傳的值是0,匹配到switch語句的0纹因,返回onChangeUserInfoNickName()的結(jié)果喷屋,這里會(huì)重新將臟數(shù)據(jù)進(jìn)行位或運(yùn)算,將nickName對(duì)應(yīng)的二進(jìn)制標(biāo)記位置為1瞭恰,并返回true屯曹,handleFieldChange()方法中會(huì)接受結(jié)果為true則調(diào)用requestRebind()重新綁定更改過的數(shù)據(jù),在requestRebind()會(huì)重新獲取nickName的值惊畏,并判斷臟標(biāo)記位是否滿足更新UI控件條件恶耽,滿足則更新UI,當(dāng)我們給ObservableField對(duì)象set值的時(shí)候會(huì)調(diào)用notifyChange()

    public void notifyChange() {
         synchronized (this) {
             if (mCallbacks == null) {
                 return;
             }
         }
         mCallbacks.notifyCallbacks(this, 0, null);
     }
    

    mCallbacks的類型是PropertyChangeRegistry颜启,通過addOnPropertyChangedCallback(OnPropertyChangedCallback callback)方法創(chuàng)建偷俭,并且把callback添加到mCallbacks中,繼續(xù)回到
    WeakPropertyListener

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

    發(fā)現(xiàn)它集成自O(shè)bservable.OnPropertyChangedCallback缰盏,并且在被調(diào)用addListener()方法是將自己添加到ObservableField對(duì)象的mCallbacks中涌萤,并在removeListener()被調(diào)用時(shí)移除回調(diào),這里就是把屬性監(jiān)聽器綁定在ObservableField對(duì)象中乳规,當(dāng)ObservableField對(duì)象值改變時(shí)會(huì)調(diào)用notifyChange()形葬,經(jīng)過一系列的回調(diào)和通信,最終會(huì)調(diào)用WeakPropertyListener類的onPropertyChanged()方法來通知ViewDataBinding來執(zhí)行rebind操作暮的。
    至此笙以,ObservableField更新后是通知UI更新的流程就分析完畢。

三冻辩、總結(jié)

DataBingding用起來很方便猖腕,可以幫助我們簡化使Activity/Fragment中部分操作View的代碼,讓我們更加關(guān)注業(yè)務(wù)邏輯的實(shí)現(xiàn)恨闪。但本文講的DataBingding原理可能理解起來比較困難倘感,你也沒有必要去計(jì)較DataBingding庫中的一些細(xì)枝末節(jié)的代碼,我們只需要疏通整個(gè)鏈路咙咽,知道DataBingding框架如何實(shí)現(xiàn)雙向綁定的大概思路即可老玛,了解其中的觀察者設(shè)計(jì)模式。通過這篇文章的學(xué)習(xí)钧敞,了解了DataBingding原理蜡豹,在日后使用DataBingding和定位使用DataBinding產(chǎn)生的bug時(shí)也會(huì)得心應(yīng)手。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末溉苛,一起剝皮案震驚了整個(gè)濱河市镜廉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌愚战,老刑警劉巖娇唯,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件齐遵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡塔插,警方通過查閱死者的電腦和手機(jī)梗摇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佑淀,“玉大人留美,你說我怎么就攤上這事∩烊校” “怎么了谎砾?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長捧颅。 經(jīng)常有香客問我景图,道長,這世上最難降的妖魔是什么碉哑? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任挚币,我火速辦了婚禮,結(jié)果婚禮上扣典,老公的妹妹穿的比我還像新娘妆毕。我一直安慰自己,他們只是感情好贮尖,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布笛粘。 她就那樣靜靜地躺著,像睡著了一般湿硝。 火紅的嫁衣襯著肌膚如雪薪前。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天关斜,我揣著相機(jī)與錄音示括,去河邊找鬼。 笑死痢畜,一個(gè)胖子當(dāng)著我的面吹牛垛膝,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播丁稀,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼繁涂,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了二驰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤秉沼,失蹤者是張志新(化名)和其女友劉穎桶雀,沒想到半個(gè)月后矿酵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡矗积,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年全肮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棘捣。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辜腺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乍恐,到底是詐尸還是另有隱情评疗,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布茵烈,位于F島的核電站百匆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏呜投。R本人自食惡果不足惜加匈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望仑荐。 院中可真熱鬧雕拼,春花似錦、人聲如沸粘招。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽男图。三九已至示姿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間逊笆,已是汗流浹背栈戳。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留难裆,地道東北人子檀。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像乃戈,于是被迫代替她去往敵國和親褂痰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

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