四大組件之Activity(二)-StartingWindow流程分析

100%原創(chuàng),轉(zhuǎn)載注明出處,多謝。

在Android系統(tǒng)中,Activity組件在啟動但窗口還未顯示出來之時,可以顯示一個啟動窗口(StartingWindow)。這個啟動窗口可以看作是Activity組件的預(yù)覽窗口。本文就針對starting Window啟動和銷毀流程進(jìn)行簡單分析, 代碼基于android 9.0。過程自己debug一下,也非常簡單。

一、顯示流程

StartingWindow與Activity的啟動流程密切相關(guān),前面Activity啟動調(diào)用流程如下圖所示:

StartingWindow啟動前流程

從上面流程看出,在ActivityStack執(zhí)行startActivityLocked的時候,通過ActivityRecord的showStartingWindow方法開始正式進(jìn)入starting window的顯示流程。

從上一篇的概覽我們知道,這個Activity啟動的這個部分是屬于前期準(zhǔn)備階段,借助PMS,確認(rèn)要啟動的Activity,并對intent component 、和權(quán)限等等進(jìn)行驗證,同時根據(jù)launcheMode和Flag配置task。在這個時候加載預(yù)覽窗口貌似也能理解,畢竟之后就要處理Activity的正式加載了,在此之前通過StartingWindow過渡能提升用戶體驗。

那么就從ActivityRecord #showStartingWindow開始看下顯示流程:

ActivityRecord #showStartingWindow

2378    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
2379        showStartingWindow(prev, newTask, taskSwitch, false /* fromRecents */);
2380    }
2381
2382    void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
2383            boolean fromRecents) {
2384        if (mWindowContainerController == null) {
2385            return;
2386        }
2387        if (mTaskOverlay) {
2388            // We don't show starting window for overlay activities.
2389            return;
2390        }
2391
2392        final CompatibilityInfo compatInfo =
2393                service.compatibilityInfoForPackageLocked(info.applicationInfo);
2394        final boolean shown = mWindowContainerController.addStartingWindow(packageName, theme,
2395                compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
2396                prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
2397                allowTaskSnapshot(),
2398                mState.ordinal() >= RESUMED.ordinal() && mState.ordinal() <= STOPPED.ordinal(),
2399                fromRecents);
2400        if (shown) {
2401            mStartingWindowState = STARTING_WINDOW_SHOWN;
2402        }
2403    }

PMS獲取到一系列的屬性與資源,傳入AppWindowContainerController的addStartingWindow方法,通過返回值shown來判斷是否把StringWindowState狀態(tài)置為顯示。

緊接著再看看 AppWindowContainerController #addStartingWindow

先交代下類相關(guān)情況:

AppWindowContainerController extends WindowContainerController:

AppWindowContainerController繼承自WindowContainerController。

WindowContainerController內(nèi)部有幾個變量需要了解下:

class WindowContainerController<E extends WindowContainer, I extends WindowContainerListener>
31        implements ConfigurationContainerListener{
33    final WindowManagerService mService;
34    final RootWindowContainer mRoot;
35    final WindowHashMap mWindowMap;
37    // The window container this controller owns.
38    E mContainer;
}

繼續(xù):

public boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
444            CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
445            IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
446            boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents) {
447        synchronized(mWindowMap) {
448            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "setAppStartingWindow: token=" + mToken
449                    + " pkg=" + pkg + " transferFrom=" + transferFrom + " newTask=" + newTask
450                    + " taskSwitch=" + taskSwitch + " processRunning=" + processRunning
451                    + " allowTaskSnapshot=" + allowTaskSnapshot);
452
453            if (mContainer == null) {
454                Slog.w(TAG_WM, "Attempted to set icon of non-existing app token: " + mToken);
455                return false;
456            }
457
458            // If the display is frozen, we won't do anything until the actual window is
459            // displayed so there is no reason to put in the starting window.
460            if (!mContainer.okToDisplay()) {
461                return false;
462            }
463
464            if (mContainer.startingData != null) {
465                return false;
466            }
467
468            final WindowState mainWin = mContainer.findMainWindow();
469            if (mainWin != null && mainWin.mWinAnimator.getShown()) {
470                // App already has a visible window...why would you want a starting window?
471                return false;
472            }
473
474            final TaskSnapshot snapshot = mService.mTaskSnapshotController.getSnapshot(
475                    mContainer.getTask().mTaskId, mContainer.getTask().mUserId,
476                    false /* restoreFromDisk */, false /* reducedResolution */);
477            final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
478                    allowTaskSnapshot, activityCreated, fromRecents, snapshot);
479
480            if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
481                return createSnapshot(snapshot);
482            }
483
484            // If this is a translucent window, then don't show a starting window -- the current
485            // effect (a full-screen opaque starting window that fades away to the real contents
486            // when it is ready) does not work for this.
487            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Checking theme of starting window: 0x"
488                    + Integer.toHexString(theme));
489            if (theme != 0) {
490                AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
491                        com.android.internal.R.styleable.Window, mService.mCurrentUserId);
492                if (ent == null) {
493                    // Whoops!  App doesn't exist. Um. Okay. We'll just pretend like we didn't
494                    // see that.
495                    return false;
496                }
                  //這部分主要是獲取APP對應(yīng)的主題style,這也是app端能決定的是否要StartingWindow的設(shè)置,為true,后面的判斷直接return 不會執(zhí)行到scheduleAddStartingWindow
497                final boolean windowIsTranslucent = ent.array.getBoolean(
498                        com.android.internal.R.styleable.Window_windowIsTranslucent, false);
499                final boolean windowIsFloating = ent.array.getBoolean(
500                        com.android.internal.R.styleable.Window_windowIsFloating, false);
501                final boolean windowShowWallpaper = ent.array.getBoolean(
502                        com.android.internal.R.styleable.Window_windowShowWallpaper, false);
503                final boolean windowDisableStarting = ent.array.getBoolean(
504                        com.android.internal.R.styleable.Window_windowDisablePreview, false);
505                if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Translucent=" + windowIsTranslucent
506                        + " Floating=" + windowIsFloating
507                        + " ShowWallpaper=" + windowShowWallpaper);
508                if (windowIsTranslucent) {
509                    return false;
510                }
511                if (windowIsFloating || windowDisableStarting) {
512                    return false;
513                }
514                if (windowShowWallpaper) {
515                    if (mContainer.getDisplayContent().mWallpaperController.getWallpaperTarget()
516                            == null) {
517                        // If this theme is requesting a wallpaper, and the wallpaper
518                        // is not currently visible, then this effectively serves as
519                        // an opaque window and our starting window transition animation
520                        // can still work.  We just need to make sure the starting window
521                        // is also showing the wallpaper.
522                        windowFlags |= FLAG_SHOW_WALLPAPER;
523                    } else {
524                        return false;
525                    }
526                }
527            }
528
529            if (mContainer.transferStartingWindow(transferFrom)) {
530                return true;
531            }
532
533            // There is no existing starting window, and we don't want to create a splash screen, so
534            // that's it!
535            if (type != STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
536                return false;
537            }
538
539            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating SplashScreenStartingData");
540            mContainer.startingData = new SplashScreenStartingData(mService, pkg, theme,
541                    compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
542                    mContainer.getMergedOverrideConfiguration());
543            scheduleAddStartingWindow();
544        }
545        return true;
546    }

首先對照下APP style的設(shè)置:

<style name="AppTheme.StartingWindowTheme" parent="AppTheme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowShowWallpaper">true</item>
<item name="android:windowDisablePreview">true</item>
</style>

這四個設(shè)置系統(tǒng)默認(rèn)為false,從源碼判斷來看,只要滿足一個為true就會return掉.
其中我debug發(fā)現(xiàn):微信是設(shè)置了android:windowDisablePreview = true 禁用了StartingWindow.

好了,話不多說,如果條件都滿足,那么接下來會初始化SplashScreenStartingData,
并賦值給了mContainer.startingData,執(zhí)行scheduleAddStartingWindow()。

AppWindowContainerController #scheduleAddStartingWindow

void scheduleAddStartingWindow() {
568        // Note: we really want to do sendMessageAtFrontOfQueue() because we
569        // want to process the message ASAP, before any other queued
570        // messages.
571        if (!mService.mAnimationHandler.hasCallbacks(mAddStartingWindow)) {
572            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Enqueueing ADD_STARTING");
573            mService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
574        }
575    }

加入輪詢的消息池,具體執(zhí)行的Runnable 是mAddStartingWindow。

116    private final Runnable mAddStartingWindow = new Runnable() {
117
118        @Override
119        public void run() {
120            final StartingData startingData;
121            final AppWindowToken container;
122
123            synchronized (mWindowMap) {
                  ...
133                startingData = mContainer.startingData; //獲取之前初始化的SplashScreenStartingData
134                container = mContainer;
135            }
                  ...
149            StartingSurface surface = null;
150            try {
151                surface = startingData.createStartingSurface(container); //創(chuàng)建startingwindow的核心部分
152            } catch (Exception e) {
153                Slog.w(TAG_WM, "Exception when adding starting window", e);
154            
155            if (surface != null) {
156                boolean abort = false;
157                synchronized (mWindowMap) {
158                    // If the window was successfully added, then
159                    // we need to remove it.
160                    if (container.removed || container.startingData == null) {
161                        if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
162                                "Aborted starting " + container
163                                        + ": removed=" + container.removed
164                                        + " startingData=" + container.startingData);
165                        container.startingWindow = null;
166                        container.startingData = null;
167                        abort = true;
168                    } else {
169                        container.startingSurface = surface; //并把StartingSurface賦值給container.startingSurface
170                    }
                      ...
177                if (abort) {
178                    surface.remove();
179                }
180            } else if (DEBUG_STARTING_WINDOW) {
181                Slog.v(TAG_WM, "Surface returned was null: " + mContainer);
182            }
183        }
184    };

這個階段最核心的就是創(chuàng)建StartingSurface的過程:startingData.createStartingSurface(container)。而StartingData本身是個接口,它的實現(xiàn)類是SplashScreenStartingData。

27 class SplashScreenStartingData extends StartingData {
       …
54    @Override
55    StartingSurface createStartingSurface(AppWindowToken atoken) {
56        return mService.mPolicy.addSplashScreen(atoken.token, mPkg, mTheme, mCompatInfo,
57                mNonLocalizedLabel, mLabelRes, mIcon, mLogo, mWindowFlags,
58                mMergedOverrideConfiguration, atoken.getDisplayContent().getDisplayId());
59    }
60}

這里重點關(guān)注createStartingSurface的實現(xiàn),我們看到,返回的是mService.mPolicy.addSplashScreen,其中mService是WindowManagerService, mPolicy是WindowManagerPolicy.WindowManagerPolicy是個接口,想看addSplashScreen得找對應(yīng)實現(xiàn)類。于是找到了PhoneWindowManager.

PhoneWindowManager # addSplashScreen

3299    public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
3300            CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
3301            int logo, int windowFlags, Configuration overrideConfig, int displayId) {
                 ...
3318            // Obtain proper context to launch on the right display.
3319            final Context displayContext = getDisplayContext(context, displayId);
3320            if (displayContext == null) {
3321                // Can't show splash screen on requested display, so skip showing at all.
3322                return null;
3323            }
3324            context = displayContext;
3325
3326            if (theme != context.getThemeResId() || labelRes != 0) {
3327                try {
3328                    context = context.createPackageContext(packageName, CONTEXT_RESTRICTED);
3329                    context.setTheme(theme);
3330                } catch (PackageManager.NameNotFoundException e) {
3331                    // Ignore
3332                }
3333            }
3334
3335            if (overrideConfig != null && !overrideConfig.equals(EMPTY)) {
3336                if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "addSplashScreen: creating context based"
3337                        + " on overrideConfig" + overrideConfig + " for splash screen");
3338                final Context overrideContext = context.createConfigurationContext(overrideConfig);
3339                overrideContext.setTheme(theme);
3340                final TypedArray typedArray = overrideContext.obtainStyledAttributes(
3341                        com.android.internal.R.styleable.Window);
3342                final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
3343                if (resId != 0 && overrideContext.getDrawable(resId) != null) {
3344                    // We want to use the windowBackground for the override context if it is
3345                    // available, otherwise we use the default one to make sure a themed starting
3346                    // window is displayed for the app.
3347                    if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "addSplashScreen: apply overrideConfig"
3348                            + overrideConfig + " to starting window resId=" + resId);
3349                    context = overrideContext;
3350                }
3351                typedArray.recycle();
3352            }
3353
3354            final PhoneWindow win = new PhoneWindow(context);
3355            win.setIsStartingWindow(true); 
3356
3357            CharSequence label = context.getResources().getText(labelRes, null);
3358            // Only change the accessibility title if the label is localized
3359            if (label != null) {
3360                win.setTitle(label, true);
3361            } else {
3362                win.setTitle(nonLocalizedLabel, false);
3363            }
3364            //設(shè)置窗口類型為啟動窗口類型
3365            win.setType(
3366                WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
3367
3368            synchronized (mWindowManagerFuncs.getWindowManagerLock()) {
3369                // Assumes it's safe to show starting windows of launched apps while
3370                // the keyguard is being hidden. This is okay because starting windows never show
3371                // secret information.
3372                if (mKeyguardOccluded) {
3373                    windowFlags |= FLAG_SHOW_WHEN_LOCKED;
3374                }
3375            }
3376
3377            // Force the window flags: this is a fake window, so it is not really
3378            // touchable or focusable by the user.  We also add in the ALT_FOCUSABLE_IM
3379            // flag because we do know that the next window will take input
3380            // focus, so we want to get the IME window up on top of us right away.
                    //設(shè)置不可觸摸和聚焦
3381            win.setFlags(
3382                windowFlags|
3383                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
3384                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
3385                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
3386                windowFlags|
3387                WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
3388                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
3389                WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
3390
3391            win.setDefaultIcon(icon);
3392            win.setDefaultLogo(logo);
3393
3394            win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
3395                    WindowManager.LayoutParams.MATCH_PARENT);
3396
3397            final WindowManager.LayoutParams params = win.getAttributes();
3398            params.token = appToken;
3399            params.packageName = packageName;
3400            params.windowAnimations = win.getWindowStyle().getResourceId(
3401                    com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
3402            params.privateFlags |=
3403                    WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
3404            params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
3405
3406            if (!compatInfo.supportsScreen()) {
3407                params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
3408            }
3409
3410            params.setTitle("Splash Screen " + packageName);
3411            addSplashscreenContent(win, context);
3412            //獲取WMS
3413            wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
3414            view = win.getDecorView();
3415
3416            if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "Adding splash screen window for "
3417                + packageName + " / " + appToken + ": " + (view.getParent() != null ? view : null));
3418           //窗口添加視圖
3419            wm.addView(view, params);
3420
3421            // Only return the view if it was successfully added to the
3422            // window manager... which we can tell by it having a parent.
3423            return view.getParent() != null ? new SplashScreenSurface(view, appToken) : null;
3424        } catch (WindowManager.BadTokenException e) {
3425            // ignore
3426            Log.w(TAG, appToken + " already running, starting window not displayed. " +
3427                    e.getMessage());
3428        } catch (RuntimeException e) {
3429            // don't crash if something else bad happens, for example a
3430            // failure loading resources because we are loading from an app
3431            // on external storage that has been unmounted.
3432            Log.w(TAG, appToken + " failed creating starting window", e);
3433        } finally {
3434            if (view != null && view.getParent() == null) {
3435                Log.w(TAG, "view not successfully added to wm, removing view");
3436                wm.removeViewImmediate(view);
3437            }
3438        }
3439
3440        return null;
3441    }
3442

這里顯然是Starting Window 顯示的核心代碼了。創(chuàng)建窗口,初始窗口和視圖,并將窗口添加到WMS,完成了Starting window的顯示。

流程簡單示意如下:


StartingWindow啟動流程
二、銷毀過程

Activity組件啟動完成之后顯示對應(yīng)的窗口時,啟動窗口的過渡作用就已經(jīng)完成了,此時需要先銷毀starting window在加載顯示對應(yīng)Activity的窗口。

我們知道,在WindowManagerService服務(wù)中,每一個窗口都對應(yīng)有一個WindowState對象。每當(dāng)WindowManagerService服務(wù)需要顯示一個窗口的時候,就會調(diào)用一個對應(yīng)的WindowState對象的成員函數(shù)performShowLocked。WindowState類的成員函數(shù)performShowLocked在執(zhí)行的過程中,就會檢查當(dāng)前正在處理的WindowState對象所描述的窗口是否設(shè)置有啟動窗口。

WindowState# performShowLocked

3793    // This must be called while inside a transaction.
3794    boolean performShowLocked() {
              ...
3801        logPerformShow("performShow on ");
3802
3803        final int drawState = mWinAnimator.mDrawState;
               //HAS_DRAWN = 4; //窗口已經(jīng)顯示在屏幕上 , READY_TO_SHOW = 3;//窗口準(zhǔn)備顯示
3804        if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW)
3805                && mAttrs.type != TYPE_APPLICATION_STARTING && mAppToken != null) { // 不是starting window
3806            mAppToken.onFirstWindowDrawn(this, mWinAnimator);
3807        }
             ...
3847        return true;
3848    }

當(dāng)前準(zhǔn)備顯示的Window不是啟動窗口類型,那么執(zhí)行AppWindowToken的onFirstWindowDrawn

AppWindowToken #onFirstWindowDrawn

97    void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
298        firstWindowDrawn = true;
299
300        // We now have a good window to show, remove dead placeholders
301        removeDeadWindows();
302
303        if (startingWindow != null) {
304            if (DEBUG_STARTING_WINDOW || DEBUG_ANIM) Slog.v(TAG, "Finish starting "
305                    + win.mToken + ": first real window is shown, no animation");
306            // If this initial window is animating, stop it -- we will do an animation to reveal
307            // it from behind the starting window, so there is no need for it to also be doing its
308            // own stuff.
309            win.cancelAnimation();
310            if (getController() != null) {
311                getController().removeStartingWindow();
312            }
313        }
314        updateReportedVisibilityLocked();
315    }

getContoller獲取的是AppWindowContainerController,由它執(zhí)行removeStartingWindow.

AppWindowContainerController #removeStartingWindow

595    public void removeStartingWindow() {
596        synchronized (mWindowMap) {
597            if (mContainer.startingWindow == null) {
598                if (mContainer.startingData != null) {
599                    // Starting window has not been added yet, but it is scheduled to be added.
600                    // Go ahead and cancel the request.
601                    if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM,
602                            "Clearing startingData for token=" + mContainer);
603                    mContainer.startingData = null;
604                }
605                return;
606            }
607
608            final StartingSurface surface;
609            if (mContainer.startingData != null) {
610                surface = mContainer.startingSurface;
611                mContainer.startingData = null;
612                mContainer.startingSurface = null;
613                mContainer.startingWindow = null;
614                mContainer.startingDisplayed = false;
615                if (surface == null) {
616                    if (DEBUG_STARTING_WINDOW) {
617                        Slog.v(TAG_WM, "startingWindow was set but startingSurface==null, couldn't "
618                                + "remove");
619                    }
620                    return;
621                }
622            } else {
623                if (DEBUG_STARTING_WINDOW) {
624                    Slog.v(TAG_WM, "Tried to remove starting window but startingWindow was null:"
625                            + mContainer);
626                }
627                return;
628            }
629
630            if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Schedule remove starting " + mContainer
631                    + " startingWindow=" + mContainer.startingWindow
632                    + " startingView=" + mContainer.startingSurface
633                    + " Callers=" + Debug.getCallers(5));
634
635            // Use the same thread to remove the window as we used to add it, as otherwise we end up
636            // with things in the view hierarchy being called from different threads.
637            mService.mAnimationHandler.post(() -> {
638                if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Removing startingView=" + surface);
639                try {
640                    surface.remove();
641                } catch (Exception e) {
642                    Slog.w(TAG_WM, "Exception when removing starting window", e);
643                }
644            });
645        }
646    }

執(zhí)行銷毀startingwindow操作:
mContainer 把對應(yīng)屬性置空,StartingSurface本身remove。

流程簡單示意如下:

StartingWindow銷毀流程
三、 App StartingWindow 的處理方式:

1 不做任何操作,那么會使用系統(tǒng)默認(rèn)的StartingWindow. 但是背景是默認(rèn)的,可能跟app啟動頁形成色差。

2 自定義StartingWindow

       <style name="WelcomeTheme" parent="@style/AppTheme">
              <item name="android:windowBackground">@mipmap/ic_splash</item>
       </style>

主要設(shè)置這兩個屬性,bg可設(shè)顏色 或者 與啟動頁一致的背景圖。

3 禁止使用startingWindow

  <style name="WelcomeTheme" parent="AppTheme">
       <item name="android:windowDisablePreview">true</item>
</style>

4 使用透明背景startingWindow

<style name="WelcomeTheme" parent="AppTheme">
     <item name="android:windowIsTranslucent">true</item>
</style>

目前看小部分主流app是禁用的,高配手機(jī)上并沒有感覺到明顯的差別,可能低端手機(jī)會有區(qū)別吧,這個可以自行驗證,如果想增強(qiáng)第一幀的顯示體驗就加上,另外它本身并不會影響到冷啟時間。

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

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