Android 13 平板Taskbar加载流程

学习笔记:

从 Android 12开始,如果是大屏底下导航栏会变成显示一个任务栏,从 NavigationBar 变成 Taskbar 。

注:CentralSurfacesImpl.java 与 原来的 statusbar.java 的作用一样。

CentralSurfacesImpl 的启动流程前面有说过,这里不在说明。

// CentralSurfacesImpl.java
public class CentralSurfacesImpl extends CoreStartable implements
        CentralSurfaces {
     //省略其他代码....

    protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
        //省略其他代码....
        createNavigationBar(result);
       //省略其他代码....
  
    }

    protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
        // 调用 NavigationBarController 的 createNavigationBars()方法创建 NavigationBar
        mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);
    }

}

接着往下看:
NavigationBarController#createNavigationBars()

// NavigationBarController.java

    public void createNavigationBars(final boolean includeDefaultDisplay,
            RegisterStatusBarResult result) {
        // 如果需要,更新辅助功能按钮模式
        updateAccessibilityButtonModeIfNeeded();

        // 这里是重点,Android 12 之前版本的,没有这个判断。可以看下 initializeTaskbarIfNecessary() 方法。
        // 如果我们初始化TaskBar,则不需要在默认显示上创建导航栏
        final boolean shouldCreateDefaultNavbar = includeDefaultDisplay
                && !initializeTaskbarIfNecessary();
        Display[] displays = mDisplayManager.getDisplays();
        for (Display display : displays) {
            if (shouldCreateDefaultNavbar || display.getDisplayId() != DEFAULT_DISPLAY) {
                createNavigationBar(display, null /* savedState */, result);
            }
        }
    }

    /** @return {@code true} if taskbar is enabled, false otherwise */
    private boolean initializeTaskbarIfNecessary() {
        // 这里会判断是否是平板。
        // 跟踪代码发现,如果屏幕的(最小边长度*160/dpi值)< 600,就会判断为设备是平板设备
        // system/vendor/mediatek/proprietary/packages/apps/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
        if (mIsTablet) {
            Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary");
            // 移除导航栏
            removeNavigationBar(mContext.getDisplayId());
            // 加载任务栏
            mTaskbarDelegate.init(mContext.getDisplayId());
            Trace.endSection();
        } else {
            mTaskbarDelegate.destroy();
        }
        return mIsTablet;
    }

我们分析平板,所以这里直接看 mTaskbarDelegate.init(mContext.getDisplayId()) 方法;
TaskbarDelegate#init()

// TaskbarDelegate.java
    public void init(int displayId) {
        if (mInitialized) {
            return;
        }
        //省略其他代码....

        // 注册监听,切换导航模式时接收回调。
        mEdgeBackGestureHandler.onNavigationModeChanged(
                mNavigationModeController.addListener(this));
        // 边缘手势处理,注意:这里只加载、处理手势。
        // 手势中左右两返回图标由这里加载,底部黑色小横条不在这里加载。
        mEdgeBackGestureHandler.onNavBarAttached();

        //省略其他代码....
    }


    // 导航模式改变回调,进行处理
    @Override
    public void onNavigationModeChanged(int mode) {
        mNavigationMode = mode;
        mEdgeBackGestureHandler.onNavigationModeChanged(mode);
    }

1、手势加载
EdgeBackGestureHandler#onNavBarAttached()

// EdgeBackGestureHandler.java

    /**
     * @see NavigationBarView#onAttachedToWindow()
     */
    public void onNavBarAttached() {
        mIsAttached = true;
        mProtoTracer.add(this);
        mOverviewProxyService.addCallback(mQuickSwitchListener);
        mSysUiState.addCallback(mSysUiStateCallback);
        // 重点关注这里,更新视图
        updateIsEnabled();
        startTracking();
    }


    private void updateIsEnabled() {
        // mIsAttached 是否已经添加上了,mIsGesturalModeEnabled 是否启用手势模式
        boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;
        if (isEnabled == mIsEnabled) {
            return;
        }
        mIsEnabled = isEnabled;
        // 如果无效的话结束监听 Input
        disposeInputChannel();

        if (mEdgeBackPlugin != null) {
            // 边缘导航栏销毁
            mEdgeBackPlugin.onDestroy();
            mEdgeBackPlugin = null;
        }

        // 是否启用手势模式
        if (!mIsEnabled) {
             // 注销监听返回手势参数的设置变化
            mGestureNavigationSettingsObserver.unregister();
            if (DEBUG_MISSING_GESTURE) {
                Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener");
            }
            mPluginManager.removePluginListener(this);
            TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
            DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);

            try {
                 // 注销 WMS 里保存的除外区域监听
                mWindowManagerService.unregisterSystemGestureExclusionListener(
                        mGestureExclusionListener, mDisplayId);
            } catch (RemoteException | IllegalArgumentException e) {
                Log.e(TAG, "Failed to unregister window manager callbacks", e);
            }

        } else {
            // 监听返回手势参数的设置变化
            mGestureNavigationSettingsObserver.register();
            updateDisplaySize();
            if (DEBUG_MISSING_GESTURE) {
                Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener");
            }
            TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
                    mMainExecutor::execute, mOnPropertiesChangedListener);

            try {
                // 监听 WMS 里保存的除外区域
                mWindowManagerService.registerSystemGestureExclusionListener(
                        mGestureExclusionListener, mDisplayId);
            } catch (RemoteException | IllegalArgumentException e) {
                Log.e(TAG, "Failed to register window manager callbacks", e);
            }

            //  注册名为 edge-swipe 的InputMonitor
            mInputMonitor = InputManager.getInstance().monitorGestureInput(
                    "edge-swipe", mDisplayId);
            mInputEventReceiver = new InputChannelCompat.InputEventReceiver(
                    mInputMonitor.getInputChannel(), Looper.getMainLooper(),
                    Choreographer.getInstance(), this::onInputEvent);

            // 添加 NavigationBarEdgePanel 为 Edge Back 事件的处理实现
            setEdgeBackPlugin(
                    new NavigationBarEdgePanel(mContext, mBackAnimation, mLatencyTracker));
            mPluginManager.addPluginListener(
                    this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
        }
        // 更新 ML 模型资源.
        updateMLModelState();
    }

    // 创建返回手势视图
    private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
        if (mEdgeBackPlugin != null) {
            mEdgeBackPlugin.onDestroy();
        }
        // 缓存 NavigationEdgeBackPlugin 实现
        mEdgeBackPlugin = edgeBackPlugin;
        // 向 NavigationEdgeBackPlugin 注册 Back 手势的触发回调
        mEdgeBackPlugin.setBackCallback(mBackCallback);
        // 准备好手势视图的 Window 参数
        mEdgeBackPlugin.setLayoutParams(createLayoutParams());
        updateDisplaySize();
    }
    
    // 配置返回手势 Window 的参数
    // 包括 flag、type、title 等属性
    private WindowManager.LayoutParams createLayoutParams() {
        Resources resources = mContext.getResources();
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_width),
                resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_height),
                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
                PixelFormat.TRANSLUCENT);
        ...
        layoutParams.setTitle(TAG + mContext.getDisplayId());
        layoutParams.setFitInsetsTypes(0 /* types */);
        layoutParams.setTrustedOverlay();
        return layoutParams;
    }

到此手势导航创建完成,注意:此时 View 还是不可见的,后续事件产生的时候会进行展示和刷新。

下面粗略的看下手势相关的事件:

// EdgeBackGestureHandler.java

    private void onMotionEvent(MotionEvent ev) {
        int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {
            mInputEventReceiver.setBatchingEnabled(false);
            mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
            mMLResults = 0;
            mLogGesture = false;
            mInRejectedExclusion = false;
            boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());
            // 根据返回手势是否有效、
            // 点击区域是否是停用区域等条件
            // 得到当前是否允许视图处理该手势
            mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed && isWithinInsets
                    && !mGestureBlockingActivityRunning
                    && !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
                    && isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
            if (mAllowGesture) {
                // 更新当前是屏幕左侧还是右侧
                mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
                // 发射事件给视图
                mEdgeBackPlugin.onMotionEvent(ev);
            }
        } else if (mAllowGesture || mLogGesture) {
            if (!mThresholdCrossed) {
                mEndPoint.x = (int) ev.getX();
                mEndPoint.y = (int) ev.getY();
                // 多个手指按下的话取消事件处理
                if (action == MotionEvent.ACTION_POINTER_DOWN) {
                    if (mAllowGesture) {
                        logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH);
                        cancelGesture(ev);
                    }
                    mLogGesture = false;
                    return;
                } else if (action == MotionEvent.ACTION_MOVE) {
                    // 手指移动超过长按阈值的话
                    // 也要取消事件处理
                    if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) {
                        if (mAllowGesture) {
                            logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_LONG_PRESS);
                            cancelGesture(ev);
                        }
                        mLogGesture = false;
                        return;
                    }
                    ...
                }
            }

            // 通过上述检查的话
            // 将 MOVE、UP 交给视图
            if (mAllowGesture) {
                // forward touch
                mEdgeBackPlugin.onMotionEvent(ev);
            }
        }
        mProtoTracer.scheduleFrameUpdate();
    }

事件间传递到 NavigationBarEdgePanel ,进行展示返回手势和触发返回。

NavigationBarEdgePanel 继续进行后面的工作:手势的判断、视图的刷新、动画的展示。

onMotionEvent() 的逻辑:

  • DOWN 的时候先让视图变为可见 VISIBLE
  • MOVE 的处理通过 handleMoveEvent() 判断距离,决定是否要更新赋予 mTriggerBack
  • UP 的时候将检查该变量决定是否触发返回动作即 triggerBack()
// NavigationBarEdgePanel.java
    public void onMotionEvent(MotionEvent event) {
        if (mVelocityTracker == null) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                mDragSlopPassed = false;
                resetOnDown();
                mStartX = event.getX();
                mStartY = event.getY();
                setVisibility(VISIBLE);
                updatePosition(event.getY());
                mRegionSamplingHelper.start(mSamplingRect);
                mWindowManager.updateViewLayout(this, mLayoutParams);
                break;
            case MotionEvent.ACTION_MOVE:
                handleMoveEvent(event);
                break;
            case MotionEvent.ACTION_UP:
                if (mTriggerBack) {
                    triggerBack();
                } else {
                    cancelBack();
                }
                ...
        }
    }

手势相关的事件流程到此打住了。

2、按钮导航
按钮导航是在 TouchInteractionService.java 中的 onCreate() 方法开始的。

// TouchInteractionService.java
    @Override
    public void onCreate() {
        super.onCreate();
        //省略其他代码....
        
        mTaskbarManager = new TaskbarManager(this);

        //省略其他代码....
    }

用户配置完成后,根据配置,是否加载按钮导航。如果加载则会调用 TaskbarManager.java中的 recreateTaskbar() 方法。
TaskbarManager#recreateTaskbar()

// TaskbarManager.java
    private void recreateTaskbar() {
        // 销毁现有任务栏
        destroyExistingTaskbar();
        // 获取设备配置文件的
        DeviceProfile dp =
                mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
        // 任务栏是否启用
        boolean isTaskBarEnabled = dp != null && dp.isTaskbarPresent;
        if(dp != null){
            Log.d("yexiao","yexiao"+(dp != null)+"----"+(dp.isTaskbarPresent));
        }
        if (!isTaskBarEnabled) {
            SystemUiProxy.INSTANCE.get(mContext)
                    .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
            return;
        }
        // TaskbarActivityContext 按钮导航的视图在里面加载,包括那个底部横线也在里面。
        mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController,
                mUnfoldProgressProvider);
        // 初始化
        mTaskbarActivityContext.init(mSharedState);
        if (mActivity != null) {
            // 设置相关控制器
            mTaskbarActivityContext.setUIController(
                    createTaskbarUIControllerForActivity(mActivity));
        }
    }

在 TaskbarManager 类中有个 mDispInfoChangeListener 的监听,当导航模式切换时,会在 DisplayController 类中 去回调这里来。

接着往下看TaskbarActivityContext的构造方法:

    public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
            TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
            unfoldTransitionProgressProvider) {
        super(windowContext);
        mDeviceProfile = dp.copy(this);

        final Resources resources = getResources();

        mNavMode = DisplayController.getNavigationMode(windowContext);
        mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
        mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
                () -> getPackageManager().isSafeMode());
        mIsUserSetupComplete = SettingsCache.INSTANCE.get(this).getValue(
                Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
        mIsNavBarForceVisible = SettingsCache.INSTANCE.get(this).getValue(
                Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
        mIsNavBarKidsMode = SettingsCache.INSTANCE.get(this).getValue(
                Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);

        updateIconSize(resources);

        // 首先获取显示和焦点,因为视图可能会在构造函数中使用它们。
        Display display = windowContext.getDisplay();
        Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
                ? windowContext.getApplicationContext()
                : windowContext.getApplicationContext().createDisplayContext(display);
        mWindowManager = c.getSystemService(WindowManager.class);
        mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
        mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);

        // 初始化 views.
        mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(
                R.layout.taskbar, null, false);
        TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
        TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim);
        FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
        StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);

        mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this);

        // 构造控制器 controllers,将会在对应的控制添加对应的图标,以控制逻辑
        mControllers = new TaskbarControllers(this,
                new TaskbarDragController(this),
                buttonController,
                getPackageManager().hasSystemFeature(FEATURE_PC)
                        ? new DesktopNavbarButtonsViewController(this, navButtonsView) :     
                        new NavbarButtonsViewController(this, navButtonsView),
                new RotationButtonController(this,
                        c.getColor(R.color.taskbar_nav_icon_light_color),
                        c.getColor(R.color.taskbar_nav_icon_dark_color),
                        R.drawable.ic_sysbar_rotate_button_ccw_start_0,
                        R.drawable.ic_sysbar_rotate_button_ccw_start_90,
                        R.drawable.ic_sysbar_rotate_button_cw_start_0,
                        R.drawable.ic_sysbar_rotate_button_cw_start_90,
                        () -> getDisplay().getRotation()),
                new TaskbarDragLayerController(this, mDragLayer),
                new TaskbarViewController(this, taskbarView),
                new TaskbarScrimViewController(this, taskbarScrimView),
                new TaskbarUnfoldAnimationController(this, unfoldTransitionProgressProvider,
                        mWindowManager, WindowManagerGlobal.getWindowManagerService()),
                new TaskbarKeyguardController(this),
                new StashedHandleViewController(this, stashedHandleView),
                new TaskbarStashController(this),
                new TaskbarEduController(this),
                new TaskbarAutohideSuspendController(this),
                new TaskbarPopupController(this),
                new TaskbarForceVisibleImmersiveController(this),
                new TaskbarAllAppsController(this, dp),
                new TaskbarInsetsController(this));
    }

    public void init(@NonNull TaskbarSharedState sharedState) {
        mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
        mWindowLayoutParams = createDefaultWindowLayoutParams();

        //  在 TaskbarControllers控制器里,初始化前面构造方法中添加的各种控制器。
        mControllers.init(sharedState);
        updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
        // 生成 LayoutParams 用于将视图作为新窗口直接添加到 WindowManager
        mWindowManager.addView(mDragLayer, mWindowLayoutParams);
    }

这里以 NavbarButtonsViewController 为例子分析:

// NavbarButtonsViewController.java

    /**
     * Initializes the controller
     */
    public void init(TaskbarControllers controllers) {

         // 省略部分代码......

        // 强制导航按钮(特别是后退按钮)在设置向导期间可见。
        boolean isInSetup = !mContext.isUserSetupComplete();
        boolean isInKidsMode = mContext.isNavBarKidsModeActive();
        boolean alwaysShowButtons = isThreeButtonNav || isInSetup;

        // 省略部分代码......
         
        if (alwaysShowButtons) {
            // 初始化按钮
            initButtons(mNavButtonContainer, mEndContextualContainer,
                    mControllers.navButtonController);


        } else {
          
        }

        // 省略部分代码......
        mSeparateWindowParent.recreateControllers();
    }


    private void initButtons(ViewGroup navContainer, ViewGroup endContainer,
            TaskbarNavButtonController navButtonController) {
        // back Button
        mBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,
                mNavButtonContainer, mControllers.navButtonController, R.id.back);
        mBackButtonAlpha = new MultiValueAlpha(mBackButton, NUM_ALPHA_CHANNELS);
        mBackButtonAlpha.setUpdateVisibility(true);
        mPropertyHolders.add(new StatePropertyHolder(
                mBackButtonAlpha.getProperty(ALPHA_INDEX_KEYGUARD_OR_DISABLE),
                flags -> {
                    // Show only if not disabled, and if not on the keyguard or otherwise only when
                    // the bouncer or a lockscreen app is showing above the keyguard
                    boolean showingOnKeyguard = (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||
                            (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 ||
                            (flags & FLAG_KEYGUARD_OCCLUDED) != 0;
                    return (flags & FLAG_DISABLE_BACK) == 0
                            && ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard);
                }));
        boolean isRtl = Utilities.isRtl(mContext.getResources());
        mPropertyHolders.add(new StatePropertyHolder(mBackButton,
                flags -> (flags & FLAG_IME_VISIBLE) != 0 && !mContext.isNavBarKidsModeActive(),
                View.ROTATION, isRtl ? 90 : -90, 0));
        // Translate back button to be at end/start of other buttons for keyguard
        int navButtonSize = mContext.getResources().getDimensionPixelSize(
                R.dimen.taskbar_nav_buttons_size);
        mPropertyHolders.add(new StatePropertyHolder(
                mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0
                        || (flags & FLAG_KEYGUARD_VISIBLE) != 0,
                VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));

        // home button
        mHomeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,
                navButtonController, R.id.home);
        mHomeButtonAlpha = new MultiValueAlpha(mHomeButton, NUM_ALPHA_CHANNELS);
        mHomeButtonAlpha.setUpdateVisibility(true);
        mPropertyHolders.add(
                new StatePropertyHolder(mHomeButtonAlpha.getProperty(
                        ALPHA_INDEX_KEYGUARD_OR_DISABLE),
                flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&
                        (flags & FLAG_DISABLE_HOME) == 0));

        // Recents button
        View recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,
                navContainer, navButtonController, R.id.recent_apps);
        mHitboxExtender.init(recentsButton, mNavButtonsView, mContext.getDeviceProfile(),
                () -> {
                    float[] recentsCoords = new float[2];
                    getDescendantCoordRelativeToAncestor(recentsButton, mNavButtonsView,
                            recentsCoords, false);
                    return recentsCoords;
                }, new Handler());
        recentsButton.setOnClickListener(v -> {
            navButtonController.onButtonClick(BUTTON_RECENTS);
            mHitboxExtender.onRecentsButtonClicked();
        });
        mPropertyHolders.add(new StatePropertyHolder(recentsButton,
                flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && (flags & FLAG_DISABLE_RECENTS) == 0
                        && !mContext.isNavBarKidsModeActive()));

        // A11y button
        mA11yButton =  (R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,
                endContainer, navButtonController, R.id.accessibility_button,
                R.layout.taskbar_contextual_button);
        mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
                flags -> (flags & FLAG_A11Y_VISIBLE) != 0
                        && (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
    }

事件的在 addButton() 的时候直接设置了,而且有特殊需求的,会单独设置。

至此简单分析完成

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

推荐阅读更多精彩内容

  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,652评论 2 59
  • 原创:知识点总结性文章创作不易,请珍惜,之后会持续更新,不断完善个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈...
    时光啊混蛋_97boy阅读 2,799评论 0 3
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,067评论 1 32
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,244评论 8 265
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,107评论 29 470