亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android車載空調(diào)系統(tǒng)(HVAC)開發(fā)方法分析

 更新時(shí)間:2023年12月02日 11:10:28   作者:林栩link  
HVAC?全稱:供暖通風(fēng)與空氣調(diào)節(jié)(Heating?Ventilation?and?Air?Conditioning),用戶可以通過(guò)他來(lái)控制整個(gè)汽車的空調(diào)系統(tǒng),是汽車中非常重要的一個(gè)功能,汽車的空調(diào)HMI雖然并不復(fù)雜,但是大多都是用符號(hào)來(lái)表示功能,必須理解空調(diào)的各個(gè)符號(hào)表示的含義

HVAC 全稱:供暖通風(fēng)與空氣調(diào)節(jié)(Heating Ventilation and Air Conditioning)。用戶可以通過(guò)他來(lái)控制整個(gè)汽車的空調(diào)系統(tǒng),是汽車中非常重要的一個(gè)功能。

汽車的空調(diào)HMI雖然并不復(fù)雜,但是大多都是用符號(hào)來(lái)表示功能,對(duì)于還沒有實(shí)際用過(guò)汽車空調(diào)系統(tǒng)的開發(fā)者來(lái)說(shuō),理解空調(diào)的各個(gè)符號(hào)表示的含義也是非常有必要。

1. HVAC 功能介紹

下面就以Android 12中的HVAC來(lái)介紹空調(diào)系統(tǒng)中包含的最基礎(chǔ)的功能。

1.1 雙區(qū)溫度調(diào)節(jié)

空調(diào)的溫度調(diào)節(jié)功能,默認(rèn)是華氏度,可以在系統(tǒng)設(shè)置修改溫度單位。可調(diào)節(jié)范圍是61 - 82華氏度,對(duì)應(yīng)16 - 28 攝氏度。
左側(cè)按鈕用來(lái)調(diào)節(jié)主駕,右側(cè)按鈕用來(lái)調(diào)節(jié)副駕。在以往都是只有高配車型才有雙區(qū)空調(diào),現(xiàn)在的車上雙區(qū)空調(diào)幾乎已經(jīng)是標(biāo)配了。

1.2 空調(diào)開關(guān)

開啟關(guān)閉空調(diào)的開關(guān)

1.3 內(nèi)/外循環(huán)

內(nèi)循環(huán)是汽車空氣調(diào)節(jié)系統(tǒng)的一種狀態(tài)。這種狀態(tài)下,車內(nèi)外的換氣通道關(guān)閉,風(fēng)機(jī)關(guān)閉時(shí)車內(nèi)氣流不循環(huán),風(fēng)機(jī)開啟時(shí),吸入的氣流也僅來(lái)自車內(nèi),形成車輛內(nèi)部的氣流循環(huán)。
外循環(huán)則相反,風(fēng)機(jī)開啟時(shí),吸入的氣流也僅來(lái)自車外,可以更新車內(nèi)的空氣質(zhì)量,代價(jià)是會(huì)更耗電。

1.4 風(fēng)量調(diào)節(jié)

用于增大或減小空調(diào)的風(fēng)量。

1.5 風(fēng)向調(diào)節(jié)

從左到右分別是吹臉、吹臉+吹腳、吹腳、吹腳+吹擋風(fēng)玻璃

1.6 A/C開關(guān)

A/C按鍵,它就是制冷開關(guān),按下A/C按鍵,也就啟動(dòng)了壓縮機(jī),通俗地說(shuō)就是開冷氣。

1.7 主副駕座椅加熱

左邊的按鈕用于調(diào)節(jié)主駕座椅加熱,右邊的按鈕用于調(diào)節(jié)副駕座椅加熱

1.8 除霜

左邊的按鈕是開啟/關(guān)閉 前擋風(fēng)玻璃加熱,開啟后用來(lái)除去前擋風(fēng)玻璃上的霧氣。右邊的按鈕是開啟/關(guān)閉后擋風(fēng)玻璃加熱,開啟后用來(lái)除去后擋風(fēng)玻璃上的霧氣。

1.9 自動(dòng)模式

自動(dòng)空調(diào)其實(shí)就是省略了風(fēng)速、風(fēng)向等調(diào)節(jié)功能,自動(dòng)空調(diào)是全自動(dòng)調(diào)節(jié),只需要選擇風(fēng)向和設(shè)定溫度。AUTO按鍵按下后,就會(huì)根據(jù)車內(nèi)傳感器來(lái)控制出風(fēng)的溫度,冬天熱風(fēng),夏天冷風(fēng)。會(huì)保持車內(nèi)有較適宜的溫度,如果溫度過(guò)高或過(guò)低,空調(diào)也會(huì)自動(dòng)改變出風(fēng)口的溫度及風(fēng)速,調(diào)整車內(nèi)溫度。
以上就是車載空調(diào)系統(tǒng)中最基礎(chǔ)的功能了,實(shí)際開發(fā)中我們還會(huì)遇到如座椅通風(fēng)、座椅按摩、智能新風(fēng)、負(fù)離子等等一些近幾年才出現(xiàn)的空調(diào)新功能,在應(yīng)用開發(fā)上無(wú)非就是多幾個(gè)界面或按鈕。

2. HVAC 源碼結(jié)構(gòu)

本文中的源碼基于Android 12下HVAC APP,源碼請(qǐng)見:https://github.com/linux-link/CarHvac

原生的Hvac App中不存在Activity、Fragment等傳統(tǒng)意義上用來(lái)顯示HMI的組件,取而代之是使用Service來(lái)顯示一個(gè)Window。主要原因在于Hvac的界面層級(jí)比一般的HMI的層級(jí)要高,呼出Hvac時(shí)需要部分或全部覆蓋其他的應(yīng)用上(當(dāng)然IVI中還是有應(yīng)用比Hvac的層級(jí)要高的),這時(shí)候使用Activity就顯不合適了。

需要注意的是,Havc在Android 12中雖然有一個(gè)獨(dú)立的app,但是上圖展示空調(diào)并沒有使用這個(gè)獨(dú)立的app,它的HMI和邏輯實(shí)現(xiàn)都是直接寫在SystemUI中的。
我們可以通過(guò)adb發(fā)送一個(gè)廣播來(lái)調(diào)出獨(dú)立的Hvac應(yīng)用。

adb shell am broadcast -a android.car.intent.action.TOGGLE_HVAC_CONTROLS

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">

        <!--用于控制空調(diào)功能的Service-->
        <service
            android:name=".HvacController"
            android:exported="false"
            android:singleUser="true" />
        <!-- 用于顯示UI的Service-->
        <service
            android:name=".HvacUiService"
            android:exported="false"
            android:singleUser="true" />

        <!-- 監(jiān)聽開機(jī)廣播 -->
        <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

用于監(jiān)聽開機(jī)的廣播,當(dāng)前收到系統(tǒng)的開機(jī)廣播后,會(huì)將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 用來(lái)托管Hvac UI的Service。從名字上也能看出,整個(gè)HvacUiService都是圍繞著如何將Hvac準(zhǔn)確的繪制出來(lái),基本不含其他的邏輯。

@Override
public void onCreate() {
    ...
    // 由于不存在從服務(wù)內(nèi)部獲取系統(tǒng)ui可見性的方法,因此我們將全屏放置一些東西,并檢查其最終測(cè)量結(jié)果,作為獲取該信息的黑客手段。
    // 一旦我們有了初始狀態(tài),我們就可以安全地從那時(shí)開始注冊(cè)更改事件。
    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();
            // 我們現(xiàn)在有了初始狀態(tài),因此不再需要這個(gè)空視圖。
            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);
    // 注冊(cè)接收器,以便任何具有CONTROL_CAR_CLIMATE權(quán)限的用戶都可以調(diào)用它。
    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.注冊(cè)事件廣播。這個(gè)事件實(shí)際并沒有發(fā)送源,因?yàn)镾ystemUI中額外寫了一個(gè)Hvac,不過(guò)正是這個(gè)廣播讓我們可以把這個(gè)單獨(dú)的Hvac調(diào)出。
2.繪制UI。HvacUIService在被拉起后并沒有立即開始UI的繪制,而是在屏幕上臨時(shí)放置一個(gè)用于測(cè)量窗口的 windowSizeTest ,當(dāng)windowSizeTestView開始測(cè)量后,通過(guò)比對(duì)View的高度和屏幕的高度,即可判斷出systemUI是否已經(jīng)顯示,這時(shí)就可以開始著手繪制真正的Hvac的UI了,并且可以更安全的操作UI。
接下來(lái)就是繪制真正的Hvac界面:

/**
 * 在確定最小偏移量后調(diào)用。
 * 這將生成HVAC UI所需的所有組件的布局。
 * 啟動(dòng)時(shí),折疊視圖所需的所有窗口都可見,而展開視圖的窗口已創(chuàng)建并調(diào)整大小,但不可見。
 */
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) {
            // 當(dāng)systemUi可見時(shí),窗口系統(tǒng)坐標(biāo)從系統(tǒng)導(dǎo)航欄上方的0開始。因此,如果我們想獲得屏幕底部的實(shí)際高度,我們需要將y值設(shè)置為導(dǎo)航欄高度的負(fù)值。
            y = -mNavBarHeight;
        }
        setYPosition(mDriverTemperatureBar, y);
        setYPosition(mPassengerTemperatureBar, y);
        setYPosition(mDriverTemperatureBarCollapsed, y);
        setYPosition(mPassengerTemperatureBarCollapsed, y);
        setYPosition(mContainer, y);
    });

    // 頂部填充應(yīng)根據(jù)屏幕高度和擴(kuò)展hvac面板的高度進(jìn)行計(jì)算。由填充物定義的空間意味著可以單擊以關(guān)閉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);
    // 創(chuàng)建溫度計(jì)bar
    createTemperatureBars(inflater);

    // UI狀態(tài)控制器,用來(lái)控制展開/收起時(shí)UI的各種狀態(tài)并執(zhí)行動(dòng)畫
    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是空調(diào)的面板控制器,在與HvacController綁定成功后,將HvacController的實(shí)例傳遞給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重新啟動(dòng)后重新連接控制器
    }
};

我們接著看HvacPanelController

3.4 HvacPanelController

HvacPanelController 主要作用是初始化其他界面Controller,并從HvacController中獲取數(shù)據(jù),顯示在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);

    // 切換按鈕不需要額外的邏輯來(lái)映射硬件和UI設(shè)置。只需使用ToggleListener來(lái)處理點(diǎn)擊。
    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界面展開和收起的動(dòng)畫也是在HvacPanelController 中處理的,不過(guò)關(guān)于動(dòng)畫部分打算以后再開個(gè)新坑講一講。

3.5 HvacController

HvacController是HvacApp與CarService之間的信息傳輸控制器,本質(zhì)上也是一個(gè)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中的設(shè)置及獲取數(shù)據(jù)的操作都是通過(guò)HvacController進(jìn)行的,在HvacController啟動(dòng)時(shí)會(huì)獲取一個(gè)Car實(shí)例,并通過(guò)connect方法連接CarService。當(dāng)連接CarService成功后初始化CarHvacManager并通過(guò)CarHvacManager獲取車輛支持的屬性列表,以及獲取界面所需的基礎(chǔ)數(shù)據(jù)。

@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獲取數(shù)據(jù)需要先得到CarHvacManager的實(shí)例,所以在連接成功后,調(diào)用mHvacManagerReady.notifyAll() 喚醒所有之前等待CarHvacManager實(shí)例的線程

// 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;
                    }
                }
            }
            // 刷新數(shù)據(jù)
            fetchTemperature(DRIVER_ZONE_ID);
            fetchTemperature(PASSENGER_ZONE_ID);
            fetchFanSpeed();
            ...
            return null;
        }

        @Override
        protected void onPostExecute(Void unused) {
            // 切換到主線程中執(zhí)行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的實(shí)例,然后刷新數(shù)據(jù)并存儲(chǔ)在DatStore中。
需要注意一點(diǎn)的是while (mHvacManager == null)不能替換成if(mHvacManager == null),這是因?yàn)镴ava有個(gè)叫“spurious wakeup”的現(xiàn)象,即線程在不該醒過(guò)來(lái)的時(shí)候醒過(guò)來(lái)。

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.
一個(gè)線程有可能會(huì)在未被通知、打斷、或超時(shí)的情況下醒來(lái),這就是所謂的“spurious wakeup”。盡管實(shí)際上這種情況很少發(fā)生,應(yīng)用程序仍然必須對(duì)此有所防范,手段是檢查正常的導(dǎo)致線程被喚醒的條件是否滿足,如果不滿足就繼續(xù)等待。

3.6 Car API

Car是Android汽車平臺(tái)最高等級(jí)的API,為外界提供汽車所有服務(wù)和數(shù)據(jù)訪問(wèn)的接口,提供了一系列與汽車有關(guān)的API。它不僅僅可以提供HvacManger,像車輛的速度、檔位狀態(tài)等等所有與汽車有關(guān)的信息都可以從Car API中獲取。
Hvac中的CarHvacManager實(shí)現(xiàn)了CarManagerBase接口,并且只要是作為CarXXXManager, 都需要實(shí)現(xiàn)CarManagerBase接口,如CarCabinManager,CarSensorManager等都實(shí)現(xiàn)了該接口。
CarHvacManager的控制操作是通過(guò)CarPropertyManager來(lái)完成的,CarPropertyManager統(tǒng)一控制汽車屬性相關(guān)的操作。CarHvacManager只是控制與Hvac相關(guān)的操作,在汽車中還有很多屬性控制的Manager,如傳感器,座艙等屬性的控制,他們都是通過(guò)CarPropertyManager進(jìn)行屬性操作,通過(guò)在操作時(shí)傳入的屬性ID,屬性區(qū)域以及屬性值,在CarPropertyManager中會(huì)將這些參數(shù)轉(zhuǎn)化為一個(gè)CarPropertyValue對(duì)象繼續(xù)往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也是通過(guò)注冊(cè)一個(gè)callback來(lái)得到 Car API 的數(shù)據(jù)回調(diào)。

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中每個(gè)Property對(duì)應(yīng)的含義如下:

// 全局屬性,只有一個(gè)
ID_MIRROR_DEFROSTER_ON  //視鏡除霧
ID_STEERING_WHEEL_HEAT  //方向盤溫度
ID_OUTSIDE_AIR_TEMP  //室外溫度
ID_TEMPERATURE_DISPLAY_UNITS  //在使用的溫度
// 區(qū)域?qū)傩裕稍诓煌瑓^(qū)域設(shè)置
ID_ZONED_TEMP_SETPOINT  //用戶設(shè)置的溫度
ID_ZONED_TEMP_ACTUAL  //區(qū)域?qū)嶋H溫度
ID_ZONED_HVAC_POWER_ON  //HVAC系統(tǒng)電源開關(guān)
ID_ZONED_FAN_SPEED_SETPOINT  //風(fēng)扇設(shè)置的速度
ID_ZONED_FAN_SPEED_RPM  //風(fēng)扇實(shí)際的速度
ID_ZONED_FAN_DIRECTION_AVAILABLE  //風(fēng)扇可設(shè)置的方向
ID_ZONED_FAN_DIRECTION  //現(xiàn)在風(fēng)扇設(shè)置的方向
ID_ZONED_SEAT_TEMP  //座椅溫度
ID_ZONED_AC_ON  //空調(diào)開關(guān)
ID_ZONED_AUTOMATIC_MODE_ON  //HVAC自動(dòng)模式開關(guān)
ID_ZONED_AIR_RECIRCULATION_ON  //空氣循環(huán)開關(guān)
ID_ZONED_MAX_AC_ON  //空調(diào)最大速度開關(guān)
ID_ZONED_DUAL_ZONE_ON  //雙區(qū)模式開關(guān)
ID_ZONED_MAX_DEFROST_ON  //最大除霧開關(guān)
ID_ZONED_HVAC_AUTO_RECIRC_ON  //自動(dòng)循環(huán)模式開關(guān)
ID_WINDOW_DEFROSTER_ON  //除霧模式開關(guān)

使用Car API時(shí)務(wù)必需要注意,注冊(cè)的callback是有可能會(huì)非常頻繁的產(chǎn)生回調(diào)的,應(yīng)用層需要先將數(shù)據(jù)存儲(chǔ)在DataStore中進(jìn)行過(guò)濾,才能更新到UI上。而且也不要實(shí)時(shí)的打印日志,否則可能會(huì)導(dǎo)致日志緩沖區(qū)EOF,也會(huì)嚴(yán)重干擾其它進(jìn)程的日志輸出。

3.7 DataStore

DataStore 用于存儲(chǔ)HvacController從 Car API 中獲取的屬性值。
用戶操作IVI界面和使用硬按鍵,都會(huì)更新Hvac的相關(guān)屬性。這兩種不同的更新方式都是從不同的線程更新到當(dāng)前狀態(tài)。此外,在某些情況下,Hvac系統(tǒng)可能會(huì)發(fā)送虛假的更新,因此這個(gè)類將所有內(nèi)容更新管理合并,從而確保在用戶看來(lái)應(yīng)用程序的界面是正常的

@GuardedBy("mFanSpeed")
private Integer mFanSpeed = 0;
private static final long COALESCE_TIME_MS = 0L;

public int getFanSpeed() {
    synchronized (mFanSpeed) {
        return mFanSpeed;
    }
}

// 僅用于主動(dòng) 獲取、設(shè)定 數(shù)據(jù)時(shí)更新speed數(shù)據(jù)。
public void setFanSpeed(int speed) {
    synchronized (mFanSpeed) {
        mFanSpeed = speed;
        mLastFanSpeedSet = SystemClock.uptimeMillis();
    }
}

// 從callback中得到數(shù)據(jù)時(shí),因?yàn)閿?shù)據(jù)可能會(huì)刷新的很頻繁,所以需要先判斷時(shí)間戳,確定數(shù)據(jù)是否真的需要更新
public boolean shouldPropagateFanSpeedUpdate(int zone, int speed) {
    // TODO:我們暫時(shí)忽略風(fēng)扇速度區(qū)域,因?yàn)槲覀儧]有多區(qū)域車。
    synchronized (mFanSpeed) {
        if (SystemClock.uptimeMillis() - mLastFanSpeedSet < COALESCE_TIME_MS) {
            return false;
        }
        mFanSpeed = speed;
    }
    return true;
}

HvacController中我們從callback得到數(shù)據(jù)刷新時(shí),先通過(guò)DataStore判斷以下是否需要更新數(shù)據(jù),如果確實(shí)需要更新,再將更新后的數(shù)據(jù)回調(diào)給其他的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:
                // 處理來(lái)自callback的數(shù)據(jù)
                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) {
    // 判斷是否需要更新本地的數(shù)據(jù)
    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) {
        // 將更新后的數(shù)據(jù)回調(diào)給各個(gè)UI控制器
        synchronized (mCallbacks) {
            for (int i = 0; i < mCallbacks.size(); i++) {
                mCallbacks.get(i).onFanSpeedChange(speed);
            }
        }
    }
}

public void setFanSpeed(final int fanSpeed) {
    // 更新當(dāng)前的數(shù)據(jù)
    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. 總結(jié)

最后我們以一張從Car API的callback中的數(shù)據(jù)更新界面的偽時(shí)序圖來(lái)把Hvac的幾個(gè)核心組件串起來(lái)

以上就是車載空調(diào)部分的講解,實(shí)際開發(fā)中,空調(diào)模塊功能性需求一般不會(huì)出現(xiàn)什么太大的技術(shù)性困難,空調(diào)模塊的技術(shù)性難度幾乎都體現(xiàn)在復(fù)雜的動(dòng)畫和交互上,有關(guān)車載應(yīng)用的復(fù)雜動(dòng)畫技術(shù),我們以后在來(lái)細(xì)講解決方案。

相關(guān)文章

  • Android中兩個(gè)Activity之間數(shù)據(jù)傳遞及返回問(wèn)題

    Android中兩個(gè)Activity之間數(shù)據(jù)傳遞及返回問(wèn)題

    本篇文章主要介紹了Android中兩個(gè)Activity之間數(shù)據(jù)傳遞及返回問(wèn)題,這里整理了詳細(xì)的代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-02-02
  • Android集成Flutter

    Android集成Flutter

    本文主要從一個(gè) Android 開發(fā)的視角,談?wù)?Android 平臺(tái)下, Flutter 的混合開發(fā)與構(gòu)建,如果你也感興趣的話一起參與學(xué)習(xí)吧
    2021-08-08
  • Android自定義仿ios加載彈窗

    Android自定義仿ios加載彈窗

    這篇文章主要為大家詳細(xì)介紹了Android自定義仿ios加載彈窗,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-05-05
  • 詳解android項(xiàng)目由Gradle 2.2 切換到 3.0的坑

    詳解android項(xiàng)目由Gradle 2.2 切換到 3.0的坑

    本篇文章主要介紹了詳解android項(xiàng)目由Gradle 2.2 切換到 3.0的坑,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-02-02
  • Android liveData與viewBinding使用教程

    Android liveData與viewBinding使用教程

    LiveData是一種可觀察的數(shù)據(jù)存儲(chǔ)器類,LiveData使用觀察者模式,每當(dāng)數(shù)據(jù)發(fā)生變化時(shí),LiveData會(huì)通知 Observer對(duì)象,我們可以在這些 Observer 對(duì)象中更新UI,ViewModel對(duì)象為特定的界面組件提供數(shù)據(jù),并包含數(shù)據(jù)處理業(yè)務(wù)邏輯,會(huì)配合LiveData一起使用
    2022-11-11
  • Android NavigationBar問(wèn)題處理的方法

    Android NavigationBar問(wèn)題處理的方法

    本篇文章主要介紹了Android NavigationBar問(wèn)題處理的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-10-10
  • Android設(shè)置當(dāng)TextView中的文字超過(guò)TextView的容量時(shí)用省略號(hào)代替

    Android設(shè)置當(dāng)TextView中的文字超過(guò)TextView的容量時(shí)用省略號(hào)代替

    這篇文章主要介紹了Android設(shè)置當(dāng)TextView中的文字超過(guò)TextView的容量時(shí)用省略號(hào)代替 ,需要的朋友可以參考下
    2017-03-03
  • Android如何獲取圖片或視頻略縮圖

    Android如何獲取圖片或視頻略縮圖

    這篇文章主要為大家詳細(xì)介紹了Android如何獲取圖片或視頻略縮圖的方法,感興趣的小伙伴們可以參考一下
    2016-08-08
  • Android?模擬地圖定位功能的實(shí)現(xiàn)

    Android?模擬地圖定位功能的實(shí)現(xiàn)

    這篇文章主要介紹了Android?模擬地圖定位功能的實(shí)現(xiàn),本工程利用手機(jī)自帶的"模擬位置"功能實(shí)現(xiàn)運(yùn)行時(shí)修改LocationManager結(jié)果,需要的朋友可以參考一下
    2022-02-02
  • Android布局耗時(shí)監(jiān)測(cè)的三種實(shí)現(xiàn)方式

    Android布局耗時(shí)監(jiān)測(cè)的三種實(shí)現(xiàn)方式

    在Android應(yīng)用開發(fā)中,性能優(yōu)化是一個(gè)至關(guān)重要的方面,為了更好地監(jiān)測(cè)布局渲染的耗時(shí),我們需要一種可靠的實(shí)現(xiàn)方案,本文將介紹三種針對(duì)Android布局耗時(shí)監(jiān)測(cè)的實(shí)現(xiàn)方案,幫助開發(fā)者及時(shí)發(fā)現(xiàn)并解決布局性能問(wèn)題,需要的朋友可以參考下
    2024-03-03

最新評(píng)論