赞
踩
Android 中关于navigationbar相关的模式主要分为三种:手势导航,“双按钮”导航,“三按钮”导致,下面主要总结下对应的启动流程以及遇到的问题。
(1)NavigationbarView 绘制
NavigationbarView在创建statusbar的时候统一被创建,具体代码流程如下:
../framework/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
StatusBar启动后就会执行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.java
Create()(之前执行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切换R.id.navigation_bar_frame,这个id定义在navigation_bar_window.xml中
- fragmentHost.getFragmentManager().beginTransaction()
- .replace(R.id.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://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/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://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/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(R.id.ends_group),
- false /* landscape */, true /* start */);
- inflateButtons(start, mVertical.findViewById(R.id.ends_group),
- true /* landscape */, true /* start */);
-
- inflateButtons(center, mHorizontal.findViewById(R.id.center_group),
- false /* landscape */, false /* start */);
- inflateButtons(center, mVertical.findViewById(R.id.center_group),
- true /* landscape */, false /* start */);
-
- addGravitySpacer(mHorizontal.findViewById(R.id.ends_group));//插入空格符
- addGravitySpacer(mVertical.findViewById(R.id.ends_group));//插入空格符
-
- inflateButtons(end, mHorizontal.findViewById(R.id.ends_group),
- false /* landscape */, false /* start */);
- inflateButtons(end, mVertical.findViewById(R.id.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://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/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(R.id.menu_container);
- final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
- R.drawable.ic_ime_switcher_default);
- final RotationContextButton rotateSuggestionButton = new RotationContextButton(
- R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button);
- final ContextualButton accessibilityButton =
- new ContextualButton(R.id.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(R.id.back, new ButtonDispatcher(R.id.back));
- mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
- mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle));
- mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
- mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton);
- mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
- mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
- mButtonDispatchers.put(R.id.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(R.id.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(R.id.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, android.app.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中处理手势相关操作,具体后续会继续研究下对应的处理流程。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。