4. 创建返回手势视图
InputMonitor 创建完毕之后,EdgeBackGestureHandler 将立即创建手势视图即 NavigationBarEdgePanel
实例。并通过 setEdgeBackPlugin()
将其缓存,同时准备好承载该视图的 Window 参数一并传递过去。
// EdgeBackGestureHandler.java 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; }
NavigationBarEdgePanel 构造函数将准备视图相关的描画、动画等相关初始化工作。
比如:
持有 WindowManager 为后续添加试图到 Window 上做准备
持有发出振动用的 mVibratorHelper,以进行后续的 click 振动
配置描画用的 Paint 属性
初始化返回箭头的颜色、淡入、角度动画
设置读取手势阈值 mSwipeThreshold
等
// NavigationBarEdgePanel.java public NavigationBarEdgePanel(Context context) { super(context); mWindowManager = context.getSystemService(WindowManager.class); mVibratorHelper = Dependency.get(VibratorHelper.class); ... mPaint.setStrokeWidth(mArrowThickness); mPaint.setStrokeCap(Paint.Cap.ROUND); ... mArrowColorAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); mArrowColorAnimator.setDuration(COLOR_ANIMATION_DURATION_MS); mArrowColorAnimator.addUpdateListener(animation -> { int newColor = ColorUtils.blendARGB( mArrowStartColor, mArrowColor, animation.getAnimatedFraction()); setCurrentArrowColor(newColor); }); mArrowDisappearAnimation = ValueAnimator.ofFloat(0.0f, 1.0f); mArrowDisappearAnimation.setDuration(DISAPPEAR_ARROW_ANIMATION_DURATION_MS); mArrowDisappearAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); mArrowDisappearAnimation.addUpdateListener(animation -> { mDisappearAmount = (float) animation.getAnimatedValue(); invalidate(); }); mAngleAnimation = new SpringAnimation(this, CURRENT_ANGLE); mAngleAppearForce = new SpringForce() .setStiffness(500) .setDampingRatio(0.5f); ... mSwipeThreshold = context.getResources() .getDimension(R.dimen.navigation_edge_action_drag_threshold); setVisibility(GONE); ... }
其后 NavigationBarEdgePanel 复写的 setLayoutParams() 会被 EdgeBackGestureHandler 调用。拿到 Handler 为其准备的 Window 参数后将本视图添加到一个专用 Window。
注意:此时 View 还是不可见的,后续事件产生的时候会进行展示和刷新。
// NavigationBarEdgePanel.java public void setLayoutParams(WindowManager.LayoutParams layoutParams) { mLayoutParams = layoutParams; mWindowManager.addView(this, mLayoutParams); }
5. 预处理 Touch 事件
当 InputDispatcher 收到 InputReader 传递过来的事件,在分发前会从 mGestureMonitorsByDisplay Map 中收集对应 Display 的 Monitor 实例,并将其中的 Server 端 InputChannel 一并放入到 Input Target 中。
// InputDispatcher.cpp InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked( ... ) { ... if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) { ... // 取出 InputMonitor std::vector<TouchedMonitor> newGestureMonitors = isDown ? findTouchedGestureMonitorsLocked(displayId, tempTouchState.portalWindows) : std::vector<TouchedMonitor>{}; ... newGestureMonitors = selectResponsiveMonitorsLocked(newGestureMonitors); ... if (newTouchedWindowHandle != nullptr) { ... tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds); } // 添加 Monitors 到 TouchedState tempTouchState.addGestureMonitors(newGestureMonitors); } ... // 将 TouchedState 中 Touched Window 添加到 InputTargets 中 for (const TouchedWindow& touchedWindow : tempTouchState.windows) { addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags, touchedWindow.pointerIds, inputTargets); } // 将 TouchedState 中 Monitors 添加到 InputTargets 中 for (const TouchedMonitor& touchedMonitor : tempTouchState.gestureMonitors) { addMonitoringTargetLocked(touchedMonitor.monitor, touchedMonitor.xOffset, touchedMonitor.yOffset, inputTargets); } ... return injectionResult; } // 从 mGestureMonitorsByDisplay map 中 // 按照 Display Id 取出 Vector 返回出去 std::vector<TouchedMonitor> InputDispatcher::findTouchedGestureMonitorsLocked( ... ) const { std::vector<TouchedMonitor> touchedMonitors; std::vector<Monitor> monitors = getValueByKey(mGestureMonitorsByDisplay, displayId); addGestureMonitors(monitors, touchedMonitors); for (const sp<InputWindowHandle>& portalWindow : portalWindows) { const InputWindowInfo* windowInfo = portalWindow->getInfo(); ... } return touchedMonitors; } // 提取 Monitor 中的 Server InputChannerl // 放入到 InputTarget Vector void InputDispatcher::addMonitoringTargetLocked( ... ) { InputTarget target; target.inputChannel = monitor.inputChannel; target.flags = InputTarget::FLAG_DISPATCH_AS_IS; ui::Transform t; t.set(xOffset, yOffset); target.setDefaultPointerTransform(t); inputTargets.push_back(target); }
之后 dispatchEventLocked 将遍历 InputTarget Vector 实例,逐一使用其 InputChannel 实例通过 Socket 向 App 进程和 SystemUI 进程发送事件。
// InputDispatcher.cpp void InputDispatcher::dispatchEventLocked( ... ) { ... for (const InputTarget& inputTarget : inputTargets) { sp<Connection> connection = getConnectionLocked(inputTarget.inputChannel->getConnectionToken()); if (connection != nullptr) { prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget); } } }
监听 Socket FD 写入的消费端 Looper 将触发 LooperCallback,进而从 Client 端 Socket 读取事件,最后通过 InputEventReceiver 回调。
// android_view_InputEventReceiver.cpp int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) { ... // 通过 Client Socket 读取事件 if (events & ALOOPER_EVENT_INPUT) { JNIEnv* env = AndroidRuntime::getJNIEnv(); status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr); mMessageQueue->raiseAndClearException(env, "handleReceiveCallback"); return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK; } ... return KEEP_CALLBACK; } status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) { ... for (;;) { // 通过 Client InputChannel 发出读取事件请求 status_t status = mInputConsumer.consume(&mInputEventFactory, consumeBatches, frameTime, &seq, &inputEvent); ... if (!skipCallbacks) { ... switch (inputEvent->getType()) { ... case AINPUT_EVENT_TYPE_MOTION: { MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent); if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) { *outConsumedBatch = true; } inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent); break; } ... // 调用 InputEventReceiver Java 端 // dispatchInputEvent() if (inputEventObj) { env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj); ... }... } } }
InputEventReceiver 的 dispatchInputEvent() 会回调 onInputEvent()。
// InputEventReceiver.java private void dispatchInputEvent(int seq, InputEvent event) { mSeqMap.put(event.getSequenceNumber(), seq); onInputEvent(event); }
onInputEvent()
作为 SystemUI 监视到系统 Input 事件回调的入口,将展开整个返回手势的判断、视图和动画的刷新以及返回事件的触发。
首先将检查一下是否是 Touch 的 MotionEvent 类型,之后交给onMotionEvent()
预处理。
// EdgeBackGestureHandler.java private void onInputEvent(InputEvent ev) { if (!(ev instanceof MotionEvent)) return; MotionEvent event = (MotionEvent) ev; ... onMotionEvent(event); }
onMotionEvent() 将先进行共通的事件拦截和停用区域检查,通过后交给返回手势视图即 EdgeBackPlugin 进一步处理。
// 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(); }
6. 展示返回手势和触发返回
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(); } ... } }
handleMoveEvent() 则是重要的环节:判断 x 轴的 offset 数值是否达到了阈值 mSwipeThreshold,进而调用 setTriggerBack()
更新 mTriggerBack 变量、同时实时展示角度动画。
// NavigationBarEdgePanel.java private void handleMoveEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); float touchTranslation = MathUtils.abs(x - mStartX); float yOffset = y - mStartY; float delta = touchTranslation - mPreviousTouchTranslation; ... mPreviousTouchTranslation = touchTranslation; // 已经超过阈值的话 // 设置达到触发返回事件条件 if (!mDragSlopPassed && touchTranslation > mSwipeThreshold) { mDragSlopPassed = true; mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK); mVibrationTime = SystemClock.uptimeMillis(); mDisappearAmount = 0.0f; setAlpha(1f); setTriggerBack(true /* triggerBack */, true /* animated */); } ... boolean triggerBack = mTriggerBack; if (Math.abs(mTotalTouchDelta) > mMinDeltaForSwitch) { triggerBack = mTotalTouchDelta > 0; } // 计算方向和偏移值 mVelocityTracker.computeCurrentVelocity(1000); float xVelocity = mVelocityTracker.getXVelocity(); float yVelocity = mVelocityTracker.getYVelocity(); float velocity = MathUtils.mag(xVelocity, yVelocity); mAngleOffset = Math.min(velocity / 1000 * ARROW_ANGLE_ADDED_PER_1000_SPEED, ARROW_MAX_ANGLE_SPEED_OFFSET_DEGREES) * Math.signum(xVelocity); if (mIsLeftPanel && mArrowsPointLeft || !mIsLeftPanel && !mArrowsPointLeft) { mAngleOffset *= -1; } // 如果纵向偏移值达到了横向偏移两倍 // 取消返回事件触发 if (Math.abs(yOffset) > Math.abs(x - mStartX) * 2) { triggerBack = false; } setTriggerBack(triggerBack, true /* animated */); if (!mTriggerBack) { touchTranslation = 0; } else if (mIsLeftPanel && mArrowsPointLeft || (!mIsLeftPanel && !mArrowsPointLeft)) { // 更新角度 touchTranslation -= getStaticArrowWidth(); } setDesiredTranslation(touchTranslation, true /* animated */); // 更新角度动画 updateAngle(true /* animated */); ... } private void setTriggerBack(boolean triggerBack, boolean animated) { if (mTriggerBack != triggerBack) { mTriggerBack = triggerBack; mAngleAnimation.cancel(); updateAngle(animated); mTranslationAnimation.cancel(); } }
7. InputManager 注入返回事件
NavigationBarEdgePanel 收到 UP 时,发现已经设置触发返回事件标志的话将通过 triggerBack() 发出注入返回事件的请求。
该函数首先会取出返回手势视图创建时带入的 BackCallback 实例并将触发 Back 手势的回调发射出去。其后就振动的触发、动画的结束、可见性改回 GONE 等收尾工作。
// NavigationBarEdgePanel.java private void triggerBack() { mBackCallback.triggerBack(); // 产生 click 振动 if (isSlow || SystemClock.uptimeMillis() - mVibrationTime >= GESTURE_DURATION_FOR_CLICK_MS) { mVibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK); } ... // 隐藏动画的执行 Runnable translationEnd = () -> { mAngleOffset = Math.max(0, mAngleOffset + 8); updateAngle(true /* animated */); mTranslationAnimation.setSpring(mTriggerBackSpring); setDesiredTranslation(mDesiredTranslation - dp(32), true /* animated */); // 最终将视图隐藏 animate().alpha(0f).setDuration(DISAPPEAR_FADE_ANIMATION_DURATION_MS) .withEndAction(() -> setVisibility(GONE)); mArrowDisappearAnimation.start(); scheduleFailsafe(); }; ... }
回调的函数也叫 triggerBack()
。其工作是准备 Code 为 KEYCODE_BACK
的 KeyEvent
并通过 InputManager#injectInputEvent()
注入到 InputDispatcher
,等待 Input 系统的进一步处理和发出。
// EdgeBackGestureHandler.java private final NavigationEdgeBackPlugin.BackCallback mBackCallback = new NavigationEdgeBackPlugin.BackCallback() { @Override public void triggerBack() { mFalsingManager.isFalseTouch(BACK_GESTURE); // 发送返回按键的按下和抬起事件 boolean sendDown = sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); boolean sendUp = sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK); ... } ... }; private boolean sendEvent(int action, int code) { long when = SystemClock.uptimeMillis(); // 构建 KeyEvent Java 实例 final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */, 0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, InputDevice.SOURCE_KEYBOARD); // 赋值 DisplayId 并注入 ev.setDisplayId(mContext.getDisplay().getDisplayId()); return InputManager.getInstance() .injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); }
8. Dispatcher 分发返回事件
InputManager 将通过 IMS 和 JNI 最终向 InputDispatcher 注入 KeyEvent。
先将注入的 InputEvent 转换为 KeyEvent
加工成 KeyEvent 先交给 PhoneWindowManager 拦截处理
之后封装成 KeyEntry 并 push 到 queue 中
queue 中 Entry 取出并通过 enqueueInboundEventLocked() 放入存放待分发事件的 mInboundQueue 中
唤醒 InputDispatcher 的 Looper 进入 Thread 的 dispatchOnce() 准备分发
// InputDispatcher.cpp InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* event...) { ... std::queue<std::unique_ptr<EventEntry>> injectedEntries; switch (event->getType()) { // 判断注入事件类型 case AINPUT_EVENT_TYPE_KEY: { const KeyEvent& incomingKey = static_cast<const KeyEvent&>(*event); int32_t action = incomingKey.getAction(); ... KeyEvent keyEvent; keyEvent.initialize(...); // 按键事件的话需要 PhoneWindowManager 预处理 if (!(policyFlags & POLICY_FLAG_FILTERED)) { android::base::Timer t; mPolicy->interceptKeyBeforeQueueing(&keyEvent, /*byref*/ policyFlags); } ... // 之后再放入到待注入队列中 std::unique_ptr<KeyEntry> injectedEntry = std::make_unique<KeyEntry>(...); injectedEntries.push(std::move(injectedEntry)); break; } ... } ... // 取出注入事件放入待分发队列 while (!injectedEntries.empty()) { needWake |= enqueueInboundEventLocked(std::move(injectedEntries.front())); injectedEntries.pop(); } mLock.unlock(); // 唤醒 Looper 去分发 if (needWake) { mLooper->wake(); } // 返回注入结果 InputEventInjectionResult injectionResult; { // acquire lock std::unique_lock _l(mLock); if (syncMode == InputEventInjectionSync::NONE) { injectionResult = InputEventInjectionResult::SUCCEEDED; ... } injectionState->release(); } // release lock return injectionResult; }
篇幅原因,省略了 InputDispatcher 分发按键事件的后续(事实上和前面讲述分发 Input 事件的流程大同小异)。
最终通过 InputChannel
抵达当前 Window 中 DecorView
的 dispatchKeyEvent()
9. App 收到返回事件
篇幅原因,省略 DecorView 树分发 KeyEvent 按键事件的流程。
一般来说 View 树不会拦截返回按键,最终将抵达 Activity#onBackPressed()
它进而决定是 Fragment 处理返回还是 Activity 进行 finish(当然 App 可以覆写它以改写行为)
// Activity.java public void onBackPressed() { ... FragmentManager fragmentManager = mFragments.getFragmentManager(); // Fragment 先处理 if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) { return; } // Activity 自己处理 if (!isTaskRoot()) { finishAfterTransition(); return; } ... }
结语
回顾一下 Back Gesture 响应的整体流程:
NavigationBarView 添加到 Window 上去的时候创建管理类 EdgeBackGestureHandler
EdgeBackGestureHandler 向 WMS 注册返回手势停用的监听者 SystemGestureExclusionListener
通过 InputManager 向 InputDispatcher 注入 InputMonitor 以监听系统 Input 事件
EdgeBackGestureHandler 创建手势视图 NavigationBarEdgePanel 的 Window 参数并将该视图添加到 Window
InputDispacher 将 Input 事件分发给见识者即 EdgeBackGestureHandler 定义的 InputEventReceiver 回调,并判断停用区域进行预处理
手势视图 NavigationBarEdgePanel 收到事件后进一步进行动画、角度和阈值的判断,决定是否触发返回事件
EdgeBackGestureHandler 收到触发请求通过 InputManager 注入 BACK 按键的返回事件
InputDispatcher 将 BACK 按键分发到背面 App 的 DecorView
经过 View 树的按键分发,Activity 的 onBackPressed 进行 Fragment 或 Activity 的返回处理
简单来讲,SystemUI 利用 InputMonitor 监视系统 Touch 事件、监听和获取 WMS 中保存的手势停用区域 Region、依据 Touch 事件展示动画和触发返回、通过 InputManager 注入返回按键事件、最终抵达背面 App。
整体篇幅挺大,还省略了诸多细节。感兴趣的朋友可以跟踪下整个流程,如果发现错误或笔者错过的重要细节,欢迎留言。