深入分析 Android 系统返回手势的实现原理(1)

简介: 深入分析 Android 系统返回手势的实现原理(1)

Android 10 正式引入了全屏手势导航(Gesture Navigation),Home 键和 History 键的功能借助上滑和悬停手势得以保留,而 Back 键则以返回手势(Back Gesture)重新与大家见面。


相较 iOS 早期便有的全局返回功能,Android 直到版本 10 才姗姗来迟。但 Google 给这个功能添加了视图、动画和角度展示,更是向用户开放了手势敏感度的设置入口。

1832b220aa754cd18c504acc7686a560.png本文就这个系统功能一探其实现原理,了解之后:


作为 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;
}



相关文章
|
3天前
|
Shell Android开发
Android系统 adb shell push/pull 禁止特定文件
Android系统 adb shell push/pull 禁止特定文件
15 1
|
3天前
|
Android开发
Android构建系统:Android.mk(2)函数详解
Android构建系统:Android.mk(2)函数详解
12 1
|
3天前
|
存储 Java API
Android系统 文件访问权限笔记
Android系统 文件访问权限笔记
32 1
|
3天前
|
移动开发 Java Unix
Android系统 自动加载自定义JAR文件
Android系统 自动加载自定义JAR文件
20 1
|
3天前
|
Shell Android开发 开发者
Android系统 自定义动态修改init.custom.rc
Android系统 自定义动态修改init.custom.rc
22 0
|
3天前
|
测试技术 Android开发 开发者
RK3568 Android系统客制化动态替换ro任意属性
RK3568 Android系统客制化动态替换ro任意属性
14 1
|
3天前
|
存储 Linux Android开发
RK3568 Android/Linux 系统动态更换 U-Boot/Kernel Logo
RK3568 Android/Linux 系统动态更换 U-Boot/Kernel Logo
17 0
|
3天前
|
存储 缓存 安全
Android系统 应用存储路径与权限
Android系统 应用存储路径与权限
6 0
Android系统 应用存储路径与权限
|
3天前
|
存储 开发工具 Android开发
Android系统 权限组管理和兼容性
Android系统 权限组管理和兼容性
14 0
|
3天前
|
存储 安全 Android开发
Android系统 自定义系统和应用权限
Android系统 自定义系统和应用权限
18 0