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

Posted TechMerger

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入分析 Android 系统返回手势的实现原理相关的知识,希望对你有一定的参考价值。

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

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

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

  • 作为 FW 开发者可以在 SystemUI 中优化 AsIs 的手势效果:包括图标、动画等角度
  • 还可以知道 InputMonitorInputManager 的作用,在需要的时候去监视和注入事件

源码版本:

  • Android 12

目录前瞻:

  1. SystemUI 启动返回手势功能
  2. 监听返回手势停用区域
  3. Monitor 监视 Input 事件
  4. 创建返回手势视图
  5. 预处理 Touch 事件
  6. 展示返回手势和触发返回
  7. InputManager 注入返回事件
  8. Dispatcher 分发返回事件
  9. 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() 里会根据开启或关闭的状态做些准备工作:

  1. 监听 Settings app 关于 Back Gesture 的手势参数调整
  2. 监听 WMS 里保存 App 设置的手势停用区域
  3. InputFlinger 中注册事件监视器 InputMonitor 以及事件的回调方 InputEventReceiver
  4. 创建和添加 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 系统返回手势的实现原理的主要内容,如果未能解决你的问题,请参考以下文章

android黑科技系列——修改锁屏密码和恶意锁机样本原理分析

Android APK 签名打包原理分析APK结构分析

Android APK 签名打包原理分析APK结构分析

深入分析Zookeeper的实现原理

Android基础系列 - 手势自定义,识别手势

深入分析线程池的实现原理