SystemUI之Quickstep探索(手势启动篇)

前篇常规启动传送门:
SystemUI之QuickStep探索(常规启动篇)

通过上划手势启动最近任务的方式:

手势启动

SystemUI部分

上划手势的起始点是在导航栏上,所以先从NavigationBarView上看起。

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (shouldDeadZoneConsumeTouchEvents(event)) {
            return true;
        }
        switch (event.getActionMasked()) {
            ...
        }
        // 交给NavigationBarGestureHelper处理
        return mGestureHelper.onInterceptTouchEvent(event);
    }

可以看到随后把event交给mGestureHelper处理,mGestureHelper是一个NavigationBarGestureHelper类的对象,onInterceptTouchEvent处理流程:

    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
            mIsInScreenPinning = mNavigationBarView.inScreenPinning();
            mNotificationsVisibleOnDown = !mStatusBar.isPresenterFullyCollapsed();
        }
        if (!canHandleGestures()) {
            return false;
        }
        // 交给QuickStepController处理
        boolean result = mQuickStepController.onInterceptTouchEvent(event);
        if (mDockWindowEnabled) {
            result |= interceptDockWindowEvent(event);
        }
        return result;
    }

然后又传到了QuickStepController中:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        return handleTouchEvent(event);
    }

    private boolean handleTouchEvent(MotionEvent event) {
        // 如果没有绑定的service,或者quickstep配置关闭了,则跳过不处理
        if (mOverviewEventSender.getProxy() == null || (!mNavigationBarView.isQuickScrubEnabled()
                && !mNavigationBarView.isQuickStepSwipeUpEnabled())) {
            return false;
        }
        mNavigationBarView.requestUnbufferedDispatch(event);

        int action = event.getActionMasked();
        switch (action) {
            case MotionEvent.ACTION_DOWN: {
                int x = (int) event.getX();
                int y = (int) event.getY();

                // End any existing quickscrub animations before starting the new transition
                if (mTrackAnimator != null) {
                    mTrackAnimator.end();
                    mTrackAnimator = null;
                }

                // 以下判断是点击还是长按
                mCurrentNavigationBarView = mNavigationBarView.getCurrentView();
                mHitTarget = mNavigationBarView.getButtonAtPosition(x, y);
                if (mHitTarget != null) {
                    // Pre-emptively delay the touch feedback for the button that we just touched
                    mHitTarget.setDelayTouchFeedback(true);
                }
                mTouchDownX = x;
                mTouchDownY = y;
                mTransformGlobalMatrix.set(Matrix.IDENTITY_MATRIX);
                mTransformLocalMatrix.set(Matrix.IDENTITY_MATRIX);
                mNavigationBarView.transformMatrixToGlobal(mTransformGlobalMatrix);
                mNavigationBarView.transformMatrixToLocal(mTransformLocalMatrix);
                mQuickStepStarted = false;
                mAllowGestureDetection = true;
                break;
            }
            case MotionEvent.ACTION_MOVE: {
                if (mQuickStepStarted || !mAllowGestureDetection){
                    break;
                }
                int x = (int) event.getX();
                int y = (int) event.getY();
                int xDiff = Math.abs(x - mTouchDownX);
                int yDiff = Math.abs(y - mTouchDownY);

                boolean exceededScrubTouchSlop, exceededSwipeUpTouchSlop;
                int pos, touchDown, offset, trackSize;

                // 判断竖屏和横屏状态时的触摸偏移量是否足够触发quickstep
                if (mIsVertical) {
                    exceededScrubTouchSlop =
                            yDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && yDiff > xDiff;
                    exceededSwipeUpTouchSlop =
                            xDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && xDiff > yDiff;
                    pos = y;
                    touchDown = mTouchDownY;
                    offset = pos - mTrackRect.top;
                    trackSize = mTrackRect.height();
                } else {
                    exceededScrubTouchSlop =
                            xDiff > NavigationBarCompat.getQuickScrubTouchSlopPx() && xDiff > yDiff;
                    exceededSwipeUpTouchSlop =
                            yDiff > NavigationBarCompat.getQuickStepTouchSlopPx() && yDiff > xDiff;
                    pos = x;
                    touchDown = mTouchDownX;
                    offset = pos - mTrackRect.left;
                    trackSize = mTrackRect.width();
                }
                // 启动quickstep
                // Decide to start quickstep if dragging away from the navigation bar, otherwise in
                // the parallel direction, decide to start quickscrub. Only one may run.
                if (!mQuickScrubActive && exceededSwipeUpTouchSlop) {
                    if (mNavigationBarView.isQuickStepSwipeUpEnabled()) {
                        startQuickStep(event);
                    }
                    break;
                }

                // Do not handle quick scrub if disabled
                if (!mNavigationBarView.isQuickScrubEnabled()) {
                    break;
                }

                if (!mDragPositive) {
                    offset -= mIsVertical ? mTrackRect.height() : mTrackRect.width();
                }

                final boolean allowDrag = !mDragPositive
                        ? offset < 0 && pos < touchDown : offset >= 0 && pos > touchDown;
                float scrubFraction = Utilities.clamp(Math.abs(offset) * 1f / trackSize, 0, 1);
                // 启动quickscrub
                if (allowDrag) {
                    // Passing the drag slop then touch slop will start quick step
                    if (!mQuickScrubActive && exceededScrubTouchSlop) {
                        startQuickScrub();
                    }
                }

                // 左右滑动切换任务
                if (mQuickScrubActive && (mDragPositive && offset >= 0
                        || !mDragPositive && offset <= 0)) {
                    try {
                        mOverviewEventSender.getProxy().onQuickScrubProgress(scrubFraction);
                        if (DEBUG_OVERVIEW_PROXY) {
                            Log.d(TAG_OPS, "Quick Scrub Progress:" + scrubFraction);
                        }
                    } catch (RemoteException e) {
                        Log.e(TAG, "Failed to send progress of quick scrub.", e);
                    }
                }
                break;
            }
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                // 结束quickscrub,回到应用
                endQuickScrub(true /* animate */);
                break;
        }

        // Proxy motion events to launcher if not handled by quick scrub
        // Proxy motion events up/cancel that would be sent after long press on any nav button
        if (!mQuickScrubActive && (mAllowGestureDetection || action == MotionEvent.ACTION_CANCEL
                || action == MotionEvent.ACTION_UP)) {
            // 上划event事件传递给launcher的quickstep处理
            proxyMotionEvents(event);
        }
        return mQuickScrubActive || mQuickStepStarted;
    }

在handleTouchEvent()里,首先会判断quickstep是否可用,在常规启动的文章中,已经大致讲过了SystemUI的OverviewProxyService与Launcher的TouchInteractionService之间的关系,这边就不再赘述了。其他的enable属性在framework力都有对应的config属性可以配置。

随后对event的action进行判断处理:

  1. DOWN:记录起始位置的x和y,记录后续是点击还是长按,初始化一些flag
  2. MOVE:首先根据move的坐标判断滑动的偏移量是否足够触发quickstep,如果满足条件的话则调用启动quickstep的函数,否则继续判断是否足够触发quickscrub(quickscrub是指在导航栏上左右划动快速切换最近任务卡片),满足条件则启动quickscrub,并计算划动量具体切换到哪张卡片
  3. UP&CANCEL:如果当前是quickscrub,啧结束quickscrub流程;如果当前是quickstep,则将event通过proxyMotionEvents()函数传递给Launcher的service处理
    private void startQuickStep(MotionEvent event) {
        mQuickStepStarted = true;
        event.transform(mTransformGlobalMatrix);
        try {
            mOverviewEventSender.getProxy().onQuickStep(event);
            if (DEBUG_OVERVIEW_PROXY) {
                Log.d(TAG_OPS, "Quick Step Start");
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to send quick step started.", e);
        } finally {
            event.transform(mTransformLocalMatrix);
        }
        mOverviewEventSender.notifyQuickStepStarted();
        mHandler.removeCallbacksAndMessages(null);
        ...
    }

    private boolean proxyMotionEvents(MotionEvent event) {
        final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
        event.transform(mTransformGlobalMatrix);
        try {
            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                overviewProxy.onPreMotionEvent(mNavigationBarView.getDownHitTarget());
            }
            overviewProxy.onMotionEvent(event);
            if (DEBUG_OVERVIEW_PROXY) {
                Log.d(TAG_OPS, "Send MotionEvent: " + event.toString());
            }
            return true;
        }
        ...
        return false;
    }

可以看到这几个函数最后都要通过OverviewProxyService的SystemUIProxy调用Launcher端的TouchInteractionService来处理。

Launcher部分

TouchInteractionService

    private final IBinder mMyBinder = new IOverviewProxy.Stub() {

        @Override
        public void onPreMotionEvent(@HitTarget int downHitTarget) throws RemoteException {
            TraceHelper.beginSection("SysUiBinder");
            setupTouchConsumer(downHitTarget);
            TraceHelper.partitionSection("SysUiBinder", "Down target " + downHitTarget);
        }

        @Override
        public void onMotionEvent(MotionEvent ev) {
            mEventQueue.queue(ev);

            String name = sMotionEventNames.get(ev.getActionMasked());
            if (name != null){
                TraceHelper.partitionSection("SysUiBinder", name);
            }
        }

        @Override
        public void onBind(ISystemUiProxy iSystemUiProxy) {
            mISystemUiProxy = iSystemUiProxy;
            mRecentsModel.setSystemUiProxy(mISystemUiProxy);
            mOverviewInteractionState.setSystemUiProxy(mISystemUiProxy);
        }

        @Override
        public void onQuickScrubStart() {
            mEventQueue.onQuickScrubStart();
            TraceHelper.partitionSection("SysUiBinder", "onQuickScrubStart");
        }

        @Override
        public void onQuickScrubProgress(float progress) {
            mEventQueue.onQuickScrubProgress(progress);
        }

        @Override
        public void onQuickScrubEnd() {
            mEventQueue.onQuickScrubEnd();
            TraceHelper.endSection("SysUiBinder", "onQuickScrubEnd");
        }

        @Override
        public void onOverviewToggle() {
            mOverviewCommandHelper.onOverviewToggle();
        }

        @Override
        public void onOverviewShown(boolean triggeredFromAltTab) {
            if (triggeredFromAltTab) {
                setupTouchConsumer(HIT_TARGET_NONE);
                mEventQueue.onOverviewShownFromAltTab();
            } else {
                mOverviewCommandHelper.onOverviewShown();
            }
        }

        @Override
        public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
            if (triggeredFromAltTab && !triggeredFromHomeKey) {
                // onOverviewShownFromAltTab initiates quick scrub. Ending it here.
                mEventQueue.onQuickScrubEnd();
            }
        }

        @Override
        public void onQuickStep(MotionEvent motionEvent) {
            mEventQueue.onQuickStep(motionEvent);
            TraceHelper.endSection("SysUiBinder", "onQuickStep");

        }

        @Override
        public void onTip(int actionType, int viewType) {
            mOverviewCommandHelper.onTip(actionType, viewType);
        }
    };

以上是所有在Launcher端实现的SystemUIProxy接口对应的实现函数,其中onOverviewToggle是上一章中讲过的常规启动方式调用的地方,而另外几个这次涉及到的函数,无一例外都和mEventQueue有关。mEventQueue是MotionEventQueue类的对象,类似SystemUI的CommandQueue,在这个类里每个被调用的函数都有一个定义好的action(queue() 比较特殊,他是处理event事件的函数,所以直接用event里的action),随后用Choreographer处理启动quickstep相关的动画。

关于Android系统的编舞者Choreographer类,可参考下面的文章了解,本文中不再细讲。
Android8.1 Choreographer机制与源码分析

MotionEventQueue

    private void runFor(Choreographer caller) {
        synchronized (mExecutionLock) {
            EventArray array = swapAndGetCurrentArray(caller);
            int size = array.size();
            for (int i = 0; i < size; i++) {
                MotionEvent event = array.get(i);
                // 自定义的action类型
                if (event.getActionMasked() == ACTION_VIRTUAL) {
                    switch (event.getAction()) {
                        case ACTION_QUICK_SCRUB_START:
                            mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
                            break;
                        case ACTION_QUICK_SCRUB_PROGRESS:
                            mConsumer.onQuickScrubProgress(event.getX());
                            break;
                        case ACTION_QUICK_SCRUB_END:
                            mConsumer.onQuickScrubEnd();
                            break;
                        case ACTION_RESET:
                            // 对应Service的onPreMotionEvent,初始化Consumer
                            mConsumer.reset();
                            break;
                        case ACTION_DEFER_INIT:
                            mConsumer.deferInit();
                            break;
                        case ACTION_SHOW_OVERVIEW_FROM_ALT_TAB:
                            mConsumer.onShowOverviewFromAltTab();
                            mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
                            break;
                        case ACTION_QUICK_STEP:
                            // 对应Service的onQuickStep
                            mConsumer.onQuickStep(event);
                            break;
                        case ACTION_COMMAND:
                            mConsumer.onCommand(event.getSource());
                            break;
                        default:
                            Log.e(TAG, "Invalid virtual event: " + event.getAction());
                    }
                // MotionEvent类型的action
                } else {
                    // 对应Service的onMotionEvent
                    mConsumer.accept(event);
                }
                event.recycle();
            }
            array.clear();
            array.lastEventAction = ACTION_CANCEL;
        }
    }

mConsumer为OtherActivityTouchConsumer

【注意】:这个函数里面的event,无论是新实例化的含有自定义action的,还是通过SystemUI参数传过来的,在处理完毕后都必须调用recycle(),新实例化的自不用说,就算是SystemUI里onInterceptTouchEvent的参数,在跨进程通讯的Service里,参数都必须是实现Parcelable,且会被重新序列化,也相当于一个新的对象了,因此原先onInterceptTouchEvent周期里自动recycle()并不适用于现在的对象,所以需要手动去recycle()回收。

最后再看下onQuickStep的流程吧

    @Override
    public void onQuickStep(MotionEvent ev) {
        if (mIsDeferredDownTarget) {
            // Deferred gesture, start the animation and gesture tracking once we pass the actual
            // touch slop
            // 启动最近任务以及对应的缩放渐隐动画
            startTouchTrackingForWindowAnimation(ev.getEventTime());
            mPassedInitialSlop = true;
            mStartDisplacement = getDisplacement(ev);
        }
        notifyGestureStarted();
    }

    private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
        // Create the shared handler
        RecentsAnimationState animationState = new RecentsAnimationState();
        final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler(
                animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper);

        ...
        // 启动launcher的recent activity
        Runnable startActivity = () -> ActivityManagerWrapper.getInstance().startRecentsActivity(
                mHomeIntent,
                new AssistDataReceiver() {
                    @Override
                    public void onHandleAssistData(Bundle bundle) {
                        mRecentsModel.preloadAssistData(mRunningTask.id, bundle);
                    }
                }, animationState, null, null);

        if (Looper.myLooper() != Looper.getMainLooper()) {
            startActivity.run();
            try {
                drawWaitLock.await(LAUNCHER_DRAW_TIMEOUT_MS, TimeUnit.MILLISECONDS);
            } catch (Exception e) {
                // We have waited long enough for launcher to draw
            }
        } else {
            // We should almost always get touch-town on background thread. This is an edge case
            // when the background Choreographer has not yet initialized.
            BackgroundExecutor.get().submit(startActivity);
        }
    }

可以看到这边就是最后启动recent activity的地方了,其中mHomeIntent里的componentName为OverviewCommandHelper里设置的,具体可查看上一章常规启动篇最后的initOverviewTargets.

总结

以上就是Quickstep的手势启动流程了,因为启动过程是伴随着动画的,所以其中涉及到的动画相关的代码非常多,这些本文中暂时都没有细讲,Quickstep需要研究的知识点太多了,后续有待更深入的分析。

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

推荐阅读更多精彩内容