前言
现在的时节除了寒冷便是炎热;而轿车作为咱们重要的交通工具。空调简直小车一发动就敞开。而作为Android车载运用开发,咱们要怎样去开发一款空调体系呢?
1. HVAC 功用介绍
HVAC 全称:供暖通风与空气调理(Heating Ventilation and Air Conditioning)。用户能够经过他来操控整个轿车的空调体系,是轿车中非常重要的一个功用。
轿车的空调HMI虽然并不杂乱,可是大多都是用符号来表明功用,关于还没有实践用过轿车空调体系的开发者来说,理解空调的各个符号表明的意义也是非常有必要。 下面就以Android 12中的HVAC来介绍空调体系中包括的最根底的功用。
1.1 双区温度调理
空调的温度调理功用,默认是华氏度,能够在体系设置修改温度单位。可调理规模是61 – 82华氏度,对应16 – 28 摄氏度。 左面按钮用来调理主驾,右侧按钮用来调理副驾。在以往都是只要高配车型才有双区空调,现在的车上双区空调简直现已是标配了。
1.2 空调开关
敞开封闭空调的开关
1.3 内/外循环
内循环是轿车空气调理体系的一种状况。这种状况下,车内外的换气通道封闭,风机封闭时车内气流不循环,风机敞开时,吸入的气流也仅来自车内,构成车辆内部的气流循环。 外循环则相反,风机敞开时,吸入的气流也仅来自车外,能够更新车内的空气质量,价值是会更耗电。
1.4 风量调理
用于增大或减小空调的风量。
1.5 风向调理
从左到右分别是吹脸、吹脸+吹脚、吹脚、吹脚+吹挡风玻璃
1.6 A/C开关
A/C按键,它便是制冷开关,按下A/C按键,也就发动了压缩机,通俗地说便是开凉气。
1.7 主副驾座椅加热
左面的按钮用于调理主驾座椅加热,右边的按钮用于调理副驾座椅加热
1.8 除霜
左面的按钮是敞开/封闭 前挡风玻璃加热,敞开后用来除掉前挡风玻璃上的雾气。右边的按钮是敞开/封闭后挡风玻璃加热,敞开后用来除掉后挡风玻璃上的雾气。
1.9 自动形式
自动空调其实便是省略了风速、风向等调理功用,自动空调是全自动调理,只需求选择风向和设定温度。AUTO按键按下后,就会依据车内传感器来操控出风的温度,冬季热风,夏天冷风。会坚持车内有较适合的温度,假如温度过高或过低,空调也会自动改变出风口的温度及风速,调整车内温度。 以上便是车载空调体系中最根底的功用了,实践开发中咱们还会遇到如座椅通风、座椅按摩、智能新风、负离子等等一些近几年才呈现的空调新功用,在运用开发上无非便是多几个界面或按钮。
2. HVAC 源码结构
本文中的源码基于Android 12下HVAC APP 原生的Hvac App中不存在Activity、Fragment等传统意义上用来显现HMI的组件,取而代之是运用Service来显现一个Window。首要原因在于Hvac的界面层级比一般的HMI的层级要高,呼出Hvac时需求部分或悉数覆盖其他的运用上(当然IVI中还是有运用比Hvac的层级要高的),这时分运用Activity就显不合适了。
需求注意的是,Havc在Android 12中虽然有一个独立的app,可是上图中Hvac其实并不是运用这个独立的app,它的HMI和相关其实都是写在SystemUI中的。 经过运用adb发送一个播送,能够调出真实的Hvac。
adb shell am broadcast -a android.car.intent.action.TOGGLE_HVAC_CONTROLS
以下是Hvac App的要害部分的源码结构图
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精确的制作出来,基本不含其他的逻辑。
@Override
public 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获取车辆支持的特点列表,以及获取界面所需的根底数据。
@Override
public 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.java
public 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.java
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_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();
}
以上便是有关车机开发的空调体系开发(HVAC),如需求学习更多Android车载运用开发和车机体系开发,我们能够私信。
4. 总结
最后咱们以一张从Car API的callback中的数据更新界面的伪时序图来把Hvac的几个中心组件串起来
以上便是车载空调部分的解说,实践开发中,空调模块功用性需求一般不会呈现什么太大的技能性困难,空调模块的技能性难度简直都体现在杂乱的动画和交互上,有关车载运用的杂乱动画技能,咱们今后在来细解说决方案。
本篇摘要blog.csdn.net/linkwj/arti…