为什么view的setOnClickListener引用Activity不会内存泄漏

学就完事

本文:https://blog.csdn.net/CrazyApes/article/details/123250512

@[toc]

前言

问:一个Activity实现了onClickListener,这时1个Button使用了setOnClickListener(this)
为什么不会出现内存泄露?

答:因为Activity销毁的时候会释放View引用对象,所以不会内存泄露啊 。

问:能不能在细点?

答:?_? ,厄,windowdetach所有的view,然后view逐个释放引用。

问:再细点。

答:。。。

什么是内存泄露

首先,什么是内存泄露?
内存泄漏是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成内存空间的浪费称为内存泄漏。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。
Java内存泄漏的根本原因是什么呢?
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄漏,尽管短生命周期对象已经不再需要,但是因为长生命周期持有它的引用而导致不能被回收,这就是Java中内存泄漏的发生场景。

Android 中的内存泄露也是这个意思,比如当退出Activity的时候,如果由于Activity被其它生命周期更长的对象所持有引用,而无法释放其所持有的内存,就会导致内存泄露。

答题

    1. Activity在销毁时肯定是会释放对应的资源的,比如将Activity出栈,移除Window上所有的View,释放View中的各种事件,最后Window啥的资源全置空。呼叫GC来一波回收。
    1. 关于ViewActivity循环引用的问题,GC会通过可达性分析来解决循环引用的问题。
      GC会从GC roots对象节点向下遍历,将所有不可达的对象进行标记,标记后确认是否有必要执行其finalize()方法,如果没有,或者已经被执行,则回收。如果有,则进入一个专门的队列,等待执行。
      GC会对队列中的对象进行第二次小规模标记,如果对象没有在finalize()方法中重新与引用链关联,就会被回收。

兴趣

由于对于Activity在何时去释放View的了解不够细致,所以,后面篇幅写了大概从源码去追溯View释放的一个过程。有兴趣的可以一起看一下,如果我描述的不太对,欢迎大家评论区指正。
我会非常感激您的指导。

OnClickListener在View中的引用位置

首先,咱们得先知道最终setOnClickListener(this)this引用在View中如何被引用

View#setOnClickListener

可以看到引用被赋值于getListenerInfo().mOnClickListener

    // android31  android.view.View#setOnClickListener
    public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

我们再去看看getListenerInfo()是谁

View#getListenerInfo

ListenerInfoView用来存储一系列listener的成员信息类

那我们基本就可以确定最终将我们传入的OnClickListener引用在mListenerInfo中。

    // android31  android.view.View#getListenerInfo
    ListenerInfo mListenerInfo;
    ListenerInfo getListenerInfo() {
        if (mListenerInfo != null) {
            return mListenerInfo;
        }
        mListenerInfo = new ListenerInfo();
        return mListenerInfo;
    }

    static class ListenerInfo {
            ...
            public OnClickListener mOnClickListener;
            ...
    }

探索该引用何时被释放

View中查找mListenerInfo的使用,以及何时被置空释放。

在文件中检索mListenerInfo = null 或者 getListenerInfo = null 或者其它类似操作。

显然,我没有找到类似的方法和操作,哈哈哈,好尴尬。

既然它没有,那我们就接着找找View是什么时候被释放引用的,如果View的引用被释放,那他引用的这个对象也属于无效对象了,仍可以被释放。

Activity#onDestroy

既然是销毁的过程,咱就是Activity.onDestroy方法开始。

  • 发现通过mManagedDialogs关闭所有弹框
  • 发现通过mManagedCursors 关闭所有cursor
  • 发现通过mSearchManager 停止搜索
  • 发现通过mActionBar 销毁ActionBar
  • 发现通过dispatchActivityDestroyed分发ActivityLifecycleCallbacks
  • 发现通过notifyContentCaptureManagerIfNeeded 哎呀,不知道做什么的。回头补补
  • 发现通过mUiTranslationController分发UiTranslationStateCallback
    // android31 android.app.Activity#onDestroy
    @CallSuper
    protected void onDestroy() {
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this);
        mCalled = true;

        // dismiss any dialogs we are managing.
        if (mManagedDialogs != null) {
            final int numDialogs = mManagedDialogs.size();
            for (int i = 0; i < numDialogs; i++) {
                final ManagedDialog md = mManagedDialogs.valueAt(i);
                if (md.mDialog.isShowing()) {
                    md.mDialog.dismiss();
                }
            }
            mManagedDialogs = null;
        }

        // close any cursors we are managing.
        synchronized (mManagedCursors) {
            int numCursors = mManagedCursors.size();
            for (int i = 0; i < numCursors; i++) {
                ManagedCursor c = mManagedCursors.get(i);
                if (c != null) {
                    c.mCursor.close();
                }
            }
            mManagedCursors.clear();
        }

        // Close any open search dialog
        if (mSearchManager != null) {
            mSearchManager.stopSearch();
        }

        // SDK 24 Add , But it Add in 19 ,and remove in 20
        if (mActionBar != null) {
            mActionBar.onDestroy();
        }

        dispatchActivityDestroyed();

        // SDK 29 Add 
        notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_STOP);

        // SDK 31 Add
        if (mUiTranslationController != null) {
            mUiTranslationController.onActivityDestroyed();
        }
    }

唉,没看到windowview相关操作呢。
那咱往上找一下。

Activity#performDestroy

哎~ 我们看到一句合适的代码mWindow.destroy
其它的暂时先不看,我们直接去看mWindow.destroy干了什么

    // android31 android.app.Activity#performDestroy
    final void performDestroy() {
        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performDestroy:"
                    + mComponent.getClassName());
        }
        dispatchActivityPreDestroyed();
        mDestroyed = true;
        mWindow.destroy();
        mFragments.dispatchDestroy();
        onDestroy();
        EventLogTags.writeWmOnDestroyCalled(mIdent, getComponentName().getClassName(),
                "performDestroy");
        mFragments.doLoaderDestroy();
        if (mVoiceInteractor != null) {
            mVoiceInteractor.detachActivity();
        }
        dispatchActivityPostDestroyed();
        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
    }

确认下mWindow是谁,找到初始化的地方

    // android31 android.app.Activity#attach
    @UnsupportedAppUsage
    private Window mWindow;

    final void attach(Context context, ...) {
        ...
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ...
    }

PhoneWindow并没有重写destroy

所以最终还是走了继承自Windowdestroy方法

    // android31 android.view.Window#destroy
    /** @hide */
    public final void destroy() {
        mDestroyed = true;
    }

好吧,依然没看到什么有用的信息。
咱么接着向上扒!
看看谁调用的performDestroy

Instrumentation#callActivityOnDestroy

比较眼熟的Instrumentation类哈
没有合适的信息,继续向上

    // android-31 android.app.Instrumentation#callActivityOnDestroy
    public void callActivityOnDestroy(Activity activity) {
      ...
      activity.performDestroy();
    }

ActivityThread#performDestroyActivity

我们暂时剃掉了部分影响不大的代码,直接从mInstrumentation.callActivityOnDestroy之后的流程继续看

  • r.window.closeAllPanels(),移除系统菜单(ActionBar菜单)View
  • mActivities.remove(r.token); ,移除Activity引用
  • 其它释放操作
    // android-31 android.app.ActivityThread#performDestroyActivity
    /** Core implementation of activity destroy call. */
    void performDestroyActivity(ActivityClientRecord r, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {

        ...
        try {
            r.activity.mCalled = false;
            mInstrumentation.callActivityOnDestroy(r.activity);
            if (!r.activity.mCalled) {
                throw new SuperNotCalledException("Activity " + safeToComponentShortString(r.intent)
                        + " did not call through to super.onDestroy()");
            }
            if (r.window != null) {
                r.window.closeAllPanels();
            }
        } catch (SuperNotCalledException e) {
            throw e;
        } catch (Exception e) {
            if (!mInstrumentation.onException(r.activity, e)) {
                throw new RuntimeException("Unable to destroy activity "
                        + safeToComponentShortString(r.intent) + ": " + e.toString(), e);
            }
        }
        r.setState(ON_DESTROY);
        mLastReportedWindowingMode.remove(r.activity.getActivityToken());
        schedulePurgeIdler();
        synchronized (this) {
            if (mSplashScreenGlobal != null) {
                mSplashScreenGlobal.tokenDestroyed(r.token);
            }
        }
        // updatePendingActivityConfiguration() reads from mActivities to update
        // ActivityClientRecord which runs in a different thread. Protect modifications to
        // mActivities to avoid race.
        synchronized (mResourcesManager) {
            mActivities.remove(r.token);
        }
        StrictMode.decrementExpectedActivityCount(activityClass);
    }

感觉还不够啊,继续向上扒

ActivityThread#handleDestroyActivity


    @Override
    public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
            boolean getNonConfigInstance, String reason) {
        performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
        cleanUpPendingRemoveWindows(r, finishing);
        WindowManager wm = r.activity.getWindowManager();
        View v = r.activity.mDecor;
        if (v != null) {
            if (r.activity.mVisibleFromServer) {
                mNumVisibleActivities--;
            }
            IBinder wtoken = v.getWindowToken();
            if (r.activity.mWindowAdded) {
                if (r.mPreserveWindow) {
                    // Hold off on removing this until the new activity's window is being added.
                    r.mPendingRemoveWindow = r.window;
                    r.mPendingRemoveWindowManager = wm;
                    // We can only keep the part of the view hierarchy that we control,
                    // everything else must be removed, because it might not be able to
                    // behave properly when activity is relaunching.
                    r.window.clearContentView();
                } else {
                    wm.removeViewImmediate(v);
                }
            }
            if (wtoken != null && r.mPendingRemoveWindow == null) {
                WindowManagerGlobal.getInstance().closeAll(wtoken,
                        r.activity.getClass().getName(), "Activity");
            } else if (r.mPendingRemoveWindow != null) {
                // We're preserving only one window, others should be closed so app views
                // will be detached before the final tear down. It should be done now because
                // some components (e.g. WebView) rely on detach callbacks to perform receiver
                // unregister and other cleanup.
                WindowManagerGlobal.getInstance().closeAllExceptView(r.token, v,
                        r.activity.getClass().getName(), "Activity");
            }
            r.activity.mDecor = null;
        }
        if (r.mPendingRemoveWindow == null) {
            // If we are delaying the removal of the activity window, then
            // we can't clean up all windows here.  Note that we can't do
            // so later either, which means any windows that aren't closed
            // by the app will leak.  Well we try to warning them a lot
            // about leaking windows, because that is a bug, so if they are
            // using this recreate facility then they get to live with leaks.
            WindowManagerGlobal.getInstance().closeAll(r.token,
                    r.activity.getClass().getName(), "Activity");
        }

        // Mocked out contexts won't be participating in the normal
        // process lifecycle, but if we're running with a proper
        // ApplicationContext we need to have it tear down things
        // cleanly.
        Context c = r.activity.getBaseContext();
        if (c instanceof ContextImpl) {
            ((ContextImpl) c).scheduleFinalCleanup(r.activity.getClass().getName(), "Activity");
        }
        if (finishing) {
            ActivityClient.getInstance().activityDestroyed(r.token);
        }
        mSomeActivitiesChanged = true;
    }


到此处,window开始移除所有的子view
我们不太确定是哪个方法最终生效了。

但是我们可以借助Debug Log,通过监控Viewdetached事件来确定流程。

如下图

view detached

哎呀呀呀,你看,后面的流程很明白了。

  • wm.removeViewImmediate(v) 调用 WindowManagerImpl#removeViewImmediate 方法
    // android-30 android.view.WindowManagerImpl#removeViewImmediate
    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }
  • 然后进入mGlobal.removeView(view, true);方法就是WindowManagerGlobal#removeView
  • 调用removeViewLocked(index, immediate);
    // android-30 android.view.WindowManagerGlobal#removeView
    @UnsupportedAppUsage
    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }

    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (root != null) {
            root.getImeFocusController().onWindowDismissed();
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

  • removeViewLocked方法中进入ViewRootImpl#die方法
  • ViewRootImpl#die方法中执行ViewRootImpl#doDie开始执行die操作
  • ViewRootImpl#doDie中调用ViewRootImpl#dispatchDetachedFromWindow
  • ViewRootImpl#dispatchDetachedFromWindow中终于mView.dispatchDetachedFromWindow();
  • 我们可以去看viewdispatchDetachedFromWindow
    /**
     * @param immediate True, do now if not in traversal. False, put on queue and do later.
     * @return True, request has been queued. False, request has been completed.
     */
    boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

    void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }

            if (mAdded && !mFirst) {
                destroyHardwareRenderer();

                if (mView != null) {
                    int viewVisibility = mView.getVisibility();
                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                    if (mWindowAttributesChanged || viewVisibilityChanged) {
                        // If layout params have been changed, first give them
                        // to the window manager to make sure it has the correct
                        // animation info.
                        try {
                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                                mWindowSession.finishDrawing(
                                        mWindow, null /* postDrawTransaction */);
                            }
                        } catch (RemoteException e) {
                        }
                    }

                    destroySurface();
                }
            }

            mAdded = false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

    void dispatchDetachedFromWindow() {
        // Make sure we free-up insets resources if view never received onWindowFocusLost()
        // because of a die-signal
        mInsetsController.onWindowFocusLost();
        mFirstInputStage.onDetachedFromWindow();
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            mView.dispatchDetachedFromWindow();
        }

        mAccessibilityInteractionConnectionManager.ensureNoConnection();
        mAccessibilityManager.removeAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAccessibilityManager.removeHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        removeSendWindowContentChangedCallback();

        destroyHardwareRenderer();

        setAccessibilityFocus(null, null);

        mInsetsController.cancelExistingAnimations();

        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;

        destroySurface();

        if (mInputQueueCallback != null && mInputQueue != null) {
            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
            mInputQueue.dispose();
            mInputQueueCallback = null;
            mInputQueue = null;
        }
        try {
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }
        // Dispose receiver would dispose client InputChannel, too. That could send out a socket
        // broken event, so we need to unregister the server InputChannel when removing window to
        // prevent server side receive the event and prompt error.
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }

        mDisplayManager.unregisterDisplayListener(mDisplayListener);

        unscheduleTraversals();
    }

View.dispatchDetachedFromWindow

这条线,稍微有些不同。
开始做detached相关工作,并在onDetachedFromWindowInternal方法中释放了部分事件传递的资源


    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    void dispatchDetachedFromWindow() {
        AttachInfo info = mAttachInfo;
        if (info != null) {
            int vis = info.mWindowVisibility;
            if (vis != GONE) {
                onWindowVisibilityChanged(GONE);
                if (isShown()) {
                    // Invoking onVisibilityAggregated directly here since the subtree
                    // will also receive detached from window
                    onVisibilityAggregated(false);
                }
            }
        }

        onDetachedFromWindow();
        onDetachedFromWindowInternal();

        if (info != null) {
            info.mViewRootImpl.getImeFocusController().onViewDetachedFromWindow(this);
        }

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
            // perform the dispatching. The iterator is a safe guard against listeners that
            // could mutate the list by calling the various add/remove methods. This prevents
            // the array from being modified while we iterate it.
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewDetachedFromWindow(this);
            }
        }

        if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER_ADDED) != 0) {
            mAttachInfo.mScrollContainers.remove(this);
            mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
        }

        mAttachInfo = null;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchDetachedFromWindow();
        }

        notifyEnterOrExitForAutoFillIfNeeded(false);
        notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
    }

View.onDetachedFromWindowInternal

可以看到这个方法中,我们remove了很多操作
比如removeUnsetPressCallbackremoveLongPressCallbackremovePerformClickCallback
clearAccessibilityThrottles

    @CallSuper
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    protected void onDetachedFromWindowInternal() {
        mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
        mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
        mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;

        removeUnsetPressCallback();
        removeLongPressCallback();
        removePerformClickCallback();
        clearAccessibilityThrottles();
        stopNestedScroll();

        // Anything that started animating right before detach should already
        // be in its final state when re-attached.
        jumpDrawablesToCurrentState();

        destroyDrawingCache();

        cleanupDraw();
        mCurrentAnimation = null;

        if ((mViewFlags & TOOLTIP) == TOOLTIP) {
            hideTooltip();
        }

        AccessibilityNodeIdManager.getInstance().unregisterViewWithId(getAccessibilityViewId());
    }

为什么要remove这些Callback,他们做了什么,咱们接着查

View.removePerformClickCallback

这里可以看到,判断了mPerformClick != null 就会去remove

   /**
     * Remove the pending click action
     */
    @UnsupportedAppUsage
    private void removePerformClickCallback() {
        if (mPerformClick != null) {
            removeCallbacks(mPerformClick);
        }
    }

removeCallbacks函数做了什么?
移除了一些特殊的message操作,我们看到了attachInfo.mHandler,也就是说,如果没有取消的话,确实可能存在内存泄露的风险,具体要看这个mHandler是怎么实现得。

    /**
     * <p>Removes the specified Runnable from the message queue.</p>
     *
     * @param action The Runnable to remove from the message handling queue
     *
     * @return true if this view could ask the Handler to remove the Runnable,
     *         false otherwise. When the returned value is true, the Runnable
     *         may or may not have been actually removed from the message queue
     *         (for instance, if the Runnable was not in the queue already.)
     *
     * @see #post
     * @see #postDelayed
     * @see #postOnAnimation
     * @see #postOnAnimationDelayed
     */
    public boolean removeCallbacks(Runnable action) {
        if (action != null) {
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) {
                attachInfo.mHandler.removeCallbacks(action);
                attachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
                        Choreographer.CALLBACK_ANIMATION, action, null);
            }
            getRunQueue().removeCallbacks(action);
        }
        return true;
    }

哈。跑深了哈,不过确认了子ViewdetachedFromWindow之后确实释放了很多东西,移除了所有可能延迟执行的Callback,关闭动画等,取消UI操作啥的。

回归上一个方法ActivityThread#handleDestroyActivity方法中做了如上的操作,那么我们继续向上看看,还做了什么操作

ActivityThread#handleRelaunchActivityInner

这个方法很多明显的置空操作了。

  • r.activity = null;
  • r.window = null;
  • r.nextIdle = null;
    // android-30 android.app.ActivityThread#handleRelaunchActivityInner
    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
        // Preserve last used intent, it may be set from Activity#setIntent().
        final Intent customIntent = r.activity.mIntent;
        // Need to ensure state is saved.
        if (!r.paused) {
            performPauseActivity(r, false, reason, null /* pendingActions */);
        }
        if (!r.stopped) {
            callActivityOnStop(r, true /* saveState */, reason);
        }

        handleDestroyActivity(r.token, false, configChanges, true, reason);

        r.activity = null;
        r.window = null;
        r.hideForNow = false;
        r.nextIdle = null;
        // Merge any pending results and pending intents; don't just replace them
        if (pendingResults != null) {
            if (r.pendingResults == null) {
                r.pendingResults = pendingResults;
            } else {
                r.pendingResults.addAll(pendingResults);
            }
        }
        if (pendingIntents != null) {
            if (r.pendingIntents == null) {
                r.pendingIntents = pendingIntents;
            } else {
                r.pendingIntents.addAll(pendingIntents);
            }
        }
        r.startsNotResumed = startsNotResumed;
        r.overrideConfig = overrideConfig;

        handleLaunchActivity(r, pendingActions, customIntent);
    }

到这里,基本window,view都被释放空了。

参考文献

暂无

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容