Android车载应用开辟与分析(10)- 车载空调体系(HVAC)

手机游戏开发者 2024-9-24 23:07:40 14 0 来自 中国
1. HVAC 功能先容

HVAC 全称:供暖透风与氛围调治(Heating Ventilation and Air Conditioning)。用户可以通过他来控制整个汽车的空调体系,是汽车中非常告急的一个功能。


汽车的空调HMI固然并不复杂,但是大多都是用符号来体现功能,对于还没有实际用过汽车空调体系的开辟者来说,明白空调的各个符号体现的寄义也好坏常有须要。
下面就以Android 12中的HVAC来先容空调体系中包罗的最底子的功能。
1.1 双区温度调治

空调的温度调治功能,默认是华氏度,可以在体系设置修改温度单元。可调治范围是61 - 82华氏度,对应16 - 28 摄氏度。
左侧按钮用来调治主驾,右侧按钮用来调治副驾。在以往都是只有高配车型才有双区空调,现在的车上双区空调险些已经是标配了。
1.2 空调开关

开启关闭空调的开关
1.3 内/外循环

内循环是汽车氛围调治体系的一种状态。这种状态下,车表里的换气通道关闭,风构造闭时车内气流不循环,风机开启时,吸入的气流也仅来自车内,形成车辆内部的气流循环。
外循环则相反,风机开启时,吸入的气流也仅来自车外,可以更新车内的氛围质量,代价是会更耗电。
1.4 风量调治

5.gif
用于增大或减小空调的风量。
1.5 风向调治


从左到右分别是吹脸、吹脸+吹脚、吹脚、吹脚+吹挡风玻璃
1.6 A/C开关

7.gif
A/C按键,它就是制冷开关,按下A/C按键,也就启动了压缩机,普通地说就是开寒气。
1.7 主副驾座椅加热

8.gif
左边的按钮用于调治主驾座椅加热,右边的按钮用于调治副驾座椅加热
1.8 除霜


左边的按钮是开启/关闭 前挡风玻璃加热,开启后用来撤除前挡风玻璃上的雾气。右边的按钮是开启/关闭后挡风玻璃加热,开启后用来撤除后挡风玻璃上的雾气。
1.9 主动模式

10.gif
主动空调实在就是省略了风速、风向等调治功能,主动空调是全主动调治,只必要选择风向和设定温度。AUTO按键按下后,就会根据车内传感器来控制出风的温度,冬天热风,炎天冷风。会保持车内有较相宜的温度,假如温度过高或过低,空调也会主动改变出风口的温度及风速,调解车内温度。
以上就是车载空调体系中最底子的功能了,实际开辟中我们还会遇到如座椅透风、座椅按摩、智能新风、负离子等等一些近几年才出现的空调新功能,在应用开辟上无非就是多几个界面或按钮。
2. HVAC 源码结构

本文中的源码基于Android 12下HVAC APP,源码请见:https://github.com/linux-link/CarHvac
原生的Hvac App中不存在Activity、Fragment等传统意义上用来体现HMI的组件,取而代之是利用Service来体现一个Window。重要缘故原由在于Hvac的界面层级比一样平常的HMI的层级要高,呼出Hvac时必要部分或全部覆盖其他的应用上(固然IVI中还是有应用比Hvac的层级要高的),这时间利用Activity就显不符合了。

11.gif
必要注意的是,Havc在Android 12中固然有一个独立的app,但是上图展示空调并没有利用这个独立的app,它的HMI和逻辑实现都是直接写在SystemUI中的。
我们可以通过adb发送一个广播来调出独立的Hvac应用。
adb shell am broadcast -a android.car.intent.action.TOGGLE_HVAC_CONTROLS
以下是Hvac App的关键部分的源码结构图

13.png 3. HVAC 焦点源码分析

3.1 AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.android.car.hvac">    <uses-sdk        android:minSdkVersion="22"        android:targetSdkVersion="29" />    <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />    <!-- Required to use the TYPE_DISPLAY_OVERLAY layout param for the overlay hvac ui-->    <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />    <!-- Allow Hvac to go across all users-->    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />    <protected-broadcast android:name="android.car.intent.action.TOGGLE_HVAC_CONTROLS" />    <application        android:icon="@drawable/ic_launcher_hvac"        android:label="@string/hvac_label"        android:persistent="true">        <!--用于控制空调功能的Service-->        <service            android:name=".HvacController"            android:exported="false"            android:singleUser="true" />        <!-- 用于体现UI的Service-->        <service            android:name=".HvacUiService"            android:exported="false"            android:singleUser="true" />        <!-- 监听开机广播 -->        <receiver            android:name=".BootCompleteReceiver"            android:exported="true">            <intent-filter>                <action android:name="android.intent.action.BOOT_COMPLETED" />            </intent-filter>        </receiver>    </application></manifest>3.2 BootCompleteReceiver

用于监听开机的广播,当前收到体系的开机广播后,会将HvacUiService拉起。
public class BootCompleteReceiver extends BroadcastReceiver {    @Override    public void onReceive(Context context, Intent intent) {        Intent hvacUiService = new Intent(context, HvacUiService.class);        context.startService(hvacUiService);    }}3.3 HvacUiService

HvacUiService 用来托管Hvac UI的Service。从名字上也能看出,整个HvacUiService都是围绕着怎样将Hvac正确的绘制出来,根本不含其他的逻辑。
@Overridepublic void onCreate() {    ...    // 由于不存在从服务内部获取体系ui可见性的方法,因此我们将全屏放置一些东西,并查抄其终极丈量效果,作为获取该信息的黑客本领。    // 一旦我们有了初始状态,我们就可以安全地从其时开始注册更改事故。    View windowSizeTest = new View(this) {        @Override        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {            Log.i(TAG, "onLayout: changed" + changed + ";left:" + left + ";top:" + top + ";right:" + right + ";bottom" + bottom);            boolean sysUIShowing = (mDisplayMetrics.heightPixels != bottom);            mInitialYOffset = (sysUIShowing) ? -mNavBarHeight : 0;            Log.i(TAG, "onLayout: sysUIShowing:" + sysUIShowing + ";mInitialYOffset" + mInitialYOffset);            layoutHvacUi();            // 我们现在有了初始状态,因此不再必要这个空视图。            mWindowManager.removeView(this);            mAddedViews.remove(this);        }    };    addViewToWindowManagerAndTrack(windowSizeTest, testparams);    // 吸收事故的广播    IntentFilter filter = new IntentFilter();    filter.addAction(CAR_INTENT_ACTION_TOGGLE_HVAC_CONTROLS);    filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);    // 注册吸收器,以便任何具有CONTROL_CAR_CLIMATE权限的用户都可以调用它。    registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,            Car.PERMISSION_CONTROL_CAR_CLIMATE, null);}private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {    @Override    public void onReceive(Context context, Intent intent) {        String action = intent.getAction();        Log.i(TAG, "onReceive: " + action);        // 自定义广播,用于睁开Hvac的HMI        if (action.equals(CAR_INTENT_ACTION_TOGGLE_HVAC_CONTROLS)) {            mHvacPanelController.toggleHvacUi();        } else if (action.equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {        // home 按键的广播,收起Hvac的HMI            mHvacPanelController.collapseHvacUi();        }    }};// 添加View到WindowManager中private void addViewToWindowManagerAndTrack(View view, WindowManager.LayoutParams params) {    mWindowManager.addView(view, params);    mAddedViews.add(view);}HvacUIService在onCreate()中重要完成两件事:
1.注册事故广播。这个事故实际并没有发送源,由于SystemUI中额外写了一个Hvac,不外正是这个广播让我们可以把这个单独的Hvac调出。
2.绘制UI。HvacUIService在被拉起后并没有立刻开始UI的绘制,而是在屏幕上暂时放置一个用于丈量窗口的 windowSizeTest ,当windowSizeTestView开始丈量后,通过比对View的高度和屏幕的高度,即可判断出systemUI是否已经体现,这时就可以开始动手绘制真正的Hvac的UI了,而且可以更安全的利用UI。
接下来就是绘制真正的Hvac界面:
/** * 在确定最小偏移量后调用。 * 这将天生HVAC UI所需的所有组件的结构。 * 启动时,折叠视图所需的所有窗口都可见,而睁开视图的窗口已创建并调解巨细,但不可见。 */private void layoutHvacUi() {    LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);    WindowManager.LayoutParams params = new WindowManager.LayoutParams(            WindowManager.LayoutParams.WRAP_CONTENT,            WindowManager.LayoutParams.WRAP_CONTENT,            WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY,            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS                    & ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,            PixelFormat.TRANSLUCENT);    params.packageName = this.getPackageName();    params.gravity = Gravity.BOTTOM | Gravity.LEFT;    params.x = 0;    params.y = mInitialYOffset;    params.width = mScreenWidth;    params.height = mScreenBottom;    params.setTitle("HVAC Container");    disableAnimations(params);    // required of the sysui visiblity listener is not triggered.    params.hasSystemUiListeners = true;    mContainer = inflater.inflate(R.layout.hvac_panel, null);    mContainer.setLayoutParams(params);    mContainer.setOnSystemUiVisibilityChangeListener(visibility -> {        Log.i(TAG, "layoutHvacUi: visibility:" + visibility);        boolean systemUiVisible = (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0;        int y = 0;        if (systemUiVisible) {            // 当systemUi可见时,窗口体系坐标从体系导航栏上方的0开始。因此,假如我们想得到屏幕底部的实际高度,我们必要将y值设置为导航栏高度的负值。            y = -mNavBarHeight;        }        setYPosition(mDriverTemperatureBar, y);        setYPosition(mPassengerTemperatureBar, y);        setYPosition(mDriverTemperatureBarCollapsed, y);        setYPosition(mPassengerTemperatureBarCollapsed, y);        setYPosition(mContainer, y);    });    // 顶部添补应根据屏幕高度和扩展hvac面板的高度举行盘算。由添补物定义的空间意味着可以单击以关闭hvac面板。    int topPadding = mScreenBottom - mPanelFullExpandedHeight;    mContainer.setPadding(0, topPadding, 0, 0);    mContainer.setFocusable(false);    mContainer.setFocusableInTouchMode(false);    View panel = mContainer.findViewById(R.id.hvac_center_panel);    panel.getLayoutParams().height = mPanelCollapsedHeight;    addViewToWindowManagerAndTrack(mContainer, params);    // 创建温度计bar    createTemperatureBars(inflater);    // UI状态控制器,用来控制睁开/收起时UI的各种状态并实举措画    mHvacPanelController = new HvacPanelController(this /* context */, mContainer,            mWindowManager, mDriverTemperatureBar, mPassengerTemperatureBar,            mDriverTemperatureBarCollapsed, mPassengerTemperatureBarCollapsed    );    // 绑定 HvacController Service    Intent bindIntent = new Intent(this /* context */, HvacController.class);    if (!bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE)) {        Log.e(TAG, "Failed to connect to HvacController.");    }}HvacPanelController是空调的面板控制器,在与HvacController绑定成功后,将HvacController的实例转达给HvacPanelController。
private ServiceConnection mServiceConnection = new ServiceConnection() {    @Override    public void onServiceConnected(ComponentName className, IBinder service) {        mHvacController = ((HvacController.LocalBinder) service).getService();        final Context context = HvacUiService.this;        final Runnable r = () -> {            // hvac控制器从车辆革新其值后,绑定所有值。            mHvacPanelController.updateHvacController(mHvacController);        };        if (mHvacController != null) {            mHvacController.requestRefresh(r, new Handler(context.getMainLooper()));        }    }    @Override    public void onServiceDisconnected(ComponentName className) {        mHvacController = null;        mHvacPanelController.updateHvacController(null);        //TODO:b/29126575重新启动后重新毗连控制器    }};我们接着看HvacPanelController
3.4 HvacPanelController

HvacPanelController 重要作用是初始化其他界面Controller,并从HvacController中获取数据,表现在UI上。
private FanSpeedBarController mFanSpeedBarController;private FanDirectionButtonsController mFanDirectionButtonsController;private TemperatureController mTemperatureController;private TemperatureController mTemperatureControllerCollapsed;private SeatWarmerController mSeatWarmerController;public void updateHvacController(HvacController controller) {    mHvacController = controller;    mFanSpeedBarController = new FanSpeedBarController(mFanSpeedBar, mHvacController);    mFanDirectionButtonsController            = new FanDirectionButtonsController(mFanDirectionButtons, mHvacController);    mTemperatureController = new TemperatureController(            mPassengerTemperatureBarExpanded,            mDriverTemperatureBarExpanded,            mPassengerTemperatureBarCollapsed,            mDriverTemperatureBarCollapsed,            mHvacController);    mSeatWarmerController = new SeatWarmerController(mPassengerSeatWarmer,            mDriverSeatWarmer, mHvacController);    // 切换按钮不必要额外的逻辑来映射硬件和UI设置。只需利用ToggleListener来处理惩罚点击。    mAcButton.setIsOn(mHvacController.getAcState());    mAcButton.setToggleListener(new ToggleButton.ToggleListener() {        @Override        public void onToggled(boolean isOn) {            mHvacController.setAcState(isOn);        }    });    ...    setAutoMode(mHvacController.getAutoModeState());    mHvacPowerSwitch.setIsOn(mHvacController.getHvacPowerState());    mHvacPowerSwitch.setToggleListener(isOn -> mHvacController.setHvacPowerState(isOn));    mHvacController.registerCallback(mToggleButtonCallbacks);    mToggleButtonCallbacks.onHvacPowerChange(mHvacController.getHvacPowerState());}Hvac界面睁开和收起的动画也是在HvacPanelController 中处理惩罚的,不外关于动画部分计划以后再开个新坑讲一讲。
3.5 HvacController

HvacController是HvacApp与CarService之间的信息传输控制器,本质上也是一个Service。
public class HvacController extends Service {    private final Binder mBinder = new LocalBinder();    @Override    public int onStartCommand(Intent intent, int flags, int startId) {        return START_STICKY;    }    @Override    public IBinder onBind(Intent intent) {        return mBinder;    }    public class LocalBinder extends Binder {        HvacController getService() {            return HvacController.this;        }    }    ...}在Hvac中的设置及获取数据的利用都是通过HvacController举行的,在HvacController启动时会获取一个Car实例,并通过connect方法毗连CarService。当毗连CarService成功后初始化CarHvacManager并通过CarHvacManager获取车辆支持的属性列表,以及获取界面所需的底子数据。
@Overridepublic void onCreate() {    super.onCreate();    if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {        // 毗连 CarService        mCarApiClient = Car.createCar(this, mCarServiceConnection);        mCarApiClient.connect();    }}private final ServiceConnection mCarServiceConnection = new ServiceConnection() {    @Override    public void onServiceConnected(ComponentName name, IBinder service) {        synchronized (mHvacManagerReady) {            try {                // 毗连上CarService后,获取到此中的HvacManager.                initHvacManager((CarHvacManager) mCarApiClient.getCarManager(Car.HVAC_SERVICE));                // 毗连成功后,叫醒正在等待CarHvacManager的线程                mHvacManagerReady.notifyAll();            } catch (CarNotConnectedException e) {                Log.e(TAG, "Car not connected in onServiceConnected");            }        }    }    @Override    public void onServiceDisconnected(ComponentName name) {    }};向CarService获取数据必要先得到CarHvacManager的实例,以是在毗连成功后,调用mHvacManagerReady.notifyAll() 叫醒所有之前等待CarHvacManager实例的线程
// HvacUiService.java - mServiceConnection{    final Runnable r = () -> {        // hvac控制器从车辆革新其值后,绑定所有值。        mHvacPanelController.updateHvacController(mHvacController);    };    if (mHvacController != null) {        mHvacController.requestRefresh(r, new Handler(context.getMainLooper()));    }}// HvacController.javapublic void requestRefresh(final Runnable r, final Handler h) {    final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {        @Override        protected Void doInBackground(Void... unused) {            synchronized (mHvacManagerReady) {                while (mHvacManager == null) {                    try {                        mHvacManagerReady.wait();                    } catch (InterruptedException e) {                        // We got interrupted so we might be shutting down.                        return null;                    }                }            }            // 革新数据            fetchTemperature(DRIVER_ZONE_ID);            fetchTemperature(PASSENGER_ZONE_ID);            fetchFanSpeed();            ...            return null;        }        @Override        protected void onPostExecute(Void unused) {            // 切换到主线程中实验runnable            h.post(r);        }    };    task.execute();}private void fetchFanSpeed() {    if (mHvacManager != null) {        int zone = SEAT_ALL; //特定于汽车的办理方法。        try {            int speed = mHvacManager.getIntProperty(CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone);            mDataStore.setFanSpeed(speed);        } catch (android.car.CarNotConnectedException e) {            Log.e(TAG, "Car not connected in fetchFanSpeed");        }    }}上面的代码就是利用AsyncTask在子线程中等待CarHvacManager的实例,然后革新数据并存储在DatStore中。
必要注意一点的是while (mHvacManager == null)不能更换成if(mHvacManager == null),这是由于Java有个叫“spurious wakeup”的征象,即线程在不应醒过来的时间醒过来。
A thread can wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied.
一个线程有大概会在未被关照、打断、或超时的环境下醒来,这就是所谓的“spurious wakeup”。只管实际上这种环境很少发生,应用步调仍旧必须对此有所防范,本领是查抄正常的导致线程被叫醒的条件是否满足,假如不满足就继承等待。
3.6 Car API

Car是Android汽车平台最高品级的API,为外界提供汽车所有服务和数据访问的接口,提供了一系列与汽车有关的API。它不光仅可以提供HvacManger,像车辆的速率、档位状态等等所有与汽车有关的信息都可以从Car API中获取。
Hvac中的CarHvacManager实现了CarManagerBase接口,而且只要是作为CarXXXManager, 都必要实现CarManagerBase接口,如CarCabinManager,CarSensorManager等都实现了该接口。
CarHvacManager的控制利用是通过CarPropertyManager来完成的,CarPropertyManager同一控制汽车属性干系的利用。CarHvacManager只是控制与Hvac干系的利用,在汽车中尚有许多属性控制的Manager,如传感器,座舱等属性的控制,他们都是通过CarPropertyManager举行属性利用,通过在利用时传入的属性ID,属性地区以及属性值,在CarPropertyManager中会将这些参数转化为一个CarPropertyValue对象继承往CarService转达。
mHvacManager.getIntProperty(CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone);private final CarPropertyManager mCarPropertyMgr;public int getIntProperty(int propertyId, int area) {    return this.mCarPropertyMgr.getIntProperty(propertyId, area);}CarHvacManager也是通过注册一个callback来得到 Car API 的数据回调。
mHvacManager.registerCallback(mHardwareCallback);private final CarHvacManager.CarHvacEventCallback mHardwareCallback = new CarHvacManager.CarHvacEventCallback() {    @Override    public void onChangeEvent(final CarPropertyValue val) {        int areaId = val.getAreaId();        switch (val.getPropertyId()) {            case CarHvacManager.ID_ZONED_AC_ON:                handleAcStateUpdate(getValue(val));                break;            case CarHvacManager.ID_ZONED_FAN_DIRECTION:                handleFanPositionUpdate(areaId, getValue(val));                break;            case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT:                handleFanSpeedUpdate(areaId, getValue(val));                break;            case CarHvacManager.ID_ZONED_TEMP_SETPOINT:                handleTempUpdate(val);                break;            case CarHvacManager.ID_WINDOW_DEFROSTER_ON:                handleDefrosterUpdate(areaId, getValue(val));                break;            case CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON:                handleAirCirculationUpdate(getValue(val));                break;            case CarHvacManager.ID_ZONED_SEAT_TEMP:                handleSeatWarmerUpdate(areaId, getValue(val));                break;            case CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON:                handleAutoModeUpdate(getValue(val));                break;            case CarHvacManager.ID_ZONED_HVAC_POWER_ON:                handleHvacPowerOn(getValue(val));                break;            default:                if (Log.isLoggable(TAG, Log.DEBUG)) {                    Log.d(TAG, "Unhandled HVAC event, id: " + val.getPropertyId());                }        }    }    @Override    public void onErrorEvent(final int propertyId, final int zone) {    }};Hvac中每个Property对应的寄义如下:
// 全局属性,只有一个ID_MIRROR_DEFROSTER_ON  //视镜除雾ID_STEERING_WHEEL_HEAT  //方向盘温度ID_OUTSIDE_AIR_TEMP  //室外温度ID_TEMPERATURE_DISPLAY_UNITS  //在利用的温度// 地区属性,可在差别地区设置ID_ZONED_TEMP_SETPOINT  //用户设置的温度ID_ZONED_TEMP_ACTUAL  //地区实际温度ID_ZONED_HVAC_POWER_ON  //HVAC体系电源开关ID_ZONED_FAN_SPEED_SETPOINT  //风扇设置的速率ID_ZONED_FAN_SPEED_RPM  //风扇实际的速率ID_ZONED_FAN_DIRECTION_AVAILABLE  //风扇可设置的方向ID_ZONED_FAN_DIRECTION  //现在风扇设置的方向ID_ZONED_SEAT_TEMP  //座椅温度ID_ZONED_AC_ON  //空调开关ID_ZONED_AUTOMATIC_MODE_ON  //HVAC主动模式开关ID_ZONED_AIR_RECIRCULATION_ON  //氛围循环开关ID_ZONED_MAX_AC_ON  //空调最大速率开关ID_ZONED_DUAL_ZONE_ON  //双区模式开关ID_ZONED_MAX_DEFROST_ON  //最大除雾开关ID_ZONED_HVAC_AUTO_RECIRC_ON  //主动循环模式开关ID_WINDOW_DEFROSTER_ON  //除雾模式开关利用Car API时务必必要注意,注册的callback是有大概会非常频仍的产生回调的,应用层必要先将数据存储在DataStore中举行过滤,才能更新到UI上。而且也不要及时的打印日记,否则大概会导致日记缓冲区EOF,也会严肃干扰别的历程的日记输出。


3.7 DataStore

DataStore 用于存储HvacController从 Car API 中获取的属性值。
用户利用IVI界面和利用硬按键,都会更新Hvac的干系属性。这两种差别的更新方式都是从差别的线程更新到当前状态。别的,在某些环境下,Hvac体系大概会发送卖弄的更新,因此这个类将所有内容更新管理归并,从而确保在用户看来应用步调的界面是正常的
@GuardedBy("mFanSpeed")private Integer mFanSpeed = 0;private static final long COALESCE_TIME_MS = 0L;public int getFanSpeed() {    synchronized (mFanSpeed) {        return mFanSpeed;    }}// 仅用于主动 获取、设定 数据时更新speed数据。public void setFanSpeed(int speed) {    synchronized (mFanSpeed) {        mFanSpeed = speed;        mLastFanSpeedSet = SystemClock.uptimeMillis();    }}// 从callback中得到数据时,由于数据大概会革新的很频仍,以是必要先判断时间戳,确定命据是否真的必要更新public boolean shouldPropagateFanSpeedUpdate(int zone, int speed) {    // TODO:我们暂时忽略风扇速率地区,由于我们没有多地区车。    synchronized (mFanSpeed) {        if (SystemClock.uptimeMillis() - mLastFanSpeedSet < COALESCE_TIME_MS) {            return false;        }        mFanSpeed = speed;    }    return true;}在HvacController中我们从callback得到数据革新时,先通过DataStore判断以下是否必要更新数据,假如确实必要更新,再将更新后的数据回调给其他的UI控制器。
// HvacController.javaprivate final CarHvacManager.CarHvacEventCallback mHardwareCallback = new CarHvacManager.CarHvacEventCallback() {    @Override    public void onChangeEvent(final CarPropertyValue val) {        int areaId = val.getAreaId();        switch (val.getPropertyId()) {            case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT:                // 处理惩罚来自callback的数据                handleFanSpeedUpdate(areaId, getValue(val));                break;                // ... 省略            default:                if (Log.isLoggable(TAG, Log.DEBUG)) {                    Log.d(TAG, "Unhandled HVAC event, id: " + val.getPropertyId());                }        }    }};private void handleFanSpeedUpdate(int zone, int speed) {    // 判断是否必要更新本地的数据    boolean shouldPropagate = mDataStore.shouldPropagateFanSpeedUpdate(zone, speed);    if (Log.isLoggable(TAG, Log.DEBUG)) {        Log.d(TAG, "Fan Speed Update, zone: " + zone + " speed: " + speed +                " should propagate: " + shouldPropagate);    }    if (shouldPropagate) {        // 将更新后的数据回调给各个UI控制器        synchronized (mCallbacks) {            for (int i = 0; i < mCallbacks.size(); i++) {                mCallbacks.get(i).onFanSpeedChange(speed);            }        }    }}public void setFanSpeed(final int fanSpeed) {    // 更新当前的数据    mDataStore.setFanSpeed(fanSpeed);    final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {        int newFanSpeed;        protected Void doInBackground(Void... unused) {            if (mHvacManager != null) {                int zone = SEAT_ALL; // Car specific workaround.                try {                    if (Log.isLoggable(TAG, Log.DEBUG)) {                        Log.d(TAG, "Setting fanspeed to: " + fanSpeed);                    }                    mHvacManager.setIntProperty(                            CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone, fanSpeed);                    newFanSpeed = mHvacManager.getIntProperty(                            CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT, zone);                } catch (android.car.CarNotConnectedException e) {                    Log.e(TAG, "Car not connected in setFanSpeed");                }            }            return null;        }    };    task.execute();}4. 总结

末了我们以一张从Car API的callback中的数据更新界面的伪时序图来把Hvac的几个焦点组件串起来

15.jpg
以上就是车载空调部分的讲解,实际开辟中,空调模块功能性需求一样平常不会出现什么太大的技能性困难,空调模块的技能性难度险些都表现在复杂的动画和交互上,有关车载应用的复杂动画技能,我们以后在来细讲办理方案。
您需要登录后才可以回帖 登录 | 立即注册

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

GMT+8, 2024-10-18 19:26, Processed in 0.195971 second(s), 35 queries.© 2003-2025 cbk Team.

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