Android車載應(yīng)用開發(fā)與分析(13)- 系統(tǒng)設(shè)置-藍(lán)牙設(shè)置

1. 前言

Android 車載應(yīng)用開發(fā)與分析是一個系列性的文章钉嘹,這個是第13篇分析系統(tǒng)設(shè)置,該系列文章旨在分析原生車載Android系統(tǒng)中核心應(yīng)用的實(shí)現(xiàn)方式烛芬,幫助初次從事車載應(yīng)用開發(fā)的同學(xué)隧期,更好地理解車載應(yīng)用開發(fā)的方式飒责,積累android系統(tǒng)應(yīng)用的開發(fā)經(jīng)驗(yàn)。

2. 系統(tǒng)設(shè)置概述

系統(tǒng)設(shè)置是車載Android系統(tǒng)中非常重要的一個系統(tǒng)級應(yīng)用仆潮,是整個車載IVI系統(tǒng)的控制中心宏蛉,整車的音效、無線通信性置、狀態(tài)信息拾并、安全信息等等都是需要通過系統(tǒng)設(shè)置來查看和控制。例如鹏浅,開啟/關(guān)閉 wifi 和藍(lán)牙嗅义,查看每個應(yīng)用的網(wǎng)絡(luò)流量,開啟調(diào)試信息等隐砸。

有車載經(jīng)驗(yàn)的同學(xué)之碗,應(yīng)該都見過下面這種字體顏色怪異的系統(tǒng)設(shè)置,這其實(shí)是手機(jī)的系統(tǒng)設(shè)置移植到車載系統(tǒng)中的樣子季希。一個車載 Android 項目啟動時褪那,大都會選擇保留功能更全的手機(jī)原生系統(tǒng)設(shè)置,而不是使用車載版本的系統(tǒng)設(shè)置式塌。


車載原生的系統(tǒng)設(shè)置是長這樣的



鑒于系統(tǒng)設(shè)置的功能非常多博敬,由于系統(tǒng)設(shè)置的源碼也比較復(fù)雜,而且一般我們編寫車載系統(tǒng)設(shè)置也不會沿用原生的代碼架構(gòu)峰尝,所以本篇不再介紹系統(tǒng)設(shè)置源碼架構(gòu)和初始化流程偏窝,主要聚焦于系統(tǒng) API 的運(yùn)用。

本次就先從藍(lán)牙模塊開始入手武学。

3. 藍(lán)牙簡介

藍(lán)牙Bluetooth)祭往,是一種無線通訊技術(shù)標(biāo)準(zhǔn),用來讓固定與移動設(shè)備劳淆,在短距離間交換資料链沼,以形成個人局域網(wǎng)(PAN)。其使用短波特高頻(UHF)無線電波沛鸵,經(jīng)由2.4至2.485 GHz的ISM頻段來進(jìn)行通信括勺。1994年由電信商愛立信(Ericsson)發(fā)展出這個技術(shù)。它最初的設(shè)計曲掰,是希望創(chuàng)建一個RS-232數(shù)據(jù)線的無線通信替代版本疾捍。它能夠連接多個設(shè)備,克服同步的問題栏妖。

藍(lán)牙技術(shù)目前由藍(lán)牙技術(shù)聯(lián)盟(SIG)來負(fù)責(zé)維護(hù)其技術(shù)標(biāo)準(zhǔn)乱豆,其成員已超過三萬,分布在電信吊趾、電腦宛裕、網(wǎng)絡(luò)與消費(fèi)性電子產(chǎn)品等領(lǐng)域瑟啃。IEEE曾經(jīng)將藍(lán)牙技術(shù)標(biāo)準(zhǔn)化為IEEE 802.15.1,但是這個標(biāo)準(zhǔn)已經(jīng)不再繼續(xù)使用揩尸。

3.1. 藍(lán)牙分類

2010年7月7日蛹屿,藍(lán)牙技術(shù)聯(lián)盟推出了藍(lán)牙4.0規(guī)范,藍(lán)牙4.0包括3個子規(guī)范岩榆,即“低功耗藍(lán)牙”错负、“傳統(tǒng)藍(lán)牙”和“高速藍(lán)牙”。

  • 低功耗藍(lán)牙

藍(lán)牙低功耗Bluetooth Low Energy勇边,或稱Bluetooth LE犹撒、BLE,舊商標(biāo)Bluetooth Smart)也稱藍(lán)牙低能耗粒褒、低功耗藍(lán)牙识颊,是藍(lán)牙技術(shù)聯(lián)盟設(shè)計和銷售的一種個人局域網(wǎng)技術(shù),旨在用于醫(yī)療保健怀浆、運(yùn)動健身谊囚、信標(biāo)怕享、安防执赡、家庭娛樂等領(lǐng)域的新興應(yīng)用。相較經(jīng)典藍(lán)牙函筋,低功耗藍(lán)牙旨在保持同等通信范圍的同時顯著降低功耗和成本沙合。

  • 經(jīng)典藍(lán)牙

經(jīng)典藍(lán)牙模塊,一般用于數(shù)量比較大的傳輸:如語音跌帐、音樂等較高數(shù)據(jù)量傳輸

  • 高速藍(lán)牙

高速藍(lán)牙主攻數(shù)據(jù)交換與傳輸

3.2. 藍(lán)牙規(guī)范

藍(lán)牙規(guī)范(Bluetooth profile)首懈,藍(lán)牙技術(shù)聯(lián)盟定義了許多Profile。Profile目的是要確保Bluetooth設(shè)備間的互通性(interoperability)谨敛。但Bluetooth產(chǎn)品無須實(shí)現(xiàn)所有的Bluetooth規(guī)范Profile究履。Bluetooth 版本 1.1 定義了13個Profiles。下面幾個是Android中常用的:

PBAP 協(xié)議脸狸,電話本訪問協(xié)議(Phone Book Access Profile)最仑,是一種基于OBEX的上層協(xié)議,該協(xié)議可以同步手機(jī)這些具有電話本功能設(shè)備上的通訊錄和通話記錄等信息炊甲。

HFP 協(xié)議泥彤,免手持設(shè)備規(guī)范(Hands-Free Profile),移動電話和免提裝置之間的遠(yuǎn)程無線控制和語音連接就是通過 HFP 協(xié)議卿啡。

A2DP 協(xié)議吟吝, 藍(lán)牙立體聲音頻傳輸規(guī)范(Advance Audio Distribution Profile),規(guī)定了使用藍(lán)牙異步傳輸信道方式颈娜,傳輸高質(zhì)量音樂文件數(shù)據(jù)的協(xié)議堆棧軟件和使用方法剑逃,基于該協(xié)議就能通過以藍(lán)牙方式傳輸高質(zhì)量的立體聲音樂浙宜。分為1.1版和1.2版,只要連接雙方支持A2DP協(xié)議都能以16 bits蛹磺,44.1 kHz的質(zhì)量傳輸聲音信號梆奈。假如有一方?jīng)]有支持A2DP的話,只能以8 bits称开,8 kHz的質(zhì)量的免手持設(shè)備規(guī)范(Handsfree Profile)傳輸模式亩钟,聲音質(zhì)量會大打折扣。

3. 藍(lán)牙設(shè)置關(guān)鍵API

3.1. BluetoothAdapter

API 文檔地址:https://developer.android.google.cn/reference/kotlin/android/bluetooth/BluetoothAdapter

BluetoothAdapter表示本地設(shè)備藍(lán)牙適配器(此類中的操作是線程安全的)鳖轰。必須通過BluetoothAdapter才能執(zhí)行基本的藍(lán)牙任務(wù)清酥,例如啟動設(shè)備發(fā)現(xiàn)、查詢綁定(配對)設(shè)備列表蕴侣、使用已知MAC地址實(shí)例化Bluetooth device焰轻、創(chuàng)建BluetootServerSocket以偵聽來自其他設(shè)備的連接請求,以及開始掃描Bluetooch LE設(shè)備昆雀。

BluetoothAdapter的初始化方式有兩種:

  • JELLY_BEAN_MR1(API 17)及以下辱志,使用getDefaultAdapter()
 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
  • JELLY_BEAN_MR1(API 17)以上,使用 BluetoothManager.getAdapter()
BluetoothManager btManager = (BluetoothManager)mContext.getSystemService(Context.BLUETOOTH_SERVICE);
if (btManager != null) {
    BluetoothAdapter btAdapter = btManager.getAdapter();
}

從根本上來說狞膘,BluetoothAdapter是所有藍(lán)牙操作的起點(diǎn)揩懒。

擁有BluetoothAdapter后,可以使用getBondedDevices()獲取一組BluetoothDevice對象挽封,表示所有配對過的設(shè)備已球;使用startDiscovery()啟動設(shè)備發(fā)現(xiàn);或創(chuàng)建BluetoothServerSocket以監(jiān)聽傳入的RFComm連接請求辅愿,并使用listenUsingRfcommWithServiceRecord(java.lang.String智亮,java.util.UUID);使用listenUsingL2capChannel()監(jiān)聽傳入的L2CAP面向連接的通道(CoC)連接請求;或使用startLeScan(android.Bluetooth.BluetoothAdapter.LeScanCallback)啟動藍(lán)牙LE設(shè)備掃描点待。

3.2. BluetoothDevice

API 文檔地址:https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice

BluetoothDevice是遠(yuǎn)程藍(lán)牙設(shè)備的實(shí)體類阔蛉。 通過BluetoothDevice可以創(chuàng)建與相應(yīng)設(shè)備的連接或查詢有關(guān)藍(lán)牙設(shè)備的信息,例如名稱癞埠、地址状原、類和綁定狀態(tài)。

要獲取BluetoothDevice有多種方式:

  • 如果已經(jīng)知道藍(lán)牙的mac地址燕差,可以使用BluetoothAdapter.getRemoteDevice(String mac)創(chuàng)建一個藍(lán)牙設(shè)備遭笋。

  • BluetoothAdapter.getBondedDevices()返回的一組綁定設(shè)備中獲取一個。然后徒探,可以通過藍(lán)牙 BR/EDR 使用 createRfcommSocketToServiceRecord(java.util.UUID) 或通過藍(lán)牙LE使用 createL2capChannel(int)瓦呼,打開一個BluetoothSocket與遠(yuǎn)程設(shè)備通信。

  • 使用BluetoothAdapter.startDiscovery()開啟藍(lán)牙搜索,然后監(jiān)聽BluetoothDevice.ACTION_FOUND廣播也可以獲取到BluetoothDevice央串。

3.3. 其它關(guān)鍵類

在 Android 的 framework 目錄下封裝了很多實(shí)用的藍(lán)牙組件磨澡,不過這些類是 framework 的私有類,并不能通過應(yīng)用層的Android API直接調(diào)用质和,實(shí)際項目中根據(jù)需要將這些類移植到應(yīng)用中再做修改稳摄。不建議直接修改 framework 層的代碼!這樣可能會導(dǎo)致一些原生應(yīng)用無法正常運(yùn)行饲宿。

源碼位置:/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/

4. 藍(lán)牙設(shè)置關(guān)鍵功能實(shí)現(xiàn)

系統(tǒng)設(shè)置作為系統(tǒng)級應(yīng)用厦酬,在使用藍(lán)牙設(shè)置功能時,需要添加以下權(quán)限瘫想。

1)基本藍(lán)牙權(quán)限仗阅,需要此權(quán)限才能執(zhí)行任何藍(lán)牙通信,例如請求連接国夜、接受連接和傳輸數(shù)據(jù)等

<uses-permission android:name="android.permission.BLUETOOTH" />

2)藍(lán)牙設(shè)置“超級管理員”權(quán)限减噪,需要此權(quán)限才能啟動設(shè)備發(fā)現(xiàn)或操縱藍(lán)牙設(shè)置

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

3)允許應(yīng)用程序在無需用戶交互的情況下配對藍(lán)牙設(shè)備,并允許或禁止電話簿訪問或消息訪問

<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />

4)位置權(quán)限车吹,因?yàn)樗{(lán)牙掃描可用于收集用戶的位置信息筹裕。此類信息可能來自用戶自己的設(shè)備,以及在商店和交通設(shè)施等位置使用的藍(lán)牙信標(biāo)

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

4.1. 開啟/關(guān)閉 藍(lán)牙

車載藍(lán)牙設(shè)置的主頁是BluetoothSettingsFragment窄驹,它管理藍(lán)牙適配器的開關(guān)朝卒, 它還顯示已配對的設(shè)備和設(shè)備配對功能的入口點(diǎn)。

源碼位置:/packages/apps/Car/Settings/src/com/android/car/settings/bluetooth/BluetoothSettingsFragment.java

設(shè)定藍(lán)牙開啟或關(guān)閉的方法如下所示馒吴,BluetoothAdapter的初始化以及各個 API 的含義在上面已經(jīng)介紹過了扎运,這里就不再贅述。

private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

private final MenuItem.OnClickListener mBluetoothSwitchListener = item -> {
        item.setEnabled(false);
        if (item.isChecked()) {
            // 開啟藍(lán)牙
            mBluetoothAdapter.enable();
        } else {
            // 關(guān)閉藍(lán)牙
            mBluetoothAdapter.disable();
        }
    };

此外饮戳,我們必須要監(jiān)聽BluetoothAdapter.ACTION_STATE_CHANGED廣播,該廣播表示藍(lán)牙狀態(tài)發(fā)生變化洞拨,此時我們需要同步一下藍(lán)牙的狀態(tài)扯罐,來保證內(nèi)部的狀態(tài)機(jī)或 UI 一直是正確的。

private  final IntentFilter mIntentFilter  = new  IntentFilter(
        BluetoothAdapter.ACTION_STATE_CHANGED)  ;

@Override
public void onStart() {
    super.onStart();
    // 注冊藍(lán)牙狀態(tài)的廣播  
    requireContext().registerReceiver(mReceiver, mIntentFilter) ;
    mLocalBluetoothManager.setForegroundActivity(requireActivity());
    // 頁面初始化后烦衣,要同步一次藍(lán)牙開關(guān)的狀態(tài)
    handleStateChanged(mBluetoothAdapter.getState());
}

private  final BroadcastReceiver mReceiver  = new BroadcastReceiver() {
    @Override
    public  void onReceive(Context context, Intent intent) {
        int  state = intent.getIntExtra(BluetoothAdapter.E XTRA_STATE,  BluetoothAdapter.ERROR)  ;
        handleStateChanged(state);
    }
};

private  void handleStateChanged(int state) {
    // 暫時清除監(jiān)聽器歹河,以便我們在嘗試反映適配器狀態(tài)時不會更新適配器。  mBluetoothSwitch.setOnClickListener(null ) ;
    switch ( state) {
        case BluetoothAdapter.S TATE_TURNING_ON: 
            mBluetoothSwitch.setEnabled(false ) ;
            mBluetoothSwitch.setChecked(true ) ;
            break ;
        case BluetoothAdapter.S TATE_ON: 
            mBluetoothSwitch.setEnabled(!isUserRestricted());
            mBluetoothSwitch.setChecked(true ) ;
            break ;
        case  BluetoothAdapter.S TATE_TURNING_OFF: 
            mBluetoothSwitch.setEnabled(false ) ;
            mBluetoothSwitch.setChecked(false ) ;
            break ;
        case BluetoothAdapter.S TATE_OFF: 
        default :
            mBluetoothSwitch.setEnabled(!isUserRestricted());
            mBluetoothSwitch.setChecked(false ) ;
    }
    mBluetoothSwitch.setOnClickListener(mBluetoothSwitchListener);
}

有的博客中可能會看到使用的是LocalBluetoothAdapter花吟,它的源碼位置是/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java 秸歧,不過根據(jù)官方的注釋,該類已經(jīng)過時衅澈,現(xiàn)在更推薦使用BluetoothAdapter键菱。

4.2. 查找已連接、已配對的藍(lán)牙設(shè)備

開啟藍(lán)牙后今布,緊接著我們就需要開始搜索藍(lán)牙設(shè)備经备,但是在執(zhí)行搜索之前拭抬,應(yīng)該先查詢配對設(shè)備集,以查看所需的設(shè)備是否已知侵蒙。已連接造虎、已配對的藍(lán)牙還是顯示這個頁面中

需要注意文字上的描述差異:已配對的設(shè)備和已連接的設(shè)備之間是有區(qū)別的:

  • 已配對(paired 或 bonded)意味著兩個設(shè)備知道彼此的存在,具有可用于身份驗(yàn)證的共享鏈接密鑰纷闺,并且能夠彼此建立加密連接算凿。
  • 已連接(connected)意味著設(shè)備當(dāng)前共享RFCOMM信道,并且能夠相互傳輸數(shù)據(jù)犁功。當(dāng)前的藍(lán)牙 API 要求在建立 RFCOMM 連接之前配對設(shè)備澎媒。當(dāng)啟動與藍(lán)牙 API 的加密連接時,將自動執(zhí)行配對波桩。

借用手機(jī)的藍(lán)牙設(shè)置界面舉個例子戒努,紅框內(nèi)的是已連接的設(shè)備,綠框內(nèi)的是已配對的設(shè)備镐躲,如下圖所示

獲取連接藍(lán)牙設(shè)備有以下幾步:

1)注冊廣播BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED監(jiān)聽藍(lán)牙的連接狀態(tài)

該廣播的intent中有三個extras储玫,分別是

  • BluetoothAdapter.EXTRA_CONNECTION_STATE:當(dāng)前連接狀態(tài)
  • BluetoothAdapter . EXTRA_PREVIOUS_CONNECTION_STATE:之前的連接狀態(tài)
  • BluetoothDevice.EXTRA_DEVICE:藍(lán)牙設(shè)備

注冊此廣播需要藍(lán)牙權(quán)限android.Manifest.permission.BLUETOOTH。

2)判斷連接狀態(tài)萤皂,如果已連接狀態(tài)撒穷,則通過EXTRA_DEVICE獲取已連接的藍(lán)牙設(shè)備


獲取配對藍(lán)牙設(shè)備有以下幾步:

1)注冊廣播BluetoothDevice . ACTION_BOND_STATE_CHANGED監(jiān)聽藍(lán)牙的配對狀態(tài)

該廣播的intent中有四個extras,分別是

  • BluetoothDevice.EXTRA_BOND_STATE:當(dāng)前配對狀態(tài)
  • BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE:之前的配對狀態(tài)
  • BluetoothDevice.EXTRA_DEVICE:藍(lán)牙設(shè)備
  • BluetoothDevice.EXTRA_REASON: 當(dāng) EXTRA_BOND_STATE 為 BOND_NONE 時裆熙,可以通過EXTRA_REASON 獲取一個結(jié)果代碼端礼。

2)判斷配對狀態(tài),如果已配對狀態(tài),則通過EXTRA_DEVICE獲取已連接的藍(lán)牙設(shè)備


了解步驟之后,我們來看在車載Settings的源碼中是如何處理的寻咒。

BluetoothSettingsFragment的布局文件bluetooth_settings_fragment.xml中偶宫,使用了一個BluetoothBondedDevicesPreferenceController的類,這個類的上一層繼承自BluetoothPreferenceController,通過在BluetoothPreferenceController中向LocalBluetoothManager.BluetoothEventManager注冊了一個BluetoothCallback來監(jiān)聽藍(lán)牙設(shè)備的狀態(tài)回調(diào)。

private final LocalBluetoothManager mBluetoothManager;
   
protected void onStartInternal() {
    mBluetoothManager.getEventManager().registerCallback(this);
}

LocalBluetoothManager.BluetoothEventManager是 framework 的私有類,藍(lán)牙所有廣播事件都是在這里完成注冊和分發(fā)的缅刽,是我們需要重點(diǎn)關(guān)注的類。

// 藍(lán)牙開關(guān)的廣播 
addHandler(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedHandler());

// 藍(lán)牙連接狀態(tài)的廣播 
addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED,new ConnectionStateChangedHandler());

// 藍(lán)牙掃描的廣播 
addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED,new ScanningStateChangedHandler(true));
addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED,new ScanningStateChangedHandler(false));
addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());
addHandler(BluetoothDevice.ACTION_NAME_CHANGED, new NameChangedHandler());
addHandler(BluetoothDevice.ACTION_ALIAS_CHANGED, new NameChangedHandler());

// 藍(lán)牙配對狀態(tài)的廣播 
addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());

// Fine-grained state broadcasts a
ddHandler(BluetoothDevice.ACTION_CLASS_CHANGED, new ClassChangedHandler());
addHandler(BluetoothDevice.ACTION_UUID, new UuidChangedHandler());
addHandler(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED, new BatteryLevelChangedHandler());

// 活躍設(shè)備的廣播 
addHandler(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, new ActiveDeviceChangedHandler());
addHandler(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED, new ActiveDeviceChangedHandler());
addHandler(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,new ActiveDeviceChangedHandler());

// 耳機(jī)狀態(tài)改變廣播 
addHandler(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED,new AudioModeChangedHandler());
addHandler(TelephonyManager.ACTION_PHONE_STATE_CHANGED,new AudioModeChangedHandler());

// ACL 連接更改的廣播 
addHandler(BluetoothDevice.ACTION_ACL_CONNECTED, new AclStateChangedHandler());
addHandler(BluetoothDevice.ACTION_ACL_DISCONNECTED, new AclStateChangedHandler());

源碼位置:/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java

以下是處理連接狀態(tài)的藍(lán)牙設(shè)備

 // Generic connected/not broadcast
addHandler(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED, new ConnectionStateChangedHandler());

// 這個 Handler 不是Android.OS中的handler,它只是一個接口
private  class ConnectionStateChangedHandler implements Handler {
    @Override
    public  void onReceive(Context context, Intent intent, BluetoothDevice device) {
        // 更新本地緩存蠢络,并返回一個二次封裝類
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, BluetoothAdapter.ERROR);
        // 分發(fā) 連接 狀態(tài)
dispatchConnectionStateChanged(cachedDevice, state);
    }
}

以下是處理配對狀態(tài)的藍(lán)牙設(shè)備

 // Pairing broadcasts
addHandler(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedHandler());

public  void onReceive (Context context, Intent intent, BluetoothDevice device){
    if (device == null) {
        Log.e(TAG, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE" );
        return;
    }
    int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR);
    // 更新本地緩存衰猛,并返回一個二次封裝的藍(lán)牙實(shí)體類
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
    if (cachedDevice == null) {
        Log.w(TAG, "Got bonding state changed for " + device + ", but we have no record of that device." );
        cachedDevice = mDeviceManager.addDevice(device);
    }
    // 分發(fā) 配對 狀態(tài)
 for (BluetoothCallback callback : mCallbacks) {
        callback.onDeviceBondStateChanged(cachedDevice, bondState);
    }
    cachedDevice.onBondingStateChanged(bondState);

    if (bondState == BluetoothDevice.BOND_NONE) {
        /* 檢查我們是否需要移除其他hearing aid設(shè)備 */
 if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) {
            mDeviceManager.onDeviceUnpaired(cachedDevice);
        }
        int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON,
                BluetoothDevice.ERROR);
        // 顯示錯誤信息
        showUnbondMessage(context, cachedDevice.getName(), reason);
    }
}

4.3. 掃描藍(lán)牙設(shè)備

開啟藍(lán)牙后,緊接著我們就需要開始對藍(lán)牙設(shè)備的掃描刹孔,檢索外部藍(lán)牙設(shè)備有如下幾個步驟:

1)注冊 BluetoothAdapter.ACTION_DISCOVERY_STARTED啡省、BluetoothAdapter.ACTION_DISCOVERY_FINISHED 監(jiān)聽藍(lán)牙掃描狀態(tài)

2)注冊 BluetoothDevice.ACTION_FOUND 監(jiān)聽掃描期間是否發(fā)現(xiàn)藍(lán)牙設(shè)備

該廣播的 intent 包含以下 extras

  • BluetoothDevice.EXTRA_DEVICE:藍(lán)牙設(shè)備
  • BluetoothDevice.EXTRA_CLASS:BluetoothClass,它表示藍(lán)牙類,它描述了設(shè)備的一般特性和功能冕杠。 例如微姊,藍(lán)牙類將指定通用設(shè)備類型,如電話分预、計算機(jī)或耳機(jī)兢交,以及它是否能夠提供音頻或電話等服務(wù)。每個藍(lán)牙類都由零個或多個服務(wù)類和一個設(shè)備類組成笼痹。 設(shè)備類進(jìn)一步分為主要和次要設(shè)備類組件配喳。

下面這些 extras 不一定總是可用的,而且也不常用凳干,要注意

  • BluetoothDevice.EXTRA_NAME:藍(lán)牙設(shè)備的名稱
  • BluetoothDevice.EXTRA_RSSI:藍(lán)牙設(shè)備的信號強(qiáng)度
  • BluetoothDevice.EXTRA_IS_COORDINATED_SET_MEMBER:它包含設(shè)備是否被發(fā)現(xiàn)為協(xié)調(diào)集成員的信息晴裹。 與屬于集合的設(shè)備配對將觸發(fā)與其余集合成員的配對。 有關(guān)詳細(xì)信息救赐,請參閱藍(lán)牙 CSIP 規(guī)范涧团。

3)調(diào)用BluetoothAdapter.startDiscovery()開啟藍(lán)牙掃描

4)從 intent 中獲取掃描到的藍(lán)牙設(shè)備

以上的步驟需要android.permission.BLUETOOTH權(quán)限,對于API 31以上的Android系統(tǒng)需要 android.permission.BLUETOOTH_SCAN權(quán)限经磅。


ok泌绣,繼續(xù)來看車載Settings的源碼中是如何處理掃描的。
在車載Settings中BluetoothPairingSelectionFragment顯示藍(lán)牙設(shè)備列表预厌。 當(dāng)此fragment可見時阿迈,會有一個進(jìn)度條以指示發(fā)現(xiàn)或配對進(jìn)度。

UI的源碼位置:/packages/apps/Car/Settings/src/com/android/car/settings/bluetooth/BluetoothPairingSelectionFragment.java

<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:title="@string/bluetooth_pair_new_device"
    android:key="@string/psk_bluetooth_pairing_selection">
    <!-- 本機(jī)藍(lán)牙的名稱 -->
    <Preference
        android:key="@string/pk_bluetooth_name"
        android:title="@string/bluetooth_name"
        settings:controller="com.android.car.settings.bluetooth.BluetoothNamePreferenceController"/>
    <!-- 未配對的藍(lán)牙設(shè)備 -->
    <PreferenceCategory
        android:key="@string/pk_bluetooth_available_devices"
        android:title="@string/bluetooth_available_devices"
        settings:controller="com.android.car.settings.bluetooth.BluetoothUnbondedDevicesPreferenceController"/>
    <!-- 本機(jī)藍(lán)牙設(shè)備的地址 -->
    <Preference
        android:icon="@drawable/ic_settings_about"
        android:key="@string/pk_bluetooth_address"
        android:selectable="false"
        settings:controller="com.android.car.settings.bluetooth.BluetoothAddressPreferenceController"/>
</PreferenceScreen>

頁面邏輯源碼位置:/packages/apps/Car/Settings/src/com/android/car/settings/bluetooth/BluetoothUnbondedDevicesPreferenceController.java

開始或停止藍(lán)牙搜索的源碼如下所示

private void enableScanning() {
    mIsScanningEnabled = true;
    if (!mBluetoothAdapter.isDiscovering()) {
        // 開啟掃描
        mBluetoothAdapter.startDiscovery();
    }
    // 開啟藍(lán)牙可見
    mAlwaysDiscoverable.start();
    getPreference().setEnabled(true);
}

private  void disableScanning() {
    mIsScanningEnabled = false;
    getPreference().setEnabled(false);
    // 關(guān)閉藍(lán)牙可見
    mAlwaysDiscoverable.stop();
    if (mBluetoothAdapter.isDiscovering()) {
        // 取消掃描
        mBluetoothAdapter.cancelDiscovery();
    }
}

在界面主動開啟藍(lán)牙搜索后轧叽,對于ACTION_DISCOVERY_STARTED苗沧、ACTION_DISCOVERY_FINISHEDACTION_FOUND*這三個廣播的監(jiān)聽都是在 framework 層私有代碼中完成的炭晒。就像之前說的待逞,藍(lán)牙的廣播時間基本都是在這個類中完成監(jiān)聽和事件分發(fā)的。

源碼位置:/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java

addHandler(BluetoothAdapter.ACTION_DISCOVERY_STARTED, new ScanningStateChangedHandler(true));
addHandler(BluetoothAdapter.ACTION_DISCOVERY_FINISHED, new ScanningStateChangedHandler(false));

private  class ScanningStateChangedHandler implements Handler {
    private  final  boolean mStarted;

    ScanningStateChangedHandler(boolean started) {
        mStarted = started;
    }

    public  void onReceive(Context context, Intent intent, BluetoothDevice device) {
        for (BluetoothCallback callback : mCallbacks) {
            callback.onScanningStateChanged(mStarted);
        }
        mDeviceManager.onScanningStateChanged(mStarted);
    }
}   

最后在 UI 界面收到的回調(diào)時腰埂,條件允許則開啟搜索飒焦。

@Override
public  void onScanningStateChanged(boolean started) {
    LOG.d( "onScanningStateChanged started: " + started + " mIsScanningEnabled: " + mIsScanningEnabled);
    if (!started && mIsScanningEnabled) {
        enableScanning();
    }
}

開啟搜索后,就需要處理搜索到的藍(lán)牙設(shè)備屿笼。

addHandler(BluetoothDevice.ACTION_FOUND, new DeviceFoundHandler());

private  class BluetoothBroadcastReceiver extends BroadcastReceiver {

    @Override
    public  void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        Handler handler = mHandlerMap.get(action);
        if (handler != null) {
            handler.onReceive(context, intent, device);
        }
    }
}

在獲取到BluetoothDevice后,還需要對其進(jìn)行過濾翁巍,只保留未配對驴一、未連接的實(shí)體,最后把BluetoothDevice封裝成CachedBluetoothDevice回調(diào)給顯示UI的類灶壶,將搜索到藍(lán)牙設(shè)備顯示在 UI 上肝断。

CachedBluetoothDevice是對BluetoothDevice的進(jìn)一步封裝,其內(nèi)部實(shí)現(xiàn)了藍(lán)牙的連接、配對胸懈、狀態(tài)獲取等功能担扑。它是 framework 層的一個私有類,源碼位置:/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java

// BluetoothEventManager.java
private  class DeviceFoundHandler implements Handler {

    public  void onReceive(Context context, Intent intent, BluetoothDevice device) {
        short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE);
        String name = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
        // TODO 獲取UUID趣钱。它們應(yīng)適用于2.1版本涌献。 
 // 現(xiàn)在跳過,有一個bluez問題首有,即使是2.1版本燕垃,也無法獲得uuid。
CachedBluetoothDevice cachedDevice = mDeviceManager.findDevice(device);
        if (cachedDevice == null) {
            cachedDevice = mDeviceManager.addDevice(device);
            Log.d(TAG, "DeviceFoundHandler created new CachedBluetoothDevice: " + cachedDevice);
        } else  if (cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
&& !cachedDevice.getDevice().isConnected()) {
            // 調(diào)度設(shè)備添加回調(diào)以在發(fā)現(xiàn)模式下顯示綁定但未連接的設(shè)備
dispatchDeviceAdded(cachedDevice);
            Log.d(TAG, "DeviceFoundHandler found bonded and not connected device:" + cachedDevice);
        } else {
            Log.d(TAG, "DeviceFoundHandler found existing CachedBluetoothDevice:" + cachedDevice);
        }
        cachedDevice.setRssi(rssi);
        cachedDevice.setJustDiscovered(true);
    }
}

void dispatchDeviceAdded(CachedBluetoothDevice cachedDevice){
    for (BluetoothCallback callback : mCallbacks) {
        callback.onDeviceAdded(cachedDevice);
    }
}
// BluetoothDevicesGroupPreferenceController.java
@Override
public  final  void onDeviceAdded(CachedBluetoothDevice cachedDevice) {
    // 刷新 UI
    refreshUi();
}

最后一步refreshUi可以看出井联,并沒有用到cachedDevice來更新 UI卜壕,是因?yàn)?code>LocalBluetoothManager中已經(jīng)緩存了所有的掃描到的藍(lán)牙設(shè)備,只需要將從LocalBluetoothManager中把 list 取出更新UI 界面即可烙常。

4.4 藍(lán)牙配對

藍(lán)牙的配對有如下幾步:

1)注冊android.bluetooth.device.action.PAIRING_REQUEST廣播

2)取消掃描過程

在執(zhí)行配對之前轴捎, 務(wù)必停止藍(lán)牙搜索,因?yàn)樗阉鬟^程會顯著減少可用于連接的帶寬蚕脏,導(dǎo)致連接操作失敗侦副。

3)執(zhí)行BluetoothDevice.createBond()進(jìn)行配對

執(zhí)行配對后,根據(jù)需要開啟藍(lán)牙設(shè)備的以下權(quán)限

BluetoothDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED)
BluetoothDevice.setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED)

4)處理PAIRING_REQUEST廣播消息蝗锥,顯示對應(yīng)的UI


繼續(xù)看源碼中是如何處理的跃洛,在藍(lán)牙設(shè)備列表中點(diǎn)擊未配對的藍(lán)牙設(shè)備

源碼位置:/packages/apps/Car/Settings/src/com/android/car/settings/bluetooth/BluetoothUnbondedDevicesPreferenceController.java

@Override
protected  void onDeviceClickedInternal(CachedBluetoothDevice cachedDevice) {
    if (cachedDevice.startPairing()) {
        LOG.d( "startPairing" );
        // 如果有服務(wù)端允許(通常是電話),則表明該客戶端(車輛)希望訪問聯(lián)系人(PBAP)和消息(MAP)终议。
cachedDevice.getDevice().setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
        cachedDevice.getDevice().setMessageAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
    } else {
        BluetoothUtils.showError(getContext(), cachedDevice.getName(),
                R.string.bluetooth_pairing_error_message);
        refreshUi();
    }
}

源碼位置:/frameworks/base/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java

public boolean startPairing() {
    // 掃描時配對是不可靠的汇竭,因此取消掃描
if (mLocalAdapter.isDiscovering()) {
        mLocalAdapter.cancelDiscovery();
    }
    if (!mDevice.createBond()) {
        return false;
    }
    return true;
}

藍(lán)牙的配對過程會有一個 dialog 的提示給到用戶,這個dialog 也需要通過監(jiān)聽廣播實(shí)現(xiàn)穴张。

<receiver android:name=".bluetooth.BluetoothPairingRequest">
    <intent-filter>
        <action android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
    </intent-filter>
</receiver>

BluetoothPairingRequest是任何藍(lán)牙配對請求的接收器细燎。它會檢查藍(lán)牙設(shè)置當(dāng)前是否可見,并顯示 PIN皂甘、密碼或確認(rèn)輸入對話框玻驻。 否則,它會啟動BluetoothPairingService偿枕,它會在狀態(tài)欄中啟動一個通知璧瞬,單擊該通知會顯示相同的對話框。

public final class BluetoothPairingRequest extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (!action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
            return;
        }
        // 將廣播意圖轉(zhuǎn)換為活動意圖
Intent pairingIntent = BluetoothPairingService.getPairingDialogIntent(context, intent);

        PowerManager powerManager =
                (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        BluetoothDevice device =
                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        String deviceAddress = device != null ? device.getAddress() : null;
        String deviceName = device != null ? device.getName() : null;
        // 判斷dialog 是否已經(jīng)顯示
boolean shouldShowDialog = BluetoothUtils.shouldShowDialogInForeground(
                context, deviceAddress, deviceName);
        // 判斷屏幕是否開啟
if (powerManager.isInteractive() && shouldShowDialog) {
            // 由于屏幕已打開且BT相關(guān)的活動在前臺渐夸,因此只需打開對話框
context.startActivityAsUser(pairingIntent, UserHandle.CURRENT);
        } else {
            // 發(fā)布一個通知嗤锉,用于觸發(fā) dialog
intent.setClass(context, BluetoothPairingService.class);
            context.startServiceAsUser(intent, UserHandle.CURRENT);
        }
    }
}
 

BluetoothPairingService核心代碼如下,在BluetoothPairingService中還需要監(jiān)聽ACTION_BOND_STATE_CHANGED廣播墓塌,如果配對完成了需要取消狀態(tài)欄的消息瘟忱。

 // 轉(zhuǎn)換 intent 的方法奥额。
public static Intent getPairingDialogIntent(Context context, Intent intent) {
    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    // 獲取配對類型
int type = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
            BluetoothDevice.ERROR);
    Intent pairingIntent = new Intent();
    pairingIntent.setClass(context, BluetoothPairingDialog.class);
    pairingIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    pairingIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, type);
    // 獲取配對的key
if (type == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION ||
            type == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY ||
            type == BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN) {
        int pairingKey = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY,
                BluetoothDevice.ERROR);
        pairingIntent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pairingKey);
    }
    pairingIntent.setAction(BluetoothDevice.ACTION_PAIRING_REQUEST);
    pairingIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    return pairingIntent;
}

藍(lán)牙進(jìn)行配對時會顯示 PIN,以及是否同意讀取電話本等信息访诱,這些內(nèi)容都包含在ACTION_PAIRING_REQUEST廣播的intent中垫挨,具體獲取方式在上述代碼已經(jīng)添加注釋。其中需要注意配對時的不同的type需要顯示不同的界面触菜。

提示用于需要輸入密鑰/PIN:

BluetoothDevice.PAIRING_VARIANT_PIN
BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS
BluetoothDevice.PAIRING_VARIANT_PASSKEY

提示用戶是否同意配對請求:

BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
BluetoothDevice.PAIRING_VARIANT_CONSENT:
BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:

通知用戶配對請求并向他們顯示設(shè)備的 PIN/ 密鑰:

BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN

接下就是由用戶確認(rèn)九榔,是否同意配對請求:

  • 用戶拒絕配對請求的處理流程:
private BluetoothDevice mDevice;

@Override
public void onDialogNegativeClick(BluetoothPairingDialogFragment dialog) {
    onCancel();
}

/**
* 一種正確結(jié)束與藍(lán)牙設(shè)備通信的方法。
* BluetoothPairingDialogFragment 關(guān)閉時將調(diào)用它玫氢。
*/
public void onCancel() {
    LOG.d("Pairing dialog canceled");
    mDevice.cancelPairing();
}
  • 用戶同意配對請求的處理流程:
@Override
public void onDialogPositiveClick(BluetoothPairingDialogFragment dialog) {
    if (getDialogType() == USER_ENTRY_DIALOG) {
        onPair(mUserInput);
    } else {
        onPair(null);
    }
}

/**
* 處理與藍(lán)牙設(shè)備的必要通信以建立成功配對
* 參數(shù):密碼 - - 我們將嘗試與設(shè)備配對的密碼帚屉。
*/
private void onPair(String passkey) {
    LOG.d("Pairing dialog accepted");
    switch (mType) {
        case BluetoothDevice.PAIRING_VARIANT_PIN:
        case BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS:
            mDevice.setPin(passkey);
            break;
        case BluetoothDevice.PAIRING_VARIANT_PASSKEY:
            int pass = Integer.parseInt(passkey);
            break;
        
        case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION:
        case BluetoothDevice.PAIRING_VARIANT_CONSENT:
            mDevice.setPairingConfirmation(true);
            break;

        case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY:
        case BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN:
        case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT:
            // Do nothing.
break;
        default:
            LOG.e("Incorrect pairing type received");
    }
}

以上就是一個藍(lán)牙配對的全部流程。如果是已配對的藍(lán)牙設(shè)備漾峡,則直接連接即可

public  void connect() {
    if (!ensurePaired()) {
        return;
    }
    mConnectAttempted = SystemClock.elapsedRealtime();
    connectAllEnabledProfiles();
}

private  void connectAllEnabledProfiles() {
    synchronized (mProfileLock) {
        // 如果沒有攻旦,請嘗試初始化配置文件。
 if (mProfiles.isEmpty()) {
            // 如果 mProfiles 為空生逸,則不要調(diào)用 updateProfiles牢屋。
 // 這會在配對期間導(dǎo)致與 carkits 的競爭條件,其中 RemoteDevice.UUIDs 已從藍(lán)牙堆棧更新槽袄,但 ACTION.uuid 尚未發(fā)送烙无。
 // 最終將收到 ACTION.uuid,這將觸發(fā)各種配置文件的連接如果 UUID 尚不可用遍尺,則連接將在 ACTION_UUID 意圖到達(dá)時發(fā)生截酷。
Log.d(TAG, "No profiles. Maybe we will connect later for device " + mDevice);
            return;
        }
        mLocalAdapter.connectAllEnabledProfiles(mDevice);
    }
}

private  boolean ensurePaired() {
    if (getBondState() == BluetoothDevice.BOND_NONE) {
        startPairing();
        return  false;
    } else {
        return  true;
    }
}
  1. 設(shè)置藍(lán)牙可見性

默認(rèn)情況下,其它藍(lán)牙設(shè)備是無法搜索到當(dāng)前的藍(lán)牙設(shè)備的乾戏,必須使用下面的代碼將藍(lán)牙設(shè)備設(shè)定為可見狀態(tài)迂苛,timeout 為藍(lán)牙可見時間,超過這個時間鼓择,藍(lán)牙就會恢復(fù)到默認(rèn)狀態(tài)三幻,最長可以設(shè)定為1個小時。

BluetoothAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, timeout);

在原生系統(tǒng)設(shè)置中由AlwaysDiscoverable管理藍(lán)牙可見性的類呐能。

源碼位置:/packages/apps/Car/Settings/src/com/android/car/settings/bluetooth/BluetoothDevicePreferenceController.java

該類注冊了BluetoothAdapter.ACTION_SCAN_MODE_CHANGED念搬,并在SCAN_MODE發(fā)生變化時,再次設(shè)定藍(lán)牙是可見的摆出,這樣就可以無限期地保持 BluetoothAdapter 處于可發(fā)現(xiàn)模式朗徊。默認(rèn)情況下,將掃描模式設(shè)置為 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE 將超時偎漫,但對于配對荣倾,我們希望在頁面正在掃描時始終保持設(shè)備可發(fā)現(xiàn)。

private static final class AlwaysDiscoverable extends BroadcastReceiver {

    private final Context mContext;
    private final BluetoothAdapter mAdapter;
    private final IntentFilter mIntentFilter = new IntentFilter(
            BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);

    private boolean mStarted;

    AlwaysDiscoverable(Context context, BluetoothAdapter adapter) {
        mContext = context;
        mAdapter = adapter;
    }

    /**
     * 將適配器掃描模式設(shè)置為 BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE骑丸。 
     * 當(dāng)不再需要發(fā)現(xiàn)模式時舌仍,start() 調(diào)用應(yīng)該有對 stop() 的匹配調(diào)用。
     */
    void start() {
        if (mStarted) {
            return;
        }
        mContext.registerReceiver(this, mIntentFilter);
        mStarted = true;
        setDiscoverable();
    }

    void stop() {
        if (!mStarted) {
            return;
        }
        mContext.unregisterReceiver(this);
        mStarted = false;
        mAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        setDiscoverable();
    }

    private void setDiscoverable() {
        if (mAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
            mAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
        }
    }
}

5. 總結(jié)

以上就是原生系統(tǒng)設(shè)置中藍(lán)牙設(shè)置關(guān)鍵部分的解析通危,讀完本篇博客其實(shí)铸豁,并不能讓你立即精通藍(lán)牙設(shè)置的開發(fā),因?yàn)樵O(shè)置功能中還有許多的細(xì)節(jié)沒有面面俱到菊碟,例如:監(jiān)聽活躍設(shè)備等节芥,所以開發(fā)系統(tǒng)應(yīng)用時我們閱讀原生的代碼才是最好的辦法。

本篇博客的目的就像前言說的那樣逆害,是為了讓開發(fā)者對車載系統(tǒng)應(yīng)用本身有一個大致的了解头镊。我個人從移動互聯(lián)網(wǎng)轉(zhuǎn)行做車載的第一個應(yīng)用就是寫系統(tǒng)設(shè)置,由于當(dāng)時對系統(tǒng)設(shè)置完全不了解魄幕,一直在使用Android應(yīng)用層API進(jìn)行開發(fā)相艇,也沒有想到去移植framework的代碼,結(jié)果就是成噸的BUG纯陨,相信讀完本篇或許可以少走一些彎路了坛芽。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市翼抠,隨后出現(xiàn)的幾起案子咙轩,更是在濱河造成了極大的恐慌,老刑警劉巖阴颖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件活喊,死亡現(xiàn)場離奇詭異,居然都是意外死亡量愧,警方通過查閱死者的電腦和手機(jī)钾菊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侠畔,“玉大人结缚,你說我怎么就攤上這事∪砉祝” “怎么了红竭?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長喘落。 經(jīng)常有香客問我茵宪,道長,這世上最難降的妖魔是什么瘦棋? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任稀火,我火速辦了婚禮,結(jié)果婚禮上赌朋,老公的妹妹穿的比我還像新娘凰狞。我一直安慰自己篇裁,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布赡若。 她就那樣靜靜地躺著达布,像睡著了一般。 火紅的嫁衣襯著肌膚如雪逾冬。 梳的紋絲不亂的頭發(fā)上黍聂,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音身腻,去河邊找鬼产还。 笑死,一個胖子當(dāng)著我的面吹牛嘀趟,可吹牛的內(nèi)容都是我干的脐区。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼去件,長吁一口氣:“原來是場噩夢啊……” “哼坡椒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起尤溜,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤倔叼,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后宫莱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丈攒,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年授霸,在試婚紗的時候發(fā)現(xiàn)自己被綠了巡验。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡碘耳,死狀恐怖显设,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情辛辨,我是刑警寧澤捕捂,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站斗搞,受9級特大地震影響指攒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜僻焚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一允悦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧虑啤,春花似錦隙弛、人聲如沸架馋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽绩蜻。三九已至,卻和暖如春室埋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背伊约。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工姚淆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屡律。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓腌逢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親超埋。 傳聞我的和親對象是個殘疾皇子搏讶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容