深入分析 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;
}



相关文章
|
28天前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
53 15
Android 系统缓存扫描与清理方法分析
|
20天前
|
算法 JavaScript Android开发
|
22天前
|
安全 搜索推荐 Android开发
揭秘安卓与iOS系统的差异:技术深度对比
【10月更文挑战第27天】 本文深入探讨了安卓(Android)与iOS两大移动操作系统的技术特点和用户体验差异。通过对比两者的系统架构、应用生态、用户界面、安全性等方面,揭示了为何这两种系统能够在市场中各占一席之地,并为用户提供不同的选择。文章旨在为读者提供一个全面的视角,理解两种系统的优势与局限,从而更好地根据自己的需求做出选择。
58 2
|
1月前
|
安全 搜索推荐 Android开发
深入探索安卓与iOS系统的差异及其对用户体验的影响
在当今的智能手机市场中,安卓和iOS是两大主流操作系统。它们各自拥有独特的特性和优势,为用户提供了不同的使用体验。本文将深入探讨安卓与iOS系统之间的主要差异,包括它们的设计理念、用户界面、应用生态以及安全性等方面,并分析这些差异如何影响用户的使用体验。
|
30天前
|
安全 搜索推荐 Android开发
揭秘iOS与Android系统的差异:一场技术与哲学的较量
在当今数字化时代,智能手机操作系统的选择成为了用户个性化表达和技术偏好的重要标志。iOS和Android,作为市场上两大主流操作系统,它们之间的竞争不仅仅是技术的比拼,更是设计理念、用户体验和生态系统构建的全面较量。本文将深入探讨iOS与Android在系统架构、应用生态、用户界面及安全性等方面的本质区别,揭示这两种系统背后的哲学思想和市场策略,帮助读者更全面地理解两者的优劣,从而做出更适合自己的选择。
|
21天前
|
安全 搜索推荐 程序员
深入探索Android系统的碎片化问题及其解决方案
在移动操作系统的世界中,Android以其开放性和灵活性赢得了广泛的市场份额。然而,这种开放性也带来了一个众所周知的问题——系统碎片化。本文旨在探讨Android系统碎片化的现状、成因以及可能的解决方案,为开发者和用户提供一种全新的视角来理解这一现象。通过分析不同版本的Android系统分布、硬件多样性以及更新机制的影响,我们提出了一系列针对性的策略,旨在减少碎片化带来的影响,提升用户体验。
|
21天前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统的差异性及优化策略
在当今数字化时代,移动操作系统的竞争尤为激烈,其中iOS和Android作为市场上的两大巨头,各自拥有庞大的用户基础和独特的技术特点。本文旨在通过对比分析iOS与Android的核心差异,探讨各自的优势与局限,并提出针对性的优化策略,以期为用户提供更优质的使用体验和为开发者提供有价值的参考。
|
23天前
|
安全 Android开发 iOS开发
安卓系统与iOS系统的比较####
【10月更文挑战第26天】 本文将深入探讨安卓(Android)和iOS这两大主流移动操作系统的各自特点、优势与不足。通过对比分析,帮助读者更好地理解两者在用户体验、应用生态、系统安全等方面的差异,从而为消费者在选择智能手机时提供参考依据。无论你是技术爱好者还是普通用户,这篇文章都将为你揭示两大系统背后的故事和技术细节。 ####
40 0
|
1月前
|
IDE Android开发 iOS开发
探索安卓与iOS系统的技术差异:开发者的视角
本文深入分析了安卓(Android)与苹果iOS两大移动操作系统在技术架构、开发环境、用户体验和市场策略方面的主要差异。通过对比这两种系统的不同特点,旨在为移动应用开发者提供有价值的见解,帮助他们在不同平台上做出更明智的开发决策。
|
1月前
|
Ubuntu Shell API
Ubuntu 64系统编译android arm64-v8a 的openssl静态库libssl.a和libcrypto.a
Ubuntu 64系统编译android arm64-v8a 的openssl静态库libssl.a和libcrypto.a