深入分析 Android 系统返回手势的实现原理
Posted TechMerger
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入分析 Android 系统返回手势的实现原理相关的知识,希望对你有一定的参考价值。
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)以上是关于深入分析 Android 系统返回手势的实现原理的主要内容,如果未能解决你的问题,请参考以下文章