Android 10 正式引入了全屏手势导航(Gesture Navigation),Home 键和 History 键的功能借助上滑和悬停手势得以保留,而 Back 键则以返回手势(Back Gesture)重新与大家见面。
相较 iOS 早期便有的全局返回功能,Android 直到版本 10 才姗姗来迟。但 Google 给这个功能添加了视图、动画和角度展示,更是向用户开放了手势敏感度的设置入口。
本文就这个系统功能一探其实现原理,了解之后:
作为 FW 开发者可以在 SystemUI 中优化 AsIs 的手势效果:包括图标、动画等角度
还可以知道 InputMonitor 和 InputManager 的作用,在需要的时候去监视和注入事件
源码版本:
Android 12
目录前瞻:
SystemUI 启动返回手势功能
监听返回手势停用区域
Monitor 监视 Input 事件
创建返回手势视图
预处理 Touch 事件
展示返回手势和触发返回
InputManager 注入返回事件
Dispatcher 分发返回事件
App 收到返回事件
1. SystemUI 启动返回手势功能
SystemUI App 的 NavigationBarView
在构造的时候通过 DI 创建 EdgeBackGestureHandler
实例,其是整个返回手势的核心管理类。
// NavigationBarView.java public NavigationBarView(Context context, AttributeSet attrs) { ... mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class) .create(mContext); mEdgeBackGestureHandler.setStateChangeCallback(this::updateStates); ... }
EdgeBackGestureHandler 类在构造的时候初始化一些手势判断需要的参数和变量。
// EdgeBackGestureHandler.java EdgeBackGestureHandler(...) { super(broadcastDispatcher); mContext = context; mDisplayId = context.getDisplayId(); ... mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT, ViewConfiguration.getLongPressTimeout()); mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( mContext.getMainThreadHandler(), mContext, this::onNavigationSettingsChanged); updateCurrentUserResources(); }
NavigationBarView 初次添加到 Window 上的时候会调用 EdgeBackGestureHandler 开始工作。
// NavigationBarView.java protected void onAttachedToWindow() { ... mEdgeBackGestureHandler.onNavBarAttached(); ... }
onNavBarAttached() 里会根据开启或关闭的状态做些准备工作:
监听 Settings app 关于 Back Gesture 的手势参数调整
监听 WMS 里保存 App 设置的手势停用区域
向 InputFlinger 中注册事件监视器 InputMonitor 以及事件的回调方 InputEventReceiver
创建和添加 NavigationBarEdgePanel 作为手势视图的实现
// EdgeBackGestureHandler.java public void onNavBarAttached() { ... updateIsEnabled(); } private void updateIsEnabled() { boolean isEnabled = mIsAttached && mIsGesturalModeEnabled; if (isEnabled == mIsEnabled) { return; } mIsEnabled = isEnabled; // 如果无效的话结束监听 Input disposeInputChannel(); ... // 无效的话 if (!mIsEnabled) { // 注销监听返回手势参数的设置变化 mGestureNavigationSettingsObserver.unregister(); ... // 注销 WMS 里保存的除外区域监听 try { mWindowManagerService.unregisterSystemGestureExclusionListener( mGestureExclusionListener, mDisplayId); ... } } else { // 监听返回手势参数的设置变化 mGestureNavigationSettingsObserver.register(); ... // 监听 WMS 里保存的除外区域 try { mWindowManagerService.registerSystemGestureExclusionListener( mGestureExclusionListener, mDisplayId); ... } // 注册名为 edge-swipe 的InputMonitor mInputMonitor = InputManager.getInstance().monitorGestureInput( "edge-swipe", mDisplayId); // 设置 Input 事件回调为 onInputEvent() mInputEventReceiver = new InputChannelCompat.InputEventReceiver( mInputMonitor.getInputChannel(), Looper.getMainLooper(), Choreographer.getInstance(), this::onInputEvent); // 添加 NavigationBarEdgePanel 为 Edge Back 事件的处理实现 setEdgeBackPlugin(new NavigationBarEdgePanel(mContext)); ... } ... }
2. 监听返回手势停用区域
EdgeBackGestureHandler 通过 WMS 注册了返回手势停用区域的监听者,他们的 Binder 接口最终被存放在 DisplayContent
中。
// WindowManagerService.java public void registerSystemGestureExclusionListener(...) { synchronized (mGlobalLock) { final DisplayContent displayContent = mRoot.getDisplayContent(displayId); displayContent.registerSystemGestureExclusionListener(listener); } } // DisplayContent.java void registerSystemGestureExclusionListener(ISystemGestureExclusionListener listener) { // 监听实例缓存 mSystemGestureExclusionListeners.register(listener); final boolean changed; // 立即检查一次是否恰好发生了变化 if (mSystemGestureExclusionListeners.getRegisteredCallbackCount() == 1) { changed = updateSystemGestureExclusion(); } else { changed = false; } // 立马回调一次 if (!changed) { final Region unrestrictedOrNull = mSystemGestureExclusionWasRestricted ? mSystemGestureExclusionUnrestricted : null; try { listener.onSystemGestureExclusionChanged(...); ... } } }
区域变化时 WMS 将通过 Binder 将区域回调过来,EdgeBackGestureHandler 遂更新存放当前 Display
停用手势区域的 mExcludeRegion
变量。
// EdgeBackGestureHandler.java private ISystemGestureExclusionListener mGestureExclusionListener = new ISystemGestureExclusionListener.Stub() { @Override public void onSystemGestureExclusionChanged(int displayId, Region systemGestureExclusion, Region unrestrictedOrNull) { if (displayId == mDisplayId) { mMainExecutor.execute(() -> { mExcludeRegion.set(systemGestureExclusion); ... }); } } };
DisplayContent 里的停用区域 Region
来自于 App 的设置,而 App 一般会在需要停用返回手势的 View 视图里覆写这两个方法,并设置停用区域的 Rect
List。
// XXXView.kt var exclusionRects = listOf(rect1, rect2, rect3) fun onLayout( ... ) { setSystemGestureExclusionRects(exclusionRects) } fun onDraw(canvas: Canvas) { setSystemGestureExclusionRects(exclusionRects) }
父类 View 负责将区域通过 Handler 交给根 View 管理者 ViewRootImpl
。
// View.java public void setSystemGestureExclusionRects(@NonNull List<Rect> rects) { // List 为空并且 ListenerInfo 也不存在的话 // 不处理 if (rects.isEmpty() && mListenerInfo == null) return; final ListenerInfo info = getListenerInfo(); // 如果已存在,先清除再添加;反之,创建一个 if (info.mSystemGestureExclusionRects != null) { info.mSystemGestureExclusionRects.clear(); info.mSystemGestureExclusionRects.addAll(rects); } else { info.mSystemGestureExclusionRects = new ArrayList<>(rects); } if (rects.isEmpty()) { // rects 是空的话移除更新的监听 if (info.mPositionUpdateListener != null) { mRenderNode.removePositionUpdateListener(info.mPositionUpdateListener); } } else { // rects 合法但更新的监听尚未建立的话 if (info.mPositionUpdateListener == null) { info.mPositionUpdateListener = new RenderNode.PositionUpdateListener() { ... }; // 创建一个并放入 RenderNode 中 mRenderNode.addPositionUpdateListener(info.mPositionUpdateListener); } } // 向 ViewRootImpl 中 Handler // 发送插队 Message // 任务是向 ViewRootImpl 发出进一步请求 postUpdateSystemGestureExclusionRects(); } void postUpdateSystemGestureExclusionRects() { // Potentially racey from a background thread. It's ok if it's not perfect. final Handler h = getHandler(); if (h != null) { h.postAtFrontOfQueue(this::updateSystemGestureExclusionRects); } } void updateSystemGestureExclusionRects() { final AttachInfo ai = mAttachInfo; if (ai != null) { ai.mViewRootImpl.updateSystemGestureExclusionRectsForView(this); } }
ViewRootImpl 是 View 树和 WMS 产生联系的桥梁,其继续将 Rect 通过 WindowSession
进一步交给系统。
// ViewRootImpl.java void updateSystemGestureExclusionRectsForView(View view) { mGestureExclusionTracker.updateRectsForView(view); mHandler.sendEmptyMessage(MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED); } // 发送的 msg 为如下函数处理 void systemGestureExclusionChanged() { final List<Rect> rectsForWindowManager = mGestureExclusionTracker.computeChangedRects(); if (rectsForWindowManager != null && mView != null) { try { mWindowSession.reportSystemGestureExclusionChanged(mWindow, rectsForWindowManager); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } // 回调监听停用区域变化的 Observer mAttachInfo.mTreeObserver .dispatchOnSystemGestureExclusionRectsChanged(rectsForWindowManager); } }
Binder 调用之后 Session 抵达,之后交给 WMS 并将区域存放在对应的 WindowState
中,管理起来。
// Session.java public void reportSystemGestureExclusionChanged(IWindow window, List<Rect> exclusionRects) { final long ident = Binder.clearCallingIdentity(); try { mService.reportSystemGestureExclusionChanged(this, window, exclusionRects); } finally { Binder.restoreCallingIdentity(ident); } } // WindowManagerService.java void reportSystemGestureExclusionChanged(Session session, IWindow window, List<Rect> exclusionRects) { synchronized (mGlobalLock) { final WindowState win = windowForClientLocked(session, window, true); // 区域保存在在 WindowState 中 // 并告知 DisplayContent 刷新和回调监听者 if (win.setSystemGestureExclusion(exclusionRects)) { win.getDisplayContent().updateSystemGestureExclusion(); } } } // WindowState.java boolean setSystemGestureExclusion(List<Rect> exclusionRects) { // 检查区域是否发生变化 if (mExclusionRects.equals(exclusionRects)) { return false; } // 清空 & 放入全新的 List mExclusionRects.clear(); mExclusionRects.addAll(exclusionRects); return true; }
同时要求 DisplayContent 立即检查区域是否发生更新,这里面将需要从 WindowState 中取出管理着的 Rect List,封装和转换成 Region
。
// DisplayContent.java boolean updateSystemGestureExclusion() { ... final Region systemGestureExclusion = Region.obtain(); // 取得当前的停用区域 mSystemGestureExclusionWasRestricted = calculateSystemGestureExclusion( systemGestureExclusion, mSystemGestureExclusionUnrestricted); try { // 没有发生变化不用通知 if (mSystemGestureExclusion.equals(systemGestureExclusion)) { return false; } ... // 遍历监听者和回调 for (int i = mSystemGestureExclusionListeners.beginBroadcast() - 1; i >= 0; --i) { try { mSystemGestureExclusionListeners.getBroadcastItem(i) .onSystemGestureExclusionChanged(mDisplayId, systemGestureExclusion, unrestrictedOrNull); } } ... } } boolean calculateSystemGestureExclusion(Region outExclusion, @Nullable Region outExclusionUnrestricted) { // 遍历 WindowState 获取停用区域 forAllWindows(w -> { ... if (w.isImplicitlyExcludingAllSystemGestures()) { local.set(touchableRegion); } else { rectListToRegion(w.getSystemGestureExclusion(), local); ... local.op(touchableRegion, Op.INTERSECT); } ... return remainingLeftRight[0] < mSystemGestureExclusionLimit || remainingLeftRight[1] < mSystemGestureExclusionLimit; }
3. Monitor 监视 Input 事件
InputManager 经过 Binder 将 monitorGestureInput() 的调用传递到 InputManagerService。
// InputManagerService.java public InputMonitor monitorGestureInput(String inputChannelName, int displayId) { ... try { InputChannel inputChannel = nativeCreateInputMonitor( mPtr, displayId, true /*isGestureMonitor*/, inputChannelName, pid); InputMonitorHost host = new InputMonitorHost(inputChannel.getToken()); return new InputMonitor(inputChannel, host); } finally { Binder.restoreCallingIdentity(ident); } }
IMS 的 JNI 将负责向 InputDispatcher 发出调用,并将其创建的 Client 端 InputChannel 实例转为 Java 实例返回。
虽然命名为 InputMonitor 事实上还是 InputChannel,只不过要和普通的 Window 所创建的 InputChannel 区分开来。
可以说留给某些特权 App 监视输入事件的后门吧,比如这次的 SystemUI。
// com_android_server_input_InputManagerService.cpp static jobject nativeCreateInputMonitor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint displayId, jboolean isGestureMonitor, jstring nameObj, jint pid) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); ... // 调用 NativeInputManager base::Result<std::unique_ptr<InputChannel>> inputChannel = im->createInputMonitor(env, displayId, isGestureMonitor, name, pid); ... // 将 Native 端返回的实例转为 Java 对象 jobject inputChannelObj = android_view_InputChannel_createJavaObject(env, std::move(*inputChannel)); if (!inputChannelObj) { return nullptr; } return inputChannelObj; } // 从持有的 InputManager 实例中 // 取出 InputDispatcher 实例 // 发出创建 Monitor 请求 base::Result<std::unique_ptr<InputChannel>> NativeInputManager::createInputMonitor(...) { ATRACE_CALL(); return mInputManager->getDispatcher()->createInputMonitor(...); }
InputDispatcher 创建 InputMonitor 的流程和普通 InputChannel 差不多,区别体现在 Server 端 InputChannel 需要额外存放在 mGestureMonitorsByDisplay
Map 中。
// InputDispatcher.cpp Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputMonitor(...) { std::shared_ptr<InputChannel> serverChannel; std::unique_ptr<InputChannel> clientChannel; status_t result = openInputChannelPair(name, serverChannel, clientChannel); { // acquire lock std::scoped_lock _l(mLock); sp<Connection> connection = new Connection(serverChannel, true /*monitor*/, mIdGenerator); const sp<IBinder>& token = serverChannel->getConnectionToken(); const int fd = serverChannel->getFd(); mConnectionsByToken.emplace(token, connection); std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback, this, std::placeholders::_1, token); auto& monitorsByDisplay = isGestureMonitor ? mGestureMonitorsByDisplay : mGlobalMonitorsByDisplay; monitorsByDisplay[displayId].emplace_back(serverChannel, pid); mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, new LooperEventCallback(callback), nullptr); } // Wake the looper because some connections have changed. mLooper->wake(); return clientChannel; }