Android SystemUI的一些知识点的记录

1.各模块启动时机

SystemUI分成了很多的模块,每个模块都继承类SystemUI。SystemUIApplication中的方法startServicesIfNeeded用于启动各模块,它调用的地方有两个,都是位于Service的onCreate方法中,分别是KeyguardService和SystemUIService。
SystemUIService的onBind方法返回null,说明它不对外提供binder服务,并且没有重写onStartCommand,再看onCreate只有启动各个SystemUI的操作,很显然,这个类的职能只是用于启动各模块。
KeyguardService对外提供binder服务,这个binder服务实现了IKeyguardService,显然供给System Service使用,通过搜索代码发现是KeyguardServiceDelegate来绑定KeyguardService服务,并由KeyguardServiceWrapper对其包装。
下面分析SystemUIService的启动时机,AndroidManifest中这个服务没有指定Action,所以直接搜索这个类名,然后在SystemServer中搜到了如下代码:

 static final void startSystemUi(Context context, WindowManagerService windowManager) {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.android.systemui",
                    "com.android.systemui.SystemUIService"));
        intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
        context.startServiceAsUser(intent, UserHandle.SYSTEM);
        windowManager.onSystemUiStarted();
    }

很显然由这个类来启动SystemUIService,进而启动SystemUI的各个模块。SystemServer就不再继续赘述。

2. android:directBootAware="true"的作用

经过实验发现,去掉这个属性,重启后SystemUI将不能启动,不过进程存在。表现是导航栏和状态栏,音量调整等SystemUI组件都不能显示。意外发现关机dialog能够正常运行,然后发现它的包是android,一直以为也是SystemUI的一个组件。事实上长按电源键先显示由SystemUI显示的Dialog,点击关机后再调用系统服务(PowerManagerService)进行关机,系统服务会弹出确认dialog来提示用户,对应的类是com.android.server.power.ShutdownThread。
查询了一下directBootAware的作用,实验的行为跟搜索到的描述是符合的,开机后在未解锁前先进入Direct Boot Mode,而SystemUI就在这时启动,因为android:directBootAware="false"导致两个Service(KeyguardService和SystemUIService)无法启动,所以各个组件没有能够正常启动。

3.Android10手势导航功能分析

Android10推出了全新的手势导航功能,通过从侧边滑动可以执行返回的功能,这里分析一下其实现办法。这个功能的主要处理逻辑位于SystemUI的EdgeBackGestureHandler类。
创建时期:NavigationBarView的构造方法中,NavigationBarView是导航栏对应的Window的根View。
触摸事件监控:EdgeBackGestureHandler通过InputMonitor来获取全局事件的监听。

/**
 * An {@code InputMonitor} allows privileged applications and components to monitor streams of
 * {@link InputEvent}s without having to be the designated recipient for the event.
 *
 * For example, focus dispatched events would normally only go to the focused window on the
 * targeted display, but an {@code InputMonitor} will also receive a copy of that event if they're
 * registered to monitor that type of event on the targeted display.
 *
 * @hide
 */
public final class InputMonitor implements Parcelable {
...
}

通过注释可以了解到,使用这个API只是拿到了一份事件的copy,所以应用本身仍然会收到事件。
下面给出注册的具体API使用:

  private void updateIsEnabled() {
        boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;
        if (isEnabled == mIsEnabled) {
            return;
        }
        mIsEnabled = isEnabled;
        disposeInputChannel();

        if (mEdgePanel != null) {
            mWm.removeView(mEdgePanel);
            mEdgePanel = null;
            mRegionSamplingHelper.stop();
            mRegionSamplingHelper = null;
        }

        if (!mIsEnabled) {
            WindowManagerWrapper.getInstance().removePinnedStackListener(mImeChangedListener);
            mContext.getSystemService(DisplayManager.class).unregisterDisplayListener(this);

            try {
                WindowManagerGlobal.getWindowManagerService()
                        .unregisterSystemGestureExclusionListener(
                                mGestureExclusionListener, mDisplayId);
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to unregister window manager callbacks", e);
            }

        } else {
            updateDisplaySize();
            mContext.getSystemService(DisplayManager.class).registerDisplayListener(this,
                    mContext.getMainThreadHandler());

            try {
                WindowManagerWrapper.getInstance().addPinnedStackListener(mImeChangedListener);
                //这里很重要,注册排除区域变化(该区域不适用手势导航)的监听回调
                WindowManagerGlobal.getWindowManagerService()
                        .registerSystemGestureExclusionListener(
                                mGestureExclusionListener, mDisplayId);
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to register window manager callbacks", e);
            }

            // 这里注册事件监听器  mDisplayId = context.getDisplayId();
            mInputMonitor = InputManager.getInstance().monitorGestureInput(
                    "edge-swipe", mDisplayId);
            mInputEventReceiver = new SysUiInputEventReceiver(
                    mInputMonitor.getInputChannel(), Looper.getMainLooper());

            // 这里创建view,就是手势导航显示的返回箭头
            mEdgePanel = new NavigationBarEdgePanel(mContext);
            mEdgePanelLp = new WindowManager.LayoutParams(
                    mContext.getResources()
                            .getDimensionPixelSize(R.dimen.navigation_edge_panel_width),
                    mContext.getResources()
                            .getDimensionPixelSize(R.dimen.navigation_edge_panel_height),
                    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                            | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                            | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
                    PixelFormat.TRANSLUCENT);
            mEdgePanelLp.privateFlags |=
                    WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
            mEdgePanelLp.setTitle(TAG + mDisplayId);
            mEdgePanelLp.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
            mEdgePanelLp.windowAnimations = 0;
            mEdgePanel.setLayoutParams(mEdgePanelLp);
            mWm.addView(mEdgePanel, mEdgePanelLp);
            //监听区域亮度变化,当区域比较黑时显示成白色,区域比较白时显示成灰色
            mRegionSamplingHelper = new RegionSamplingHelper(mEdgePanel,
                    new RegionSamplingHelper.SamplingCallback() {
                        @Override
                        public void onRegionDarknessChanged(boolean isRegionDark) {
                            mEdgePanel.setIsDark(!isRegionDark, true /* animate */);
                        }

                        @Override
                        public Rect getSampledRegion(View sampledView) {
                            return mSamplingRect;
                        }
                    });
        }
       //InputEventReceiver用于接收framework产生的事件,事实上每个Window都会有一个InputEventReceiver在ViewRootImpl中被注册
       class SysUiInputEventReceiver extends InputEventReceiver {
        SysUiInputEventReceiver(InputChannel channel, Looper looper) {
            super(channel, looper);
        }

        public void onInputEvent(InputEvent event) {
            EdgeBackGestureHandler.this.onInputEvent(event);
            finishInputEvent(event, true);
        }
    }
    private void onInputEvent(InputEvent ev) {
        if (ev instanceof MotionEvent) {
            onMotionEvent((MotionEvent) ev);
        }
    }
}

下面重点来看事件处理:

 private void onMotionEvent(MotionEvent ev) {
        int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {
            // Verify if this is in within the touch region and we aren't in immersive mode, and
            // either the bouncer is showing or the notification panel is hidden
            int stateFlags = mOverviewProxyService.getSystemUiStateFlags();
            //这里主要判断滑动手势是左边还是右边
            mIsOnLeftEdge = ev.getX() <= mEdgeWidth + mLeftInset;
            //看是否开启了此功能,并且判断是否在排除区域,关于排除区域上面说明过
            mAllowGesture = !QuickStepContract.isBackGestureDisabled(stateFlags)
                    && isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
            if (mAllowGesture) {
            //更新window的显示位置
                mEdgePanelLp.gravity = mIsOnLeftEdge
                        ? (Gravity.LEFT | Gravity.TOP)
                        : (Gravity.RIGHT | Gravity.TOP);
                mEdgePanel.setIsLeftPanel(mIsOnLeftEdge);
                mEdgePanel.handleTouch(ev);
                updateEdgePanelPosition(ev.getY());
                mWm.updateViewLayout(mEdgePanel, mEdgePanelLp);
                mRegionSamplingHelper.start(mSamplingRect);

                mDownPoint.set(ev.getX(), ev.getY());
                mThresholdCrossed = false;
            }
        } else if (mAllowGesture) {
        	//是否达到了阈值
            if (!mThresholdCrossed) {
                if (action == MotionEvent.ACTION_POINTER_DOWN) {
                    // We do not support multi touch for back gesture
                    cancelGesture(ev);
                    return;
                } else if (action == MotionEvent.ACTION_MOVE) {
                    if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) {
                        cancelGesture(ev);
                        return;
                    }
                    float dx = Math.abs(ev.getX() - mDownPoint.x);
                    float dy = Math.abs(ev.getY() - mDownPoint.y);
                    if (dy > dx && dy > mTouchSlop) {
                        cancelGesture(ev);
                        return;
                    
                    } else if (dx > dy && dx > mTouchSlop) {
                        mThresholdCrossed = true;
                        // Capture inputs
                        mInputMonitor.pilferPointers();
                    }
                }
            }

            // forward touch
            mEdgePanel.handleTouch(ev);

            boolean isUp = action == MotionEvent.ACTION_UP;
            if (isUp) {
                boolean performAction = mEdgePanel.shouldTriggerBack();
                if (performAction) {
                    // Perform back
                    //模拟按键事件
                    sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
                    sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
                }
                mOverviewProxyService.notifyBackAction(performAction, (int) mDownPoint.x,
                        (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
            }
            if (isUp || action == MotionEvent.ACTION_CANCEL) {
            //停止监听亮度
                mRegionSamplingHelper.stop();
            } else {
            //根据当前位置刷新监听区域
                updateSamplingRect();
                mRegionSamplingHelper.updateSamplingRect();
            }
        }
    }

应用适配,根据上面的分析可以知道,应用仍然可以正常的获取相应的事件,所以需要考虑事件的冲突,屏蔽本应用的事件处理或者屏蔽手势导航的事件处理。
下面是有效区域的判断:

    private boolean isWithinTouchRegion(int x, int y) {
    //导航栏上面,输入法窗口上面
        if (y > (mDisplaySize.y - Math.max(mImeHeight, mNavBarHeight))) {
            return false;
        }
//mEdgeWidth + mLeftInset以内
        if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) {
            return false;
        }
        boolean isInExcludedRegion = mExcludeRegion.contains(x, y);
        if (isInExcludedRegion) {
            mOverviewProxyService.notifyBackAction(false /* completed */, -1, -1,
                    false /* isButton */, !mIsOnLeftEdge);
        }
        return !isInExcludedRegion;
    }
    //注册SystemGestureExclusion区域变化的监听,顾名思义这是系统手势排除区域
        private ISystemGestureExclusionListener mGestureExclusionListener =
            new ISystemGestureExclusionListener.Stub() {
                @Override
                public void onSystemGestureExclusionChanged(int displayId,
                        Region systemGestureExclusion) {
                    if (displayId == mDisplayId) {
                        mMainExecutor.execute(() -> mExcludeRegion.set(systemGestureExclusion));
                    }
                }
            };

通过层层跳跃可以看到下面的代码:

DisplayContent.java
    void registerSystemGestureExclusionListener(ISystemGestureExclusionListener listener) {
        mSystemGestureExclusionListeners.register(listener);
        final boolean changed;
        if (mSystemGestureExclusionListeners.getRegisteredCallbackCount() == 1) {
            changed = updateSystemGestureExclusion();
        } else {
            changed = false;
        }
        //可以看出是粘性的,注册时立刻可以收到回调
        if (!changed) {
            // If updateSystemGestureExclusion changed the exclusion, it will already have
            // notified the listener. Otherwise, we'll do it here.
            try {
                listener.onSystemGestureExclusionChanged(mDisplayId, mSystemGestureExclusion);
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to notify SystemGestureExclusionListener during register", e);
            }
        }
    }
    //这里是获取区域的方法
    DisplayContent.java
    Region calculateSystemGestureExclusion() {
...
        // Traverse all windows top down to assemble the gesture exclusion rects.
        // For each window, we only take the rects that fall within its touchable region.
        forAllWindows(w -> {
...
            if (w.isImplicitlyExcludingAllSystemGestures()) {
                local.set(touchableRegion);
            } else {
            //这里获取区域
                rectListToRegion(w.getSystemGestureExclusion(), local);
...
    }

WindowStat.setSystemGestureExclusion(List exclusionRects) 设置区域,此方法由Session.reportSystemGestureExclusionChanged调用,这个方法是通过AIDL对外提供的方法。通过搜索发现由ViewRootImpl调用,进一步不难发现View的setSystemGestureExclusionRects方法,也就是通过这个方法设置屏蔽区域。
下面时整个修改过程
View-》ViewRootImpl-》aidl-》WindowManagerService-》Session-》DisplayContent-》aidl-》EdgeBackGestureHandler

后面会持续更新内容

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值