热点新闻
Android 车载应用开发与分析(12) - SystemUI (一)
2023-07-11 07:40  浏览:596  搜索引擎搜索“早勤网”
温馨提示:信息一旦丢失不一定找得到,请务必收藏信息以备急用!本站所有信息均是注册会员发布如遇到侵权请联系文章中的联系方式或客服删除!
联系我时,请说明是在早勤网看到的信息,谢谢。
展会发布 展会网站大全 报名观展合作 软文发布

1.前言

Android 车载应用开发与分析是一个系列性的文章,这个是第12篇,该系列文章旨在分析原生车载Android系统中核心应用的实现方式,帮助初次从事车载应用开发的同学,更好地理解车载应用开发的方式,积累android系统应用的开发经验。

注意:本文的源码分析部分非常的枯燥,最好还是下载android源码然后对着看,逐步理顺逻辑。
本文中使用的源码基于android-11.0.0_r48
在线源码可以使用下面的网址(基于android-11.0.0_r21)
http://aospxref.com/android-11.0.0_r21/xref/frameworks/base/packages/CarSystemUI/
http://aospxref.com/android-11.0.0_r21/xref/frameworks/base/packages/SystemUI/

2.车载 SystemUI

2.1 SystemUI 概述

SystemUI通俗的解释就是系统的 UI,在Android 系统中由SystemUI负责统一管理整个系统层的UI,它也是一个系统级应用程序(APK),但是与我们之前接触过的系统应用程序不同,SystemUI的源码在/frameworks/base/packages/目录下,而不是在/packages/目录下,这也说明了SystemUI这个应用的本质上可以归属于framework层。

  • SystemUI

Android - Phone中SystemUI从源码量看就是一个相当复杂的程序,常见的如:状态栏、消息中心、近期任务、截屏以及一系列功能都是在SystemUI中实现的。

源码位置:/frameworks/base/packages/SystemUI






  • CarSystemUI

Android-AutoMotive 中的SystemUI相对手机中要简单不少,目前商用车载系统中几乎必备的顶部状态栏、消息中心、底部导航栏在原生的Android系统中都已经实现了。

源码位置:frameworks/base/packages/CarSystemUI






虽然CarSystemUISystemUI的源码位置不同,但是二者实际上是复用关系。通过阅读CarSystemUI的Android.bp文件可以发现CarSystemUI在编译时把SystemUI以静态库的方式引入进来了。

android.bp源码位置:/frameworks/base/packages/CarSystemUI/Android.bp

android_library { name: "CarSystemUI-core", ... static_libs: [ "SystemUI-core", "SystemUIPluginLib", "SystemUISharedLib", "SystemUI-tags", "SystemUI-proto", ... ], ... }

2.2 SystemUI 启动流程

Android开发者应该都听说SystemServer,它是Android framework中关键系统的服务,由Android系统最核心的进程Zygotefork生成,进程名为system_server。我们常说的ActivityManagerServicePackageManagerServiceWindowManageService都是由SystemServer启动的。

而在ActivityManagerService完成启动后(SystemReady),SystemServer就会去着手启动SystemUI

SystemServer 的源码路径:frameworks/base/services/java/com/android/server/SystemServer.java

mActivityManagerService.systemReady(() -> { Slog.i(TAG, "Making services ready"); t.traceBegin("StartSystemUI"); try { startSystemUi(context, windowManagerF); } catch (Throwable e) { reportWtf("starting System UI", e); } t.traceEnd(); }, t);

startSystemUi()代码细节如下.从这里我们可以看出,SystemUI本质就是一个Service,通过Pm获取到的Component 是com.android.systemui/.SystemUIService。

private static void startSystemUi(Context context, WindowManagerService windowManager) { PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class); Intent intent = new Intent(); intent.setComponent(pm.getSystemUiServiceComponent()); intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING); //Slog.d(TAG, "Starting service: " + intent); context.startServiceAsUser(intent, UserHandle.SYSTEM); windowManager.onSystemUiStarted(); }

startSystemUi()中启动SystemUIService,在SystemUIServiceoncreate()方法中再通过SystemUIApplication.startServicesIfNeeded()来完成SystemUI的组件的初始化。

SystemUIService 源码位置:/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java

// SystemUIService @Override public void onCreate() { super.onCreate(); Slog.e("SystemUIService", "onCreate"); // Start all of SystemUI ((SystemUIApplication) getApplication()).startServicesIfNeeded(); ... }

startServicesIfNeeded()中,通过SystemUIFactory获取到配置在config.xml中每个子模块的className。

SystemUIApplication 源码位置:/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java

// SystemUIApplication public void startServicesIfNeeded() { String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources()); startServicesIfNeeded("StartServices", names); } // SystemUIFactory public String[] getSystemUIServiceComponents(Resources resources) { return resources.getStringArray(R.array.config_systemUIServiceComponents); }

<!-- SystemUI Services: The classes of the stuff to start. --> <string-array name="config_systemUIServiceComponents" translatable="false"> <item>com.android.systemui.util.NotificationChannels</item> <item>com.android.systemui.keyguard.KeyguardViewMediator</item> <item>com.android.systemui.recents.Recents</item> <item>com.android.systemui.volume.VolumeUI</item> <item>com.android.systemui.stackdivider.Divider</item> <item>com.android.systemui.statusbar.phone.StatusBar</item> <item>com.android.systemui.usb.StorageNotification</item> <item>com.android.systemui.power.PowerUI</item> <item>com.android.systemui.media.RingtonePlayer</item> <item>com.android.systemui.keyboard.KeyboardUI</item> <item>com.android.systemui.pip.PipUI</item> <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item> <item>@string/config_systemUIVendorServiceComponent</item> <item>com.android.systemui.util.leak.GarbageMonitor$Service</item> <item>com.android.systemui.LatencyTester</item> <item>com.android.systemui.globalactions.GlobalActionsComponent</item> <item>com.android.systemui.ScreenDecorations</item> <item>com.android.systemui.biometrics.AuthController</item> <item>com.android.systemui.SliceBroadcastRelayHandler</item> <item>com.android.systemui.SizeCompatModeActivityController</item> <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> <item>com.android.systemui.theme.ThemeOverlayController</item> <item>com.android.systemui.accessibility.WindowMagnification</item> <item>com.android.systemui.accessibility.SystemActions</item> <item>com.android.systemui.toast.ToastUI</item> </string-array>

最终在startServicesIfNeeded()中通过反射完成了每个SystemUI组件的创建,然后再调用各个SystemUIonStart()方法来继续执行子模块的初始化。

private SystemUI[] mServices; private void startServicesIfNeeded(String metricsPrefix, String[] services) { if (mServicesStarted) { return; } mServices = new SystemUI[services.length]; ... final int N = services.length; for (int i = 0; i < N; i++) { String clsName = services[i]; if (DEBUG) Log.d(TAG, "loading: " + clsName); try { SystemUI obj = mComponentHelper.resolveSystemUI(clsName); if (obj == null) { Constructor constructor = Class.forName(clsName).getConstructor(Context.class); obj = (SystemUI) constructor.newInstance(this); } mServices[i] = obj; } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException ex) { throw new RuntimeException(ex); } if (DEBUG) Log.d(TAG, "running: " + mServices[i]); // 调用各个子模块的start() mServices[i].start(); // 首次启动时,这里始终为false,不会被调用 if (mBootCompleteCache.isBootComplete()) { mServices[i].onBootCompleted(); } } mServicesStarted = true; }

SystemUIApplicationonCreate()方法中注册了一个开机广播,当接收到开机广播后会调用SystemUIonBootCompleted()方法来告诉每个子模块Android系统已经完成开机。

@Override public void onCreate() { super.onCreate(); Log.v(TAG, "SystemUIApplication created."); // 设置所有服务继承的应用程序主题。 // 请注意,在清单中设置应用程序主题仅适用于activity。这里是让Service保持与主题设置同步。 setTheme(R.style.Theme_SystemUI); if (Process.myUserHandle().equals(UserHandle.SYSTEM)) { IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); bootCompletedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mBootCompleteCache.isBootComplete()) return; if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received"); unregisterReceiver(this); mBootCompleteCache.setBootComplete(); if (mServicesStarted) { final int N = mServices.length; for (int i = 0; i < N; i++) { mServices[i].onBootCompleted(); } } } }, bootCompletedFilter); ... } else { // 我们不需要为正在执行某些任务的子进程启动服务。 ... } }

这里的SystemUI是一个抽象类,状态栏、近期任务等等模块都是继承自SystemUI,通过这种方式可以很大程度上简化复杂的SystemUI程序中各个子模块创建方式,同时我们可以通过配置资源的方式动态加载需要的SystemUI模块。

在实际的项目中开发我们自己的SystemUI时,这种初始化子模块的方式是值得我们学习的,不过由于原生的SystemUI使用了AOP框架 - Dagger来创建组件,所以SystemUI子模块的初始化细节就不再介绍了。






SystemUI的源码如下,方法基本都能见名知意,就不再介绍了。

public abstract class SystemUI implements Dumpable { protected final Context mContext; public SystemUI(Context context) { mContext = context; } public abstract void start(); protected void onConfigurationChanged(Configuration newConfig) { } // 非核心功能,可以不用关心 @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { } protected void onBootCompleted() { }

总结一下,SystemUI的大致启动流程可以归纳如下(时序图语法并不严谨,理解即可)






3.CarSystemUI 的启动流程

之前也提到过CarSystemUI复用了手机SystemUI的代码,所以CarSystemUI的启动流程和SystemUI的是完全一致的。

这里就有个疑问,CarSystemUI中需要的功能与SystemUI中是有差异的,那么是这些差异化的功能是如何引入并完成初始化?以及一些手机的SystemUI才需要的功能是如何去除的呢?

其实很简单,在SystemUI的启动流程中我们得知,各个子模块的className是通过SystemUIFactorygetSystemUIServiceComponents()获取到的,那么只要继承SystemUIFactory并重写getSystemUIServiceComponents()就可以了。

public class CarSystemUIFactory extends SystemUIFactory { @Override protected SystemUIRootComponent buildSystemUIRootComponent(Context context) { return DaggerCarSystemUIRootComponent.builder() .contextHolder(new ContextHolder(context)) .build(); } @Override public String[] getSystemUIServiceComponents(Resources resources) { Set<String> names = new HashSet<>(); // 先引入systemUI中的components for (String s : super.getSystemUIServiceComponents(resources)) { names.add(s); } // 再移除CarsystemUI不需要的components for (String s : resources.getStringArray(R.array.config_systemUIServiceComponentsExclude)) { names.remove(s); } // 最后再添加CarsystemUI特有的components for (String s : resources.getStringArray(R.array.config_systemUIServiceComponentsInclude)) { names.add(s); } String[] finalNames = new String[names.size()]; names.toArray(finalNames); return finalNames; } }

<!-- 需要移除的Components. --> <string-array name="config_systemUIServiceComponentsExclude" translatable="false"> <item>com.android.systemui.recents.Recents</item> <item>com.android.systemui.volume.VolumeUI</item> <item>com.android.systemui.stackdivider.Divider</item> <item>com.android.systemui.statusbar.phone.StatusBar</item> <item>com.android.systemui.keyboard.KeyboardUI</item> <item>com.android.systemui.pip.PipUI</item> <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item> <item>com.android.systemui.LatencyTester</item> <item>com.android.systemui.globalactions.GlobalActionsComponent</item> <item>com.android.systemui.SliceBroadcastRelayHandler</item> <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item> <item>com.android.systemui.accessibility.WindowMagnification</item> <item>com.android.systemui.accessibility.SystemActions</item> </string-array> <!-- 新增的Components. --> <string-array name="config_systemUIServiceComponentsInclude" translatable="false"> <item>com.android.systemui.car.navigationbar.CarNavigationBar</item> <item>com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier</item> <item>com.android.systemui.car.window.SystemUIOverlayWindowManager</item> <item>com.android.systemui.car.volume.VolumeUI</item> </string-array>

通过以上方式,就完成了CarSystemUI子模块的替换。

由于CarSystemUI模块的源码量极大,全部分析一遍再写成文章耗费的时间将无法估计,这里结合我个人在车载方面的工作经验,拣出了一些在商用车载项目必备的功能,来分析它们在原生系统中是如何实现的。

3.顶部状态栏与底部导航栏

  • 顶部状态栏

状态栏是CarSystemUI中一个功能重要的功能,它负责向用户展示操作系统当前最基本信息,例如:时间、蜂窝网络的信号强度、蓝牙信息、wifi信息等。






  • 底部导航栏

在原生的车载Android系统中,底部的导航按钮由经典的三颗返回、主页、菜单键替换成如下图所示的七颗快捷功能按钮。从左到右依次主页、地图、蓝牙音乐、蓝牙电话、桌面、消息中心、语音助手。







3.1 布局方式

  • 顶部状态栏
    顶部状态栏的布局方式比较简单,如下图所示:







布局文件的源码就不贴了,量比较大,而且包含了许多的自定义View,如果不是为了学习如何自定义View阅读的意义不大。

源码位置:frameworks/base/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml

  • 底部导航栏
    底部状态栏的布局方式就更简单了,如下图所示:







不过比较有意思的是,导航栏、状态栏每个按钮对应的Action的intent都是直接定义在布局文件的xml中的,这点或许值得参考。

<com.android.systemui.car.navigationbar.CarNavigationButton android:id="@+id/grid_nav" style="@style/NavigationBarButton" systemui:componentNames="com.android.car.carlauncher/.AppGridActivity" systemui:highlightWhenSelected="true" systemui:icon="@drawable/car_ic_apps" systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end" systemui:selectedIcon="@drawable/car_ic_apps_selected" />

3.2 初始化流程

SystemUI的启动流程中,SystemUIApplication在通过反射创建好CarNavigationBar后,紧接就调用了start()方法,那么我们就从start()入手,开始UI的初始化流程。

在start()方法中,首先是向IStatusBarService中注册一个CommandQueue,然后执行createNavigationBar()方法,并把注册的结果下发。

CommandQueue继承自IStatusBar.Stub。因此它是IStatusBar的服务(Bn)端。在完成注册后,这一Binder对象的客户端(Bp)端将会保存在IStatusBarService之中。因此它是IStatusBarServicebaseStatusBar进行通信的桥梁。

IStatusBarService,即系统服务StatusBarManagerService是状态栏导航栏向外界提供服务的前端接口,运行于system_server进程中。

注意:定制SystemUI时,我们可以不使用 IStatusBarService 和 IStatusBar 来保存 SystemUI 的状态

// CarNavigationBar private final CommandQueue mCommandQueue; private final IStatusBarService mBarService; @Override public void start() { ... RegisterStatusBarResult result = null; try { result = mBarService.registerStatusBar(mCommandQueue); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } ... createNavigationBar(result); ... }

createNavigationBar()中依次执行buildNavBarWindows()buildNavBarContent()attachNavBarWindows()

// CarNavigationBar private void createNavigationBar(RegisterStatusBarResult result) { buildNavBarWindows(); buildNavBarContent(); attachNavBarWindows(); // 如果注册成功,尝试设置导航条的初始状态。 if (result != null) { setImeWindowStatus(Display.DEFAULT_DISPLAY, result.mImeToken, result.mImeWindowVis, result.mImeBackDisposition, result.mShowImeSwitcher); } }

下面依次介绍每个方法的实际作用。

  • buildNavBarWindows() 这个方法目的是创建出状态栏的容器 - navigation_bar_window。

// CarNavigationBar private final CarNavigationBarController mCarNavigationBarController; private void buildNavBarWindows() { mTopNavigationBarWindow = mCarNavigationBarController.getTopWindow(); mBottomNavigationBarWindow = mCarNavigationBarController.getBottomWindow(); ... } // CarNavigationBarController private final NavigationBarViewFactory mNavigationBarViewFactory; public ViewGroup getTopWindow() { return mShowTop ? mNavigationBarViewFactory.getTopWindow() : null; } // NavigationBarViewFactory public ViewGroup getTopWindow() { return getWindowCached(Type.TOP); } private ViewGroup getWindowCached(Type type) { if (mCachedContainerMap.containsKey(type)) { return mCachedContainerMap.get(type); } ViewGroup window = (ViewGroup) View.inflate(mContext, R.layout.navigation_bar_window, null); mCachedContainerMap.put(type, window); return mCachedContainerMap.get(type); }

navigation_bar_window 是一个自定义View(NavigationBarframe),它的核心类是DeadZone.

DeadZone字面意思就是“死区”,它的作用是消耗沿导航栏顶部边缘的无意轻击。当用户在输入法上快速输入时,他们可能会尝试点击空格键、“overshoot”,并意外点击主页按钮。每次点击导航栏外的UI后,死区会暂时扩大(因为这是偶然点击更可能发生的情况),然后随着时间的推移,死区又会缩小(因为稍后的点击可能是针对导航栏顶部的)。

navigation_bar_window 源码位置:/frameworks/base/packages/SystemUI/res/layout/navigation_bar_window.xml

  • buildNavBarContent()

这个方法目的是将状态栏的实际View添加到上一步创建出的容器中,并对触摸和点击事件进行初始化。

// CarNavigationBar private void buildNavBarContent() { mTopNavigationBarView = mCarNavigationBarController.getTopBar(isDeviceSetupForUser()); if (mTopNavigationBarView != null) { mSystemBarConfigs.insetSystemBar(SystemBarConfigs.TOP, mTopNavigationBarView); mTopNavigationBarWindow.addView(mTopNavigationBarView); } mBottomNavigationBarView = mCarNavigationBarController.getBottomBar(isDeviceSetupForUser()); if (mBottomNavigationBarView != null) { mSystemBarConfigs.insetSystemBar(SystemBarConfigs.BOTTOM, mBottomNavigationBarView); mBottomNavigationBarWindow.addView(mBottomNavigationBarView); } ... } // CarNavigationBarController public CarNavigationBarView getTopBar(boolean isSetUp) { if (!mShowTop) { return null; } mTopView = mNavigationBarViewFactory.getTopBar(isSetUp); setupBar(mTopView, mTopBarTouchListener, mNotificationsShadeController); return mTopView; } // 初始化 private void setupBar(CarNavigationBarView view, View.onTouchListener statusBarTouchListener, NotificationsShadeController notifShadeController) { view.setStatusBarWindowTouchListener(statusBarTouchListener); view.setNotificationsPanelController(notifShadeController); mButtonSelectionStateController.addAllButtonsWithSelectionState(view); mButtonRoleHolderController.addAllButtonsWithRoleName(view); mHvacControllerLazy.get().addTemperatureViewToController(view); } // NavigationBarViewFactory public CarNavigationBarView getTopBar(boolean isSetUp) { return getBar(isSetUp, Type.TOP, Type.TOP_UNPROVISIONED); } private CarNavigationBarView getBar(boolean isSetUp, Type provisioned, Type unprovisioned) { CarNavigationBarView view; if (isSetUp) { view = getBarCached(provisioned, sLayoutMap.get(provisioned)); } else { view = getBarCached(unprovisioned, sLayoutMap.get(unprovisioned)); } if (view == null) { String name = isSetUp ? provisioned.name() : unprovisioned.name(); Log.e(TAG, "CarStatusBar failed inflate for " + name); throw new RuntimeException( "Unable to build " + name + " nav bar due to missing layout"); } return view; } private CarNavigationBarView getBarCached(Type type, @LayoutRes int barLayout) { if (mCachedViewMap.containsKey(type)) { return mCachedViewMap.get(type); } // CarNavigationBarView view = (CarNavigationBarView) View.inflate(mContext, barLayout, null); // 在开头包括一个FocusParkingView。当用户导航到另一个窗口时,旋转控制器将焦点“停”在这里。这也用于防止wrap-around.。 view.addView(new FocusParkingView(mContext), 0); mCachedViewMap.put(type, view); return mCachedViewMap.get(type); }

  • attachNavBarWindows()

最后一步,将创建的View通过windowManger显示到屏幕上。

private void attachNavBarWindows() { mSystemBarConfigs.getSystemBarSidesByZOrder().forEach(this::attachNavBarBySide); } private void attachNavBarBySide(int side) { switch(side) { case SystemBarConfigs.TOP: if (mTopNavigationBarWindow != null) { mWindowManager.addView(mTopNavigationBarWindow, mSystemBarConfigs.getLayoutParamsBySide(SystemBarConfigs.TOP)); } break; case SystemBarConfigs.BOTTOM: if (mBottomNavigationBarWindow != null && !mBottomNavBarVisible) { mBottomNavBarVisible = true; mWindowManager.addView(mBottomNavigationBarWindow, mSystemBarConfigs.getLayoutParamsBySide(SystemBarConfigs.BOTTOM)); } break; ... break; default: return; } }

简单总结一下,UI初始化的流程图如下。







3.3 关键功能

3.3.1 打开/关闭消息中心

在原生车载Android中有两种方式打开消息中心分别是,1.通过点击消息中心按钮,2.通过手势下拉状态栏。

我们先来看第一种实现方式 ,通过点击按钮展开消息中心。







CarNavigationBarController中对外暴露了一个可以注册监听回调的方法,CarNavigationBarController会把外部注册的监听事件会传递到CarNavigationBarView中。

public void registerNotificationController( NotificationsShadeController notificationsShadeController) { mNotificationsShadeController = notificationsShadeController; if (mTopView != null) { mTopView.setNotificationsPanelController(mNotificationsShadeController); } ... }

CarNavigationBarView中的notifications按钮被按下时,就会将打开消息中心的消息回调给之前注册进来的接口。

// CarNavigationBarView @Override public void onFinishInflate() { ... mNotificationsButton = findViewById(R.id.notifications); if (mNotificationsButton != null) { mNotificationsButton.setonClickListener(this::onNotificationsClick); } ... } protected void onNotificationsClick(View v) { if (mNotificationsShadeController != null) { mNotificationsShadeController.togglePanel(); } }

消息中心的控制器在接收到回调消息后,根据需要执行展开消息中心面板的方法即可

// NotificationPanelViewMediator mCarNavigationBarController.registerNotificationController( new CarNavigationBarController.NotificationsShadeController() { @Override public void togglePanel() { mNotificationPanelViewController.toggle(); } // 这个方法用于告知外部类,当前消息中心的面板是否处于展开状态 @Override public boolean isNotificationPanelOpen() { return mNotificationPanelViewController.isPanelExpanded(); } });

再来看第二种实现方式 ,通过下拉手势展开消息中心,这也是我们最常用的方式。







实现思路第一种方式一样,CarNavigationBarController中对外暴露了一个可以注册监听回调的方法,接着会把外部注册的监听事件会传递给CarNavigationBarView

// CarNavigationBarController public void registerTopBarTouchListener(View.onTouchListener listener) { mTopBarTouchListener = listener; if (mTopView != null) { mTopView.setStatusBarWindowTouchListener(mTopBarTouchListener); } }

这次在CarNavigationBarView中则是拦截了触摸事件的分发,如果当前消息中心已经展开,则CarNavigationBarView直接消费触摸事件,后续事件不再对外分发。如果当前消息中心没有展开,则将触摸事件分外给外部,这里的外部就是指消息中心中的TopNotificationPanelViewMediator

// CarNavigationBarView // 用于连接通知的打开/关闭手势 private onTouchListener mStatusBarWindowTouchListener; public void setStatusBarWindowTouchListener(onTouchListener statusBarWindowTouchListener) { mStatusBarWindowTouchListener = statusBarWindowTouchListener; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (mStatusBarWindowTouchListener != null) { boolean shouldConsumeEvent = mNotificationsShadeController == null ? false : mNotificationsShadeController.isNotificationPanelOpen(); // 将触摸事件转发到状态栏窗口,以便在需要时拖动窗口(Notification shade) mStatusBarWindowTouchListener.onTouch(this, ev); if (mConsumeTouchWhenPanelOpen && shouldConsumeEvent) { return true; } } return super.onInterceptTouchEvent(ev); }

TopNotificationPanelViewMediator在初始化过程中就向CarNavigationBarController注册了触摸事件的监听。

.// TopNotificationPanelViewMediator @Override public void registerListeners() { super.registerListeners(); getCarNavigationBarController().registerTopBarTouchListener( getNotificationPanelViewController().getDragOpenTouchListener()); }

最终状态栏的触摸事件会在OverlayPanelViewController中得到处理。

// OverlayPanelViewController public final View.onTouchListener getDragOpenTouchListener() { return mDragOpenTouchListener; } mDragOpenTouchListener = (v, event) -> { if (!mCarDeviceProvisionedController.isCurrentUserFullySetup()) { return true; } if (!isInflated()) { getOverlayViewGlobalStateController().inflateView(this); } boolean consumed = openGestureDetector.onTouchEvent(event); if (consumed) { return true; } // 判断是否要展开、收起 消息中心的面板 maybeCompleteAnimation(event); return true; };

3.3.2 占用应用的显示区域

不知道你有没有这样的疑问,既然顶部的状态栏和底部导航栏都是通过WindowManager.addView()显示到屏幕上,那么打开应用为什么会自动“让出”状态栏占用的区域呢?

主要原因在于状态栏的Window的Type和我们平常使用的TYPE_APPLICATION是不一样的。

private WindowManager.LayoutParams getLayoutParams() { WindowManager.LayoutParams lp = new WindowManager.LayoutParams( isHorizontalBar(mSide) ? ViewGroup.LayoutParams.MATCH_PARENT : mGirth, isHorizontalBar(mSide) ? mGirth : ViewGroup.LayoutParams.MATCH_PARENT, mapZOrderToBarType(mZOrder), WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); lp.setTitle(BAR_TITLE_MAP.get(mSide)); lp.providesInsetsTypes = new int[]{BAR_TYPE_MAP[mBarType], BAR_GESTURE_MAP.get(mSide)}; lp.setFitInsetsTypes(0); lp.windowAnimations = 0; lp.gravity = BAR_GRAVITY_MAP.get(mSide); return lp; } private int mapZOrderToBarType(int zOrder) { return zOrder >= HUN_ZORDER ? WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL : WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL; }

CarSystemUI顶部的状态栏WindowType是 TYPE_STATUS_BAR_ADDITIONAL






底部导航栏的WindowType是 TYPE_NAVIGATION_BAR_PANEL






4. 总结

SystemUI在原生的车载Android系统是一个极其复杂的模块,考虑多数从手机应用转行做车载应用的开发者并对SystemUI的了解并不多,本篇介绍了CarSystemUI的启动、和状态栏的实现方式,希望能帮到正在或以后会从事SystemUI开发的同学。

除此以外,车载SystemUI中还有“消息中心”、“近期任务”等一些关键模块,这些内容就放到以后再做介绍吧。

发布人:400d****    IP:120.244.05.***     举报/删稿
展会推荐
让朕来说2句
评论
收藏
点赞
转发