車載BlueTooth通話機(jī)制原理及開發(fā)
1, 藍(lán)牙框架
主要代碼路徑:
路徑1: frameworks\base\core\java\android\bluetooth\??
藍(lán)牙相關(guān)接口,藍(lán)牙各種功能的發(fā)起點(diǎn)翩蘸。
路徑2:packages\apps\Bluetooth\src\com\android\bluetooth\??
獨(dú)立的Bluetooth.apk,里面包含藍(lán)牙相關(guān)的各種服務(wù),是java層和C/C++層的橋梁人乓。
路徑3: packages\apps\Bluetooth\jni\
? 調(diào)用底層C/C++實(shí)現(xiàn)各種藍(lán)牙功能,并且反饋給java層盅惜。
在路徑2里面還有各種相互獨(dú)立的java代碼包,每一個(gè)包都包含一個(gè)協(xié)議,實(shí)現(xiàn)一個(gè)具體的功能:
btservice: 統(tǒng)一管理,控制其他服務(wù)坪仇。
a2dp: 和藍(lán)牙耳機(jī),音頻有關(guān),比如聽歌等拉背。
avrcp: 音頻/視頻通過連接的藍(lán)牙控制,比如放歌時(shí)控制暫停等芍碧。
gatt:低功耗BLE有關(guān),比如藍(lán)牙按鍵飞醉。
hdp: 藍(lán)牙醫(yī)療有關(guān)
hfp和hfpclient?: 藍(lán)牙通話有關(guān),比如藍(lán)牙通話的相關(guān)操作
hid: 藍(lán)牙鍵盤鍵盤/鼠標(biāo)
map: 同步藍(lán)牙短信相關(guān)
opp: 藍(lán)牙傳輸,比如傳輸文件等
pan: 個(gè)人局域網(wǎng)
pbap: 同步電話本,比如聯(lián)系人/通話記錄等
sap : 藍(lán)牙通話,主要和SIM卡相關(guān)
sdp: 藍(lán)牙服務(wù)發(fā)現(xiàn)/獲取相關(guān)
這12個(gè)包分別實(shí)現(xiàn)了12中藍(lán)牙功能,大多數(shù)以服務(wù)的形式存在,運(yùn)行在Bluetooth.apk中芍躏。不僅如此,還具有以下特點(diǎn):
1,每一個(gè)服務(wù)相互獨(dú)立,互相毫無任何影響, 繼承自 ProfileService,由
AdapterService服務(wù)統(tǒng)一管理。
2,每一個(gè)服務(wù)在路徑1中都存在對(duì)應(yīng)的客戶端類,通過Binder進(jìn)行跨進(jìn)程通信宪巨。
3,每一個(gè)服務(wù)在路徑3中都存在對(duì)應(yīng)的C/C++類,通過JNI機(jī)制互相調(diào)用磷杏。
4,每一個(gè)服務(wù)的啟動(dòng),對(duì)應(yīng)的Binder以及JNI機(jī)制的調(diào)用原理,方法,流程幾乎都是一樣的。
下面以藍(lán)牙通話功能為例來解析相關(guān)接口以及代碼實(shí)現(xiàn)框架圖捏卓。
藍(lán)牙通話上層代碼主要分為3個(gè)部分:
1,藍(lán)牙api相關(guān)代碼, 路徑4: frameworks\base\core\java\android\bluetooth\
主要有2個(gè)類
BluetoothHeadsetClient.java主要負(fù)責(zé)藍(lán)牙通話的相關(guān)動(dòng)作,比如接聽等等
BluetoothHeadsetClientCall.java主要負(fù)責(zé)藍(lán)牙通話的狀態(tài),比如是來電還是去電等等极祸。
2,藍(lán)牙服務(wù)端的相關(guān)代碼,路徑5:
packages\apps\Bluetooth\src\com\android\bluetooth\hfpclient\
有3個(gè)類
HeadsetClientHalConstants.java類里面只是定義了一些int/boolean 類型的值。
HeadsetClientService.java從名字就知道它是一個(gè)服務(wù),它的設(shè)計(jì)很有意思,里面還有一個(gè)BluetoothHeadsetClientBinder內(nèi)部類,該內(nèi)部類主要負(fù)責(zé)和
BluetoothHeadsetClient進(jìn)行跨進(jìn)程通信。另外,HeadsetClientService也是
BluetoothHeadsetClientBinder和HeadsetClientStateMachine之間的橋梁遥金。
HeadsetClientStateMachine是一個(gè)狀態(tài)機(jī),即管理連接的狀態(tài)也是通話時(shí)java和C/C++之間的橋梁,通過JNI機(jī)制和com_android_bluetooth_hfpclient 里面的方法互相調(diào)用浴捆。
3,JNI相關(guān)代碼,路徑6: packages\apps\Bluetooth\jni\
有1個(gè)文件:?
com_android_bluetooth_hfpclient? 藍(lán)牙通話動(dòng)作,撥號(hào)/接聽/掛斷/拒接 實(shí)際的執(zhí)行者。
DialerBTHfpService服務(wù)是開機(jī)之后啟動(dòng)的稿械。
圖一?類圖
BluetoothHeadsetClient是一個(gè)api,由第三方apk直接調(diào)用,可以進(jìn)行撥號(hào)/接聽/拒接/掛斷操作,對(duì)應(yīng)的方法依次為dial()/acceptCall()/rejectCall()/terminateCall().
HeadsetClientService是一個(gè)服務(wù), BluetoothHeadsetClientBinder是它的內(nèi)部類, BluetoothHeadsetClientBinder是BluetoothHeadsetClient在HeadsetClientService中的代理,通過aidl進(jìn)行方法的調(diào)用选泻。
Connected(已連接狀態(tài))是HeadsetClientStateMachine 的其中一種狀態(tài),通話的相關(guān)操作都建立在已連接狀態(tài)之上,其它三種狀態(tài)為Disconnected, Connecting,
AudioOn狀態(tài)。HeadsetClientService 根據(jù)不同的方法給狀態(tài)機(jī)發(fā)送不同的消息,最后通過HeadsetClientStateMachine根據(jù)JNI機(jī)制調(diào)用
com_android_bluetooth_hfpclient.cpp對(duì)應(yīng)的方法,最后調(diào)用底層C/C++ 來真正的實(shí)現(xiàn)撥號(hào)/接聽/拒接/掛斷操作溜哮。
撥號(hào)/接聽/拒接/掛斷方法調(diào)用的流程完全是一模一樣的,下小節(jié)給出接聽方法具體調(diào)用的完整流程圖滔金。
圖二?接聽電話流程圖
除了dial 方法最后調(diào)用從C/C++ dialNative之外,其它的3個(gè)方法最后都是調(diào)用handleCallActionNative,只是參數(shù)不同而已。
撥號(hào)/接聽/拒接/掛斷都是主動(dòng)完成的,那么如果有來電,對(duì)方接通電話或者對(duì)方掛斷電話,我們?cè)趺粗滥孛ぃ窟@些都是com_android_bluetooth_hfpclient.cpp通過JNI機(jī)制調(diào)用通話狀態(tài)機(jī)的方法sendCallChangedIntent,將電話的狀態(tài)(包含在
BluetoothHeadsetClientCall.java中)通過廣播發(fā)送出來,第三方apk監(jiān)聽狀態(tài)就可以進(jìn)行相應(yīng)的操作了餐茵。具體的來電流程圖如下:
?圖三 來電流程圖
在自己的apk中,只需要做2件事情就可以完成藍(lán)牙通話的幾乎所有動(dòng)作,
1,根據(jù)上一小節(jié)的論述,注冊(cè)BluetoothHeadsetClientCall相關(guān)廣播,監(jiān)聽來電/接通/對(duì)方掛斷的狀態(tài),獲取藍(lán)牙電話的相關(guān)信息,比如號(hào)碼,設(shè)備信息等等。
比如,如果收到來電廣播,就可以根據(jù)BluetoothHeadsetClientCall獲取來電的號(hào)碼等信息,然后顯示來電界面述吸。
2,通過BluetoothAdapter 獲取并且初始化BluetoothHeadsetClient對(duì)象,然后就可以直接調(diào)用dial()/acceptCall()/rejectCall()/terminateCall() 方法進(jìn)行撥號(hào)/接聽/拒接/掛斷的操作了忿族。
利用api實(shí)現(xiàn)藍(lán)牙通話不是很難,但是如果弄清楚具體的藍(lán)牙通話機(jī)制以及細(xì)節(jié)甚至藍(lán)牙相關(guān)功能,這就得下功夫了,關(guān)于藍(lán)牙,還可以進(jìn)一步研究的android知識(shí)點(diǎn)以及藍(lán)牙涉及的上層java要點(diǎn)如下:
1,基本藍(lán)牙功能:打開/關(guān)閉/掃描/配對(duì)/連接
2,藍(lán)牙功能:通過藍(lán)牙傳輸文件/藍(lán)牙鍵盤/藍(lán)牙醫(yī)療服務(wù)/藍(lán)牙同步聯(lián)系人等等
3,android知識(shí):跨進(jìn)程通信機(jī)制/JNI機(jī)制/反射機(jī)制/狀態(tài)機(jī)機(jī)制等等。
實(shí)際開發(fā)過程
公司用的是android8.1的源碼蝌矛,系統(tǒng)api有改動(dòng)道批,改動(dòng)的地方會(huì)稍微標(biāo)明一下。我是在系統(tǒng)源碼上開發(fā)的入撒,所以有些類或者api@hide了 在開發(fā)工具上會(huì)報(bào)錯(cuò)隆豹,但是可以編譯通過。如果是純應(yīng)用上層需要利用反射茅逮,有一部分功能需要移植代碼璃赡。
車載藍(lán)牙主要是實(shí)現(xiàn)藍(lán)牙電話,藍(lán)牙音樂献雅,同步通訊錄碉考。這些功能都是用到藍(lán)牙的配置文件協(xié)議。下面簡單介紹一下這幾個(gè)協(xié)議挺身。
1.HFP(Hands-free Profile)侯谁,讓藍(lán)牙設(shè)備可以控制電話,如接聽章钾、掛斷墙贱、拒接、語音撥號(hào)等贱傀,拒接惨撇、語音撥號(hào)要視藍(lán)牙耳機(jī)及電話是否支持。
2.A2DP(Advanced Audio Distribution Profile)是藍(lán)牙的音頻傳輸協(xié)議窍箍,典型應(yīng)用為藍(lán)牙耳機(jī)。A2DP協(xié)議的音頻數(shù)據(jù)在ACL Link上傳輸。
3.AVRCP(Audio/Video Remote Control Profile)作用是支持CT控制TG椰棘,具體來說如果手機(jī)和一個(gè)藍(lán)牙音箱設(shè)備連接上了纺棺,那么音箱可以控制手機(jī)播放/暫停/切歌以及獲得手機(jī)上播放歌曲的信息,如專輯邪狞,歌名祷蝌,歌手,時(shí)長等信息帆卓。
4.PBAP(Phone Book Access Profile)訪問下載通訊錄以及通話記錄巨朦。
以上是簡單介紹這些協(xié)議的用途,想具體了解可以分別取查資料剑令,這方面資料很多糊啡。
一.打開藍(lán)牙,查找藍(lán)牙設(shè)備吁津,連接藍(lán)牙協(xié)議棚蓄。
要使用藍(lán)牙必須先在文件清單加權(quán)限
<uses-permission android:name="android.permission.BLUETOOTH"/>
? ? <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
檢測(cè)設(shè)備藍(lán)牙是否打開,沒有可以自動(dòng)打開碍脏,也可以請(qǐng)求梭依,給用戶提示,讓用戶自己打開典尾。
查找藍(lán)牙之前可以先查看是否有以前綁定過的藍(lán)牙設(shè)備
Set<BluetoothDevice>devices=mBluetoothAdapter.getBondedDevices();
if(devices !=null&&devices.size()>0) {
for(Iterator<BluetoothDevice>it=devices.iterator(); it.hasNext();) {
BluetoothDevice device=it.next();
if(device !=null) {
? ? ? ? addBluetoothDevice(device);
? ? }
}
? ? }
mBluetoothManager=(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter=mBluetoothManager.getAdapter();
if(!mBluetoothAdapter.isEnabled()) {
? mBluetoothAdapter.enable();
}
查找藍(lán)牙可以先查找低功耗藍(lán)牙役拴,一般查找時(shí)間為8-10秒,然后再查找經(jīng)典藍(lán)牙钾埂。兩種查找用的api不同河闰,先說一下查找低功耗藍(lán)牙設(shè)備。
拿到BluetoothLeScanner:mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
調(diào)用startScan勃教,參數(shù)為ScanCallback對(duì)象淤击,它里面有各種回調(diào),根據(jù)自己的需求實(shí)現(xiàn)故源。
privatefinalScanCallbackscanCallback=newScanCallback() {
@Override
publicvoidonScanResult(intcallbackType, ScanResult result){
super.onScanResult(callbackType, result);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
BluetoothDevicedevice=result.getDevice();
Log.i(TAG,"scan succeed device ==? "+device);
? ? ? ? ? ? ? ? addBluetoothDevice(device);
? ? ? ? ? ? }
? ? ? ? }
@Override
publicvoidonBatchScanResults(List<ScanResult> results){
super.onBatchScanResults(results);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
for(ScanResult result: results){
Log.i(TAG,"scan succeed device ==? "+result.getDevice());
? ? ? ? ? ? ? ? ? ? addBluetoothDevice(result.getDevice());
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
@Override
publicvoidonScanFailed(interrorCode){
super.onScanFailed(errorCode);
Log.i(TAG,"scan fail");
? ? ? ? }
? ? };
查看經(jīng)典藍(lán)牙直接使用BluetoothAdapter的startDiscovery方法污抬,注冊(cè)BluetoothDevice.ACTION_FOUND廣播接收搜索到的藍(lán)牙設(shè)備。使用intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)可以得到BluetoothDevice绳军。
接著連接設(shè)備印机,連接設(shè)備之前需要綁定設(shè)備,就是確認(rèn)雙方設(shè)備的存在通過相同秘鑰门驾,如果需要訪問通訊錄射赛,需要勾選權(quán)限。
可以先判斷BluetoothDevice的狀態(tài)奶是,如果狀態(tài)為BluetoothDevice.BOND_NONE楣责,調(diào)用bluetoothDevice.createBond()進(jìn)行綁定竣灌。
講連接之前說一下自己踩過的坑,也不能說是坑秆麸,自己能力不足所以花了不少時(shí)間初嘹。開始我以為連接藍(lán)牙需要socket通訊,查google文檔說要? ?兩臺(tái)設(shè)備一臺(tái)充當(dāng)客戶端一臺(tái)充當(dāng)服務(wù)端沮趣,然后用io流傳輸數(shù)據(jù)屯烦。我一直在糾結(jié),我只能操作車載應(yīng)用房铭,用戶手機(jī)安裝不了我開發(fā)的應(yīng)用驻龟,那這怎么連接呢。后來看到系統(tǒng)setting有連接藍(lán)牙的效果就果斷去setting的Bluetooth模塊查看連接藍(lán)牙的代碼缸匪,終于找到了翁狐。在frameworks\base\packages\SettingsLib\src\com\android\settingslib\bluetooth中專門有個(gè)類存儲(chǔ)管理藍(lán)牙設(shè)備CachedBluetoothDevice.java,他連接遠(yuǎn)程藍(lán)牙設(shè)備就是
synchronizedvoid connectInt(LocalBluetoothProfile profile) {
if(!ensurePaired()) {
return;
? ? ? ? }
if(profile.connect(mDevice)) {
if(Utils.D) {
Log.d(TAG,"Command sent successfully:CONNECT "+describe(profile));
? ? ? ? ? ? }
return;
? ? ? ? }
Log.i(TAG,"Failed to connect "+profile.toString()+" to "+mName);
? ? }
LocalBluetoothProfile是一個(gè)接口豪嗽,各種協(xié)議的封裝者實(shí)現(xiàn)了該接口谴蔑,例如HfpClientProfile它里面封裝的是BluetoothHeadsetClient,就是HFP協(xié)議龟梦。主要看它的connect方法
@Override
publicbooleanconnect(BluetoothDevice device) {
if(mService==null)returnfalse;
List<BluetoothDevice>srcs=getConnectedDevices();
if(srcs !=null) {
for(BluetoothDevice src : srcs) {
if(src.equals(device)) {
//Connecttosamedevice, Ignore it
Log.d(TAG,"Ignoring Connect");
returntrue;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
returnmService.connect(device);
? ? }
先獲取已連接的藍(lán)牙設(shè)備隐锭,如果藍(lán)牙設(shè)備已連接返回true,不在列表中則調(diào)用BluetoothHeadsetClient的connect方法。接著看下這個(gè)BluetoothHeadsetClient怎么實(shí)例化计贰。
mLocalAdapter.getProfileProxy(context,newHfpClientServiceListener(),
? ? ? ? ? ? ? ? BluetoothProfile.HEADSET_CLIENT);
通過BluetoothAdapter的getProfileProxy方法去請(qǐng)求這個(gè)協(xié)議對(duì)象钦睡。
privatefinalclassHfpClientServiceListener
implementsBluetoothProfile.ServiceListener {
@Override
publicvoidonServiceConnected(intprofile, BluetoothProfile proxy){
if(V) Log.d(TAG,"Bluetooth service connected");
? ? ? ? ? ? mService = (BluetoothHeadsetClient) proxy;
// We just bound to the service, so refresh the UI for any connected HFP devices.
? ? ? ? ? ? List<BluetoothDevice> deviceList = mService.getConnectedDevices();
while(!deviceList.isEmpty()) {
BluetoothDevicenextDevice=deviceList.remove(0);
CachedBluetoothDevicedevice=mDeviceManager.findDevice(nextDevice);
// we may add a new device here, but generally this should not happen
if(device ==null) {
Log.w(TAG,"HfpClient profile found new device: "+ nextDevice);
? ? ? ? ? ? ? ? ? ? device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? device.onProfileStateChanged(
HfpClientProfile.this, BluetoothProfile.STATE_CONNECTED);
? ? ? ? ? ? ? ? device.refresh();
? ? ? ? ? ? }
mIsProfileReady=true;
? ? ? ? }
@Override
publicvoidonServiceDisconnected(intprofile){
if(V) Log.d(TAG,"Bluetooth service disconnected");
mIsProfileReady=false;
? ? ? ? }
? ? }
need-to-insert-img
監(jiān)聽返回狀態(tài),賦值躁倒。其實(shí)不僅僅是BluetoothHeadsetClient荞怒,BluetoothA2dpSink等等請(qǐng)求方式一模一樣,參數(shù)不同秧秉『肿溃看到這個(gè)之后恍然大悟,車載藍(lán)牙實(shí)現(xiàn)的功能不需要進(jìn)行socket通信象迎,只需要連接這些協(xié)議荧嵌。
好了,整理一下連接過程砾淌,先拿到BluetoothAdapter啦撮,調(diào)用getProfileProxy方法請(qǐng)求協(xié)議,第三個(gè)參數(shù)為協(xié)議的種類汪厨,都定義在BluetoothProfile中赃春,例如BluetoothProfile.AVRCP_CONTROLLER(藍(lán)牙控制協(xié)議),BluetoothProfile.A2DP_SINK(音頻協(xié)議)劫乱。實(shí)現(xiàn)BluetoothProfile.ServiceListener接口织中,監(jiān)聽返回狀態(tài)锥涕。如果返回成功得到對(duì)象,就可以調(diào)用connect(BluetoothDevice device)連接狭吼。
mAdapter=BluetoothAdapter.getDefaultAdapter();
mAdapter.getProfileProxy(mContext, new ProxyServiceListener(), BluetoothProfile.AVRCP_CONTROLLER);
mAdapter.getProfileProxy(mContext, new ProxyServiceListener(), BluetoothProfile.A2DP_SINK);
privatefinalclassProxyServiceListener implements BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
Log.d(TAG,"Bluetooth service connected profile == "+profile);
if(profile==BluetoothProfile.AVRCP_CONTROLLER) {
isAvrcpProfileReady=true;
mAvrcpCt=(BluetoothAvrcpController)proxy;
Log.d(TAG,"AvrcpController Profile Proxy Connected");
}
if(profile==BluetoothProfile.A2DP_SINK) {
isA2dpProfileReady=true;
mA2dpSink=(BluetoothA2dpSink)proxy;
Log.d(TAG,"BluetoothA2dpSink Profile Proxy Connected");
}
}
@Override
public void onServiceDisconnected(int profile) {
if(profile==BluetoothProfile.AVRCP_CONTROLLER) {
isAvrcpProfileReady=false;
mAvrcpCt=null;
Log.d(TAG,"AvrcpController Profile Proxy Disconnected");
}
if(profile==BluetoothProfile.A2DP_SINK) {
isA2dpProfileReady=false;
mA2dpSink=null;
Log.d(TAG,"BluetoothA2dpSink Profile Proxy Disconnected");
}
}
}
publicbooleanconnect(BluetoothDevice device) {
if(null!=mA2dpSink) {
returnmA2dpSink.connect(device);
}
Log.i(TAG,"mA2dpSink == null");
returnfalse;
}
來源于?https://blog.csdn.net/dl6655/article/details/82798431