以AOSP为例,如果没有使用AOSP的Launcher3,那应该在各自厂商的vendor目录下
/packages/apps/Launcher3/quickstep/src/com/android/launcher3/taskbar/
从Android 12.1之后,大屏设备上TaskBar替换了传统的NavigationBar
TaskBar的启动过程
启动TaskBar的一个核心工具类是TaskbarManager,TaskbarManager是在Launcher3中的TouchInteractionService创建时被初始化的,TouchInteractionService及之前的流程不在本篇的讨论范围之内。
/packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java
382 @Override
383 public void onCreate() {
384 super.onCreate();
385 // Initialize anything here that is needed in direct boot mode.
386 // Everything else should be initialized in onUserUnlocked() below.
387 mMainChoreographer = Choreographer.getInstance();
388 mAM = ActivityManagerWrapper.getInstance();
389 mDeviceState = new RecentsAnimationDeviceState(this, true);
390 mTaskbarManager = new TaskbarManager(this);
391 mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
在TaskbarManager被创建出来之后,就会注册监听config的变化
/packages/apps/Launcher3/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
120 @Override
121 public void onConfigurationChanged(Configuration newConfig) {
//判断是否创建Taskbar的关键点就是DeviceProfile
122 DeviceProfile dp = mUserUnlocked
123 ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext)
124 : null;
125 int configDiff = mOldConfig.diff(newConfig);
126 int configsRequiringRecreate = ActivityInfo.CONFIG_ASSETS_PATHS
127 | ActivityInfo.CONFIG_LAYOUT_DIRECTION | ActivityInfo.CONFIG_UI_MODE
128 | ActivityInfo.CONFIG_SCREEN_SIZE;
129 boolean requiresRecreate = (configDiff & configsRequiringRecreate) != 0;
130 if ((configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0
131 && mTaskbarActivityContext != null && dp != null) {
132 // Additional check since this callback gets fired multiple times w/o
133 // screen size changing, or when simply rotating the device.
134 DeviceProfile oldDp = mTaskbarActivityContext.getDeviceProfile();
135 boolean isOrientationChange =
136 (configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0;
137 int oldWidth = isOrientationChange ? oldDp.heightPx : oldDp.widthPx;
138 int oldHeight = isOrientationChange ? oldDp.widthPx : oldDp.heightPx;
139 if (dp.widthPx == oldWidth && dp.heightPx == oldHeight) {
140 configDiff &= ~ActivityInfo.CONFIG_SCREEN_SIZE;
141 requiresRecreate = (configDiff & configsRequiringRecreate) != 0;
142 }
143 }
144
145 if (requiresRecreate) {
//提前进入recreateTaskbar的流程
146 recreateTaskbar();
147 } else {
148 // Config change might be handled without re-creating the taskbar
149 if (mTaskbarActivityContext != null) {
150 if (dp != null && dp.isTaskbarPresent) {
151 mTaskbarActivityContext.updateDeviceProfile(dp);
152 }
153 mTaskbarActivityContext.onConfigurationChanged(configDiff);
154 }
155 }
156 mOldConfig = newConfig;
157 }
158
159 @Override
160 public void onLowMemory() { }
161 };
162 mShutdownReceiver = new SimpleBroadcastReceiver(i -> destroyExistingTaskbar());
163 mDispInfoChangeListener = (context, info, flags) -> {
164 if ((flags & CHANGE_FLAGS) != 0) {
165 recreateTaskbar();
166 }
167 };
168 mDisplayController.addChangeListener(mDispInfoChangeListener);
169 SettingsCache.INSTANCE.get(mContext).register(USER_SETUP_COMPLETE_URI,
170 mUserSetupCompleteListener);
171 SettingsCache.INSTANCE.get(mContext).register(NAV_BAR_KIDS_MODE,
172 mNavBarKidsModeListener);
173 mContext.registerComponentCallbacks(mComponentCallbacks);
174 mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
175 //这里可以看到每次configChange其实都会走recreateTaskbar
176 recreateTaskbar();
177 }
在TaskbarManager的onConfigurationChange()回调中,是对config的很多综合判断,也有很多地方在调用recreateTaskbar(),基本上是取决于config中的一些device信息。
263 private void recreateTaskbar() {
//每次调用之前都会先清除 pre-Taskbar
264 destroyExistingTaskbar();
265
266 DeviceProfile dp =
267 mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
268 //判断TaskBar的关键信息
269 boolean isTaskBarEnabled = dp != null && dp.isTaskbarPresent;
270
271 if (!isTaskBarEnabled) {
//如果当前的config配置不需要taskBar就返回
272 SystemUiProxy.INSTANCE.get(mContext)
273 .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
274 return;
275 }
//能走到这里就说明需要TaskBar了,正式开始创建
276 //TaskbarActivityContext是TaskBar的另一个重要工具类,这里先创建
277 mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController,
278 mUnfoldProgressProvider);
279 //对TaskbarActivityContext初始化
280 mTaskbarActivityContext.init(mSharedState);
281 if (mActivity != null) {
282 mTaskbarActivityContext.setUIController(
283 createTaskbarUIControllerForActivity(mActivity));
284 }
285 }
Taskbar的相关内容实际上是在TouchInteractionService创建时就已经全部初始化完毕了,并且狠毒oconfig change都会回调到TaskbarManager当中,每次回调都会去走recreateTaskbar,先清除之前的TaskBar,再根据是否需要来重新创建。
所以TaskBar是一个持续动态消失与重构的过程。比如我们在Launcher页面上滑时,Taskbar是有变化的,返回home页面时Taskbar又再次更新。作为从系统的角度来讲,Taskbar相对于NavigationBar的loading显然加重了很多。不过Android 版本的升级带来性能drop是可以理解的。只是针对个别高版本低配置的平台,这里可能也是一个小的优化方向。
上面有注释到boolean isTaskBarEnabled 是判断Taskbar的关键信息,但如果仅仅修改这里的参数,作用域仅仅局限于Taskbar,比如将这里置为false,在大屏设备上最终的结果是TaskBar没有创建,NavigationBar也没有创建。可以说明NavigationBar创建与否,和TaskBar没有必然联系。再来看另外一个重要的工具类 TaskbarActivityContext
/packages/apps/Launcher3/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
在创建TaskbarActivityContext时,是先new出这个对象,再去init的,我们就先来看构造函数
135 public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
136 TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
137 unfoldTransitionProgressProvider) {
138 super(windowContext);
//保存了当前的DeviceProfile信息
139 mDeviceProfile = dp.copy(this);
140
141 final Resources resources = getResources();
142
//区分3-Buttons导航或是虚拟导航
143 mNavMode = DisplayController.getNavigationMode(windowContext);
144 mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
145 mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
146 () -> getPackageManager().isSafeMode());
147 mIsUserSetupComplete = SettingsCache.INSTANCE.get(this).getValue(
148 Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
149 mIsNavBarForceVisible = SettingsCache.INSTANCE.get(this).getValue(
150 Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
151 mIsNavBarKidsMode = SettingsCache.INSTANCE.get(this).getValue(
152 Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
153
// Taskbar中显示的UI Icon大小
154 updateIconSize(resources);
155
156 // Get display and corners first, as views might use them in constructor.
//显示在哪个display上
157 Display display = windowContext.getDisplay();
158 Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
159 ? windowContext.getApplicationContext()
160 : windowContext.getApplicationContext().createDisplayContext(display);
161 mWindowManager = c.getSystemService(WindowManager.class);
162 mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
163 mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
164
165 // Inflate出一些重要view,当遇到按键相关问题时,可以从这里debug
166 mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(
167 R.layout.taskbar, null, false);
168 TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
169 TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim);
170 FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
171 StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
172
173 mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this);
174
175 //new出来很多和Taskbar相关的Controller,各自分工明确
176 mControllers = new TaskbarControllers(this,
177 new TaskbarDragController(this),
178 buttonController,
179 getPackageManager().hasSystemFeature(FEATURE_PC)
180 ? new DesktopNavbarButtonsViewController(this, navButtonsView) :
181 new NavbarButtonsViewController(this, navButtonsView),
182 new RotationButtonController(this,
183 c.getColor(R.color.taskbar_nav_icon_light_color),
184 c.getColor(R.color.taskbar_nav_icon_dark_color),
185 R.drawable.ic_sysbar_rotate_button_ccw_start_0,
186 R.drawable.ic_sysbar_rotate_button_ccw_start_90,
187 R.drawable.ic_sysbar_rotate_button_cw_start_0,
188 R.drawable.ic_sysbar_rotate_button_cw_start_90,
189 () -> getDisplay().getRotation()),
190 new TaskbarDragLayerController(this, mDragLayer),
191 new TaskbarViewController(this, taskbarView),
192 new TaskbarScrimViewController(this, taskbarScrimView),
193 new TaskbarUnfoldAnimationController(this, unfoldTransitionProgressProvider,
194 mWindowManager, WindowManagerGlobal.getWindowManagerService()),
195 new TaskbarKeyguardController(this),
196 new StashedHandleViewController(this, stashedHandleView),
197 new TaskbarStashController(this),
198 new TaskbarEduController(this),
199 new TaskbarAutohideSuspendController(this),
200 new TaskbarPopupController(this),
201 new TaskbarForceVisibleImmersiveController(this),
202 new TaskbarAllAppsController(this, dp),
203 new TaskbarInsetsController(this));
204 }
在构造函数中,有多个ime、NavBar等相关对象,在解决Taskbar相关问题时,如果有怀疑这些组件,可以从这里开始debug继续排查。比如遇到过TaskBar上的Activity UI显示异常,就可以从上面的updateIconSize(resources)来调查,一般都是xml适配方面的问题。此外,new 出来的Controllers是解决问题的重要工具,这些Controllers根据定义的名字,都很直观。构造函数之后,再看下 init 过程:
206 public void init(@NonNull TaskbarSharedState sharedState) {
207 mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
208 mWindowLayoutParams = createDefaultWindowLayoutParams();
209
210 // Initialize controllers after all are constructed.
211 mControllers.init(sharedState);
212 updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
213
214 mWindowManager.addView(mDragLayer, mWindowLayoutParams);
215 }
这个就非常简单直接了,看到addView应该就知道,此时TaskBar已经显示出来了,当然算是创建完成了。显示创建了Taskbar显示区域的window属性createDefaultWindowLayoutParams(),然后也初始化了之前创建的很多Controllers,更新一个systemUI的状态,就算是完成了。
对于NavigationBar,我们可以通过getNavigationBarWidth 和getNavigationBarHeight来获取它的宽高,那么对于Taskbar,是否有提供给应用层的类似接口呢?
答案是没有的,但我们仍能通过类似的方式来获取。参考google开发者指南 developer.android.google.cn/develop/ui/…
Note:Although The Taskbar doesn't have its own inset type, its dimension can be retrieved using WindowInsetsCompat.Type.NavigationBars() or WindowInsetsCompat.Type.systemBars()
这是google 给出的一个demo code
ViewCompat.setOnApplyWindowInsetsListener(view, (v, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
// Apply the insets as a margin to the view. Here the system is setting
// only the bottom, left, and right dimensions, but apply whichever insets are
// appropriate to your layout. You can also update the view padding
// if that's more appropriate.
MarginLayoutParams mlp = (MarginLayoutParams) v.getLayoutParams();
mlp.leftMargin = insets.left;
mlp.bottomMargin = insets.bottom;
mlp.rightMargin = insets.right;
v.setLayoutParams(mlp);
// Return CONSUMED if you don't want want the window insets to keep being
// passed down to descendant views.
return WindowInsetsCompat.CONSUMED;
});
在Android 13的new feature也有关于Taskbar的简单介绍 developer.android.google.cn/about/versi…