Android 车载应用开发与分析(12) - SystemUI (一)

源代码 2024-10-1 07:55:54 86 0 来自 中国
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
1.gif

  • CarSystemUI
Android-AutoMotive 中的SystemUI相对手机中要简朴不少,如今商用车载体系中险些必备的顶部状态栏、消息中心、底部导航栏在原生的Android体系中都已经实现了。
源码位置:frameworks/base/packages/CarSystemUI
2.gif 固然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信息等。

5.png

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

6.png 3.1 布局方式


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

    7.png
布局文件的源码就不贴了,量比力大,而且包罗了许多的自界说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


  • buildNavBarContent()
这个方法目标是将状态栏的实际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);}

  • 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初始化的流程图如下。

9.jpeg 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中尚有“消息中心”、“近期任务”等一些关键模块,这些内容就放到以后再做先容吧。
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-12-4 01:33, Processed in 0.199855 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表