探索Android視圖加載器LayoutInflater

這次跟大家分享的是關(guān)于LayoutInflater的使用忌傻,在開發(fā)的過(guò)程中,LayoutInfalter經(jīng)常用于加載視圖焊夸,對(duì),今天咱們來(lái)聊的就是蓝角,關(guān)于加載視圖的一些事兒阱穗,我記得之前一位曾共事過(guò)的一位同事問到我一個(gè)問題,activity是如何加載資源文件來(lái)顯示界面的使鹅,古話說(shuō)得好揪阶,知其然不知其所以然,因此在寫這篇文章的時(shí)候我也做了不少的準(zhǔn)備患朱,在這里我先引出幾個(gè)問題鲁僚,然后我們通過(guò)問題在源碼中尋找答案。

1.如何獲取LayoutInflater裁厅?
2.如何使用LayoutInflater冰沙?為什么?
3.Activity是如何加載視圖的执虹?
4.如何優(yōu)化我們的布局拓挥?

首先我們先看一下LayoutInflater是如何獲取的。

LayoutInflater inflater=LayoutInflater.from(context);

我們通過(guò)LayoutInflater.from(Context)獲取LayoutInflater袋励,我們繼續(xù)進(jìn)入LayoutInflater.java探索一番侥啤。
LayoutInflater.java:

/**
 * Obtains the LayoutInflater from the given context.
 */
public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}

在LayoutInflater里面当叭,通過(guò)靜態(tài)方法from(Context),然后繼續(xù)調(diào)用Context中的方法getSystemService獲取LayoutInflater,我們往Context繼續(xù)看。

Context.java:

public abstract Object getSystemService(@ServiceName @NonNull String name);

大家會(huì)發(fā)現(xiàn)盖灸,怎么點(diǎn)進(jìn)去這是個(gè)抽象方法蚁鳖,其實(shí)Context是一個(gè)抽象類,真正實(shí)現(xiàn)的是ContextImpl這個(gè)類赁炎,我們就繼續(xù)看ContextImpl:
ContextImpl.java:

@Override
public Object getSystemService(String name) {
    //繼續(xù)調(diào)用SystemServiceRegistry.getSystemService
    return SystemServiceRegistry.getSystemService(this, name);
}

SystemServiceRegistry.java:

/**
 * Gets a system service from a given context.
 */
public static Object getSystemService(ContextImpl ctx, String name) {
    //先獲取ServiceFetcher才睹,在通過(guò)fetcher獲取LayoutInflate
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

SystemServiceRegistry這個(gè)類中的靜態(tài)方法getSystemService,通過(guò)SYSTEM_SERVICE_FETCHERS獲取ServiceFetcher,我們先看看SYSTEM_SERVICE_FETCHERS跟ServiceFetcher在SystemServiceRegistry中的定義。
SystemServiceRegistry.java:

//使用鍵值對(duì)來(lái)保存
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
        new HashMap<String, ServiceFetcher<?>>();

/**
 * Base interface for classes that fetch services.
 * These objects must only be created during static initialization.
 */
static abstract interface ServiceFetcher<T> {
    //只有一條接口甘邀,通過(guò)context獲取服務(wù)琅攘,先看一下其實(shí)現(xiàn)類
    T getService(ContextImpl ctx);
}

SYSTEM_SERVICE_FETCHERS在SystemServiceRegistry這個(gè)類中作為全局常量,通過(guò)鍵值對(duì)的方式用來(lái)保存ServiceFetcher,而ServiceFetcher又是什么松邪?在源碼中坞琴,ServiceFetcher是一條接口,通過(guò)泛型T定義了getService(ContextImpl)來(lái)獲取服務(wù)對(duì)象逗抑。那么具體ServiceFetcher具體的實(shí)現(xiàn)在什么地方剧辐?在SystemServiceRegistry中,有一段這樣的代碼:
SystemServiceRegistry.java:

static {
    .....
    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
        new CachedServiceFetcher<LayoutInflater>() {
    @Override
    public LayoutInflater createService(ContextImpl ctx) {
        return new PhoneLayoutInflater(ctx.getOuterContext());
    }});

......
}

在這個(gè)靜態(tài)代碼塊中邮府,通過(guò)registerService進(jìn)行初始化注冊(cè)服務(wù)荧关。我們先看看這個(gè)靜態(tài)方法。

/**
 * Statically registers a system service with the context.
 * This method must be called during static initialization only.
 */
private static <T> void   registerService(String serviceName, Class<T> serviceClass,
        ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

registerService這個(gè)一段函數(shù)的作用就是用來(lái)通過(guò)鍵值對(duì)的方式褂傀,保存服務(wù)對(duì)象忍啤,也就是說(shuō),SystemServiceRegistry會(huì)初始化的時(shí)候注冊(cè)各種服務(wù)仙辟,而我們的也看到Context.LAYOUT_INFLATER_SERVICE作為key來(lái)獲取LayoutInfalter同波。

Context.java:

/**
 * 定義這個(gè)常量,用于獲取系統(tǒng)服務(wù)中的LayoutInflate
 * Use with {@link #getSystemService} to retrieve a
 * {@link android.view.LayoutInflater} for inflating layout resources in this
 * context.
 *
 * @see #getSystemService
 * @see android.view.LayoutInflater
 */
public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";

我們繼續(xù)看看ServiceFetcher的實(shí)現(xiàn)類:

/**
 * Override this class when the system service constructor needs a
 * ContextImpl and should be cached and retained by that context.
 */
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
    private final int mCacheIndex;

    public CachedServiceFetcher() {
        mCacheIndex = sServiceCacheSize++;
    }

    @Override
    @SuppressWarnings("unchecked")
    public final T getService(ContextImpl ctx) {
        final Object[] cache = ctx.mServiceCache;
        synchronized (cache) {
            // Fetch or create the service.
            Object service = cache[mCacheIndex];
            if (service == null) {
                service = createService(ctx);
                cache[mCacheIndex] = service;
            }
            return (T)service;
        }
    }

    public abstract T createService(ContextImpl ctx);
}

CachedServiceFetcher的作用用于保存我們的泛型T,同時(shí)這個(gè)CachedServiceFetcher有一個(gè)抽象方法createService叠国,createService這個(gè)方法用來(lái)創(chuàng)建這個(gè)服務(wù)未檩,因此使用這個(gè)類就必須重寫這個(gè)方法,我們繼續(xù)看回:

ServiceFetcher.java:

staic{
    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
            new CachedServiceFetcher<LayoutInflater>() {
        @Override
        public LayoutInflater createService(ContextImpl ctx) {
            return new PhoneLayoutInflater(ctx.getOuterContext());
    }});
}

現(xiàn)在看回來(lái)這里粟焊,注冊(cè)服務(wù)不就是通過(guò)通過(guò)鍵值對(duì)的方式進(jìn)行保存這個(gè)對(duì)象冤狡,然而我們獲取到的LayoutInflater其實(shí)是PhoneLayoutInflater。PhoneLayoutInflater繼承于LayoutInfalter.

小結(jié):

我們獲取LayoutInflater對(duì)象项棠,可以通過(guò)兩種方法獲缺ā:

LayoutInflater inflater1=LayoutInflater.from(context);
LayoutInflater inflater2= (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

context的實(shí)現(xiàn)類contextImpl,調(diào)用SystemServiceRegistry.getSystemService沾乘,通過(guò)鍵值對(duì)的方式獲取PhoneLayoutInflater對(duì)象怜奖,從中我們也看到浑测,這種方式通過(guò)鍵值對(duì)的方式緩存起這個(gè)對(duì)象翅阵,避免創(chuàng)建過(guò)多的對(duì)象歪玲,這是也一種單例的設(shè)計(jì)模式。

現(xiàn)在咱們來(lái)看一下掷匠,我們是如何使用LayoutInflater來(lái)獲取View,我們先從一段小代碼看看滥崩。

我新建一個(gè)布局文件,my_btn.xml:

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:text="我是一個(gè)按鈕">

</Button>

在布局文件中,我設(shè)置其layoutwidth與layout_height分別是填充屏幕讹语。
在activity的content_main.xml布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/content_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="ffzxcom.mytest.toucheventapplication.MainActivity"
    tools:showIn="@layout/activity_main">


</RelativeLayout>

LayoutInflater.java:
方法一:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}
 
 

方法二:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
    }
    //通過(guò)資源加載器和資源Id,獲取xml解析器
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

我們從代碼中看到钙皮,無(wú)論是方法一,還是方法二顽决,最終還是會(huì)調(diào)用方法二進(jìn)行加載短条,我們就從方法二的三個(gè)參數(shù),進(jìn)行分析一下才菠。

@LayoutRes int resource 資源文件的Id
@Nullable ViewGroup root 根view,就是待加載view的父布局
boolean attachToRoot 是否加載到父布局中

從方法一看到茸时,其實(shí)就是在調(diào)用方法二,只是方法一的第三個(gè)傳參利用root!=null進(jìn)行判斷而已赋访,實(shí)際上最終還是調(diào)用方法二可都。

我們先利用代碼進(jìn)行分析一下:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    mContainer = (RelativeLayout) findViewById(R.id.content_main);

    View view1 = LayoutInflater.from(this).inflate(R.layout.my_btn, null);
    View view2 = LayoutInflater.from(this).inflate(R.layout.my_btn, mContainer, false);
    View view3 = LayoutInflater.from(this).inflate(R.layout.my_btn, mContainer, true);
    View view4 = LayoutInflater.from(this).inflate(R.layout.my_btn, mContainer);

    Log.e("view1:", view1 + "");
    Log.e("view2:", view2 + "");
    Log.e("view3:", view3 + "");
    Log.e("view4:", view4 + "");
}

我們加載同一個(gè)布局文件my_btn.xml,獲取到view,然后分別輸出蚓耽,觀察有什么不一樣:

view1:: android.support.v7.widget.AppCompatButton{27f4a822 VFED..C. ......I. 0,0-0,0}
view2:: android.support.v7.widget.AppCompatButton{14fb5dd2 VFED..C. ......I. 0,0-0,0}
view3:: android.widget.RelativeLayout{2a6bba10 V.E..... ......I. 0,0-0,0 #7f0c006f app:id/content_main}
view4:: android.widget.RelativeLayout{2a6bba10 V.E..... ......I. 0,0-0,0 #7f0c006f app:id/content_main}

問題來(lái)了渠牲,為什么我加載同一個(gè)布局,得到的view一個(gè)是Button,一個(gè)是RelativeLayout,我們每一個(gè)分析一下:

View1:
LayoutInflater.from(this).inflate(R.layout.my_btn, null);
我們看到步悠,第二個(gè)參數(shù)root為空签杈,也就是說(shuō)實(shí)際上是調(diào)用方法二(root!=null):
LayoutInflater.from(this).inflate(R.layout.my_btn, null,false);
第三個(gè)參數(shù)attachToRoot 的意思是,是否把這個(gè)view添加到root里面鼎兽,如果為false則不返回root芹壕,而是這個(gè)的本身,如果為true的話接奈,就是返回添加view后的root.
因此踢涌,view1得到的是Button.

View2:
LayoutInflater.from(this).inflate(R.layout.my_btn, mContainer, false);
同上可得,第三個(gè)參數(shù)attachToRoot 為false.也就是不把這個(gè)view添加到root里面去
因此序宦,返回的是view2,就是Button.

View3:
LayoutInflater.from(this).inflate(R.layout.my_btn, mContainer, true);
第三個(gè)參數(shù)為true,也就是意味待加載的view會(huì)附在root上睁壁,并且返回root.
因此,我們view3返回的是這個(gè)RelativeLayout互捌,并且是添加button后的RelativeLayout.

View4:
LayoutInflater.from(this).inflate(R.layout.my_btn, mContainer);
根據(jù)方法一跟方法二的比較潘明,root!=null.view4跟view3的加載是一樣的,同理返回的是RelativeLayout.

根據(jù)以上的結(jié)論我們繼續(xù)往下面探究秕噪,我們通過(guò)LayoutInflater.from(this).inflate(R.layout.my_btn, null)獲取到了button钳降,再把這個(gè)Button添加到mContainer中。再觀察一下效果腌巾,注意遂填,這個(gè)按鈕的布局寬高是占全屏的铲觉。

<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:text="我是一個(gè)按鈕">

</Button>
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    mContainer = (RelativeLayout) findViewById(R.id.content_main);
    Button btn = (Button) LayoutInflater.from(this).inflate(R.layout.my_btn, null);
    mContainer.addView(btn);
}
無(wú)標(biāo)題.png

大家看到問題了嗎?為什么我在my_btn.xml中設(shè)置了button的布局寬高是全屏吓坚,怎么不起作用了撵幽?難道說(shuō)在my_btn.xml中Button的layout_width和layout_height起不了作用?我們從源碼中看一下:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    //獲取資源加載器
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
    }
    //通過(guò)資源加載器和資源Id,獲取xml解析器
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

通過(guò)資源管理獲取xml解析器礁击,繼續(xù)往下看:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        ......
        //保存?zhèn)鬟M(jìn)來(lái)的這個(gè)view
        View result = root;

        try {
            // Look for the root node.
            int type;
            //在這里找到root標(biāo)簽
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }

            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }
            //獲取這個(gè)root標(biāo)簽的名字
            final String name = parser.getName();
             ......

            //判斷是否merge標(biāo)簽
            if (TAG_MERGE.equals(name)) {
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }
                //這里直接加載頁(yè)面盐杂,忽略merge標(biāo)簽,直接傳root進(jìn)rInflate進(jìn)行加載子view
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                //通過(guò)標(biāo)簽來(lái)獲取view
                //先獲取加載資源文件中的根view
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                
                //布局參數(shù)          
                ViewGroup.LayoutParams params = null;
               
                //關(guān)鍵代碼A
                if (root != null) {
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        //temp設(shè)置布局參數(shù)
                        temp.setLayoutParams(params);
                    }
                }
                  ......
                //關(guān)鍵代碼B
                //在這里,先獲取到了temp,再把temp當(dāng)做root傳進(jìn)去rInflateChildren
                //進(jìn)行加載temp后面的子view
                rInflateChildren(parser, temp, attrs, true);
                  ......
               
                 //關(guān)鍵代碼C
                if (root != null && attachToRoot) {
                    //把view添加到root中并設(shè)置布局參數(shù)
                    root.addView(temp, params);
                }

                //關(guān)鍵代碼D
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

        } catch (XmlPullParserException e) {
            ......
        } catch (Exception e) {
            ......
        } finally {
            ......
        }

        return result;
    }
}

在這一塊代碼中哆窿,先聲明一個(gè)變量result链烈,這個(gè)result用來(lái)返回最終的結(jié)果,在我們的演示中挚躯,如果inflate(resource,root,isAttachRoot)中的root為空测垛,那么布局參數(shù)params為空,并且根據(jù)關(guān)鍵代碼D可得秧均,返回的result就是temp,也就是Button本身食侮。因此在以上例子中,如果說(shuō)root不為空的話目胡,Button中聲明的layout_width與layout_height起到了作用锯七。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);

    mContainer = (RelativeLayout) findViewById(R.id.content_main);

    View view1 = LayoutInflater.from(this).inflate(R.layout.my_btn, mContainer,false);
    mContainer.addView(view1);
}
無(wú)標(biāo)題.png

注:如果通過(guò)LayoutInflater.from(this).inflate(R.layout.my_btn, mContainer)或者LayoutInflater.from(this).inflate(R.layout.my_btn, mContainer,true)加載視圖誉己,不需要再額外的使用mContainer.addView(view),因?yàn)榉祷氐哪J(rèn)就是root本身眉尸,在關(guān)鍵代碼C中可看到:

if (root != null && attachToRoot) {
    root.addView(temp, params);
}
 

root會(huì)添加temp進(jìn)去,在代碼初始化的時(shí)候巨双,result默認(rèn)就是root,我們不需要addView,在inflate中會(huì)幫我們操作噪猾,如果我們還要addView的話,就會(huì)拋出異常:
The specified child already has a parent. You must call removeView() on the child's parent first.

小結(jié):

如果LayoutInflater的inflate中筑累,傳參root為空時(shí)袱蜡,加載視圖的根view布局寬高無(wú)效。反之根據(jù)關(guān)鍵代碼C與關(guān)鍵代碼A慢宗,分別對(duì)view進(jìn)行設(shè)置布局參數(shù)坪蚁。

咱們來(lái)看一下activity是如何加載視圖,我們從這一段代碼開始:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

}

我們往setContentView繼續(xù)探索,找到Activity的setContentView()

/**
 * Set the activity content from a layout resource.  The resource will be
 * inflated, adding all top-level views to the activity.
 *
 * @param layoutResID Resource ID to be inflated.
 *
 * @see #setContentView(android.view.View)
 * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
 */
public void setContentView(@LayoutRes int layoutResID) {
    //獲取窗口镜沽,設(shè)置contentView
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

getWindow()實(shí)際上是獲取window對(duì)象敏晤,但Window類是抽象類,具體的實(shí)現(xiàn)是PhoneWindow,我們往這個(gè)類看看:
PhoneWindow.java:

@Override
public void setContentView(int layoutResID) {
    //判斷mContentParent是否為空缅茉,如果為空嘴脾,創(chuàng)建
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        // 清空mContentParent 所有子view       
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
       ……
    } else {
        //通過(guò)layoutInflate加載視圖         
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    ……
}

activity加載視圖,最后還是通過(guò)LayoutInflater進(jìn)行加載視圖蔬墩,activity的界面結(jié)構(gòu)如下:

無(wú)標(biāo)題.png

我們的mContentView就是ContentView,因此通過(guò)LayoutInflater加載視圖進(jìn)入ContentView译打。而root就是mContentView耗拓,因此我們?cè)贏ctivity不需要自己addView().

總結(jié):
知其然不知其所以然,這對(duì)于LayoutInflater描述再合適不過(guò)了扶平,文章中本來(lái)還涉及到了關(guān)于如何使用LayoutInflater中遍歷view,代碼太長(zhǎng)就不一一展示帆离,而且在inflate中我們可以看到蔬蕊,通過(guò)使用merge標(biāo)簽结澄,可以減少view的層級(jí),直接把merge標(biāo)簽內(nèi)的子view直接添加到rootview中岸夯,因此布局優(yōu)化能提高視圖加載的性能麻献,提高效率。還有獲取LayoutInflater的方式猜扮,通過(guò)鍵值對(duì)進(jìn)行緩存LayoutInflater勉吻,這是在android中單例設(shè)計(jì)的一種體驗(yàn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旅赢,一起剝皮案震驚了整個(gè)濱河市齿桃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌煮盼,老刑警劉巖短纵,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異僵控,居然都是意外死亡香到,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門报破,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)悠就,“玉大人,你說(shuō)我怎么就攤上這事充易」Fⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵盹靴,是天一觀的道長(zhǎng)藐唠。 經(jīng)常有香客問我,道長(zhǎng)鹉究,這世上最難降的妖魔是什么宇立? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮自赔,結(jié)果婚禮上妈嘹,老公的妹妹穿的比我還像新娘。我一直安慰自己绍妨,他們只是感情好润脸,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布柬脸。 她就那樣靜靜地躺著,像睡著了一般毙驯。 火紅的嫁衣襯著肌膚如雪倒堕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天爆价,我揣著相機(jī)與錄音垦巴,去河邊找鬼。 笑死铭段,一個(gè)胖子當(dāng)著我的面吹牛骤宣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播序愚,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼憔披,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了爸吮?” 一聲冷哼從身側(cè)響起芬膝,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎形娇,沒想到半個(gè)月后锰霜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡埂软,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年锈遥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勘畔。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡所灸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出炫七,到底是詐尸還是另有隱情爬立,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布万哪,位于F島的核電站侠驯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏奕巍。R本人自食惡果不足惜吟策,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望的止。 院中可真熱鬧檩坚,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至赂乐,卻和暖如春薯鳍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背挨措。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工挖滤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人运嗜。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓壶辜,卻偏偏與公主長(zhǎng)得像悯舟,于是被迫代替她去往敵國(guó)和親担租。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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