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层。
Android - Phone中SystemUI从源码量看就是一个相称复杂的步伐,常见的如:状态栏、消息中心、近期任务、截屏以及一系列功能都是在SystemUI中实现的。
源码位置:/frameworks/base/packages/SystemUI
Android-AutoMotive 中的SystemUI相对手机中要简朴不少,如今商用车载体系中险些必备的顶部状态栏、消息中心、底部导航栏在原生的Android体系中都已经实现了。
源码位置:frameworks/base/packages/CarSystemUI
固然CarSystemUI与SystemUI的源码位置差别,但是二者实际上是复用关系。通过阅读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。我们常说的ActivityManagerService、PackageManagerService、WindowManageService都是由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,在SystemUIService的oncreate()方法中再通过SystemUIApplication.startServicesIfNeeded()来完成SystemUI的组件的初始化。
SystemUIService 源码位置:/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java
// SystemUIService@Overridepublic 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
// SystemUIApplicationpublic void startServicesIfNeeded() { String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources()); startServicesIfNeeded("StartServices", names);}// SystemUIFactory/** Returns the list of system UI components that should be started. */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组件的创建,然后再调用各个SystemUI的onStart()方法来继续实行子模块的初始化。
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; 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 = obj; } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException ex) { throw new RuntimeException(ex); } if (DEBUG) Log.d(TAG, "running: " + mServices); // 调用各个子模块的start() mServices.start(); // 初次启动时,这里始终为false,不会被调用 if (mBootCompleteCache.isBootComplete()) { mServices.onBootCompleted(); } } mServicesStarted = true;}SystemUIApplication在OnCreate()方法中注册了一个开机广播,当吸收到开机广播后会调用SystemUI的onBootCompleted()方法来告诉每个子模块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.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是通过SystemUIFactory的getSystemUIServiceComponents()获取到的,那么只要继续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之中。因此它是IStatusBarService与BaseStatusBar进行通讯的桥梁。
IStatusBarService,即体系服务StatusBarManagerService是状态栏导航栏向外界提供服务的前端接口,运行于system_server历程中。
留意:定制SystemUI时,我们可以不使用 IStatusBarService 和 IStatusBar 来生存 SystemUI 的状态
// CarNavigationBarprivate final CommandQueue mCommandQueue;private final IStatusBarService mBarService;@Overridepublic void start() { ... RegisterStatusBarResult result = null; try { result = mBarService.registerStatusBar(mCommandQueue); } catch (RemoteException ex) { ex.rethrowFromSystemServer(); } ... createNavigationBar(result); ...}在createNavigationBar()中依次实行buildNavBarWindows()、buildNavBarContent()、attachNavBarWindows()。
// CarNavigationBarprivate 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。
// CarNavigationBarprivate final CarNavigationBarController mCarNavigationBarController;private void buildNavBarWindows() { mTopNavigationBarWindow = mCarNavigationBarController.getTopWindow(); mBottomNavigationBarWindow = mCarNavigationBarController.getBottomWindow(); ...}// CarNavigationBarControllerprivate final NavigationBarViewFactory mNavigationBarViewFactory;public ViewGroup getTopWindow() { return mShowTop ? mNavigationBarViewFactory.getTopWindow() : null;}// NavigationBarViewFactorypublic 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, /* root= */ 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
这个方法目标是将状态栏的实际View添加到上一步创建出的容器中,并对触摸和点击事件进行初始化。
// CarNavigationBarprivate 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); } ...}// CarNavigationBarControllerpublic 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);}// NavigationBarViewFactorypublic 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, /* root= */ null); // 在开头包罗一个FocusParkingView。当用户导航到另一个窗口时,旋转控制器将核心“停”在这里。这也用于防止wrap-around.。view.addView(new FocusParkingView(mContext), 0); mCachedViewMap.put(type, view); return mCachedViewMap.get(type);}
末了一步,将创建的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@Overridepublic void onFinishInflate() { ... mNotificationsButton = findViewById(R.id.notifications); if (mNotificationsButton != null) { mNotificationsButton.setOnClickListener(this:nNotificationsClick); } ...}protected void onNotificationsClick(View v) { if (mNotificationsShadeController != null) { mNotificationsShadeController.togglePanel(); }}消息中心的控制器在吸收到回调消息后,根据必要实行展开消息中心面板的方法即可
// NotificationPanelViewMediatormCarNavigationBarController.registerNotificationController( new CarNavigationBarController.NotificationsShadeController() { @Override public void togglePanel() { mNotificationPanelViewController.toggle(); } // 这个方法用于告知外部类,当前消息中心的面板是否处于展开状态 @Override public boolean isNotificationPanelOpen() { return mNotificationPanelViewController.isPanelExpanded(); } });再来看第二种实现方式 ,通过下拉手势展开消息中心,这也是我们最常用的方式。
实现思绪第一种方式一样,CarNavigationBarController中对外暴露了一个可以注册监听回调的方法,接着会把外部注册的监听事件会通报给CarNavigationBarView。
// CarNavigationBarControllerpublic 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;}@Overridepublic 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@Overridepublic void registerListeners() { super.registerListeners(); getCarNavigationBarController().registerTopBarTouchListener( getNotificationPanelViewController().getDragOpenTouchListener());}最终状态栏的触摸事件会在OverlayPanelViewController中得到处置惩罚。
// OverlayPanelViewControllerpublic 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中尚有“消息中心”、“近期任务”等一些关键模块,这些内容就放到以后再做先容吧。 |