美文网首页
SystemUI之Quickstep探索(手势启动篇)

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

作者: Soetsu | 来源:发表于2019-01-26 17:33 被阅读0次

    前篇常规启动传送门:
    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需要研究的知识点太多了,后续有待更深入的分析。

    相关文章

      网友评论

          本文标题:SystemUI之Quickstep探索(手势启动篇)

          本文链接:https://www.haomeiwen.com/subject/ujscjqtx.html