ActivityManagerService-C 管理場景分析

版權說明:本文為 開開向前沖 記錄文章,轉載請注明出處;
注:限于作者水平有限,文中有不對的地方還請指教

在文章ActivityManagerService-A中對AMS 中核心的幾個數據類都有說道,在ActivityManagerService-B中對ActivityRecord 的狀態變化做了一個介紹,Activity的狀態只是管理的結果,具體的過程需要需要詳細分析;本文將從幾個簡單的場景結合代碼來分析Activity的管理過程;

場景1:Activity在AMS中的存在形式是ActivityRecord,應用程序的組件是Activity,ActivityRecord與Activity是如何關聯的呢?——>static class Token extends IApplicationToken.Stub

這里涉及到到應用程序的主線程-ActivityThread(ActivityThread其實并不是一個線程類),這個類已經有很多人講解,這里推薦一篇關于ActivityThread和ApplicationThread的解析,ActivityThread有一個內部類 ApplicationThread;
class ApplicationThread extends ApplicationThreadNative
class ApplicationThreadNative extends Binder implements IApplicationThread
AMS 和ActivityStackSupervisor中的 app.thread 就是這里ActivityThread的內部類ApplicationThread對象;

Activity在ActivityThread中的存在形式是ActivityClientRecord;ActivityRecord有一個IApplicationToken.Stub類型的變量appToken,在構造ActivityRecord的時候被初始化;

------>frameworks\base\services\core\java\com\android\server\am\ActivityRecord.java
  static class Token extends IApplicationToken.Stub {
        final WeakReference<ActivityRecord> weakActivity;
        Token(ActivityRecord activity) {
            weakActivity = new WeakReference<ActivityRecord>(activity);
        }
        ......
------>frameworks\base\services\core\java\com\android\server\am\ActivityRecord.java
    ActivityRecord(ActivityManagerService _service, ProcessRecord _caller,
            int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType,
            ActivityInfo aInfo, Configuration _configuration,
            ActivityRecord _resultTo, String _resultWho, int _reqCode,
            boolean _componentSpecified, ActivityStackSupervisor supervisor,
            ActivityContainer container, Bundle options) {
          service = _service;
          appToken = new Token(this);
          ......
    }

構造一個ActivityRecord時,會將自己(this)傳給Token,變量ActivityRecord.appToken存的就是最終創建出來的Token。

上述ActivityRecord.appToken的功能是什么呢?ActivityRecord是在Activity的啟動過程中被初始化,繼續跟進Activity的啟動過程;

------>frameworks\base\services\core\java\com\android\server\am\ActivityStackSupervisor.java
------>frameworks\base\core\java\android\app\ActivityThread.java
ActivityStackSupervisor.realStartActivityLocked(ActivityRecord, ...)
  ——> IApplicationThread.scheduleLaunchActivity(...,token, ...)
      ——>跨進程調用,調用ActivityThread.ApplicationThread.scheduleLaunchActivity(...,token,...)
      ——>ApplicationThread.scheduleLaunchActivity(...token, ...)
        ——>ActivityThread.handleLaunchActivity(LAUNCH_ACTIVITY)
          ——>ActivityThread.performLaunchActivity(ActivityClientRecord, ...)
               ——>從ActivityActivityClientRecord取出token使用
               ——> Activity.attch(...token, ...) //attach中將token 賦值給Activity的mToken變量

通過上述函數調用,ActivityRecord的appToken就和應用進程的mToken建立關聯;在發生Activity切換時,應用進程會將上一個Activity的Token(AMS.startActivity()的輸入參數IBinder resultTo)傳遞給系統進程,系統進程會根據這個Token找到ActivityRecord,對其完成調度后,再通知應用進程:Activity狀態發生了變化。

場景2:啟動新Activity時,需要將新ActivityRecord壓入任務棧頂;

這里先用自然語言描述一下可能的情況:1:新的ActivityRecord 的任務棧已經存在,處于后臺,此時需要將這個后臺任務棧切換為前臺任務棧,然后將這個ActivityRecord置于前臺任務棧棧頂; 2:新ActivityRecord所在的任務棧沒有,需要新建任務棧,然后將ActivityRecord置于棧頂;

代碼邏輯:任務棧存在于后臺的情況:

第一步:需要先找到ActivityRecord所在的任務(TaskRecord);
第二步:將TaskRecord所在的ActivityStack移動到前臺;
第三步:將TaskRecord移動到ActivityStack的棧頂;

1:找到ActivityRecord所在的TaskRecord: findTaskLocked()
------>frameworks\base\services\core\java\com\android\server\am\ActivityStackSupervisor.java
/**
 * Returns the top activity in any existing task matching the given
 * Intent.  Returns null if no such task is found.
 */
ActivityRecord findTaskLocked(ActivityRecord target) {
    Intent intent = target.intent;
    ActivityInfo info = target.info;
    ComponentName cls = intent.getComponent();
    if (info.targetActivity != null) {
        cls = new ComponentName(info.packageName, info.targetActivity);
    }
    ...
    for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
        final TaskRecord task = mTaskHistory.get(taskNdx);
        ...
        final ActivityRecord r = task.getTopActivity();
            if (r == null || r.finishing || r.userId != userId ||
                    r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {//這些條件的直接過濾
                continue;
            }
        ...
        final Intent taskIntent = task.intent;
        final Intent affinityIntent = task.affinityIntent;
        ...
        if (!isDocument && !taskIsDocument && task.rootAffinity != null) {
            if (task.rootAffinity.equals(target.taskAffinity)) { //Affinity和Task的rootAffinity相同,
            //則就是這個task 了;
                return r;
            }
        } else if (taskIntent != null && taskIntent.getComponent() != null &&
            taskIntent.getComponent().compareTo(cls) == 0 &&
            Objects.equals(documentData, taskDocumentData)) {//Task的Intent 和target的包名相同
            return r;
        } else if if (affinityIntent != null && affinityIntent.getComponent() != null &&
            affinityIntent.getComponent().compareTo(cls) == 0 &&
            Objects.equals(documentData, taskDocumentData)) { //Affinity Intent的包名相同
            return r
        }
        ...
    }
    return null;
}

改函數主要是對ActivityStack中的所有Task進行遍歷,找到ActivityRecord的所在的Task,如果找到Task,則返回Task最上面的ActivityRecord;找到Task的條件如下:

  • ActivityRecord target 的affinity和正在遍歷的Task的rootAffinity相同;
  • ActivityRecord target的包名和Task的intent 的包名相同;
  • ActivityRecord target的包名和Task的affinityIntent的包名相同;
2:將TaskRecord所在的ActivityStack調整到前臺
  • 2.1:findTaskLocked()方法找到Activity所在的Task的頂部ActivityRecord,根據這個ActivityRecord找到所在的TaskRecord和ActivityStack;
------> frameworks\base\services\core\java\com\android\server\am\ActivityStackSupervisor.java
    final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,
            boolean doResume, Bundle options, TaskRecord inTask) {
                ......
                ActivityRecord intentActivity = !launchSingleInstance ?
                        findTaskLocked(r) : findActivityLocked(intent, r.info);
                if (intentActivity != null) {
                    ......
                    if (r.task == null) {
                        r.task = intentActivity.task;
                    }
                    targetStack = intentActivity.task.stack;//找到ActivityRecord r所在的ActivityStack
                    targetStack.mLastPausedActivity = null;
                    targetStack.moveToFront("intentActivityFound");
                  ......
  • 2.2:調整ActivityStack,主要根據ActivityStack的類型來進行ActivityStack棧(mStacks)調整;moveToFront();
------>frameworks\base\services\core\java\com\android\server\am\ActivityStack.java
final void moveToFront(String reason) {
        if (isAttached()) {//是否綁定到顯示設備
            if (isOnHomeDisplay()) {//是否顯示在默認顯示設備
                mStackSupervisor.moveHomeStack(isHomeStack(), reason);//ActivityStack棧調整
            }
            mStacks.remove(this);//刪除mStacks棧中的該ActivityStack
            mStacks.add(this);//將該ActivityStack添加到mStacks中
            final TaskRecord task = topTask();
            if (task != null) {
                mWindowManager.moveTaskToTop(task.taskId);
            }
        }
    }

final boolean isHomeStack() {//判斷當前ActivityStack 是不是HomeStack
        return mStackId == HOME_STACK_ID;
    }

void moveHomeStack(boolean toFront, String reason) {//棧調整
    // 獲取當前的Top Stack
    ArrayList<ActivityStack> stacks = mHomeStack.mStacks;//mStacks代表的是綁定到顯示設備的所有Stack;
                          //所以這里也可以用mFocusedStack.mStacks
    final int topNdx = stacks.size() - 1;
    if (topNdx <= 0) {
        return;
    }
    ActivityStack topStack = stacks.get(topNdx);
    // HomeStack是否需要調整
    final boolean homeInFront = topStack == mHomeStack;//判斷stacks 棧頂的ActivityStack是不是HomeStack,
    // 即判斷當前HomeStack是不是在前臺;
    if (homeInFront != toFront) {
        mLastFocusedStack = topStack;
        stacks.remove(mHomeStack);
        stacks.add(toFront ? topNdx : 0, mHomeStack);//這里根據toFront來調整HomeStack位置
        mFocusedStack = stacks.get(topNdx);//調整mFocusedStack
    }
    ...
    if (mService.mBooting || !mService.mBooted) {  // 開機過程處理
        final ActivityRecord r = topRunningActivityLocked();
        if (r != null && r.idle) {
            checkFinishBootingLocked();
        }
    }
}

moveHomeStack()方法根據mStacks棧頂的ActivityStack是否是HomeStack來對HomeStack和mFocusedStack 進行調整;homeInFront 表示mStacks棧頂是不是HomeStack(即HomeStack 是否在前臺);toFront 是isHomeStack()的返回值,即當前ActivityStack是否是HomeStack,也就是需要將當前ActivityStack調整到前臺還是后臺;

這里存在如下4種情況:

  • homeInFront = true,即topStack == mHomeStack, 表示當前mStacks最頂的棧是HomeStack,即表示HomeStack在前臺;toFront = false 即isHomeStack()返回false,即表示ActivityRecord所在的棧不是HomeStack,所以需要將HomeStack調整到mStacks的底部,即將HomeStack調整到mStacks的0號位置;
  • homeInFront = true, toFront = true: 表示HomeStack在前臺,要顯示的ActivityStack就是HomeStack,所以不需要對mStacks進行調整;
  • homeInFront = false, toFront = true: 表示HomeStack在后臺,要顯示的ActivityStack就是HomeStack,需要將HomeStack調整到mStacks的最頂端;
  • homeInFront = false, toFront = false: 表示HomeStack在后臺,要顯示的ActivityStack 也 是HomeStack,不需要對mStacks進行調整。

moveToFront中moveHomeStack()調整完HomeStack位置后,調整mStacks棧中當前ActivityStack位置,即先刪除當前ActivityStack,然后再向mStacks中添加當前ActivityStack(這樣做時為了保證將當前ActivityStack置于棧頂);

3:將當前需要顯示的ActivityRecord的TaskRecord 移動到ActivityStack 的棧頂;moveTaskToFrontLocked()

ActivityStack有前臺和后臺之分,前臺ActivityStack代表當前顯示的ActivityRecord在的TaskRecord所在的ActivityStack(不是前臺ActivityStack中所有的TaskRecord 和ActivityRecord都處于可見,只有ActivityStack 棧頂的TaskRecord 棧頂的ActivityRecord處于可見狀態);

第二步已經將ActivityRecord所在的Task的Stack移動到前臺,現在需要將需要顯示的ActivityRecord 所在的TaskRecord 移動到ActivityStack的棧頂,以及將ActivityRecord移動到TaskRecord的棧頂;

------> frameworks\base\services\core\java\com\android\server\am\ActivityStack.java
final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord source, Bundle options,
            String reason) {
        final int numTasks = mTaskHistory.size();
        final int index = mTaskHistory.indexOf(tr);
        if (numTasks == 0 || index < 0)  { //numTasks 代表當前ActivityStack中的所有TaskRecord數量,
        //index 表示當前需要調整的TaskRecord在ActivityStack mTaskHistory中的位置
            // nothing to do!
            if (source != null &&
                    (source.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
                ActivityOptions.abort(options);
            } else {
                updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options);
            }
            return;
        }

        // Shift all activities with this task up to the top
        // of the stack, keeping them in the same internal order.
        insertTaskAtTop(tr);//將Task置于ActivityStack棧頂
        moveToFront(reason);//前面說過moveToFront()方法,前面的情況只調用了moveToFront(),
        //不會調用moveTaskToFrontLocked(); 
        //一旦要將任務調整到ActivityStack棧頂,意味著ActivityStack也一定要調整到前臺;

        if (source != null &&
                (source.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) {
            mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false);
            ActivityRecord r = topRunningActivityLocked(null);
            if (r != null) {
                mNoAnimActivities.add(r);
            }
            ActivityOptions.abort(options);
        } else {
            updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options);
        }

        mStackSupervisor.resumeTopActivitiesLocked();//當Activity各種棧調整好后,就可以將當前Activity的狀態置為RESUMED
        ......
    }

private void insertTaskAtTop(TaskRecord task) {
        // If this is being moved to the top by another activity or being launched from the home
        // activity, set mOnTopOfHome accordingly.
        if (isOnHomeDisplay()) {
            ActivityStack lastStack = mStackSupervisor.getLastStack();
            final boolean fromHome = lastStack.isHomeStack();
            if (!isHomeStack() && (fromHome || topTask() != task)) {
                task.setTaskToReturnTo(fromHome
                        ? lastStack.topTask() == null
                                ? HOME_ACTIVITY_TYPE
                                : lastStack.topTask().taskType
                        : APPLICATION_ACTIVITY_TYPE);//設置ReturnTo Task類型
            }
        } else {
            task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
        }
        mTaskHistory.remove(task);//先從mTaskHistory中移除task
        // Now put task at top.
        int taskNdx = mTaskHistory.size();
        if (!isCurrentProfileLocked(task.userId)) {
            // Put non-current user tasks below current user tasks.
            while (--taskNdx >= 0) {
                if (!isCurrentProfileLocked(mTaskHistory.get(taskNdx).userId)) {
                    break;
                }
            }
            ++taskNdx;
        }
        mTaskHistory.add(taskNdx, task);//將task添加到mTaskHistory,確保添加到了頂部
        updateTaskMovement(task, true);
    }

這里將ActivityRecord所在的TaskRecord 移動到ActivityStack的棧頂,還需要將需要顯示的ActivityRecord移動到TaskRecord的棧頂,這個過程通過addActivityToTop()來實現;addActivityToTop()在ActivityStack.java的startActivityLocked()方法中被調用,startActivityLocked()在ActivityStackSupervisor.java的startActivityUncheckedLocked()方法中被調用;

------> frameworks\base\services\core\java\com\android\server\am\TaskRecord.java
    void addActivityToTop(ActivityRecord r) {
        addActivityAtIndex(mActivities.size(), r);
    }

    void addActivityAtIndex(int index, ActivityRecord r) {
        // Remove r first, and if it wasn't already in the list and it's fullscreen, count it.
        if (!mActivities.remove(r) && r.fullscreen) {//刪除mActivities中已經存在的r
            // Was not previously in list.
            numFullscreen++;
        }
        // Only set this based on the first activity
        if (mActivities.isEmpty()) { //判斷當前啟動的Activity是否為該任務Task的第一個Activity
            taskType = r.mActivityType;
            isPersistable = r.isPersistable();
            mCallingUid = r.launchedFromUid;
            mCallingPackage = r.launchedFromPackage;
            // Clamp to [1, max].
            maxRecents = Math.min(Math.max(r.info.maxRecents, 1),
                    ActivityManager.getMaxAppRecentsLimitStatic());
        } else {
            // Otherwise make all added activities match this one.
            r.mActivityType = taskType;
        }
        Log.d(TAG+"mActivities.add",Log.getStackTraceString(new Throwable()));//Keiven-chen
        mActivities.add(index, r);//指定將當前Activity添加到mActivities中的位置
        updateEffectiveIntent();//如函數名,更新任務棧相關Intent
        if (r.isPersistable()) {
            mService.notifyTaskPersisterLocked(this, false);
        }
    }

這里需要明確一點問題,mActivities就是我說的TaskRecord棧, mTaskHistory也就是ActivityStack棧;

場景3: 任務棧會經常調整,任務棧剛開始從底到頂是 A - B - C, 經過調整后任務棧變化為 C - B - A,這個過程會對整個任務棧帶來哪些改變呢?

該問題看似只需要調整mActivities的順序,但是會這么簡單嗎?ActivityManagerService-A文中有說過TaskRecord類,隨著mActivities棧的改變,TaskRecord中的屬性是否也應該改變呢?比如Affinity,Intent;這些值是如何改變的呢?這里涉及updateEffectiveIntent()和setFrontOfTask();

每次調整完mActivities后,需要調用updateEffectiveIntent方法來修改該TaskRecord相關的Intent;

void updateEffectiveIntent() {
    final int effectiveRootIndex = findEffectiveRootIndex();//找到根Activity索引
    final ActivityRecord r = mActivities.get(effectiveRootIndex);
    setIntent(r);//設置Intent相關屬性,順著代碼往下看,
}
    /** Sets the original intent, and the calling uid and package. */
    void setIntent(ActivityRecord r) {
        setIntent(r.intent, r.info);
        mCallingUid = r.launchedFromUid;
        mCallingPackage = r.launchedFromPackage;
    }

    /** Sets the original intent, _without_ updating the calling uid or package. */
    private void setIntent(Intent _intent, ActivityInfo info) { //該重載方法更新affinity, 
    //origActivity ,realActivity等屬性;
        if (intent == null) {
            mNeverRelinquishIdentity =
                    (info.flags & ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY) == 0;
        } else if (mNeverRelinquishIdentity) {
            return;
        }

        affinity = info.taskAffinity;
        if (intent == null) {
            // If this task already has an intent associated with it, don't set the root
            // affinity -- we don't want it changing after initially set, but the initially
            // set value may be null.
            rootAffinity = affinity;
        }
        effectiveUid = info.applicationInfo.uid;
        stringName = null;

        if (info.targetActivity == null) {
            if (_intent != null) {
                // If this Intent has a selector, we want to clear it for the
                // recent task since it is not relevant if the user later wants
                // to re-launch the app.
                if (_intent.getSelector() != null || _intent.getSourceBounds() != null) {
                    _intent = new Intent(_intent);
                    _intent.setSelector(null);
                    _intent.setSourceBounds(null);
                }
            }
            if (ActivityManagerService.DEBUG_TASKS) Slog.v(ActivityManagerService.TAG,
                    "Setting Intent of " + this + " to " + _intent);
            intent = _intent;
            realActivity = _intent != null ? _intent.getComponent() : null;
            origActivity = null;
        } else {
            ComponentName targetComponent = new ComponentName(
                    info.packageName, info.targetActivity);
            if (_intent != null) {
                Intent targetIntent = new Intent(_intent);
                targetIntent.setComponent(targetComponent);
                targetIntent.setSelector(null);
                targetIntent.setSourceBounds(null);
                if (ActivityManagerService.DEBUG_TASKS) Slog.v(ActivityManagerService.TAG,
                        "Setting Intent of " + this + " to target " + targetIntent);
                intent = targetIntent;
                realActivity = targetComponent;
                origActivity = _intent.getComponent();
            } else {
                intent = null;
                realActivity = targetComponent;
                origActivity = new ComponentName(info.packageName, info.name);
            }
        }

        final int intentFlags = intent == null ? 0 : intent.getFlags();
        if ((intentFlags & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
            // Once we are set to an Intent with this flag, we count this
            // task as having a true root activity.
            rootWasReset = true;
        }

        userId = UserHandle.getUserId(info.applicationInfo.uid);
        if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) {
            // If the activity itself has requested auto-remove, then just always do it.
            autoRemoveRecents = true;
        } else if ((intentFlags & (Intent.FLAG_ACTIVITY_NEW_DOCUMENT
                | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS)) == Intent.FLAG_ACTIVITY_NEW_DOCUMENT) {
            // If the caller has not asked for the document to be retained, then we may
            // want to turn on auto-remove, depending on whether the target has set its
            // own document launch mode.
            if (info.documentLaunchMode != ActivityInfo.DOCUMENT_LAUNCH_NONE) {
                autoRemoveRecents = false;
            } else {
                autoRemoveRecents = true;
            }
        } else {
            autoRemoveRecents = false;
        }
    }

任務棧A - B - C的棧底是 A,調整后, C - B - A的棧底是C,然而,TaskRecord并沒有標注當前棧底的屬性, 這個棧底是根據任務棧中每個ActivityRecord的frontOfTask屬性來標識:這個屬性通過setFrontOfTask()方法來修改;

/** Call after activity movement or finish to make sure that frontOfTask is set correctly */
    final void setFrontOfTask() {
        boolean foundFront = false;
        final int numActivities = mActivities.size();
        for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {//重棧底開始
            final ActivityRecord r = mActivities.get(activityNdx);
            if (foundFront || r.finishing) {
                r.frontOfTask = false; //非棧底Activity 該屬性都置為false
            } else {
                r.frontOfTask = true;//棧底Activity 該屬性置為true
                // Set frontOfTask false for every following activity.
                foundFront = true;
            }
        }
        if (!foundFront && numActivities > 0) {
            // All activities of this task are finishing. As we ought to have a frontOfTask
            // activity, make the bottom activity front.
            mActivities.get(0).frontOfTask = true; //這種情況將mActivities第一個Activity的frontOfTask置為true;
        }
    }

該函數對任務棧從底到頂進行遍歷,找到第一個未結束(finishing = false)的ActivityRecord, 將其frontOfTask屬性設置成true;其他所有ActivtyRecord的frontOfTask屬性設置為false。這樣就能標記該ActivityRecord為所在任務棧的棧底;

參考文章:Android四大組件之Activity--管理方式

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

推薦閱讀更多精彩內容