Android | 帶你探究 LayoutInflater 布局解析原理

前言

  • 在 Android UI 開發(fā)中,經(jīng)常需要用到 LayoutInflater 類,它的基本作用是將 xml 布局文件解析成 View / View 樹。除了基本的布局解析功能,LayoutInflater 還可以用于實(shí)現(xiàn) 動(dòng)態(tài)換膚視圖轉(zhuǎn)換屬性轉(zhuǎn)換 等需求。
  • 在這篇文章里,我將帶你理解 LayoutInflater 的源碼。另外,文末的應(yīng)試建議也不要錯(cuò)過哦,如果能幫上忙,請(qǐng)務(wù)必點(diǎn)贊加關(guān)注,這真的對(duì)我非常重要。

相關(guān)文章


目錄


1. 獲取 LayoutInflater 對(duì)象

@SystemService(Context.LAYOUT_INFLATER_SERVICE)
public abstract class LayoutInflater {
    ...
}

首先,你要獲得 LayoutInflater 的實(shí)例,由于 LayoutInflater 是抽象類,不能直接創(chuàng)建對(duì)象,因此這里總結(jié)一下獲取 LayoutInflater 對(duì)象的方法。具體如下:

  • 1. View.inflate(...)
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
    LayoutInflater factory = LayoutInflater.from(context);
    return factory.inflate(resource, root);
}
  • 2. Activity#getLayoutInflater()
public LayoutInflater getLayoutInflater() {
    return getWindow().getLayoutInflater();
}
  • 3. PhoneWindow#getLayoutInflater()
private LayoutInflater mLayoutInflater;

public PhoneWindow(Context context) {
    super(context);
    mLayoutInflater = LayoutInflater.from(context);
}

public LayoutInflater getLayoutInflater() {
    return mLayoutInflater;
}
  • 4. LayoutInflater#from(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;
}

可以看到,前面 3 種方法最后走到LayoutInflater#from(Context),這其實(shí)也是平時(shí)用的最多的方式。現(xiàn)在,我們看getSystemService(...)內(nèi)的邏輯:

ContextImpl.java

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}

SystemServiceRegistry.java

private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS = new ArrayMap<String, ServiceFetcher<?>>();

static {
    ...
    1. 注冊(cè) Context.LAYOUT_INFLATER_SERVICE 與服務(wù)獲取器
    關(guān)注點(diǎn):CachedServiceFetcher
    關(guān)注點(diǎn):PhoneLayoutInflater
    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class, new CachedServiceFetcher<LayoutInflater>() {
        @Override
        public LayoutInflater createService(ContextImpl ctx) {
            注意:getOuterContext(),參數(shù)使用的是 ContextImpl 的代理對(duì)象,一般是 Activity
            return new PhoneLayoutInflater(ctx.getOuterContext());
        }});
    ...
}

2. 根據(jù) name 獲取服務(wù)對(duì)象
public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}

注冊(cè)服務(wù)與服務(wù)獲取器
private static <T> void registerService(String serviceName, Class<T> serviceClass, ServiceFetcher<T> serviceFetcher) {
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

3. 服務(wù)獲取器創(chuàng)建對(duì)象
static abstract interface ServiceFetcher<T> {
    T getService(ContextImpl ctx);
}

可以看到,ContextImpl 內(nèi)部通過 SystemServiceRegistry 來獲取服務(wù)對(duì)象,邏輯并不復(fù)雜:

1、靜態(tài)代碼塊注冊(cè)了 name - ServiceFetcher 的映射
2、根據(jù) name 獲得 ServiceFetcher
3、ServiceFetcher 創(chuàng)建對(duì)象

ServiceFetcher 的子類有三種類型,它們的getSystemService()都是線程安全的,主要差別體現(xiàn)在 單例范圍,具體如下:

ServiceFetcher子類 單例范圍 描述 舉例
CachedServiceFetcher ContextImpl域 / LayoutInflater、LocationManager等(最多)
StaticServiceFetcher 進(jìn)程域 / InputManager、JobScheduler等
StaticApplicationContextServiceFetcher 進(jìn)程域 使用 ApplicationContext 創(chuàng)建服務(wù) ConnectivityManager

對(duì)于 LayoutInflater 來說,服務(wù)獲取器是 CachedServiceFetcher 的子類,最終獲得的服務(wù)對(duì)象為 PhoneLayoutInflater

這里有一個(gè)重點(diǎn),這句代碼非常隱蔽,要留意:

 return new PhoneLayoutInflater(ctx.getOuterContext());

LayoutInflater.java

public Context getContext() {
    return mContext; 
}

protected LayoutInflater(Context context) {
    mContext = context;
    initPrecompiledViews();
}

可以看到,實(shí)例化 PhoneLayoutInflater 時(shí)使用了 getOuterContext(),也就是參數(shù)使用的是 ContextImpl 的代理對(duì)象,一般就是 Activity 了。也就是說,在 Activity / Fragment / View / Dialog 中,獲取LayoutInflater#getContext(),返回的就是 Activity。

小結(jié):

  • 1、獲取 LayoutInflater 對(duì)象只有通過LayoutInflater.from(context),內(nèi)部委派給Context#getSystemService(...),線程安全;
  • 2、使用同一個(gè) Context 對(duì)象,獲得的 LayoutInflater 是單例;
  • 3、LayoutInflater 的實(shí)現(xiàn)類是 PhoneLayoutInflater。

2. inflate(...) 主流程源碼分析

上一節(jié),我們分析了獲取 LayoutInflater 對(duì)象的過程,現(xiàn)在我們可以調(diào)用inflate()進(jìn)行布局解析了。LayoutInflater#inflate(...)有多個(gè)重載方法,最終都會(huì)調(diào)用到:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    1. 解析預(yù)編譯的布局
    View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    if (view != null) {
        return view;
    }
    2. 構(gòu)造 XmlPull 解析器 
    XmlResourceParser parser = res.getLayout(resource);
    try {
    3. 執(zhí)行解析
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}
  1. tryInflatePrecompiled(...)是解析預(yù)編譯的布局,我后文再說;
  2. 構(gòu)造 XmlPull 解析器 XmlResourceParser
  3. 執(zhí)行解析,是解析的主流程

提示: 在這里,我剔除了與 XmlPull 相關(guān)的代碼,只保留了我們關(guān)心的邏輯:

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    1. 結(jié)果變量
    View result = root;
    2. 最外層的標(biāo)簽
    final String name = parser.getName();
    3. <merge>
    if (TAG_MERGE.equals(name)) {
        3.1 異常
        if (root == null || !attachToRoot) {
            throw new InflateException("<merge /> can be used only with a valid "
                + "ViewGroup root and attachToRoot=true");
        }
        3.2 遞歸執(zhí)行解析
        rInflate(parser, root, inflaterContext, attrs, false);
    } else {
        4.1 創(chuàng)建最外層 View
        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
        
        ViewGroup.LayoutParams params = null;

        if (root != null) {
            4.2 創(chuàng)建匹配的 LayoutParams
            params = root.generateLayoutParams(attrs);
            if (!attachToRoot) {
                4.3 如果 attachToRoot 為 false,設(shè)置LayoutParams
                temp.setLayoutParams(params);
            }
        }

        5. 以 temp 為 root,遞歸執(zhí)行解析
        rInflateChildren(parser, temp, attrs, true);
        
        6. attachToRoot 為 true,addView()
        if (root != null && attachToRoot) {
            root.addView(temp, params);
        }

        7. root 為空 或者 attachToRoot 為 false,返回 temp
        if (root == null || !attachToRoot) {
            result = temp;
        }
    }
    return result;
}

-> 3.2
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) {
    while(parser 未結(jié)束) {
        if (TAG_INCLUDE.equals(name)) {
            1) <include>
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            2) <merge>
            throw new InflateException("<merge /> must be the root element");
        } else {
            3) 創(chuàng)建 View 
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            4) 遞歸
            rInflateChildren(parser, view, attrs, true);
            5) 添加到視圖樹
            viewGroup.addView(view, params);
        }
    }
}

-> 5. 遞歸執(zhí)行解析
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}

關(guān)于 <include> & <merge>,我后文再說。對(duì)于參數(shù) root & attachToRoot的不同情況,對(duì)應(yīng)得到的輸出不同,我總結(jié)為一張圖:


3. createViewFromTag():從 <tag> 到 View

第 2 節(jié) 主流程代碼中,用到了 createViewFromTag(),它負(fù)責(zé)由 <tag> 創(chuàng)建 View 對(duì)象:

已簡(jiǎn)化
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {

    1. 應(yīng)用 ContextThemeWrapper 以支持 android:theme
    if (!ignoreThemeAttr) {
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        if (themeResId != 0) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();
    }

    2. 先使用 Factory2 / Factory 實(shí)例化 View,相當(dāng)于攔截
    View view;
    if (mFactory2 != null) {
        view = mFactory2.onCreateView(parent, name, context, attrs);
    } else if (mFactory != null) {
        view = mFactory.onCreateView(name, context, attrs);
    } else {
        view = null;
    }

    3. 使用 mPrivateFactory 實(shí)例化 View,相當(dāng)于攔截
    if (view == null && mPrivateFactory != null) {
        view = mPrivateFactory.onCreateView(parent, name, context, attrs);
    }

    4. 調(diào)用自身邏輯
    if (view == null) {
        if (-1 == name.indexOf('.')) {
            4.1 <tag> 中沒有.
            view = onCreateView(parent, name, attrs);
        } else {
            4.2 <tag> 中有.
            view = createView(name, null, attrs);
        }
    }
    return view;     
}

-> 4.2 <tag> 中有.

構(gòu)造器方法簽名
static final Class<?>[] mConstructorSignature = new Class[] {
            Context.class, AttributeSet.class};

緩存 View 構(gòu)造器的 Map
private static final HashMap<String, Constructor<? extends View>> sConstructorMap =
            new HashMap<String, Constructor<? extends View>>();

public final View createView(String name, String prefix, AttributeSet attrs) {
    1) 緩存的構(gòu)造器
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    Class<? extends View> clazz = null;
    2) 新建構(gòu)造器
    if (constructor == null) {
        2.1) 拼接 prefix + name 得到類全限定名
        clazz = mContext.getClassLoader().loadClass(prefix != null ? (prefix + name) : name).asSubclass(View.class);
        2.2) 創(chuàng)建構(gòu)造器對(duì)象
        constructor = clazz.getConstructor(mConstructorSignature);
        constructor.setAccessible(true);
        2.3) 緩存到 Map
        sConstructorMap.put(name, constructor);
    }
    
    3) 實(shí)例化 View 對(duì)象
    final View view = constructor.newInstance(args);

    4) ViewStub 特殊處理
    if (view instanceof ViewStub) {
        // Use the same context when inflating ViewStub later.
        final ViewStub viewStub = (ViewStub) view;
        viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
    }
    return view;
}

----------------------------------------------------

-> 4.1 <tag> 中沒有.

PhoneLayoutInflater.java

private static final String[] sClassPrefixList = {
    "android.widget.",
    "android.webkit.",
    "android.app."
};

已簡(jiǎn)化
protected View onCreateView(String name, AttributeSet attrs) {
    for (String prefix : sClassPrefixList) {
        View view = createView(name, prefix, attrs);
            if (view != null) {
                return view;
            }
    }
    return super.onCreateView(name, attrs);
}
    1. 應(yīng)用 ContextThemeWrapper 以支持android:theme,這是處理針對(duì)特定 View 設(shè)置主題;
    1. 使用 Factory2 / Factory 實(shí)例化 View,相當(dāng)于攔截,我后文再說;
    1. 使用 mPrivateFactory 實(shí)例化View,相當(dāng)于攔截,我后文再說;
    1. 調(diào)用 LayoutInflater 自身邏輯,分為:
    • 4.1 <tag> 中沒有.,這是處理<linearlayout>、<TextView>等標(biāo)簽,依次嘗試拼接 3 個(gè)路徑前綴,進(jìn)入 3.2 實(shí)例化 View
    • 4.2 <tag> 中有.,真正實(shí)例化 View 的地方,主要分為 4 步:
    1) 緩存的構(gòu)造器
    2) 新建構(gòu)造器
    3) 實(shí)例化 View 對(duì)象
    4) ViewStub 特殊處理
    

小結(jié):

  • 使用 Factory2 接口可以攔截實(shí)例化 View 對(duì)象的步驟;
  • 實(shí)例化 View 的優(yōu)先順序?yàn)椋篎actory2 / Factory -> mPrivateFactory -> PhoneLayoutInflater;
  • 使用反射實(shí)例化 View 對(duì)象,同時(shí)構(gòu)造器對(duì)象做了緩存;

4. Factory2 接口

現(xiàn)在我們來討論Factory2接口,上一節(jié)提到,Factory2可以攔截實(shí)例化 View 的步驟,在 LayoutInflater 中有兩個(gè)方法可以設(shè)置:
LayoutInflater.java

方法1:
public void setFactory2(Factory2 factory) {
    if (mFactorySet) {
        關(guān)注點(diǎn):禁止重復(fù)設(shè)置
        throw new IllegalStateException("A factory has already been set on this LayoutInflater");
    }
    if (factory == null) {
        throw new NullPointerException("Given factory can not be null");
    }
    mFactorySet = true;
    if (mFactory == null) {
        mFactory = mFactory2 = factory;
    } else {
        mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
    }
}

方法2 @hide
public void setPrivateFactory(Factory2 factory) {
    if (mPrivateFactory == null) {
        mPrivateFactory = factory;
    } else {
        mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
    }
}

現(xiàn)在,我們來看源碼中哪里調(diào)用這兩個(gè)方法:

4.1 setFactory2()

在 AppCompatActivity & AppCompatDialog 中,相關(guān)源碼簡(jiǎn)化如下:

AppCompatDialog.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    設(shè)置 Factory2
    getDelegate().installViewFactory();
    super.onCreate(savedInstanceState);
    getDelegate().onCreate(savedInstanceState);
}

AppCompatActivity.java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    final AppCompatDelegate delegate = getDelegate();
    設(shè)置 Factory2
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    夜間主題相關(guān)
    if (delegate.applyDayNight() && mThemeId != 0) {
        if (Build.VERSION.SDK_INT >= 23) {
            onApplyThemeResource(getTheme(), mThemeId, false);
        } else {
            setTheme(mThemeId);
        }
    }
    super.onCreate(savedInstanceState);
}

AppCompatDelegateImpl.java

public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    if (layoutInflater.getFactory() == null) {
        關(guān)注點(diǎn):設(shè)置 Factory2 = this(AppCompatDelegateImpl)
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    } else {
        if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
            Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
        }
    }
}

LayoutInflaterCompat.java

public static void setFactory2(@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
    inflater.setFactory2(factory);

    if (Build.VERSION.SDK_INT < 21) {
        final LayoutInflater.Factory f = inflater.getFactory();
        if (f instanceof LayoutInflater.Factory2) {
            forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
        } else {
            forceSetFactory2(inflater, factory);
        }
    }
}

可以看到,在 AppCompatDialog & AppCompatActivity 初始化時(shí),都通過setFactory2()設(shè)置了攔截器,設(shè)置的對(duì)象是 AppCompatDelegateImpl:

AppCompatDelegateImpl.java

已簡(jiǎn)化
class AppCompatDelegateImpl extends AppCompatDelegate
        implements MenuBuilder.Callback, LayoutInflater.Factory2 {

    @Override
    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        return createView(parent, name, context, attrs);
    }

    @Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        if (mAppCompatViewInflater == null) {
            mAppCompatViewInflater = new AppCompatViewInflater();
        }
    }
    委托給 AppCompatViewInflater 處理
    return mAppCompatViewInflater.createView(...)
}

AppCompatViewInflater 與 LayoutInflater 的核心流程差不多,主要差別是前者會(huì)<TextView>等標(biāo)簽解析為AppCompatTextView對(duì)象:

AppCompatViewInflater.java

final View createView(...) {
    ...

    switch (name) {
        case "TextView":
            view = createTextView(context, attrs);
            break;
        ...
        default:
            view = createView(context, name, attrs);
    }
    return view;
}

@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
    return new AppCompatTextView(context, attrs);
}

4.2 setPrivateFactory()

setPrivateFactory()是 hide 方法,在 Activity 中調(diào)用,相關(guān)源碼簡(jiǎn)化如下:

Activity.java

final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

final void attach(Context context, ActivityThread aThread,...) {
    attachBaseContext(context);
    mFragments.attachHost(null /*parent*/);

    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    關(guān)注點(diǎn):設(shè)置 Factory2
    mWindow.getLayoutInflater().setPrivateFactory(this);
    ...
}

可以看到,這里設(shè)置的 Factory2 其實(shí)就是 Activity 本身(this),這說明 Activity 也實(shí)現(xiàn)了 Factory2 :

public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2,...{

    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        if (!"fragment".equals(name)) {
            return onCreateView(name, context, attrs);
        }

        return mFragments.onCreateView(parent, name, context, attrs);
    }
}

原來<fragment>標(biāo)簽的處理是在這里設(shè)置的 Factory2 處理的,關(guān)于FragmentController#onCreateView(...)內(nèi)部如何生成 Fragment 以及返回 View 的邏輯,我們?cè)谶@篇文章里討論,請(qǐng)關(guān)注:《Android | 考點(diǎn)爆滿,帶你全方位圖解 Fragment 源碼》

小結(jié):

  • 使用 setFactory2() 和 setPrivateFactory() 可以設(shè)置 Factory2 接口(攔截器),其中同一個(gè) LayoutInflater 的setFactory2()不能重復(fù)設(shè)置,setPrivateFactory() 是 hide 方法;
  • AppCompatDialog & AppCompatActivity 初始化時(shí),調(diào)用了setFactory2(),會(huì)將一些<tag>轉(zhuǎn)換為AppCompat版本;
  • Activity 初始化時(shí),調(diào)用了setPrivateFactory(),用來處理<fragment>標(biāo)簽。

5. <include> & <merge> & <ViewStub>

這一節(jié),我們專門來討論<include> & <merge> & <ViewStub>的用法與注意事項(xiàng):

5.1 <include> 布局重用

5.2 <merge> 降低布局層次

5.3 <viewstub> 布局懶加載

Editting...


6. 總結(jié)

  • 應(yīng)試建議
  1. 理解 獲取 LayoutInflater 對(duì)象的方式,知曉 3 種 getSystemService() 單例域的區(qū)別,其中 Context 域是最多的,LayoutInflater 是屬于 Context 域。
  2. 重點(diǎn)理解 LayoutInflater 布局解析的 核心流程
  3. Factory2 是一個(gè)很實(shí)用的接口,需要掌握通過 setFactory2() 攔截布局解析 的技巧。

推薦閱讀

感謝喜歡!你的點(diǎn)贊是對(duì)我最大的鼓勵(lì)!歡迎關(guān)注彭旭銳的GitHub!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評(píng)論 6 535
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,744評(píng)論 3 421
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評(píng)論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評(píng)論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,935評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,325評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評(píng)論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,534評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,084評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,892評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,067評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,322評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評(píng)論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評(píng)論 1 289
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,800評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,084評(píng)論 2 375