Android 中关于navigationbar相关的模式主要分为三种:手势导航,“双按钮”导航,“三按钮”导致,下面主要总结下对应的启动流程以及遇到的问题。
(1)NavigationbarView 绘制
NavigationbarView在创建statusbar的时候统一被创建,具体代码流程如下:
../framework/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.javaStatusBar启动后就会执行start方法调用的时候会执行createAndAddWindow()
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
makeStatusBarView(result);
..
}然后执行makeStatusBarView()
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
..
createNavigationBar(result);//这个方法就是创建navigationbar的入口
..
}protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);
}../framework/base/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
createNavigationBar()
public void createNavigationBars(final boolean includeDefaultDisplay,
RegisterStatusBarResult result) {
//获取当前的display的数据
Display[] displays = mDisplayManager.getDisplays();
//执行for循环添加navigationbarview
for (Display display : displays) {
if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) {
void createNavigationBar(Display display, RegisterStatusBarResult result) {
if (display == null) {
return;
}
final int displayId = display.getDisplayId();
final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
try {
//framework通过属性”qemu.hw.mainkeys”来定义是否显示navigationbar
if (!wms.hasNavigationBar(displayId)) {
return;
}
} catch (RemoteException e) {
// Cannot get wms, just return with warning message.
Log.w(TAG, "Cannot get WindowManager.");
return;
}
final Context context = isOnDefaultDisplay
? mContext
: mContext.createDisplayContext(display);
//navigationbarview其实是fragement,添加到系统view中
NavigationBarFragment.create(context, (tag, fragment) -> {
NavigationBarFragment navBar = (NavigationBarFragment) fragment;
// Unfortunately, we still need it because status bar needs LightBarController
// before notifications creation. We cannot directly use getLightBarController()
// from NavigationBarFragment directly.
LightBarController lightBarController = isOnDefaultDisplay
? Dependency.get(LightBarController.class)
: new LightBarController(context,
Dependency.get(DarkIconDispatcher.class),
Dependency.get(BatteryController.class));
navBar.setLightBarController(lightBarController);
// TODO(b/118592525): to support multi-display, we start to add something which is
// per-display, while others may be global. I think it's time to add
// a new class maybe named DisplayDependency to solve per-display
// Dependency problem.
AutoHideController autoHideController = isOnDefaultDisplay
? Dependency.get(AutoHideController.class)
: new AutoHideController(context, mHandler);
navBar.setAutoHideController(autoHideController);
navBar.restoreSystemUiVisibilityState();
mNavigationBars.append(displayId, navBar);
if (result != null) {
navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
result.mImeWindowVis, result.mImeBackDisposition,
result.mShowImeSwitcher);
}
});
}
(display, result);//创建navigationbar
}
}
}../frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/PHONE/NavigationBarFragment.javaCreate()(之前执行NavigationBarFragment/onCreate/onCreateView/onViewCreate的顺序),最终add了fragment的onCreateView加载的布局
public static View create(Context context, FragmentListener listener) {
//设置view添加的属性
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
lp.token = new Binder();
lp.setTitle("NavigationBar" + context.getDisplayId());
lp.accessibilityTitle = context.getString(R.string.nav_bar);
lp.windowAnimations = 0;
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
View navigationBarView = LayoutInflater.from(context).inflate(
R.layout.navigation_bar_window, null);//加载navigation_bar_window布局
if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
if (navigationBarView == null) return null;
final NavigationBarFragment fragment = FragmentHostManager.get(navigationBarView)
.create(NavigationBarFragment.class);
navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
//使用fragment切换.navigation_bar_frame,这个id定义在navigation_bar_window.xml中
fragmentHost.getFragmentManager().beginTransaction()
.replace(.navigation_bar_frame, fragment, TAG)
.commit();
fragmentHost.addTagListener(TAG, listener);
}
@Override
public void onViewDetachedFromWindow(View v) {
FragmentHostManager.removeAndDestroy(v);
}
});
context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
return navigationBarView;
}来看WindowManager加载的这个view的布局:navigation_bar_window.xml,发现根布局是自定义的view类NavigationBarFrame
<com.android.systemui.statusbar.phone.NavigationBarFrame
xmlns:android="http:///apk/res/android"
xmlns:systemui="http:///apk/res-auto"
android:id="@+id/navigation_bar_frame"
android:theme="@style/Theme.SystemUI"
android:layout_height="match_parent"
android:layout_width="match_parent">
</com.android.systemui.statusbar.phone.NavigationBarFrame>我们进入NavigationBarFrame类。发现类里并不是我们的预期,就是一个FrameLayout,对DeadZone功能下的touch事件做了手脚,不管了。
NavigationBarFragment的生命周期呢。onCreateView()里,导航栏的真正的rootView。
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.navigation_bar, container, false);
}进入导航栏的真正根布局:navigation_bar.xml,NavigationBarView 和 NavigationBarInflaterView 都要仔细研读
<com.android.systemui.statusbar.phone.NavigationBarView
xmlns:android="http:///apk/res/android"
xmlns:systemui="http:///apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="@drawable/system_bar_background">
<com.android.systemui.statusbar.phone.NavigationBarInflaterView
android:id="@+id/navigation_inflater"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.systemui.statusbar.phone.NavigationBarView>../frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView .java
先看构造方法,因为加载xml布局首先走的是初始化(是先执行NavigationBarView再加载NavigationBarInflaterView)
//监听模式变换
public class NavigationBarInflaterView extends FrameLayout implements NavigationModeController.ModeChangedListener {
public NavigationBarInflaterView(Context context, AttributeSet attrs) {
super(context, attrs);
createInflaters();//根据屏幕旋转角度创建子view(单个back home or recent)的父布局
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
}
void createInflaters() {
mLayoutInflater = LayoutInflater.from(mContext);
Configuration landscape = new Configuration();
landscape.setTo(mContext.getResources().getConfiguration());
landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));
}再看onFinishInflate()方法,这是view的生命周期,每个view被inflate之后都会回调
protected void onFinishInflate() {
super.onFinishInflate();
inflateChildren();//加载navigation_layout/navigation_layout_verital布局,此布局复写用于定义控件点击范围
clearViews();
inflateLayout(getDefaultLayout());//关键方法:加载了 back.home.recent三个按钮的layout
}
protected String getDefaultLayout() {
final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode)
? R.string.config_navBarLayoutHandle//手势下显示的字符结构
: mOverviewProxyService.shouldShowSwipeUpUI()//是否显示上滑动的显示字符结构
? R.string.config_navBarLayoutQuickstep
: R.string.config_navBarLayout;
return getContext().getString(defaultResource);
}看inflateLayout():里面的newLayout参数很重要!!!根据上一个方法看到getDefaultLayout(),他return了一个在xml写死的字符串。字符样式显示如下:
<string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>再看inflateLayout方法,他解析分割了xml里配置的字符串,并传给了inflateButtons方法
protected void inflateLayout(String newLayout) {
mCurrentLayout = newLayout;
if (newLayout == null) {
newLayout = getDefaultLayout();
}
String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);//根据“;”号分割成长度为3的数组
if (sets.length != 3) {
Log.d(TAG, "Invalid layout.");
newLayout = getDefaultLayout();
sets = newLayout.split(GRAVITY_SEPARATOR, 3);
}
String[] start = sets[0].split(BUTTON_SEPARATOR);//根据“,”号分割,包含 left[.5W]和back[1WC]
String[] center = sets[1].split(BUTTON_SEPARATOR);//包含home
String[] end = sets[2].split(BUTTON_SEPARATOR);//包含recent[1WC]和right[.5W]
// Inflate these in start to end order or accessibility traversal will be messed up.
inflateButtons(start, mHorizontal.findViewById(.ends_group),
false /* landscape */, true /* start */);
inflateButtons(start, mVertical.findViewById(.ends_group),
true /* landscape */, true /* start */);
inflateButtons(center, mHorizontal.findViewById(.center_group),
false /* landscape */, false /* start */);
inflateButtons(center, mVertical.findViewById(.center_group),
true /* landscape */, false /* start */);
addGravitySpacer(mHorizontal.findViewById(.ends_group));//插入空格符
addGravitySpacer(mVertical.findViewById(.ends_group));//插入空格符
inflateButtons(end, mHorizontal.findViewById(.ends_group),
false /* landscape */, false /* start */);
inflateButtons(end, mVertical.findViewById(.ends_group),
true /* landscape */, false /* start */);
updateButtonDispatchersCurrentView();
}再看inflateButtons()方法,遍历加载inflateButton:
private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
boolean start) {
for (int i = 0; i < buttons.length; i++) {
inflateButton(buttons[i], parent, landscape, start);
}
}
protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
boolean start) {
LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
View v = createView(buttonSpec, parent, inflater);//这个是根据上面传来的字符加载不同布局
if (v == null) return null;
v = applySize(v, buttonSpec, landscape, start);//根据返回字符的解析,来确认图标显示的大小
parent.addView(v);//addView到父布局
addToDispatchers(v);
View lastView = landscape ? mLastLandscape : mLastPortrait;
View accessibilityView = v;
if (v instanceof ReverseRelativeLayout) {
accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);
}
if (lastView != null) {
accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
}
if (landscape) {
mLastLandscape = accessibilityView;
} else {
mLastPortrait = accessibilityView;
}
return v;
}我们来看createView()方法:以home按键为例,加载了home的button,其实是加载了 R.layout.home 的layout布局
private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
View v = null;
String button = extractButton(buttonSpec);
if (LEFT.equals(button)) {
button = extractButton(NAVSPACE);
} else if (RIGHT.equals(button)) {
button = extractButton(MENU_IME_ROTATE);
}
if (HOME.equals(button)) {
v = inflater.inflate(R.layout.home, parent, false);
} else if (BACK.equals(button)) {
v = inflater.inflate(R.layout.back, parent, false);
}
.....
else if (button.startsWith(KEY)) {
String uri = extractImage(button);
int code = extractKeycode(button);
v = inflater.inflate(R.layout.custom_key, parent, false);
((KeyButtonView) v).setCode(code);
if (uri != null) {
if (uri.contains(":")) {
((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
} else if (uri.contains("/")) {
int index = uri.indexOf('/');
String pkg = uri.substring(0, index);
int id = Integer.parseInt(uri.substring(index + 1));
((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
}
}
}
return v;
}
//home.xml
<com.android.systemui.statusbar.policy.KeyButtonView
xmlns:android="http:///apk/res/android"
xmlns:systemui="http:///apk/res-auto"
android:id="@+id/home"
android:layout_width="@dimen/navigation_key_width"
android:layout_height="match_parent"
android:layout_weight="0"
systemui:keyCode="3"//systemui自定义的属性,模拟的keycode码
android:scaleType="center"
android:contentDescription="@string/accessibility_home"
android:paddingStart="@dimen/navigation_key_padding"
android:paddingEnd="@dimen/navigation_key_padding"
/>SystemUI\src\com\android\systemui\statusbar\policy\KeyButtonView.java先来看KeyButtonView的构造方法,我们之前xml的systemui:keyCode=”3”方法在这里获取。再来看Touch事件,通过sendEvent()方法可以看出,back等view的点击touch事件不是自己处理的,而是交由系统以实体按键(keycode)的形式处理的.
当然KeyButtonView类还处理了支持长按的button,按键的响声等,这里忽略。
至此,导航栏按键事件我们梳理完毕。
public KeyButtonView(Context context, AttributeSet attrs, int defStyle, InputManager manager,
UiEventLogger uiEventLogger) {
super(context, attrs);
mUiEventLogger = uiEventLogger;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyButtonView,
defStyle, 0);
mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, KEYCODE_UNKNOWN);
mPlaySounds = a.getBoolean(R.styleable.KeyButtonView_playSound, true);
TypedValue value = new TypedValue();
if (a.getValue(R.styleable.KeyButtonView_android_contentDescription, value)) {
mContentDescriptionRes = value.resourceId;
}
a.recycle();
setClickable(true);
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mRipple = new KeyButtonRipple(context, this);
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
mInputManager = manager;
setBackground(mRipple);
setWillNotDraw(false);
forceHasOverlappingRendering(false);
}
//touch事件处理
public boolean onTouchEvent(MotionEvent ev) {
final boolean showSwipeUI = mOverviewProxyService.shouldShowSwipeUpUI();
final int action = ev.getAction();
int x, y;
if (action == MotionEvent.ACTION_DOWN) {
mGestureAborted = false;
}
if (mGestureAborted) {
setPressed(false);
return false;
}
...
}
private void sendEvent(int action, int flags, long when) {
mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_NAV_BUTTON_EVENT)
.setType(MetricsEvent.TYPE_ACTION)
.setSubtype(mCode)
.addTaggedData(MetricsEvent.FIELD_NAV_ACTION, action)
.addTaggedData(MetricsEvent.FIELD_FLAGS, flags));
logSomePresses(action, flags);
//back键单独处理
if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
Log.i(TAG, "Back button event: " + KeyEvent.actionToString(action));
if (action == MotionEvent.ACTION_UP) {
mOverviewProxyService.notifyBackAction((flags & KeyEvent.FLAG_CANCELED) == 0,
-1, -1, true /* isButton */, false /* gestureSwipeLeft */);
}
}
final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
//这里根据mCode new了一个KeyEvent事件,通过injectInputEvent使事件生效。
final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
InputDevice.SOURCE_KEYBOARD);
int displayId = INVALID_DISPLAY;
// Make KeyEvent work on multi-display environment
if (getDisplay() != null) {
displayId = getDisplay().getDisplayId();
}
// Bubble controller will give us a valid display id if it should get the back event
BubbleController bubbleController = Dependency.get(BubbleController.class);
int bubbleDisplayId = bubbleController.getExpandedDisplayId(mContext);
if (mCode == KeyEvent.KEYCODE_BACK && bubbleDisplayId != INVALID_DISPLAY) {
displayId = bubbleDisplayId;
}
if (displayId != INVALID_DISPLAY) {
ev.setDisplayId(displayId);
}
//keycode通过系统处理
mInputManager.injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
}设置图片的icon到底在哪?我们之前一直阅读的是NavigationBarInflaterView,根据布局我们还有一个类没有看,NavigationBarView
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
...
// Set up the context group of buttons
mContextualButtonGroup = new ContextualButtonGroup(.menu_container);
final ContextualButton imeSwitcherButton = new ContextualButton(.ime_switcher,
R.drawable.ic_ime_switcher_default);
final RotationContextButton rotateSuggestionButton = new RotationContextButton(
.rotate_suggestion, R.drawable.ic_sysbar_rotate_button);
final ContextualButton accessibilityButton =
new ContextualButton(.accessibility_button,
R.drawable.ic_sysbar_accessibility_button);
mContextualButtonGroup.addButton(imeSwitcherButton);
if (!isGesturalMode) {
mContextualButtonGroup.addButton(rotateSuggestionButton);
}
mContextualButtonGroup.addButton(accessibilityButton);
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
//此类主要功能是手势模式下上滑后出现的提示
mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
mFloatingRotationButton = new FloatingRotationButton(context);
mRotationButtonController = new RotationButtonController(context,
R.style.RotateButtonCCWStart90,
isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton,
mRotationButtonListener);
mConfiguration = new Configuration();
mTmpLastConfiguration = new Configuration();
mConfiguration.updateFrom(context.getResources().getConfiguration());
//pin页面的提示类
mScreenPinningNotify = new ScreenPinningNotify(mContext);
//navigationbar的颜色控制类
mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.class));
//mButtonDispatchers是维护这些home/back/recent图标view的管理类,会传递到他的 child,NavigationBarInflaterView类中
mButtonDispatchers.put(.back, new ButtonDispatcher(.back));
mButtonDispatchers.put(.home, new ButtonDispatcher(.home));
mButtonDispatchers.put(.home_handle, new ButtonDispatcher(.home_handle));
mButtonDispatchers.put(.recent_apps, new ButtonDispatcher(.recent_apps));
mButtonDispatchers.put(.ime_switcher, imeSwitcherButton);
mButtonDispatchers.put(.accessibility_button, accessibilityButton);
mButtonDispatchers.put(.rotate_suggestion, rotateSuggestionButton);
mButtonDispatchers.put(.menu_container, mContextualButtonGroup);
mDeadZone = new DeadZone(this);
mNavColorSampleMargin = getResources()
.getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
//手势模式下屏幕左右滑动的EdgeBack类
mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService,
mSysUiFlagContainer, mPluginManager, this::updateStates);
//获取范围类的RegionSampling来变化显示是light/dark
mRegionSamplingHelper = new RegionSamplingHelper(this,
new RegionSamplingHelper.SamplingCallback() {
@Override
public void onRegionDarknessChanged(boolean isRegionDark) {
getLightTransitionsController().setIconsDark(!isRegionDark ,
true /* animate */);
}
@Override
public Rect getSampledRegion(View sampledView) {
if (mOrientedHandleSamplingRegion != null) {
return mOrientedHandleSamplingRegion;
}
updateSamplingRect();
return mSamplingBounds;
}
@Override
public boolean isSamplingEnabled() {
return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode);
}
});
}下面说明下navigationbarview图标如此加载,当加载完成后会执行onAttachedToWindow,这个方法中对图标进行添加
protected void onAttachedToWindow() {
super.onAttachedToWindow();
requestApplyInsets();
//设置图标的入口方法
reorient();
//模式变化时各个类对于onNavigationModeChanged状态的处理
onNavigationModeChanged(mNavBarMode);
setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
if (mRotationButtonController != null) {
mRotationButtonController.registerListeners();
}
mEdgeBackGestureHandler.onNavBarAttached();
getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
}
public void reorient() {
//view gone对应的布局
updateCurrentView();
((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
mDeadZone.onConfigurationChanged(mCurrentRotation);
// force the low profile & disabled states into compliance
mBarTransitions.init();
// Resolve layout direction if not resolved since components changing layout direction such
// as changing languages will recreate this view and the direction will be resolved later
if (!isLayoutDirectionResolved()) {
resolveLayoutDirection();
}
//更新navButtonIcon图标
updateNavButtonIcons();
getHomeButton().setVertical(mIsVertical);
}
public void updateNavButtonIcons() {
// We have to replace or restore the back and home button icons when exiting or entering
// carmode, respectively. Recents are not available in CarMode in nav bar so change
// to recent icon is not required.
final boolean useAltBack =
(mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
KeyButtonDrawable backIcon = mBackIcon;
orientBackButton(backIcon);
KeyButtonDrawable homeIcon = mHomeDefaultIcon;
if (!mUseCarModeUi) {
orientHomeButton(homeIcon);
}
//设置home/back的button image
getHomeButton().setImageDrawable(homeIcon);
getBackButton().setImageDrawable(backIcon);
//根据是否分屏模式来显示recent对应的图标
updateRecentsIcon();
// Update IME button visibility, a11y and rotate button always overrides the appearance
mContextualButtonGroup.setButtonVisibility(.ime_switcher,
(mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
mBarTransitions.reapplyDarkIntensity();
...
// When screen pinning, don't hide back and home when connected service or back and
// recents buttons when disconnected from launcher service in screen pinning mode,
// as they are used for exiting.
//判断当前是否为pinning状态
final boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
if (mOverviewProxyService.isEnabled()) {
// Force disable recents when not in legacy mode
disableRecent |= !QuickStepContract.isLegacyMode(mNavBarMode);
if (pinningActive && !QuickStepContract.isGesturalMode(mNavBarMode)) {
disableBack = disableHome = false;
}
} else if (pinningActive) {//pinning状态禁用back/home
disableBack = disableRecent = false;
}
ViewGroup navButtons = getCurrentView().findViewById(.nav_buttons);
if (navButtons != null) {
LayoutTransition lt = navButtons.getLayoutTransition();
if (lt != null) {
if (!lt.getTransitionListeners().contains(mTransitionListener)) {
lt.addTransitionListener(mTransitionListener);
}
}
}
//根据是否禁用来显示对应的button
getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
notifyActiveTouchRegions();
}上述图标切换在横竖屏切换,分辨率变化中都会对图标进行切换,具体实现在navigationbarview/navigationbarInflateview中
../framework/base/package/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
最后一个问题navigationbar button的click事件在何处添加的,当布局加载完成后会回调onViewCreated
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mNavigationBarView = (NavigationBarView) view;
...
//back/home/recent 设置click事件方法入口
prepareNavigationBarView();
checkNavBarModes();
//亮/灭屏/用户切换 navigatinbarview变化
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_USER_SWITCHED);
mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
Handler.getMain(), UserHandle.ALL);
notifyNavigationBarScreenOn();
mOverviewProxyService.addCallback(mOverviewProxyListener);
updateSystemUiStateFlags(-1);
...
}
private void prepareNavigationBarView() {
//设置navigationbar相关图标背景
mNavigationBarView.reorient();
//设置recentsButton的setOnClickListener/setOnTouchListener/setOnLongClickListener监听
ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
recentsButton.setOnClickListener(this::onRecentsClick);
recentsButton.setOnTouchListener(this::onRecentsTouch);
recentsButton.setLongClickable(true);
recentsButton.setOnLongClickListener(this::onLongPressBackRecents);
//设置backButton 的setLongClickable属性,backbutton具体操作在keyButtonView中实现
ButtonDispatcher backButton = mNavigationBarView.getBackButton();
backButton.setLongClickable(true);
//设置homeButton 的setOnTouchListener/setOnLongClickListener监听
ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
homeButton.setOnTouchListener(this::onHomeTouch);
homeButton.setOnLongClickListener(this::onHomeLongClick);
//盲人模式下accessibilityButton的setOnClickListener/setOnLongClickListener监听
ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
accessibilityButton.setOnClickListener(this::onAccessibilityClick);
accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
updateAccessibilityServicesState(mAccessibilityManager);
updateScreenPinningGestures();
}至此,SystemUI的虚拟导航栏模块代码流程结束。
总结:创建一个window属性的父view,通过读取解析xml里config的配置,addView需要的icon,src图片资源通过代码设置,touch事件以keycode方式交由系统处理。
Settings中切换系统导航的流程分析
手机/平板等设备系统导航默认为“三按钮”导航,那么切换流程是如何实现的的,下面主要分析下“三按钮”导航切换到“双按钮”导航,“三按钮”导航切换到手势导航的流程。
- “三按钮”导航切换到“双按钮”导航分析
原生系统导航Preference定义在accessibility_settings.xml中,具体路径如下:
../packages/apps/Settings/res/xml/accessibility_settings.xml
<Preference
android:fragment="com.android.settings.gestures.SystemNavigationGestureSettings"//系统导航的fragment
android:key="gesture_system_navigation_input_summary_accessibility"
android:persistent="false"
android:title="@string/system_navigation_title"
settings:searchable="false"
settings:controller="com.android.settings.gestures.SystemNavigationPreferenceController"/>//对应的控制类由于SystemNavigationGestureSettings.java继承之RadioButtonPickerFragment,radioClick最后调用的是
setDefaultKey方法
protected boolean setDefaultKey(String key) {
//通过overlaymanager设置对应不同的模式
setCurrentSystemNavigationMode(mOverlayManager, key);
//根据不同的navigationmode设置不同的video资源
setIllustrationVideo(mVideoPreference, key);
if (TextUtils.equals(KEY_SYSTEM_NAV_GESTURAL, key) && (
isAnyServiceSupportAccessibilityButton() || isNavBarMagnificationEnabled())) {
Intent intent = new Intent(getActivity(), SettingsTutorialDialogWrapperActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
return true;
}
static void setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key) {
String overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
switch (key) {
case KEY_SYSTEM_NAV_GESTURAL:
overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
break;
case KEY_SYSTEM_NAV_2BUTTONS:
overlayPackage = NAV_BAR_MODE_2BUTTON_OVERLAY;
break;
case KEY_SYSTEM_NAV_3BUTTONS:
overlayPackage = NAV_BAR_MODE_3BUTTON_OVERLAY;
break;
}
try {
//3键切为2键后 执行系统setEnabledExclusiveInCategory方法
overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}../frameworks/base/core/java/android/content/om/OverlayManager.java
public void setEnabledExclusiveInCategory(@NonNull final String packageName,
@NonNull UserHandle user) throws SecurityException, IllegalStateException {
try {
if (!mService.setEnabledExclusiveInCategory(packageName, user.getIdentifier())) {
throw new IllegalStateException("setEnabledExclusiveInCategory failed");
}
} catch (SecurityException e) {
rethrowSecurityException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}../frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java
public boolean setEnabledExclusiveInCategory(@Nullable String packageName,final int userIdArg) {
...
try {
synchronized (mLock) {
try {
mImpl.setEnabledExclusive(packageName,
true /* withinCategory */, realUserId)
.ifPresent(mPropagateOverlayChange);
return true;
} catch (OperationFailedException e) {
return false;
}
}
...
}
private final Consumer<PackageAndUser> mPropagateOverlayChange = (pair) -> {
//更新setting文件
persistSettings();
FgThread.getHandler().post(() -> {
List<String> affectedTargets = updatePackageManager(pair.packageName, pair.userId);
updateActivityManager(affectedTargets, pair.userId);
//发送overlaychange广播,systemui会接收到此广播
broadcastActionOverlayChanged(pair.packageName, pair.userId);
});
};
private void broadcastActionOverlayChanged(@NonNull final String targetPackageName,
final int userId) {
//发送ACTION_OVERLAY_CHANGED的广播
final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
Uri.fromParts("package", targetPackageName, null));
//设置此flag限制只有动态注册才能接收次action
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
try {
ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null, null,
null, .AppOpsManager.OP_NONE, null, false, false, userId);
} catch (RemoteException e) {
// Intentionally left empty.
}
}../frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
public NavigationModeController(Context context,DeviceProvisionedController deviceProvisionedController,
ConfigurationController configurationController,@UiBackground Executor uiBgExecutor) {
...
//初始化中添加ACTION_OVERLAY_CHANGED广播监听
IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
overlayFilter.addDataScheme("package");
overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
...
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateCurrentInteractionMode(true /* notify */);
}
};
public void updateCurrentInteractionMode(boolean notify) {
mCurrentUserContext = getCurrentUserContext();
//获取当前导航栏的模式
int mode = getCurrentInteractionMode(mCurrentUserContext);
//如果是手势的话执行switchToDefaultGestureNavOverlayIfNecessary
if (mode == NAV_BAR_MODE_GESTURAL) {
switchToDefaultGestureNavOverlayIfNecessary();
}
mUiBgExecutor.execute(() ->
Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
Secure.NAVIGATION_MODE, String.valueOf(mode)));
//回调通知onNavigationModeChanged navigationbar改变
if (notify) {
for (int i = 0; i < mListeners.size(); i++) {
//这个回调很多类注册了,我们只要关注navigationbarFragment/navigationbarview的回调
mListeners.get(i).onNavigationModeChanged(mode);
}
}
}../frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigatinbarFragment.java
public void onNavigationModeChanged(int mode) {
mNavBarMode = mode;
updateScreenPinningGestures();
...
// Workaround for b/132825155, for secondary users, we currently don't receive configuration
// changes on overlay package change since SystemUI runs for the system user. In this case,
// trigger a new configuration change to ensure that the nav bar is updated in the same way.
int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
if (userId != UserHandle.USER_SYSTEM) {
mHandler.post(() -> {
FragmentHostManager fragmentHost = FragmentHostManager.get(mNavigationBarView);
//监听到navigatinbarmodechange重新加载fragment,重新执行布局
fragmentHost.reloadFragments();
});
}
}至此在setting中切换导航栏模式的流程就如上所述。
- “双按钮”导航/“三按钮”导航切换到手势导航
“双按钮”导航/“三按钮”导航切换到手势导航,流程与(2)上述基本相同,切换后navigationbar的高度会发生变化,具体的代码在NavigationBarView.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = MeasureSpec.getSize(widthMeasureSpec);
int h = MeasureSpec.getSize(heightMeasureSpec);
if (DEBUG) Log.d(TAG, String.format(
"onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight()));
final boolean newVertical = w > 0 && h > w
&& !isGesturalMode(mNavBarMode);
//方向变化后重新加载图标
if (newVertical != mIsVertical) {
mIsVertical = newVertical;
if (DEBUG) {
Log.d(TAG, String.format("onMeasure: h=%d, w=%d, vert=%s", h, w,
mIsVertical ? "y" : "n"));
}
reorient();
notifyVerticalChangedListener(newVertical);
}
//如果是2.,3键模式,navigationbar heigh的高度是不变的,当切换到手势模式下时,navigationbar height 会变小,一般为16dp
if (isGesturalMode(mNavBarMode)) {
// Update the nav bar background to match the height of the visible nav bar
int height = mIsVertical
? getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height_landscape)
: getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_height);
int frameHeight = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_frame_height);
mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h));
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}- 手势导航上滑源码分析
Android O之前的虚拟按键,基本的控制方法都是在SystemUI中做处理的,在Android R上为了在手势导航操作时其动画更加流畅,与Launcher互动效果更好,google的设计师就把手势导航相关的操作放到了Launcher3中,而且为了与SystemUI进行信息同步,利用两个aidl的文件利用binder做Launcher3与Systemui之前的进程通信。
在分析之前我们来看下AndroidManifest的一个开机启动属性android:directBootAware="true"这个属性对于手势导航的启动是有决定性的作用,有了这属性,其无论是应用还是服务都能第一时间启动,它保证了手势导航功能在系统准备好之前做好初始化。
<application
android:name=".SystemUIApplication"
android:persistent="true"//该应用是可持久的,也即是常驻的应用
android:allowClearUserData="false"//是否允许清楚用户数据
android:backupAgent=".backup.BackupHelper"
android:killAfterRestore="false"//kill之后是否restore
android:hardwareAccelerated="true"
android:label="@string/app_label"
android:icon="@drawable/icon"
android:process="com.android.systemui"
android:supportsRtl="true"
android:theme="@style/Theme.SystemUI"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true"
>而Launcher3是只对TouchInteractionService进行了设置,而且TouchInteractionService设置了一个action “android.intent.action.QUICKSTEP_SERVICE”,这为SystemUI绑定TouchInteractionService提供了向导。
下面先看下Launcher3与SystemUI之间做对接的服务,这两个服务为 SystemUI (OverviewProxyService)和Launcher3(TouchInteractionService) 。
OverviewProxyService:Class to send information from overview to launcher with a binder
是随着SystemUI应用启动而生成的,作为一个单例类存在于SystemUI这种系统应用中,而非一个正常的”Service“
OverviewProxyService的构造函数如下:
public OverviewProxyService(Context context, CommandQueue commandQueue,
NavigationBarController navBarController, NavigationModeController navModeController,
NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
PipUI pipUI, Optional<Divider> dividerOptional,
Optional<Lazy<StatusBar>> statusBarOptionalLazy,
BroadcastDispatcher broadcastDispatcher) {
super(broadcastDispatcher);
//配置mRecentsComponentName的值com.android.launcher3/com.android.quickstep.RecentsActivity,默认值在framework res下
mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
com.android.internal.R.string.config_recentsComponentName));
//指向launcher3来接收ACTION_QUICKSTEP
mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
.setPackage(mRecentsComponentName.getPackageName());
....
// 添加navigationbar mode 切换的监听
mNavBarMode = navModeController.addListener(this);
// 添加 launcher package changes的监听
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addDataScheme("package");
filter.addDataSchemeSpecificPart(mRecentsComponentName.getPackageName(),
PatternMatcher.PATTERN_LITERAL);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
mContext.registerReceiver(mLauncherStateChangedReceiver, filter);
// Listen for status bar state changes
statusBarWinController.registerCallback(mStatusBarWindowCallback);
mScreenshotHelper = new ScreenshotHelper(context);
// Listen for tracing state changes
commandQueue.addCallback(new CommandQueue.Callbacks() {
@Override
public void onTracingStateChanged(boolean enabled) {
mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
.commitUpdate(mContext.getDisplayId());
}
});
// Listen for user setup
startTracking();
// Connect to the service
updateEnabledState();
startConnectionToCurrentUser();
}OverviewProxyService中实现了ISystemUiProxy.aidl,如下所示:
SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
@Override
public void startScreenPinning(int taskId) {//开始固定屏幕
if (!verifyCaller("startScreenPinning")) {
return;
}
long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
mStatusBarOptionalLazy.ifPresent(
statusBarLazy -> statusBarLazy.get().showScreenPinningRequest(taskId,
false /* allowCancel */));
});
} finally {
Binder.restoreCallingIdentity(token);
}
}
@Override
public void stopScreenPinning() {//停止固定屏幕
if (!verifyCaller("stopScreenPinning")) {
return;
}
long token = Binder.clearCallingIdentity();
try {
mHandler.post(() -> {
try {
ActivityTaskManager.getService().stopSystemLockTaskMode();
} catch (RemoteException e) {
Log.e(TAG_OPS, "Failed to stop screen pinning");
}
});
} finally {
Binder.restoreCallingIdentity(token);
}
}
...
};
/***还有很多其他功能,在ISystemUiProxy.aidl文件中都有功能相关的描述
SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl***/mSysUiProxy是作为binder服务端存在的,在SystemUI等待着类似屏幕固定,分屏,最近任务展示等方法的回调。而mSysUiProxy与Launcher3与进行通信的开端便是“ServiceConnection”服务的链接绑定,如下所示:
SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
/*service对应的action,用于绑定服务*/
private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
//OverviewProxyService构造函数中调用startConnectionToCurrentUser()来binder Service
private void internalConnectToCurrentUser() {
/*断开之间的所有链接*/
disconnectFromLauncherService();
// If user has not setup yet or already connected, do not try to connect
if (!mDeviceProvisionedController.isCurrentUserSetup() || !isEnabled()) {
Log.v(TAG_OPS, "Cannot attempt connection, is setup "
+ mDeviceProvisionedController.isCurrentUserSetup() + ", is enabled "
+ isEnabled());
return;
}
mHandler.removeCallbacks(mConnectionRunnable);
/*Intent填入指定的action*/
Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP)
.setPackage(mRecentsComponentName.getPackageName());
try {
/*绑定服务*/
mBound = mContext.bindServiceAsUser(launcherServiceIntent,
mOverviewServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
UserHandle.of(mDeviceProvisionedController.getCurrentUser()));
} catch (SecurityException e) {
Log.e(TAG_OPS, "Unable to bind because of security error", e);
}
if (mBound) {
// Ensure that connection has been established even if it thinks it is bound
mHandler.postDelayed(mDeferredConnectionCallback, DEFERRED_CALLBACK_MILLIS);
} else {
// Retry after exponential backoff timeout
retryConnectionWithBackoff();
}
}
private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mConnectionBackoffAttempts = 0;
mHandler.removeCallbacks(mDeferredConnectionCallback);
try {
service.linkToDeath(mOverviewServiceDeathRcpt, 0);
} catch (RemoteException e) {
// Failed to link to death (process may have died between binding and connecting),
// just unbind the service for now and retry again
Log.e(TAG_OPS, "Lost connection to launcher service", e);
disconnectFromLauncherService();
retryConnectionWithBackoff();
return;
}
mCurrentBoundedUserId = mDeviceProvisionedController.getCurrentUser();
/*获取IOverviewProxy的代理*/
mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
Bundle params = new Bundle();
params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
try {
/*把ISystemUiProxy推荐给Launcher3*/
mOverviewProxy.onInitialize(params);
} catch (RemoteException e) {
mCurrentBoundedUserId = -1;
Log.e(TAG_OPS, "Failed to call onInitialize()", e);
}
dispatchNavButtonBounds();
// Update the systemui state flags
updateSystemUiStateFlags();
notifyConnectionChanged();
}
@Override
public void onNullBinding(ComponentName name) {
Log.w(TAG_OPS, "Null binding of '" + name + "', try reconnecting");
mCurrentBoundedUserId = -1;
retryConnectionWithBackoff();
}
@Override
public void onBindingDied(ComponentName name) {
Log.w(TAG_OPS, "Binding died of '" + name + "', try reconnecting");
mCurrentBoundedUserId = -1;
retryConnectionWithBackoff();
}
@Override
public void onServiceDisconnected(ComponentName name) {
// Do nothing
mCurrentBoundedUserId = -1;
}
}在mOverviewServiceConnection的onServiceConnected方法中,又看到了另外一个aidl的实现"IOverviewProxy"。这个对象是在服务绑定后通过binder传递过来的,那么"IOverviewProxy"的服务端就应该在Launcher3中了。当"IOverviewProxy"出现在SystemUI中的第一时间,mSysUiProxy就迫不及待的把自己打包好放到了Bundle(mSysUiProxy这个服务者在很尽责的推销自己嘛),然后又由mOverviewProxy反向传递给了Launcher3。
TouchInteractionService:Service connected by system-UI for handling touch interaction.
TouchInteractionService 是Launcher3中的Service,注册于AndroidManifest中,服务有directBootAware属性,为开机即启动的服务。
<service
android:name="com.android.quickstep.TouchInteractionService"
android:permission="android.permission.STATUS_BAR_SERVICE"
android:directBootAware="true" >
<intent-filter>
<action android:name="android.intent.action.QUICKSTEP_SERVICE" />
</intent-filter>
</service>下面就要看下IOverviewProxy在TouchInteractionService中的实现了:
private final IBinder mMyBinder = new IOverviewProxy.Stub() {
public void onActiveNavBarRegionChanges(Region region) {
mActiveNavBarRegion = region;
}
/*从SystemUI而来,携带着在SystemUI中实现的ISystemUiProxy*/
public void onInitialize(Bundle bundle) {
mISystemUiProxy = ISystemUiProxy.Stub
.asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
}
@Override
public void onOverviewToggle() {
mOverviewCommandHelper.onOverviewToggle();
}
@Override
public void onOverviewShown(boolean triggeredFromAltTab) {
mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
}
@Override
public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
if (triggeredFromAltTab && !triggeredFromHomeKey) {
// onOverviewShownFromAltTab hides the overview and ends at the target app
mOverviewCommandHelper.onOverviewHidden();
}
}
/***一些最近任务界面,Back按键等事件的互通,具体
SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl***/
};
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "Touch service connected");
/*当绑定成功后,反馈给SystemUI的IOverviewProxy*/
return mMyBinder;
}当上面的IOverviewProxy函数onInitialize执行完成之后,SystemUI也就和Launcher3胜利会师了,从而进入了你中有我,我中有你的状态。整个流程如下图所示:

当Launcher3成功与SystemUI能够正常通信之后Launcher3就开始注册屏幕Touch事件的监听了。
Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
private final IBinder mMyBinder = new IOverviewProxy.Stub() {
@BinderThread
public void onInitialize(Bundle bundle) {
//获取binder代理
ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
MAIN_EXECUTOR.execute(() -> {
SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy);
//监听屏幕触摸事件
TouchInteractionService.this.initInputMonitor();
preloadOverview(true /* fromInit */);
});
sIsInitialized = true;
}
private void initInputMonitor() {
disposeEventHandlers();
if (mDeviceState.isButtonNavMode() || !SystemUiProxy.INSTANCE.get(this).isActive()) {
return;
}
Bundle bundle = SystemUiProxy.INSTANCE.get(this).monitorGestureInput("swipe-up",
mDeviceState.getDisplayId());
//这个地方就是注册监听触摸事件的地方了
mInputMonitorCompat = InputMonitorCompat.fromBundle(bundle, KEY_EXTRA_INPUT_MONITOR);
mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
mMainChoreographer, this::onInputEvent);
mRotationTouchHelper.updateGestureTouchRegions();
}从上面看到,Launcher3处理触摸事件的方法是onInputEvent,其会根据系统处于不同的状态而生成不同的事件处理器,以下是具体的代码实现:
../packages/apps/Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
private void onInputEvent(InputEvent ev) {
if (!(ev instanceof MotionEvent)) {
Log.e(TAG, "Unknown event " + ev);
return;
}
MotionEvent event = (MotionEvent) ev;
TestLogging.recordMotionEvent(
TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
if (!mDeviceState.isUserUnlocked()) {
return;
}
Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
final int action = event.getAction();
if (action == ACTION_DOWN) {
if (TestProtocol.sDebugTracing) {
Log.d(TestProtocol.NO_SWIPE_TO_HOME, "TouchInteractionService.onInputEvent:DOWN");
}
....
}Laucher3中处理手势相关操作,具体后续会继续研究下对应的处理流程。
















