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
后面会持续更新内容