每日一問(wèn):View.getContext() 一定會(huì)返回 Activity 對(duì)象么?

堅(jiān)持原創(chuàng)日更,短平快的 Android 進(jìn)階系列,敬請(qǐng)直接在微信公眾號(hào)搜索:nanchen,直接關(guān)注并設(shè)為星標(biāo),精彩不容錯(cuò)過(guò)。

一般我們被問(wèn)到這樣的問(wèn)題,通常來(lái)說(shuō),答案都是否定的,但一定得知道其中的原因,不然回答肯定與否又有什么意義呢。

首先,顯而易見這個(gè)問(wèn)題有不少陷阱,比如這個(gè) View 是自己構(gòu)造出來(lái)的,那肯定它的 getContext() 返回的是構(gòu)造它的時(shí)候傳入的 Context 類型。

它也可能返回的是 TintContextWrapper

那,如果是 XML 里面的 View 呢,會(huì)怎樣?可能不少人也知道了另外一個(gè)結(jié)論:直接繼承 Activity 的 Activity 構(gòu)造出來(lái)的 View.getContext() 返回的是當(dāng)前 Activity。但是:當(dāng) View 的 Activity 是繼承自 AppCompatActivity,并且在 5.0 以下版本的手機(jī)上,View.getContext() 得到的并非是 Activity,而是 TintContextWrapper。

不太熟悉 Context 的繼承關(guān)系的小伙伴可能也會(huì)很奇怪,正常來(lái)說(shuō),自己所知悉的 Context 繼承關(guān)系圖是這樣的。

Activity.setContentView()

我們可以先看看 Activity.setContentView() 方法:

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

不過(guò)是直接調(diào)用 Window 的實(shí)現(xiàn)類 PhoneWindowsetContentView() 方法??纯?PhoneWindowsetContentView() 是怎樣的。

@Override
public void setContentView(int layoutResID) {
    // 省略部分代碼...
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    // 省略部分代碼...
}

假如沒有 FEATURE_CONTENT_TRANSITIONS 標(biāo)記的話,就直接通過(guò) mLayoutInflater.inflate() 加載出來(lái)。這個(gè)如果有 mLayoutInflater 的是在PhoneWindow 的構(gòu)造方法中被初始化的。而 PhoneWindow 的初始化是在 Activityattach() 方法中:

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback) {
    attachBaseContext(context);
    mFragments.attachHost(null /*parent*/);
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);

    // 此處省略部分代碼...
}

所以 PhoneWindowContext 實(shí)際上就是 Activity 本身。

在回到我們前面分析的 PhoneWindowsetContentView() 方法,如果有 FEATURE_CONTENT_TRANSITIONS 標(biāo)記,直接調(diào)用了一個(gè) transitionTo() 方法:

private void transitionTo(Scene scene) {
    if (mContentScene == null) {
        scene.enter();
    } else {
        mTransitionManager.transitionTo(scene);
    }
    mContentScene = scene;
}

在看看 scene.enter() 方法。

public void enter() {
    // Apply layout change, if any
    if (mLayoutId > 0 || mLayout != null) {
        // empty out parent container before adding to it
        getSceneRoot().removeAllViews();
        if (mLayoutId > 0) {
            LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
        } else {
            mSceneRoot.addView(mLayout);
        }
    }
    // 省略部分代碼...
}

基本邏輯沒必要詳解了吧?還是通過(guò)這個(gè) mContextLayoutInflaterinflate 的布局。這個(gè) mContext 初始化的地方是:

public static Scene getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) {
    // 省略部分代碼...
    if (scene != null) {
        return scene;
    } else {
        scene = new Scene(sceneRoot, layoutId, context);  // 初始化關(guān)鍵代碼
        scenes.put(layoutId, scene);
        return scene;
    }
}

Context 來(lái)源于外面?zhèn)魅氲?getContext(),這個(gè) getContext() 返回的就是初始化的 Context 也就是 Activity 本身。

AppCompatActivity.setContentView()

我們不得不看看 AppCompatActivitysetContentView() 是怎么實(shí)現(xiàn)的。

public void setContentView(@LayoutRes int layoutResID) {
    this.getDelegate().setContentView(layoutResID);
}

@NonNull
public AppCompatDelegate getDelegate() {
    if (this.mDelegate == null) {
        this.mDelegate = AppCompatDelegate.create(this, this);
    }
    return this.mDelegate;
}

這個(gè) mDelegate 實(shí)際上是一個(gè)代理類,由 AppCompatDelegate 根據(jù)不同的 SDK 版本生成不同的實(shí)際執(zhí)行類,就是代理類的兼容模式:

/**
 * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code activity}.
 *
 * @param callback An optional callback for AppCompat specific events
 */
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
    return create(activity, activity.getWindow(), callback);
}

private static AppCompatDelegate create(Context context, Window window,
        AppCompatCallback callback) {
    final int sdk = Build.VERSION.SDK_INT;
    if (BuildCompat.isAtLeastN()) {
        return new AppCompatDelegateImplN(context, window, callback);
    } else if (sdk >= 23) {
        return new AppCompatDelegateImplV23(context, window, callback);
    } else if (sdk >= 14) {
        return new AppCompatDelegateImplV14(context, window, callback);
    } else if (sdk >= 11) {
        return new AppCompatDelegateImplV11(context, window, callback);
    } else {
        return new AppCompatDelegateImplV9(context, window, callback);
    }
}

關(guān)于實(shí)現(xiàn)類 AppCompatDelegateImplsetContentView() 方法這里就不做過(guò)多分析了,感興趣的可以直接移步掘金上的 View.getContext() 里的小秘密 進(jìn)行查閱。

不過(guò)這里還是要結(jié)合小緣的回答,簡(jiǎn)單總結(jié)一下:之所以能得到上面的結(jié)論是因?yàn)槲覀冊(cè)?AppCompatActivity 里面的 layout.xml 文件里面使用原生控件,比如 TextViewImageView 等等,當(dāng)在 LayoutInflater 中把 XML 解析成 View 的時(shí)候,最終會(huì)經(jīng)過(guò) AppCompatViewInflatercreateView() 方法,這個(gè)方法會(huì)把這些原生的控件都變成 AppCompatXXX 一類。包含了哪些 View 呢?

  • RatingBar
  • CheckedTextView
  • MultiAutoCompleteTextView
  • TextView
  • ImageButton
  • SeekBar
  • Spinner
  • RadioButton
  • ImageView
  • AutoCompleteTextView
  • CheckBox
  • EditText
  • Button

那么重點(diǎn)肯定就是在 AppCompat 這些開頭的控件了,隨便打開一個(gè)源碼吧,比如 AppCompatTextView。

public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
    this.mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
    this.mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
    this.mTextHelper = new AppCompatTextHelper(this);
    this.mTextHelper.loadFromAttributes(attrs, defStyleAttr);
    this.mTextHelper.applyCompoundDrawablesTints();
}

可以看到,關(guān)鍵是 super(TintContextWrapper.wrap(context), attrs, defStyleAttr); 這行代碼。我們點(diǎn)進(jìn)去看看這個(gè) wrap() 做了什么。

public static Context wrap(@NonNull Context context) {
    if (shouldWrap(context)) {
        // 省略關(guān)鍵代碼...
        TintContextWrapper wrapper = new TintContextWrapper(context);
        sCache.add(new WeakReference(wrapper));
        return wrapper;
    } else {
        return context;
    }
}   

可以看到當(dāng),shouldWrap() 這個(gè)方法返回為 true 的時(shí)候,就會(huì)采用了 TintContextWrapper 這個(gè)對(duì)象來(lái)包裹了我們的 Context。來(lái)看看什么情況才能滿足這個(gè)條件。

private static boolean shouldWrap(@NonNull Context context) {
    if (!(context instanceof TintContextWrapper) && !(context.getResources() instanceof TintResources) && !(context.getResources() instanceof VectorEnabledTintResources)) {
        return VERSION.SDK_INT < 21 || VectorEnabledTintResources.shouldBeUsed();
    } else {
        return false;
    }
}

很明顯了吧?如果是 5.0 以前,并且沒有包裝的話,就會(huì)直接返回 true;所以也就得出了上面的結(jié)論:當(dāng)運(yùn)行在 5.0 系統(tǒng)版本以下的手機(jī),并且 Activity 是繼承自 AppCompatActivity 的,那么ViewgetConext() 方法,返回的就不是 Activity 而是 TintContextWrapper。

還有其它情況么?

上面講述了兩種非 Activity 的情況:

  1. 直接構(gòu)造 View 的時(shí)候傳入的不是 Activity;
  2. 使用 AppCompatActivity 并且運(yùn)行在 5.0 以下的手機(jī)上,XML 里面的 ViewgetContext() 方法返回的是 TintContextWrapper。

那不禁讓人想想,還有其他情況么?有。

我們直接從我前兩天線上灰測(cè)包出現(xiàn)的一個(gè) bug 說(shuō)起。先說(shuō)說(shuō) bug 背景,灰測(cè)包是 9.5.0,而線上包是 9.4.0,在灰測(cè)包上發(fā)生崩潰的代碼是三個(gè)月前編寫的代碼,也就是說(shuō)這可能是 8.43.0 或者 9.0.0 加入的代碼,在線上穩(wěn)定運(yùn)行了 4 個(gè)版本以上沒有做過(guò)任何修改。但在 9.5.0 灰測(cè)的時(shí)候,這里卻出現(xiàn)了必現(xiàn)崩潰。

Fatal Exception: java.lang.ClassCastException: android.view.ContextThemeWrapper cannot be cast to android.app.Activity
       at com.codoon.common.dialog.CommonDialog.openProgressDialog + 145(CommonDialog.java:145)
       at com.codoon.common.dialog.CommonDialog.openProgressDialog + 122(CommonDialog.java:122)
       at com.codoon.common.dialog.CommonDialog.openProgressDialog + 116(CommonDialog.java:116)
       at com.codoon.find.product.item.detail.i$a.onClick + 57(ProductReceiveCouponItem.kt:57)
       at android.view.View.performClick + 6266(View.java:6266)
       at android.view.View$PerformClick.run + 24730(View.java:24730)
       at android.os.Handler.handleCallback + 789(Handler.java:789)
       at android.os.Handler.dispatchMessage + 98(Handler.java:98)
       at android.os.Looper.loop + 171(Looper.java:171)
       at android.app.ActivityThread.main + 6699(ActivityThread.java:6699)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.Zygote$MethodAndArgsCaller.run + 246(Zygote.java:246)
       at com.android.internal.os.ZygoteInit.main + 783(ZygoteInit.java:783)

單看崩潰日志應(yīng)該非常好改吧,出現(xiàn)了一個(gè)強(qiáng)轉(zhuǎn)錯(cuò)誤,原來(lái)是在我編寫的 ProductReceiveCouponItem 類的 57 行調(diào)用項(xiàng)目中的通用對(duì)話框 CommonDialog 直接崩潰了。翻看 CommonDialog 的相關(guān)代碼發(fā)現(xiàn),原來(lái)是之前的同學(xué)在使用傳入的 Context 的時(shí)候沒有做類型驗(yàn)證,直接強(qiáng)轉(zhuǎn)為了 Activity。

// 得到等待對(duì)話框
public void openProgressDialog(String message, OnDismissListener listener, OnCancelListener mOnCancelistener) {
    if (waitingDialog != null) {
        waitingDialog.dismiss();
        waitingDialog = null;
    }
    if (mContext == null) {
        return;
    }
    if (((Activity) mContext).isFinishing()) {
        return;
    }
    waitingDialog = createLoadingDialog(mContext, message);
    waitingDialog.setCanceledOnTouchOutside(false);
    waitingDialog.setOnCancelListener(mOnCancelistener);
    waitingDialog.setCancelable(mCancel);
    waitingDialog.setOnDismissListener(listener);
    waitingDialog.show();
}

而我的代碼通過(guò) View.getContext() 傳入的 Context 類型是 ContextThemeWrapper。

// 領(lǐng)取優(yōu)惠券
val dialog = CommonDialog(binding.root.context)
dialog.openProgressDialog("領(lǐng)取中...")    // 第 57 行出問(wèn)題的代碼
ProductService.INSTANCE.receiveGoodsCoupon(data.class_id)
        .compose(RetrofitUtil.schedulersAndGetData())
        .subscribeNet(true) { 
            // 邏輯處理相關(guān)代碼
        }

看到了日志改起來(lái)就非常簡(jiǎn)單了,第一種方案是直接在 CommonDialog 強(qiáng)轉(zhuǎn)前做一下類型判斷。第二種方案是直接在我這里的代碼中通過(guò)判斷 binding.root.context 的類型,然后取出里面的 Activity。

雖然 bug 非常好解決,但作為一名 Android 程序員,絕對(duì)不可以滿足于僅僅解決 bug 上,任何事情都事出有因,這里為什么數(shù)月沒有更改的代碼,在 9.4.0 上沒有問(wèn)題,在 9.5.0 上就成了必現(xiàn)崩潰呢?

切換代碼分支到 9.4.0,debug 發(fā)現(xiàn),這里的 binding.root.context 返回的確實(shí)就是 Activity,而在 9.5.0 上 binding.root.context 確實(shí)就返回的是 ContextThemeWrapper,檢查后確定代碼沒有任何改動(dòng)。

分析出現(xiàn) ContextThemeWrapper 的原因

看到 ContextThemeWrapper,不由得想起了這個(gè)類使用的地方之一:Dialog,熟悉 Dialog 的童鞋一定都知道,我們?cè)跇?gòu)造 Dialog 的時(shí)候,會(huì)把 Context 直接變成 ContextThemeWrapper。

public Dialog(@NonNull Context context) {
    this(context, 0, true);
}

public Dialog(@NonNull Context context, @StyleRes int themeResId) {
    this(context, themeResId, true);
}

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
    if (createContextThemeWrapper) {
        if (themeResId == ResourceId.ID_NULL) {
            final TypedValue outValue = new TypedValue();
            context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
            themeResId = outValue.resourceId;
        }
        mContext = new ContextThemeWrapper(context, themeResId);
    } else {
        mContext = context;
    }

    // 省略部分代碼...
}

oh,在第三個(gè)構(gòu)造方法中,通過(guò)構(gòu)造的時(shí)候傳入的 createContextThemeWrapper 總是 true,所以它一定可以進(jìn)到這個(gè) if 語(yǔ)句里面去,把 mContext 強(qiáng)行指向了 Context 的包裝類 ContextThemeWrapper。所以這里會(huì)不會(huì)是由于這個(gè)原因呢?

我們?cè)倏纯次覀兊拇a,我這個(gè) ProductReceiveCouponItem 實(shí)際上是一個(gè) RecyclerView 的 Item,而這個(gè)相應(yīng)的 RecyclerView 是顯示在 DialogFragment 上的。熟悉 DialogFragment 的小伙伴可能知道,DialogFragment 實(shí)際上也是一個(gè) Fragment。而 DialogFragment 里面,其實(shí)是有一個(gè) Dialog 的變量 mDialog 的,這個(gè) Dialog 會(huì)在 onStart() 后通過(guò) show() 展示出來(lái)。

在我們使用 DialogFragment 的時(shí)候,一定都會(huì)重寫 onCreatView() 對(duì)吧,有一個(gè) LayoutInflater 參數(shù),返回值是一個(gè) View,我們不禁想知道這個(gè) LayoutInflater 是從哪兒來(lái)的? onGetLayoutInflater(),我們看看。

@Override
public LayoutInflater onGetLayoutInflater(Bundle savedInstanceState) {
    if (!mShowsDialog) {
        return super.onGetLayoutInflater(savedInstanceState);
    }
    mDialog = onCreateDialog(savedInstanceState);
    if (mDialog != null) {
        setupDialog(mDialog, mStyle);
        return (LayoutInflater) mDialog.getContext().getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
    }
    return (LayoutInflater) mHost.getContext().getSystemService(
            Context.LAYOUT_INFLATER_SERVICE);
}

我們是以一個(gè) Dialog 的形式展示,所以不會(huì)進(jìn)入其中的 if 條件。所以我們直接通過(guò)了 onCreateDialog() 構(gòu)造了一個(gè) Dialog。如果這個(gè) Dialog 不為空的話,那么我們的 LayoutInflater 就會(huì)直接通過(guò) DialogContext 構(gòu)造出來(lái)。我們來(lái)看看 onCreateDialog() 方法。

public Dialog onCreateDialog(Bundle savedInstanceState) {
    return new Dialog(getActivity(), getTheme());
}

很簡(jiǎn)單,直接 new 了一個(gè) Dialog,Dialog 這樣的構(gòu)造方法上面也說(shuō)了,直接會(huì)把 mContext 指向一個(gè) Context 的包裝類 ContextThemeWrapper

至此我們能做大概猜想了,DialogFragment 負(fù)責(zé) inflate 出布局的 LayoutInflater 是由 ContextThemeWrapper 構(gòu)造出來(lái)的,所以我們暫且在這里說(shuō)一個(gè)結(jié)論:DialogFragment onCreatView() 里面這個(gè) layout 文件里面的 View.getContext() 返回應(yīng)該是 `ContextThemeWrapper。

但是?。。∥覀兂鰡?wèn)題的是 Item,Item 是通過(guò) RecyclerViewAdapterViewHolder 顯示出來(lái)的,而非 DialogFragent 里面 DialogsetContentView() 的 XML 解析方法??雌饋?lái),分析了那么多,并沒有找到問(wèn)題的癥結(jié)所在。所以得看看我們的 Adapter 是怎么寫的,直接打開我們的 MultiTypeAdapteronCreateViewHolder() 方法。

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    if (typeMap.get(viewType, TYPE_DEFAULT) == TYPE_ONE) {
        return holders.get(viewType).createHolder(parent);
    }
    ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), viewType, parent, false);
    return new ItemViewHolder(binding);
}

oh,在這里我們的 LayoutInflater.from() 接受的參數(shù)是 parent.getContext()。parent 是什么?就是我們的 RecyclerView,這個(gè) RecyclerView 是從哪兒來(lái)的?通過(guò) DialogFragmentLayoutInflaterinflate 出來(lái)的。所以 parent.getContext() 返回是什么?在這里,一定是 ContextThemeWrapper。

也就是說(shuō),我們的 ViewHolderrootView 也就是通過(guò) ContextThemeWrapper 構(gòu)造的 LayoutInflaterinflate 出來(lái)的了。所以我們的 ProductReceiveCouponItem 這個(gè) Item 里面的 binding.root.context 返回值,自然也就是 ContextThemeWrapper 而不是 Activity 了。自然而然,在 CommonDialog 里面直接強(qiáng)轉(zhuǎn)為 Activity 一定會(huì)出錯(cuò)。

那為什么在 9.4.0 上沒有出現(xiàn)這個(gè)問(wèn)題呢?我們看看 9.4.0 上 MultiTypeAdapteronCreateViewHolder() 方法:

@Override
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    ViewDataBinding binding = DataBindingUtil.inflate(mInflater, viewType, parent, false);
    return new ItemViewHolder(binding);
}

咦,看起來(lái)似乎不一樣,這里直接傳入的是 mInflater,我們看看這個(gè) mInflater 是在哪兒被初始化的。

public MultiTypeAdapter(Context context) {
    mInflater = LayoutInflater.from(context);
}

oh,在 9.4.0 的分支上,我們的 ViewHolderLayoutInflaterContext,是從外面?zhèn)鬟M(jìn)來(lái)的。再看看我們 DialogFragment 中對(duì) RecyclerView 的處理。

val rvAdapter = MultiTypeAdapter(context)
binding.recyclerView.run {
    layoutManager = LinearLayoutManager(context)
    val itemDecoration = DividerItemDecoration(context, DividerItemDecoration.VERTICAL_LIST)
    itemDecoration.setDividerDrawable(R.drawable.list_divider_10_white.toDrawable())
    addItemDecoration(itemDecoration)
    adapter = rvAdapter
}

是吧,在 9.4.0 的時(shí)候,MultiTypeAdapterViewHolder 會(huì)使用外界傳入的 Context,這個(gè) ContextActivity,所以我們的Item 的 binding.root.context 返回為 Activity。而在 9.5.0 的時(shí)候,同事重構(gòu)了 MultiTypeAdapter,而讓其 ViewHolderLayoutInflater 直接取的 parent.getContext(),這里的情況即 ContextThemeWrapper,所以出現(xiàn)了幾個(gè)月沒動(dòng)的代碼,在新版本上灰測(cè)卻崩潰了。

總結(jié)

寫了這么多,還是做一些總結(jié)。首先對(duì)題目做個(gè)答案: View.getContext() 的返回不一定是 Activity。

實(shí)際上,View.getContext()inflate 這個(gè) ViewLayoutInflater 息息相關(guān),比如 ActivitysetContentView() 里面的 LayoutInflater 就是它本身,所以該 layoutRes 里面的 View.getContext() 返回的就是 Activity。但在使用 AppCompatActivity 的時(shí)候,值得關(guān)注的是, layoutRes 里面的原生 View 會(huì)被自動(dòng)轉(zhuǎn)換為 AppCompatXXX,而這個(gè)轉(zhuǎn)換在 5.0 以下的手機(jī)系統(tǒng)中,會(huì)把 Context 轉(zhuǎn)換為其包裝類 TintThemeWrapper,所以在這樣的情況下的 View.getContext() 返回是 TintThemeWrapper。

最后,從一個(gè)奇怪的 bug 中,給大家分享了一個(gè)簡(jiǎn)單的原因探索分析,也進(jìn)一步驗(yàn)證了上面的結(jié)論。任何 bug 的出現(xiàn),總是有它的原因,作為 Android 開發(fā),我們不僅要處理掉 bug,更要關(guān)注到它的更深層次的原因,這樣才能在代碼層面就發(fā)現(xiàn)其它的潛在問(wèn)題,以免帶來(lái)更多不必要的麻煩。本文就一個(gè)簡(jiǎn)單的示例進(jìn)行了此次試探的講解,但個(gè)人技術(shù)能力有限,唯恐出現(xiàn)紕漏,還望有心人士指出。

文章部分來(lái)源于:View.getContext() 里的小秘密

最后編輯于
?著作權(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閱讀 229,565評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,115評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,577評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,234評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,621評(píng)論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,822評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,380評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,128評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,319評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,548評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評(píng)論 1 291
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,048評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,285評(píng)論 2 376