当前位置:   article > 正文

基于android R源码 Navigationbar加载流程_安卓14 navigationbar启动流程

安卓14 navigationbar启动流程

Android 中关于navigationbar相关的模式主要分为三种:手势导航,“双按钮”导航,“三按钮”导致,下面主要总结下对应的启动流程以及遇到的问题。

(1)NavigationbarView 绘制

NavigationbarView在创建statusbar的时候统一被创建,具体代码流程如下:

../framework/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java

StatusBar启动后就会执行start方法调用的时候会执行createAndAddWindow()

  1. public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
  2. makeStatusBarView(result);
  3. ..
  4. }

然后执行makeStatusBarView()

  1. protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
  2. ..
  3. createNavigationBar(result);//这个方法就是创建navigationbar的入口
  4. ..
  5. }
  1. protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
  2. mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);
  3. }

../framework/base/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java

createNavigationBar()

  1. public void createNavigationBars(final boolean includeDefaultDisplay,
  2. RegisterStatusBarResult result) {
  3. //获取当前的display的数据
  4. Display[] displays = mDisplayManager.getDisplays();
  5. //执行for循环添加navigationbarview
  6. for (Display display : displays) {
  7. if (includeDefaultDisplay || display.getDisplayId() != DEFAULT_DISPLAY) {
  8. void createNavigationBar(Display display, RegisterStatusBarResult result) {
  9. if (display == null) {
  10. return;
  11. }
  12. final int displayId = display.getDisplayId();
  13. final boolean isOnDefaultDisplay = displayId == DEFAULT_DISPLAY;
  14. final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
  15. try {
  16. //framework通过属性”qemu.hw.mainkeys”来定义是否显示navigationbar
  17. if (!wms.hasNavigationBar(displayId)) {
  18. return;
  19. }
  20. } catch (RemoteException e) {
  21. // Cannot get wms, just return with warning message.
  22. Log.w(TAG, "Cannot get WindowManager.");
  23. return;
  24. }
  25. final Context context = isOnDefaultDisplay
  26. ? mContext
  27. : mContext.createDisplayContext(display);
  28. //navigationbarview其实是fragement,添加到系统view中
  29. NavigationBarFragment.create(context, (tag, fragment) -> {
  30. NavigationBarFragment navBar = (NavigationBarFragment) fragment;
  31. // Unfortunately, we still need it because status bar needs LightBarController
  32. // before notifications creation. We cannot directly use getLightBarController()
  33. // from NavigationBarFragment directly.
  34. LightBarController lightBarController = isOnDefaultDisplay
  35. ? Dependency.get(LightBarController.class)
  36. : new LightBarController(context,
  37. Dependency.get(DarkIconDispatcher.class),
  38. Dependency.get(BatteryController.class));
  39. navBar.setLightBarController(lightBarController);
  40. // TODO(b/118592525): to support multi-display, we start to add something which is
  41. // per-display, while others may be global. I think it's time to add
  42. // a new class maybe named DisplayDependency to solve per-display
  43. // Dependency problem.
  44. AutoHideController autoHideController = isOnDefaultDisplay
  45. ? Dependency.get(AutoHideController.class)
  46. : new AutoHideController(context, mHandler);
  47. navBar.setAutoHideController(autoHideController);
  48. navBar.restoreSystemUiVisibilityState();
  49. mNavigationBars.append(displayId, navBar);
  50. if (result != null) {
  51. navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
  52. result.mImeWindowVis, result.mImeBackDisposition,
  53. result.mShowImeSwitcher);
  54. }
  55. });
  56. }
  57. (display, result);//创建navigationbar
  58. }
  59. }
  60. }

../frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/PHONE/NavigationBarFragment.java

Create()(之前执行NavigationBarFragment/onCreate/onCreateView/onViewCreate的顺序),最终add了fragment的onCreateView加载的布局

  1. public static View create(Context context, FragmentListener listener) {
  2. //设置view添加的属性
  3. WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
  4. LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
  5. WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
  6. WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
  7. | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  8. | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
  9. | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
  10. | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
  11. | WindowManager.LayoutParams.FLAG_SLIPPERY,
  12. PixelFormat.TRANSLUCENT);
  13. lp.token = new Binder();
  14. lp.setTitle("NavigationBar" + context.getDisplayId());
  15. lp.accessibilityTitle = context.getString(R.string.nav_bar);
  16. lp.windowAnimations = 0;
  17. lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
  18. View navigationBarView = LayoutInflater.from(context).inflate(
  19. R.layout.navigation_bar_window, null);//加载navigation_bar_window布局
  20. if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + navigationBarView);
  21. if (navigationBarView == null) return null;
  22. final NavigationBarFragment fragment = FragmentHostManager.get(navigationBarView)
  23. .create(NavigationBarFragment.class);
  24. navigationBarView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
  25. @Override
  26. public void onViewAttachedToWindow(View v) {
  27. final FragmentHostManager fragmentHost = FragmentHostManager.get(v);
  28. //使用fragment切换R.id.navigation_bar_frame,这个id定义在navigation_bar_window.xml中
  29. fragmentHost.getFragmentManager().beginTransaction()
  30. .replace(R.id.navigation_bar_frame, fragment, TAG)
  31. .commit();
  32. fragmentHost.addTagListener(TAG, listener);
  33. }
  34. @Override
  35. public void onViewDetachedFromWindow(View v) {
  36. FragmentHostManager.removeAndDestroy(v);
  37. }
  38. });
  39. context.getSystemService(WindowManager.class).addView(navigationBarView, lp);
  40. return navigationBarView;
  41. }

来看WindowManager加载的这个view的布局:navigation_bar_window.xml,发现根布局是自定义的view类NavigationBarFrame

  1. <com.android.systemui.statusbar.phone.NavigationBarFrame
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:systemui="http://schemas.android.com/apk/res-auto"
  4. android:id="@+id/navigation_bar_frame"
  5. android:theme="@style/Theme.SystemUI"
  6. android:layout_height="match_parent"
  7. android:layout_width="match_parent">
  8. </com.android.systemui.statusbar.phone.NavigationBarFrame>

我们进入NavigationBarFrame类。发现类里并不是我们的预期,就是一个FrameLayout,对DeadZone功能下的touch事件做了手脚,不管了。

NavigationBarFragment的生命周期呢。onCreateView()里,导航栏的真正的rootView。

  1. public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
  2. Bundle savedInstanceState) {
  3. return inflater.inflate(R.layout.navigation_bar, container, false);
  4. }

进入导航栏的真正根布局:navigation_bar.xml,NavigationBarView 和 NavigationBarInflaterView 都要仔细研读

  1. <com.android.systemui.statusbar.phone.NavigationBarView
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:systemui="http://schemas.android.com/apk/res-auto"
  4. android:layout_height="match_parent"
  5. android:layout_width="match_parent"
  6. android:background="@drawable/system_bar_background">
  7. <com.android.systemui.statusbar.phone.NavigationBarInflaterView
  8. android:id="@+id/navigation_inflater"
  9. android:layout_width="match_parent"
  10. android:layout_height="match_parent" />
  11. </com.android.systemui.statusbar.phone.NavigationBarView>

../frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView .java

先看构造方法,因为加载xml布局首先走的是初始化(是先执行NavigationBarView再加载NavigationBarInflaterView)

  1. //监听模式变换
  2. public class NavigationBarInflaterView extends FrameLayout implements NavigationModeController.ModeChangedListener {
  3. public NavigationBarInflaterView(Context context, AttributeSet attrs) {
  4. super(context, attrs);
  5. createInflaters();//根据屏幕旋转角度创建子view(单个back home or recent)的父布局
  6. mOverviewProxyService = Dependency.get(OverviewProxyService.class);
  7. mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
  8. }
  9. void createInflaters() {
  10. mLayoutInflater = LayoutInflater.from(mContext);
  11. Configuration landscape = new Configuration();
  12. landscape.setTo(mContext.getResources().getConfiguration());
  13. landscape.orientation = Configuration.ORIENTATION_LANDSCAPE;
  14. mLandscapeInflater = LayoutInflater.from(mContext.createConfigurationContext(landscape));
  15. }

再看onFinishInflate()方法,这是view的生命周期,每个view被inflate之后都会回调

  1. protected void onFinishInflate() {
  2. super.onFinishInflate();
  3. inflateChildren();//加载navigation_layout/navigation_layout_verital布局,此布局复写用于定义控件点击范围
  4. clearViews();
  5. inflateLayout(getDefaultLayout());//关键方法:加载了 back.home.recent三个按钮的layout
  6. }
  7. protected String getDefaultLayout() {
  8. final int defaultResource = QuickStepContract.isGesturalMode(mNavBarMode)
  9. ? R.string.config_navBarLayoutHandle//手势下显示的字符结构
  10. : mOverviewProxyService.shouldShowSwipeUpUI()//是否显示上滑动的显示字符结构
  11. ? R.string.config_navBarLayoutQuickstep
  12. : R.string.config_navBarLayout;
  13. return getContext().getString(defaultResource);
  14. }

看inflateLayout():里面的newLayout参数很重要!!!根据上一个方法看到getDefaultLayout(),他return了一个在xml写死的字符串。字符样式显示如下:

<string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string>

再看inflateLayout方法,他解析分割了xml里配置的字符串,并传给了inflateButtons方法

  1. protected void inflateLayout(String newLayout) {
  2. mCurrentLayout = newLayout;
  3. if (newLayout == null) {
  4. newLayout = getDefaultLayout();
  5. }
  6. String[] sets = newLayout.split(GRAVITY_SEPARATOR, 3);//根据“;”号分割成长度为3的数组
  7. if (sets.length != 3) {
  8. Log.d(TAG, "Invalid layout.");
  9. newLayout = getDefaultLayout();
  10. sets = newLayout.split(GRAVITY_SEPARATOR, 3);
  11. }
  12. String[] start = sets[0].split(BUTTON_SEPARATOR);//根据“,”号分割,包含 left[.5W]和back[1WC]
  13. String[] center = sets[1].split(BUTTON_SEPARATOR);//包含home
  14. String[] end = sets[2].split(BUTTON_SEPARATOR);//包含recent[1WC]和right[.5W]
  15. // Inflate these in start to end order or accessibility traversal will be messed up.
  16. inflateButtons(start, mHorizontal.findViewById(R.id.ends_group),
  17. false /* landscape */, true /* start */);
  18. inflateButtons(start, mVertical.findViewById(R.id.ends_group),
  19. true /* landscape */, true /* start */);
  20. inflateButtons(center, mHorizontal.findViewById(R.id.center_group),
  21. false /* landscape */, false /* start */);
  22. inflateButtons(center, mVertical.findViewById(R.id.center_group),
  23. true /* landscape */, false /* start */);
  24. addGravitySpacer(mHorizontal.findViewById(R.id.ends_group));//插入空格符
  25. addGravitySpacer(mVertical.findViewById(R.id.ends_group));//插入空格符
  26. inflateButtons(end, mHorizontal.findViewById(R.id.ends_group),
  27. false /* landscape */, false /* start */);
  28. inflateButtons(end, mVertical.findViewById(R.id.ends_group),
  29. true /* landscape */, false /* start */);
  30. updateButtonDispatchersCurrentView();
  31. }

再看inflateButtons()方法,遍历加载inflateButton:

  1. private void inflateButtons(String[] buttons, ViewGroup parent, boolean landscape,
  2. boolean start) {
  3. for (int i = 0; i < buttons.length; i++) {
  4. inflateButton(buttons[i], parent, landscape, start);
  5. }
  6. }
  7. protected View inflateButton(String buttonSpec, ViewGroup parent, boolean landscape,
  8. boolean start) {
  9. LayoutInflater inflater = landscape ? mLandscapeInflater : mLayoutInflater;
  10. View v = createView(buttonSpec, parent, inflater);//这个是根据上面传来的字符加载不同布局
  11. if (v == null) return null;
  12. v = applySize(v, buttonSpec, landscape, start);//根据返回字符的解析,来确认图标显示的大小
  13. parent.addView(v);//addView到父布局
  14. addToDispatchers(v);
  15. View lastView = landscape ? mLastLandscape : mLastPortrait;
  16. View accessibilityView = v;
  17. if (v instanceof ReverseRelativeLayout) {
  18. accessibilityView = ((ReverseRelativeLayout) v).getChildAt(0);
  19. }
  20. if (lastView != null) {
  21. accessibilityView.setAccessibilityTraversalAfter(lastView.getId());
  22. }
  23. if (landscape) {
  24. mLastLandscape = accessibilityView;
  25. } else {
  26. mLastPortrait = accessibilityView;
  27. }
  28. return v;
  29. }

我们来看createView()方法:以home按键为例,加载了home的button,其实是加载了 R.layout.home 的layout布局

  1. private View createView(String buttonSpec, ViewGroup parent, LayoutInflater inflater) {
  2. View v = null;
  3. String button = extractButton(buttonSpec);
  4. if (LEFT.equals(button)) {
  5. button = extractButton(NAVSPACE);
  6. } else if (RIGHT.equals(button)) {
  7. button = extractButton(MENU_IME_ROTATE);
  8. }
  9. if (HOME.equals(button)) {
  10. v = inflater.inflate(R.layout.home, parent, false);
  11. } else if (BACK.equals(button)) {
  12. v = inflater.inflate(R.layout.back, parent, false);
  13. }
  14. .....
  15. else if (button.startsWith(KEY)) {
  16. String uri = extractImage(button);
  17. int code = extractKeycode(button);
  18. v = inflater.inflate(R.layout.custom_key, parent, false);
  19. ((KeyButtonView) v).setCode(code);
  20. if (uri != null) {
  21. if (uri.contains(":")) {
  22. ((KeyButtonView) v).loadAsync(Icon.createWithContentUri(uri));
  23. } else if (uri.contains("/")) {
  24. int index = uri.indexOf('/');
  25. String pkg = uri.substring(0, index);
  26. int id = Integer.parseInt(uri.substring(index + 1));
  27. ((KeyButtonView) v).loadAsync(Icon.createWithResource(pkg, id));
  28. }
  29. }
  30. }
  31. return v;
  32. }
  33. //home.xml
  34. <com.android.systemui.statusbar.policy.KeyButtonView
  35. xmlns:android="http://schemas.android.com/apk/res/android"
  36. xmlns:systemui="http://schemas.android.com/apk/res-auto"
  37. android:id="@+id/home"
  38. android:layout_width="@dimen/navigation_key_width"
  39. android:layout_height="match_parent"
  40. android:layout_weight="0"
  41. systemui:keyCode="3"//systemui自定义的属性,模拟的keycode码
  42. android:scaleType="center"
  43. android:contentDescription="@string/accessibility_home"
  44. android:paddingStart="@dimen/navigation_key_padding"
  45. android:paddingEnd="@dimen/navigation_key_padding"
  46. />

SystemUI\src\com\android\systemui\statusbar\policy\KeyButtonView.java

先来看KeyButtonView的构造方法,我们之前xml的systemui:keyCode=”3”方法在这里获取。再来看Touch事件,通过sendEvent()方法可以看出,back等view的点击touch事件不是自己处理的,而是交由系统以实体按键(keycode)的形式处理的.

当然KeyButtonView类还处理了支持长按的button,按键的响声等,这里忽略。

至此,导航栏按键事件我们梳理完毕。

  1. public KeyButtonView(Context context, AttributeSet attrs, int defStyle, InputManager manager,
  2. UiEventLogger uiEventLogger) {
  3. super(context, attrs);
  4. mUiEventLogger = uiEventLogger;
  5. TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.KeyButtonView,
  6. defStyle, 0);
  7. mCode = a.getInteger(R.styleable.KeyButtonView_keyCode, KEYCODE_UNKNOWN);
  8. mPlaySounds = a.getBoolean(R.styleable.KeyButtonView_playSound, true);
  9. TypedValue value = new TypedValue();
  10. if (a.getValue(R.styleable.KeyButtonView_android_contentDescription, value)) {
  11. mContentDescriptionRes = value.resourceId;
  12. }
  13. a.recycle();
  14. setClickable(true);
  15. mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
  16. mRipple = new KeyButtonRipple(context, this);
  17. mOverviewProxyService = Dependency.get(OverviewProxyService.class);
  18. mInputManager = manager;
  19. setBackground(mRipple);
  20. setWillNotDraw(false);
  21. forceHasOverlappingRendering(false);
  22. }
  23. //touch事件处理
  24. public boolean onTouchEvent(MotionEvent ev) {
  25. final boolean showSwipeUI = mOverviewProxyService.shouldShowSwipeUpUI();
  26. final int action = ev.getAction();
  27. int x, y;
  28. if (action == MotionEvent.ACTION_DOWN) {
  29. mGestureAborted = false;
  30. }
  31. if (mGestureAborted) {
  32. setPressed(false);
  33. return false;
  34. }
  35. ...
  36. }
  37. private void sendEvent(int action, int flags, long when) {
  38. mMetricsLogger.write(new LogMaker(MetricsEvent.ACTION_NAV_BUTTON_EVENT)
  39. .setType(MetricsEvent.TYPE_ACTION)
  40. .setSubtype(mCode)
  41. .addTaggedData(MetricsEvent.FIELD_NAV_ACTION, action)
  42. .addTaggedData(MetricsEvent.FIELD_FLAGS, flags));
  43. logSomePresses(action, flags);
  44. //back键单独处理
  45. if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
  46. Log.i(TAG, "Back button event: " + KeyEvent.actionToString(action));
  47. if (action == MotionEvent.ACTION_UP) {
  48. mOverviewProxyService.notifyBackAction((flags & KeyEvent.FLAG_CANCELED) == 0,
  49. -1, -1, true /* isButton */, false /* gestureSwipeLeft */);
  50. }
  51. }
  52. final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
  53. //这里根据mCode new了一个KeyEvent事件,通过injectInputEvent使事件生效。
  54. final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
  55. 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
  56. flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
  57. InputDevice.SOURCE_KEYBOARD);
  58. int displayId = INVALID_DISPLAY;
  59. // Make KeyEvent work on multi-display environment
  60. if (getDisplay() != null) {
  61. displayId = getDisplay().getDisplayId();
  62. }
  63. // Bubble controller will give us a valid display id if it should get the back event
  64. BubbleController bubbleController = Dependency.get(BubbleController.class);
  65. int bubbleDisplayId = bubbleController.getExpandedDisplayId(mContext);
  66. if (mCode == KeyEvent.KEYCODE_BACK && bubbleDisplayId != INVALID_DISPLAY) {
  67. displayId = bubbleDisplayId;
  68. }
  69. if (displayId != INVALID_DISPLAY) {
  70. ev.setDisplayId(displayId);
  71. }
  72. //keycode通过系统处理
  73. mInputManager.injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
  74. }

设置图片的icon到底在哪?我们之前一直阅读的是NavigationBarInflaterView,根据布局我们还有一个类没有看,NavigationBarView

  1. public NavigationBarView(Context context, AttributeSet attrs) {
  2. super(context, attrs);
  3. ...
  4. // Set up the context group of buttons
  5. mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
  6. final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
  7. R.drawable.ic_ime_switcher_default);
  8. final RotationContextButton rotateSuggestionButton = new RotationContextButton(
  9. R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button);
  10. final ContextualButton accessibilityButton =
  11. new ContextualButton(R.id.accessibility_button,
  12. R.drawable.ic_sysbar_accessibility_button);
  13. mContextualButtonGroup.addButton(imeSwitcherButton);
  14. if (!isGesturalMode) {
  15. mContextualButtonGroup.addButton(rotateSuggestionButton);
  16. }
  17. mContextualButtonGroup.addButton(accessibilityButton);
  18. mOverviewProxyService = Dependency.get(OverviewProxyService.class);
  19. //此类主要功能是手势模式下上滑后出现的提示
  20. mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
  21. mFloatingRotationButton = new FloatingRotationButton(context);
  22. mRotationButtonController = new RotationButtonController(context,
  23. R.style.RotateButtonCCWStart90,
  24. isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton,
  25. mRotationButtonListener);
  26. mConfiguration = new Configuration();
  27. mTmpLastConfiguration = new Configuration();
  28. mConfiguration.updateFrom(context.getResources().getConfiguration());
  29. //pin页面的提示类
  30. mScreenPinningNotify = new ScreenPinningNotify(mContext);
  31. //navigationbar的颜色控制类
  32. mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.class));
  33. //mButtonDispatchers是维护这些home/back/recent图标view的管理类,会传递到他的 child,NavigationBarInflaterView类中
  34. mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
  35. mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
  36. mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle));
  37. mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
  38. mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton);
  39. mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
  40. mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
  41. mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
  42. mDeadZone = new DeadZone(this);
  43. mNavColorSampleMargin = getResources()
  44. .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
  45. //手势模式下屏幕左右滑动的EdgeBack类
  46. mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService,
  47. mSysUiFlagContainer, mPluginManager, this::updateStates);
  48. //获取范围类的RegionSampling来变化显示是light/dark
  49. mRegionSamplingHelper = new RegionSamplingHelper(this,
  50. new RegionSamplingHelper.SamplingCallback() {
  51. @Override
  52. public void onRegionDarknessChanged(boolean isRegionDark) {
  53. getLightTransitionsController().setIconsDark(!isRegionDark ,
  54. true /* animate */);
  55. }
  56. @Override
  57. public Rect getSampledRegion(View sampledView) {
  58. if (mOrientedHandleSamplingRegion != null) {
  59. return mOrientedHandleSamplingRegion;
  60. }
  61. updateSamplingRect();
  62. return mSamplingBounds;
  63. }
  64. @Override
  65. public boolean isSamplingEnabled() {
  66. return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode);
  67. }
  68. });
  69. }

下面说明下navigationbarview图标如此加载,当加载完成后会执行onAttachedToWindow,这个方法中对图标进行添加

  1. protected void onAttachedToWindow() {
  2. super.onAttachedToWindow();
  3. requestApplyInsets();
  4. //设置图标的入口方法
  5. reorient();
  6. //模式变化时各个类对于onNavigationModeChanged状态的处理
  7. onNavigationModeChanged(mNavBarMode);
  8. setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
  9. if (mRotationButtonController != null) {
  10. mRotationButtonController.registerListeners();
  11. }
  12. mEdgeBackGestureHandler.onNavBarAttached();
  13. getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
  14. }
  15. public void reorient() {
  16. //view gone对应的布局
  17. updateCurrentView();
  18. ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
  19. mDeadZone.onConfigurationChanged(mCurrentRotation);
  20. // force the low profile & disabled states into compliance
  21. mBarTransitions.init();
  22. // Resolve layout direction if not resolved since components changing layout direction such
  23. // as changing languages will recreate this view and the direction will be resolved later
  24. if (!isLayoutDirectionResolved()) {
  25. resolveLayoutDirection();
  26. }
  27. //更新navButtonIcon图标
  28. updateNavButtonIcons();
  29. getHomeButton().setVertical(mIsVertical);
  30. }
  31. public void updateNavButtonIcons() {
  32. // We have to replace or restore the back and home button icons when exiting or entering
  33. // carmode, respectively. Recents are not available in CarMode in nav bar so change
  34. // to recent icon is not required.
  35. final boolean useAltBack =
  36. (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
  37. KeyButtonDrawable backIcon = mBackIcon;
  38. orientBackButton(backIcon);
  39. KeyButtonDrawable homeIcon = mHomeDefaultIcon;
  40. if (!mUseCarModeUi) {
  41. orientHomeButton(homeIcon);
  42. }
  43. //设置home/back的button image
  44. getHomeButton().setImageDrawable(homeIcon);
  45. getBackButton().setImageDrawable(backIcon);
  46. //根据是否分屏模式来显示recent对应的图标
  47. updateRecentsIcon();
  48. // Update IME button visibility, a11y and rotate button always overrides the appearance
  49. mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher,
  50. (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
  51. mBarTransitions.reapplyDarkIntensity();
  52. ...
  53. // When screen pinning, don't hide back and home when connected service or back and
  54. // recents buttons when disconnected from launcher service in screen pinning mode,
  55. // as they are used for exiting.
  56. //判断当前是否为pinning状态
  57. final boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
  58. if (mOverviewProxyService.isEnabled()) {
  59. // Force disable recents when not in legacy mode
  60. disableRecent |= !QuickStepContract.isLegacyMode(mNavBarMode);
  61. if (pinningActive && !QuickStepContract.isGesturalMode(mNavBarMode)) {
  62. disableBack = disableHome = false;
  63. }
  64. } else if (pinningActive) {//pinning状态禁用back/home
  65. disableBack = disableRecent = false;
  66. }
  67. ViewGroup navButtons = getCurrentView().findViewById(R.id.nav_buttons);
  68. if (navButtons != null) {
  69. LayoutTransition lt = navButtons.getLayoutTransition();
  70. if (lt != null) {
  71. if (!lt.getTransitionListeners().contains(mTransitionListener)) {
  72. lt.addTransitionListener(mTransitionListener);
  73. }
  74. }
  75. }
  76. //根据是否禁用来显示对应的button
  77. getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
  78. getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
  79. getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
  80. getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
  81. notifyActiveTouchRegions();
  82. }

上述图标切换在横竖屏切换,分辨率变化中都会对图标进行切换,具体实现在navigationbarview/navigationbarInflateview中

../framework/base/package/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java

最后一个问题navigationbar button的click事件在何处添加的,当布局加载完成后会回调onViewCreated

  1. public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
  2. super.onViewCreated(view, savedInstanceState);
  3. mNavigationBarView = (NavigationBarView) view;
  4. ...
  5. //back/home/recent 设置click事件方法入口
  6. prepareNavigationBarView();
  7. checkNavBarModes();
  8. //亮/灭屏/用户切换 navigatinbarview变化
  9. IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
  10. filter.addAction(Intent.ACTION_SCREEN_ON);
  11. filter.addAction(Intent.ACTION_USER_SWITCHED);
  12. mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
  13. Handler.getMain(), UserHandle.ALL);
  14. notifyNavigationBarScreenOn();
  15. mOverviewProxyService.addCallback(mOverviewProxyListener);
  16. updateSystemUiStateFlags(-1);
  17. ...
  18. }
  19. private void prepareNavigationBarView() {
  20. //设置navigationbar相关图标背景
  21. mNavigationBarView.reorient();
  22. //设置recentsButton的setOnClickListener/setOnTouchListener/setOnLongClickListener监听
  23. ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
  24. recentsButton.setOnClickListener(this::onRecentsClick);
  25. recentsButton.setOnTouchListener(this::onRecentsTouch);
  26. recentsButton.setLongClickable(true);
  27. recentsButton.setOnLongClickListener(this::onLongPressBackRecents);
  28. //设置backButton 的setLongClickable属性,backbutton具体操作在keyButtonView中实现
  29. ButtonDispatcher backButton = mNavigationBarView.getBackButton();
  30. backButton.setLongClickable(true);
  31. //设置homeButton 的setOnTouchListener/setOnLongClickListener监听
  32. ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
  33. homeButton.setOnTouchListener(this::onHomeTouch);
  34. homeButton.setOnLongClickListener(this::onHomeLongClick);
  35. //盲人模式下accessibilityButton的setOnClickListener/setOnLongClickListener监听
  36. ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
  37. accessibilityButton.setOnClickListener(this::onAccessibilityClick);
  38. accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
  39. updateAccessibilityServicesState(mAccessibilityManager);
  40. updateScreenPinningGestures();
  41. }

至此,SystemUI的虚拟导航栏模块代码流程结束。

总结:创建一个window属性的父view,通过读取解析xml里config的配置,addView需要的icon,src图片资源通过代码设置,touch事件以keycode方式交由系统处理。

Settings中切换系统导航的流程分析

手机/平板等设备系统导航默认为“三按钮”导航,那么切换流程是如何实现的的,下面主要分析下“三按钮”导航切换到“双按钮”导航,“三按钮”导航切换到手势导航的流程。

  1. “三按钮”导航切换到“双按钮”导航分析

原生系统导航Preference定义在accessibility_settings.xml中,具体路径如下:

../packages/apps/Settings/res/xml/accessibility_settings.xml

  1. <Preference
  2. android:fragment="com.android.settings.gestures.SystemNavigationGestureSettings"//系统导航的fragment
  3. android:key="gesture_system_navigation_input_summary_accessibility"
  4. android:persistent="false"
  5. android:title="@string/system_navigation_title"
  6. settings:searchable="false"
  7. settings:controller="com.android.settings.gestures.SystemNavigationPreferenceController"/>//对应的控制类

由于SystemNavigationGestureSettings.java继承之RadioButtonPickerFragment,radioClick最后调用的是

setDefaultKey方法

  1. protected boolean setDefaultKey(String key) {
  2. //通过overlaymanager设置对应不同的模式
  3. setCurrentSystemNavigationMode(mOverlayManager, key);
  4. //根据不同的navigationmode设置不同的video资源
  5. setIllustrationVideo(mVideoPreference, key);
  6. if (TextUtils.equals(KEY_SYSTEM_NAV_GESTURAL, key) && (
  7. isAnyServiceSupportAccessibilityButton() || isNavBarMagnificationEnabled())) {
  8. Intent intent = new Intent(getActivity(), SettingsTutorialDialogWrapperActivity.class);
  9. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  10. startActivity(intent);
  11. }
  12. return true;
  13. }
  14. static void setCurrentSystemNavigationMode(IOverlayManager overlayManager, String key) {
  15. String overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
  16. switch (key) {
  17. case KEY_SYSTEM_NAV_GESTURAL:
  18. overlayPackage = NAV_BAR_MODE_GESTURAL_OVERLAY;
  19. break;
  20. case KEY_SYSTEM_NAV_2BUTTONS:
  21. overlayPackage = NAV_BAR_MODE_2BUTTON_OVERLAY;
  22. break;
  23. case KEY_SYSTEM_NAV_3BUTTONS:
  24. overlayPackage = NAV_BAR_MODE_3BUTTON_OVERLAY;
  25. break;
  26. }
  27. try {
  28. //3键切为2键后 执行系统setEnabledExclusiveInCategory方法
  29. overlayManager.setEnabledExclusiveInCategory(overlayPackage, USER_CURRENT);
  30. } catch (RemoteException e) {
  31. throw e.rethrowFromSystemServer();
  32. }
  33. }

../frameworks/base/core/java/android/content/om/OverlayManager.java

  1. public void setEnabledExclusiveInCategory(@NonNull final String packageName,
  2. @NonNull UserHandle user) throws SecurityException, IllegalStateException {
  3. try {
  4. if (!mService.setEnabledExclusiveInCategory(packageName, user.getIdentifier())) {
  5. throw new IllegalStateException("setEnabledExclusiveInCategory failed");
  6. }
  7. } catch (SecurityException e) {
  8. rethrowSecurityException(e);
  9. } catch (RemoteException e) {
  10. throw e.rethrowFromSystemServer();
  11. }
  12. }

../frameworks/base/services/core/java/com/android/server/om/OverlayManagerService.java

  1. public boolean setEnabledExclusiveInCategory(@Nullable String packageName,final int userIdArg) {
  2. ...
  3. try {
  4. synchronized (mLock) {
  5. try {
  6. mImpl.setEnabledExclusive(packageName,
  7. true /* withinCategory */, realUserId)
  8. .ifPresent(mPropagateOverlayChange);
  9. return true;
  10. } catch (OperationFailedException e) {
  11. return false;
  12. }
  13. }
  14. ...
  15. }
  16. private final Consumer<PackageAndUser> mPropagateOverlayChange = (pair) -> {
  17. //更新setting文件
  18. persistSettings();
  19. FgThread.getHandler().post(() -> {
  20. List<String> affectedTargets = updatePackageManager(pair.packageName, pair.userId);
  21. updateActivityManager(affectedTargets, pair.userId);
  22. //发送overlaychange广播,systemui会接收到此广播
  23. broadcastActionOverlayChanged(pair.packageName, pair.userId);
  24. });
  25. };
  26. private void broadcastActionOverlayChanged(@NonNull final String targetPackageName,
  27. final int userId) {
  28. //发送ACTION_OVERLAY_CHANGED的广播
  29. final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
  30. Uri.fromParts("package", targetPackageName, null));
  31. //设置此flag限制只有动态注册才能接收次action
  32. intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
  33. try {
  34. ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null, null,
  35. null, android.app.AppOpsManager.OP_NONE, null, false, false, userId);
  36. } catch (RemoteException e) {
  37. // Intentionally left empty.
  38. }
  39. }

../frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java

  1. public NavigationModeController(Context context,DeviceProvisionedController deviceProvisionedController,
  2. ConfigurationController configurationController,@UiBackground Executor uiBgExecutor) {
  3. ...
  4. //初始化中添加ACTION_OVERLAY_CHANGED广播监听
  5. IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
  6. overlayFilter.addDataScheme("package");
  7. overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
  8. mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
  9. ...
  10. }
  11. private BroadcastReceiver mReceiver = new BroadcastReceiver() {
  12. @Override
  13. public void onReceive(Context context, Intent intent) {
  14. updateCurrentInteractionMode(true /* notify */);
  15. }
  16. };
  17. public void updateCurrentInteractionMode(boolean notify) {
  18. mCurrentUserContext = getCurrentUserContext();
  19. //获取当前导航栏的模式
  20. int mode = getCurrentInteractionMode(mCurrentUserContext);
  21. //如果是手势的话执行switchToDefaultGestureNavOverlayIfNecessary
  22. if (mode == NAV_BAR_MODE_GESTURAL) {
  23. switchToDefaultGestureNavOverlayIfNecessary();
  24. }
  25. mUiBgExecutor.execute(() ->
  26. Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
  27. Secure.NAVIGATION_MODE, String.valueOf(mode)));
  28. //回调通知onNavigationModeChanged navigationbar改变
  29. if (notify) {
  30. for (int i = 0; i < mListeners.size(); i++) {
  31. //这个回调很多类注册了,我们只要关注navigationbarFragment/navigationbarview的回调
  32. mListeners.get(i).onNavigationModeChanged(mode);
  33. }
  34. }
  35. }

../frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigatinbarFragment.java

  1. public void onNavigationModeChanged(int mode) {
  2. mNavBarMode = mode;
  3. updateScreenPinningGestures();
  4. ...
  5. // Workaround for b/132825155, for secondary users, we currently don't receive configuration
  6. // changes on overlay package change since SystemUI runs for the system user. In this case,
  7. // trigger a new configuration change to ensure that the nav bar is updated in the same way.
  8. int userId = ActivityManagerWrapper.getInstance().getCurrentUserId();
  9. if (userId != UserHandle.USER_SYSTEM) {
  10. mHandler.post(() -> {
  11. FragmentHostManager fragmentHost = FragmentHostManager.get(mNavigationBarView);
  12. //监听到navigatinbarmodechange重新加载fragment,重新执行布局
  13. fragmentHost.reloadFragments();
  14. });
  15. }
  16. }

至此在setting中切换导航栏模式的流程就如上所述。

  1. “双按钮”导航/“三按钮”导航切换到手势导航

“双按钮”导航/“三按钮”导航切换到手势导航,流程与(2)上述基本相同,切换后navigationbar的高度会发生变化,具体的代码在NavigationBarView.java

  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2. int w = MeasureSpec.getSize(widthMeasureSpec);
  3. int h = MeasureSpec.getSize(heightMeasureSpec);
  4. if (DEBUG) Log.d(TAG, String.format(
  5. "onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight()));
  6. final boolean newVertical = w > 0 && h > w
  7. && !isGesturalMode(mNavBarMode);
  8. //方向变化后重新加载图标
  9. if (newVertical != mIsVertical) {
  10. mIsVertical = newVertical;
  11. if (DEBUG) {
  12. Log.d(TAG, String.format("onMeasure: h=%d, w=%d, vert=%s", h, w,
  13. mIsVertical ? "y" : "n"));
  14. }
  15. reorient();
  16. notifyVerticalChangedListener(newVertical);
  17. }
  18. //如果是2.,3键模式,navigationbar heigh的高度是不变的,当切换到手势模式下时,navigationbar height 会变小,一般为16dp
  19. if (isGesturalMode(mNavBarMode)) {
  20. // Update the nav bar background to match the height of the visible nav bar
  21. int height = mIsVertical
  22. ? getResources().getDimensionPixelSize(
  23. com.android.internal.R.dimen.navigation_bar_height_landscape)
  24. : getResources().getDimensionPixelSize(
  25. com.android.internal.R.dimen.navigation_bar_height);
  26. int frameHeight = getResources().getDimensionPixelSize(
  27. com.android.internal.R.dimen.navigation_bar_frame_height);
  28. mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h));
  29. }
  30. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  31. }

  1. 手势导航上滑源码分析

Android O之前的虚拟按键,基本的控制方法都是在SystemUI中做处理的,在Android R上为了在手势导航操作时其动画更加流畅,与Launcher互动效果更好,google的设计师就把手势导航相关的操作放到了Launcher3中,而且为了与SystemUI进行信息同步,利用两个aidl的文件利用binder做Launcher3与Systemui之前的进程通信。

在分析之前我们来看下AndroidManifest的一个开机启动属性android:directBootAware="true"这个属性对于手势导航的启动是有决定性的作用,有了这属性,其无论是应用还是服务都能第一时间启动,它保证了手势导航功能在系统准备好之前做好初始化。

  1. <application
  2. android:name=".SystemUIApplication"
  3. android:persistent="true"//该应用是可持久的,也即是常驻的应用
  4. android:allowClearUserData="false"//是否允许清楚用户数据
  5. android:backupAgent=".backup.BackupHelper"
  6. android:killAfterRestore="false"//kill之后是否restore
  7. android:hardwareAccelerated="true"
  8. android:label="@string/app_label"
  9. android:icon="@drawable/icon"
  10. android:process="com.android.systemui"
  11. android:supportsRtl="true"
  12. android:theme="@style/Theme.SystemUI"
  13. android:defaultToDeviceProtectedStorage="true"
  14. android:directBootAware="true"
  15. >

而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的构造函数如下:

  1. public OverviewProxyService(Context context, CommandQueue commandQueue,
  2. NavigationBarController navBarController, NavigationModeController navModeController,
  3. NotificationShadeWindowController statusBarWinController, SysUiState sysUiState,
  4. PipUI pipUI, Optional<Divider> dividerOptional,
  5. Optional<Lazy<StatusBar>> statusBarOptionalLazy,
  6. BroadcastDispatcher broadcastDispatcher) {
  7. super(broadcastDispatcher);
  8. //配置mRecentsComponentName的值com.android.launcher3/com.android.quickstep.RecentsActivity,默认值在framework res下
  9. mRecentsComponentName = ComponentName.unflattenFromString(context.getString(
  10. com.android.internal.R.string.config_recentsComponentName));
  11. //指向launcher3来接收ACTION_QUICKSTEP
  12. mQuickStepIntent = new Intent(ACTION_QUICKSTEP)
  13. .setPackage(mRecentsComponentName.getPackageName());
  14. ....
  15. // 添加navigationbar mode 切换的监听
  16. mNavBarMode = navModeController.addListener(this);
  17. // 添加 launcher package changes的监听
  18. IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
  19. filter.addDataScheme("package");
  20. filter.addDataSchemeSpecificPart(mRecentsComponentName.getPackageName(),
  21. PatternMatcher.PATTERN_LITERAL);
  22. filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
  23. mContext.registerReceiver(mLauncherStateChangedReceiver, filter);
  24. // Listen for status bar state changes
  25. statusBarWinController.registerCallback(mStatusBarWindowCallback);
  26. mScreenshotHelper = new ScreenshotHelper(context);
  27. // Listen for tracing state changes
  28. commandQueue.addCallback(new CommandQueue.Callbacks() {
  29. @Override
  30. public void onTracingStateChanged(boolean enabled) {
  31. mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
  32. .commitUpdate(mContext.getDisplayId());
  33. }
  34. });
  35. // Listen for user setup
  36. startTracking();
  37. // Connect to the service
  38. updateEnabledState();
  39. startConnectionToCurrentUser();
  40. }

OverviewProxyService中实现了ISystemUiProxy.aidl,如下所示:

SystemUI/src/com/android/systemui/recents/OverviewProxyService.java

  1. private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
  2. @Override
  3. public void startScreenPinning(int taskId) {//开始固定屏幕
  4. if (!verifyCaller("startScreenPinning")) {
  5. return;
  6. }
  7. long token = Binder.clearCallingIdentity();
  8. try {
  9. mHandler.post(() -> {
  10. mStatusBarOptionalLazy.ifPresent(
  11. statusBarLazy -> statusBarLazy.get().showScreenPinningRequest(taskId,
  12. false /* allowCancel */));
  13. });
  14. } finally {
  15. Binder.restoreCallingIdentity(token);
  16. }
  17. }
  18. @Override
  19. public void stopScreenPinning() {//停止固定屏幕
  20. if (!verifyCaller("stopScreenPinning")) {
  21. return;
  22. }
  23. long token = Binder.clearCallingIdentity();
  24. try {
  25. mHandler.post(() -> {
  26. try {
  27. ActivityTaskManager.getService().stopSystemLockTaskMode();
  28. } catch (RemoteException e) {
  29. Log.e(TAG_OPS, "Failed to stop screen pinning");
  30. }
  31. });
  32. } finally {
  33. Binder.restoreCallingIdentity(token);
  34. }
  35. }
  36. ...
  37. };
  38. /***还有很多其他功能,在ISystemUiProxy.aidl文件中都有功能相关的描述
  39. SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl***/

mSysUiProxy是作为binder服务端存在的,在SystemUI等待着类似屏幕固定,分屏,最近任务展示等方法的回调。而mSysUiProxy与Launcher3与进行通信的开端便是“ServiceConnection”服务的链接绑定,如下所示:

SystemUI/src/com/android/systemui/recents/OverviewProxyService.java

  1. /*service对应的action,用于绑定服务*/
  2. private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
  3. //OverviewProxyService构造函数中调用startConnectionToCurrentUser()来binder Service
  4. private void internalConnectToCurrentUser() {
  5. /*断开之间的所有链接*/
  6. disconnectFromLauncherService();
  7. // If user has not setup yet or already connected, do not try to connect
  8. if (!mDeviceProvisionedController.isCurrentUserSetup() || !isEnabled()) {
  9. Log.v(TAG_OPS, "Cannot attempt connection, is setup "
  10. + mDeviceProvisionedController.isCurrentUserSetup() + ", is enabled "
  11. + isEnabled());
  12. return;
  13. }
  14. mHandler.removeCallbacks(mConnectionRunnable);
  15. /*Intent填入指定的action*/
  16. Intent launcherServiceIntent = new Intent(ACTION_QUICKSTEP)
  17. .setPackage(mRecentsComponentName.getPackageName());
  18. try {
  19. /*绑定服务*/
  20. mBound = mContext.bindServiceAsUser(launcherServiceIntent,
  21. mOverviewServiceConnection,
  22. Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
  23. UserHandle.of(mDeviceProvisionedController.getCurrentUser()));
  24. } catch (SecurityException e) {
  25. Log.e(TAG_OPS, "Unable to bind because of security error", e);
  26. }
  27. if (mBound) {
  28. // Ensure that connection has been established even if it thinks it is bound
  29. mHandler.postDelayed(mDeferredConnectionCallback, DEFERRED_CALLBACK_MILLIS);
  30. } else {
  31. // Retry after exponential backoff timeout
  32. retryConnectionWithBackoff();
  33. }
  34. }
  35. private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
  36. @Override
  37. public void onServiceConnected(ComponentName name, IBinder service) {
  38. mConnectionBackoffAttempts = 0;
  39. mHandler.removeCallbacks(mDeferredConnectionCallback);
  40. try {
  41. service.linkToDeath(mOverviewServiceDeathRcpt, 0);
  42. } catch (RemoteException e) {
  43. // Failed to link to death (process may have died between binding and connecting),
  44. // just unbind the service for now and retry again
  45. Log.e(TAG_OPS, "Lost connection to launcher service", e);
  46. disconnectFromLauncherService();
  47. retryConnectionWithBackoff();
  48. return;
  49. }
  50. mCurrentBoundedUserId = mDeviceProvisionedController.getCurrentUser();
  51. /*获取IOverviewProxy的代理*/
  52. mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
  53. Bundle params = new Bundle();
  54. params.putBinder(KEY_EXTRA_SYSUI_PROXY, mSysUiProxy.asBinder());
  55. params.putFloat(KEY_EXTRA_WINDOW_CORNER_RADIUS, mWindowCornerRadius);
  56. params.putBoolean(KEY_EXTRA_SUPPORTS_WINDOW_CORNERS, mSupportsRoundedCornersOnWindows);
  57. try {
  58. /*把ISystemUiProxy推荐给Launcher3*/
  59. mOverviewProxy.onInitialize(params);
  60. } catch (RemoteException e) {
  61. mCurrentBoundedUserId = -1;
  62. Log.e(TAG_OPS, "Failed to call onInitialize()", e);
  63. }
  64. dispatchNavButtonBounds();
  65. // Update the systemui state flags
  66. updateSystemUiStateFlags();
  67. notifyConnectionChanged();
  68. }
  69. @Override
  70. public void onNullBinding(ComponentName name) {
  71. Log.w(TAG_OPS, "Null binding of '" + name + "', try reconnecting");
  72. mCurrentBoundedUserId = -1;
  73. retryConnectionWithBackoff();
  74. }
  75. @Override
  76. public void onBindingDied(ComponentName name) {
  77. Log.w(TAG_OPS, "Binding died of '" + name + "', try reconnecting");
  78. mCurrentBoundedUserId = -1;
  79. retryConnectionWithBackoff();
  80. }
  81. @Override
  82. public void onServiceDisconnected(ComponentName name) {
  83. // Do nothing
  84. mCurrentBoundedUserId = -1;
  85. }
  86. }

在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属性,为开机即启动的服务。

  1. <service
  2. android:name="com.android.quickstep.TouchInteractionService"
  3. android:permission="android.permission.STATUS_BAR_SERVICE"
  4. android:directBootAware="true" >
  5. <intent-filter>
  6. <action android:name="android.intent.action.QUICKSTEP_SERVICE" />
  7. </intent-filter>
  8. </service>

下面就要看下IOverviewProxy在TouchInteractionService中的实现了:

  1. private final IBinder mMyBinder = new IOverviewProxy.Stub() {
  2. public void onActiveNavBarRegionChanges(Region region) {
  3. mActiveNavBarRegion = region;
  4. }
  5. /*从SystemUI而来,携带着在SystemUI中实现的ISystemUiProxy*/
  6. public void onInitialize(Bundle bundle) {
  7. mISystemUiProxy = ISystemUiProxy.Stub
  8. .asInterface(bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
  9. MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::initInputMonitor);
  10. MAIN_THREAD_EXECUTOR.execute(TouchInteractionService.this::onSystemUiProxySet);
  11. }
  12. @Override
  13. public void onOverviewToggle() {
  14. mOverviewCommandHelper.onOverviewToggle();
  15. }
  16. @Override
  17. public void onOverviewShown(boolean triggeredFromAltTab) {
  18. mOverviewCommandHelper.onOverviewShown(triggeredFromAltTab);
  19. }
  20. @Override
  21. public void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
  22. if (triggeredFromAltTab && !triggeredFromHomeKey) {
  23. // onOverviewShownFromAltTab hides the overview and ends at the target app
  24. mOverviewCommandHelper.onOverviewHidden();
  25. }
  26. }
  27. /***一些最近任务界面,Back按键等事件的互通,具体
  28. SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl***/
  29. };
  30. @Override
  31. public IBinder onBind(Intent intent) {
  32. Log.d(TAG, "Touch service connected");
  33. /*当绑定成功后,反馈给SystemUI的IOverviewProxy*/
  34. return mMyBinder;
  35. }

当上面的IOverviewProxy函数onInitialize执行完成之后,SystemUI也就和Launcher3胜利会师了,从而进入了你中有我,我中有你的状态。整个流程如下图所示:

当Launcher3成功与SystemUI能够正常通信之后Launcher3就开始注册屏幕Touch事件的监听了。

Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java

  1. private final IBinder mMyBinder = new IOverviewProxy.Stub() {
  2. @BinderThread
  3. public void onInitialize(Bundle bundle) {
  4. //获取binder代理
  5. ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
  6. bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
  7. MAIN_EXECUTOR.execute(() -> {
  8. SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy);
  9. //监听屏幕触摸事件
  10. TouchInteractionService.this.initInputMonitor();
  11. preloadOverview(true /* fromInit */);
  12. });
  13. sIsInitialized = true;
  14. }
  15. private void initInputMonitor() {
  16. disposeEventHandlers();
  17. if (mDeviceState.isButtonNavMode() || !SystemUiProxy.INSTANCE.get(this).isActive()) {
  18. return;
  19. }
  20. Bundle bundle = SystemUiProxy.INSTANCE.get(this).monitorGestureInput("swipe-up",
  21. mDeviceState.getDisplayId());
  22. //这个地方就是注册监听触摸事件的地方了
  23. mInputMonitorCompat = InputMonitorCompat.fromBundle(bundle, KEY_EXTRA_INPUT_MONITOR);
  24. mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
  25. mMainChoreographer, this::onInputEvent);
  26. mRotationTouchHelper.updateGestureTouchRegions();
  27. }

从上面看到,Launcher3处理触摸事件的方法是onInputEvent,其会根据系统处于不同的状态而生成不同的事件处理器,以下是具体的代码实现:

../packages/apps/Launcher3/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java

  1. private void onInputEvent(InputEvent ev) {
  2. if (!(ev instanceof MotionEvent)) {
  3. Log.e(TAG, "Unknown event " + ev);
  4. return;
  5. }
  6. MotionEvent event = (MotionEvent) ev;
  7. TestLogging.recordMotionEvent(
  8. TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
  9. if (!mDeviceState.isUserUnlocked()) {
  10. return;
  11. }
  12. Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
  13. TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
  14. final int action = event.getAction();
  15. if (action == ACTION_DOWN) {
  16. if (TestProtocol.sDebugTracing) {
  17. Log.d(TestProtocol.NO_SWIPE_TO_HOME, "TouchInteractionService.onInputEvent:DOWN");
  18. }
  19. ....
  20. }

Laucher3中处理手势相关操作,具体后续会继续研究下对应的处理流程。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/219257
推荐阅读
相关标签
  

闽ICP备14008679号