車載BlueTooth通話機制原理及開發
[摘要]: 本文主要論述基於android 6.0的藍芽上層(Java層)通話機制;總結了藍芽通話框架,並且給出了接聽電話的詳細的流程圖;最後說明了apk的實現以及總結了藍芽/android 相關的知識點。
1, 藍芽框架
主要程式碼路徑:
路徑1: frameworks\base\core\java\android\bluetooth\
藍芽相關介面,藍芽各種功能的發起點。
路徑2:packages\apps\Bluetooth\src\com\android\bluetooth\
獨立的Bluetooth.apk,裡面包含藍芽相關的各種服務,是java層和C/C++層的橋樑。
路徑3: packages\apps\Bluetooth\jni\
呼叫底層C/C++實現各種藍芽功能,並且反饋給java層。
在路徑2裡面還有各種相互獨立的java程式碼包,每一個包都包含一個協議,實現一個具體的功能:
btservice: 統一管理,控制其他服務。
a2dp: 和藍芽耳機,音訊有關,比如聽歌等。
avrcp: 音訊/視訊通過連線的藍芽控制,比如放歌時控制暫停等。
gatt:低功耗BLE有關,比如藍芽按鍵。
hdp: 藍芽醫療有關
hfp和hfpclient : 藍芽通話有關,比如藍芽通話的相關操作
hid: 藍芽鍵盤鍵盤/滑鼠
map: 同步藍芽簡訊相關
opp: 藍芽傳輸,比如傳輸檔案等
pan: 個人區域網
pbap: 同步電話本,比如聯絡人/通話記錄等
sap : 藍芽通話,主要和SIM卡相關
sdp: 藍芽服務發現/獲取相關
這12個包分別實現了12中藍芽功能,大多數以服務的形式存在,執行在Bluetooth.apk中。不僅如此,還具有以下特點:
1,每一個服務相互獨立,互相毫無任何影響, 繼承自 ProfileService,由
AdapterService服務統一管理。
2,每一個服務在路徑1中都存在對應的客戶端類,通過Binder進行跨程序通訊。
3,每一個服務在路徑3中都存在對應的C/C++類,通過JNI機制互相呼叫。
4,每一個服務的啟動,對應的Binder以及JNI機制的呼叫原理,方法,流程幾乎都是一樣的。
下面以藍芽通話功能為例來解析相關介面以及程式碼實現框架圖。
2 藍芽通話框架
2.1 相關類的說明
藍芽通話上層程式碼主要分為3個部分:
1,藍芽api相關程式碼, 路徑4: frameworks\base\core\java\android\bluetooth\
主要有2個類
BluetoothHeadsetClient.java主要負責藍芽通話的相關動作,比如接聽等等
BluetoothHeadsetClientCall.java主要負責藍芽通話的狀態,比如是來電還是去電等等。
2,藍芽服務端的相關程式碼,路徑5:
packages\apps\Bluetooth\src\com\android\bluetooth\hfpclient\
有3個類
HeadsetClientHalConstants.java類裡面只是定義了一些int/boolean 型別的值。
HeadsetClientService.java從名字就知道它是一個服務,它的設計很有意思,裡面還有一個BluetoothHeadsetClientBinder內部類,該內部類主要負責和
BluetoothHeadsetClient進行跨程序通訊。另外,HeadsetClientService也是
BluetoothHeadsetClientBinder和HeadsetClientStateMachine之間的橋樑。
HeadsetClientStateMachine是一個狀態機,即管理連線的狀態也是通話時java和C/C++之間的橋樑,通過JNI機制和com_android_bluetooth_hfpclient 裡面的方法互相呼叫。
3,JNI相關程式碼,路徑6: packages\apps\Bluetooth\jni\
有1個檔案:
com_android_bluetooth_hfpclient 藍芽通話動作,撥號/接聽/結束通話/拒接 實際的執行者。
DialerBTHfpService服務是開機之後啟動的。
2.2類圖
圖一 類圖
BluetoothHeadsetClient是一個api,由第三方apk直接呼叫,可以進行撥號/接聽/拒接/結束通話操作,對應的方法依次為dial()/acceptCall()/rejectCall()/terminateCall().
HeadsetClientService是一個服務, BluetoothHeadsetClientBinder是它的內部類, BluetoothHeadsetClientBinder是BluetoothHeadsetClient在HeadsetClientService中的代理,通過aidl進行方法的呼叫。
Connected(已連線狀態)是HeadsetClientStateMachine 的其中一種狀態,通話的相關操作都建立在已連線狀態之上,其它三種狀態為Disconnected, Connecting,
AudioOn狀態。HeadsetClientService 根據不同的方法給狀態機發送不同的訊息,最後通過HeadsetClientStateMachine根據JNI機制呼叫
com_android_bluetooth_hfpclient.cpp對應的方法,最後呼叫底層C/C++ 來真正的實現撥號/接聽/拒接/結束通話操作。
撥號/接聽/拒接/結束通話方法呼叫的流程完全是一模一樣的,下小節給出接聽方法具體呼叫的完整流程圖。
2.3流程圖
圖二 接聽電話流程圖
除了dial 方法最後呼叫從C/C++ dialNative之外,其它的3個方法最後都是呼叫handleCallActionNative,只是引數不同而已。
撥號/接聽/拒接/結束通話都是主動完成的,那麼如果有來電,對方接通電話或者對方結束通話電話,我們怎麼知道呢?這些都是com_android_bluetooth_hfpclient.cpp通過JNI機制呼叫通話狀態機的方法sendCallChangedIntent,將電話的狀態(包含在
BluetoothHeadsetClientCall.java中)通過廣播發送出來,第三方apk監聽狀態就可以進行相應的操作了。具體的來電流程圖如下:
圖三 來電流程圖
3 藍芽通話apk說明
在自己的apk中,只需要做2件事情就可以完成藍芽通話的幾乎所有動作,
1,根據上一小節的論述,註冊BluetoothHeadsetClientCall相關廣播,監聽來電/接通/對方結束通話的狀態,獲取藍芽電話的相關資訊,比如號碼,裝置資訊等等。
比如,如果收到來電廣播,就可以根據BluetoothHeadsetClientCall獲取來電的號碼等資訊,然後顯示來電介面。
2,通過BluetoothAdapter 獲取並且初始化BluetoothHeadsetClient物件,然後就可以直接呼叫dial()/acceptCall()/rejectCall()/terminateCall() 方法進行撥號/接聽/拒接/結束通話的操作了。
4, 小節
利用api實現藍芽通話不是很難,但是如果弄清楚具體的藍芽通話機制以及細節甚至藍芽相關功能,這就得下功夫了,關於藍芽,還可以進一步研究的android知識點以及藍芽涉及的上層java要點如下:
1,基本藍芽功能:開啟/關閉/掃描/配對/連線
2,藍芽功能:通過藍芽傳輸檔案/藍芽鍵盤/藍芽醫療服務/藍芽同步聯絡人等等
3,android知識:跨程序通訊機制/JNI機制/反射機制/狀態機機制等等。
實際開發過程
公司用的是android8.1的原始碼,系統api有改動,改動的地方會稍微標明一下。我是在系統原始碼上開發的,所以有些類或者[email protected]了 在開發工具上會報錯,但是可以編譯通過。如果是純應用上層需要利用反射,有一部分功能需要移植程式碼。
車載藍芽主要是實現藍芽電話,藍芽音樂,同步通訊錄。這些功能都是用到藍芽的配置檔案協議。下面簡單介紹一下這幾個協議。
1.HFP(Hands-free Profile),讓藍芽裝置可以控制電話,如接聽、結束通話、拒接、語音撥號等,拒接、語音撥號要視藍芽耳機及電話是否支援。
2.A2DP(Advanced Audio Distribution Profile)是藍芽的音訊傳輸協議,典型應用為藍芽耳機。A2DP協議的音訊資料在ACL Link上傳輸。
3.AVRCP(Audio/Video Remote Control Profile)作用是支援CT控制TG,具體來說如果手機和一個藍芽音箱裝置連線上了,那麼音箱可以控制手機播放/暫停/切歌以及獲得手機上播放歌曲的資訊,如專輯,歌名,歌手,時長等資訊。
4.PBAP(Phone Book Access Profile)訪問下載通訊錄以及通話記錄。
以上是簡單介紹這些協議的用途,想具體瞭解可以分別取查資料,這方面資料很多。
一.開啟藍芽,查詢藍芽裝置,連線藍芽協議。
要使用藍芽必須先在檔案清單加許可權
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
檢測裝置藍芽是否開啟,沒有可以自動開啟,也可以請求,給使用者提示,讓使用者自己開啟。
查詢藍芽之前可以先檢視是否有以前繫結過的藍芽裝置
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();
}
查詢藍芽可以先查詢低功耗藍芽,一般查詢時間為8-10秒,然後再查詢經典藍芽。兩種查詢用的api不同,先說一下查詢低功耗藍芽裝置。
拿到BluetoothLeScanner:mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
呼叫startScan,引數為ScanCallback物件,它裡面有各種回撥,根據自己的需求實現。
private final ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
super.onScanResult(callbackType, result);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
BluetoothDevice device = result.getDevice();
Log.i(TAG, "scan succeed device == " +device);
addBluetoothDevice(device);
}
}
@Override
public void onBatchScanResults(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
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.i(TAG,"scan fail");
}
};
檢視經典藍芽直接使用BluetoothAdapter的startDiscovery方法,註冊BluetoothDevice.ACTION_FOUND廣播接收搜尋到的藍芽裝置。使用intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)可以得到BluetoothDevice。
接著連線裝置,連線裝置之前需要繫結裝置,就是確認雙方裝置的存在通過相同祕鑰,如果需要訪問通訊錄,需要勾選許可權。
可以先判斷BluetoothDevice的狀態,如果狀態為BluetoothDevice.BOND_NONE,呼叫bluetoothDevice.createBond()進行繫結。
講連線之前說一下自己踩過的坑,也不能說是坑,自己能力不足所以花了不少時間。開始我以為連線藍芽需要socket通訊,查google文件說要 兩臺裝置一臺充當客戶端一臺充當服務端,然後用io流傳輸資料。我一直在糾結,我只能操作車載應用,使用者手機安裝不了我開發的應用,那這怎麼連線呢。後來看到系統setting有連線藍芽的效果就果斷去setting的Bluetooth模組檢視連線藍芽的程式碼,終於找到了。在frameworks\base\packages\SettingsLib\src\com\android\settingslib\bluetooth中專門有個類儲存管理藍芽裝置CachedBluetoothDevice.java,他連線遠端藍芽裝置就是
synchronized void 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是一個介面,各種協議的封裝者實現了該介面,例如HfpClientProfile它裡面封裝的是BluetoothHeadsetClient,就是HFP協議。主要看它的connect方法
@Override
public boolean connect(BluetoothDevice device) {
if (mService == null) return false;
List<BluetoothDevice> srcs = getConnectedDevices();
if (srcs != null) {
for (BluetoothDevice src : srcs) {
if (src.equals(device)) {
// Connect to same device, Ignore it
Log.d(TAG,"Ignoring Connect");
return true;
}
}
}
return mService.connect(device);
}
先獲取已連線的藍芽裝置,如果藍芽裝置已連線返回true,不在列表中則呼叫BluetoothHeadsetClient的connect方法。接著看下這個BluetoothHeadsetClient怎麼例項化。
mLocalAdapter.getProfileProxy(context, new HfpClientServiceListener(),
BluetoothProfile.HEADSET_CLIENT);
通過BluetoothAdapter的getProfileProxy方法去請求這個協議物件。
private final class HfpClientServiceListener
implements BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, 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()) {
BluetoothDevice nextDevice = deviceList.remove(0);
CachedBluetoothDevice device = 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
public void onServiceDisconnected(int profile) {
if (V) Log.d(TAG,"Bluetooth service disconnected");
mIsProfileReady=false;
}
}
監聽返回狀態,賦值。其實不僅僅是BluetoothHeadsetClient,BluetoothA2dpSink等等請求方式一模一樣,引數不同。看到這個之後恍然大悟,車載藍芽實現的功能不需要進行socket通訊,只需要連線這些協議。
好了,整理一下連線過程,先拿到BluetoothAdapter,呼叫getProfileProxy方法請求協議,第三個引數為協議的種類,都定義在BluetoothProfile中,例如BluetoothProfile.AVRCP_CONTROLLER(藍芽控制協議),BluetoothProfile.A2DP_SINK(音訊協議)。實現BluetoothProfile.ServiceListener介面,監聽返回狀態。如果返回成功得到物件,就可以呼叫connect(BluetoothDevice device)連線。
mAdapter = BluetoothAdapter.getDefaultAdapter();
mAdapter.getProfileProxy(mContext, new ProxyServiceListener(), BluetoothProfile.AVRCP_CONTROLLER);
mAdapter.getProfileProxy(mContext, new ProxyServiceListener(), BluetoothProfile.A2DP_SINK);
private final class ProxyServiceListener 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");
}
}
}
public boolean connect(BluetoothDevice device) {
if (null != mA2dpSink) {
return mA2dpSink.connect(device);
}
Log.i(TAG, "mA2dpSink == null");
return false;
}
相關推薦
車載BlueTooth通話機制原理及開發
[摘要]: 本文主要論述基於android 6.0的藍芽上層(Java層)通話機制;總結了藍芽通話框架,並且給出了接聽電話的詳細的流程圖;最後說明了apk的實現以及總結了藍芽/android 相關的知識點。 1, 藍芽框架 主要程式碼路徑: 路徑1: framewo
【RPC入門】RPC概念、原理及開發
RPC基礎概念 RPC(Remote Procedure Call)—遠端過程呼叫,它是一種通過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的協議。 在一般的應用程式中,被呼叫的過程在相同的地址空間中執行,並把結果返回給發出呼叫的過程。在分散式環境中,客戶機和伺服器在不同的機
POW共識機制原理及優缺點
PoW共識機制 POW工作量證明(英文全稱為Proof of Work)在比特幣之前就已經出現,中本聰在設計區塊鏈的共識機制的時候就是借鑑了POW工作量證明。常見的是利用HASH運算的複雜度進行CPU運算實現工作量確定。 定義 工作量證明(Proof-of-W
Java集合-05fail-fast(快速失敗)機制原理及解決方法
fail-fast簡介 fail-fast(快速失敗),是Java集合的一種錯誤檢測機制。當在遍歷集合的過程中該集合在結構(改變集合大小)上發生變化時候, 有可能發生fail-fast,丟擲java.util.ConcurrentModificationException異常。 fail-fas
OAuth2.0授權原理及開發流程詳解
OAuth2.0在認證和授權的過程中參與的幾個角色如下: Client - 第三方應用, 下面以小明使用qq登陸本站為例來講解OAuth2.0授權原理(小明為Resource Owner,本站36nu.com為Client,qq授權伺服器為Authorization Server,提供小明qq基本資訊
藍芽通話機制原理
[摘要]: 本文主要論述基於android 6.0的藍芽上層(Java層)通話機制;總結了藍芽通話框架,並且給出了接聽電話的詳細的流程圖;最後說明了apk的實現以及總結了藍芽/android 相關的知識點。 1, 藍芽框架 主要程式碼路徑: 路徑1: frameworks\
QEMU快照(SNAPSHOT)機制原理及關鍵技術理解
snapshot.xml <domainsnapshot> <name>snapshot01</name> <description>Snapshot of OS install and updates by boh</descrip
理解Attention機制原理及模型
寫在前面 目前採用編碼器-解碼器 (Encode-Decode) 結構的模型非常熱門,是因為它在許多領域較其他的傳統模型方法都取得了更好的結果。這種結構的模型通常將輸入序列編碼成一個固定長度的向量表示,對於長度較短的輸入序列而言,該模型能夠學習出對應合理的向量表示。然而,這
[Android系統原理及開發要點詳解
第1章 Android系統概述 1 1.1 基礎知識 1 1.1.1 Android開發系統的由來 1 1.1.2 行動電話系統開發模式 2 1.1.3 未來行動電話的功能及Android的優勢 4 1.2 Android的開發工作 6 1.2.1 Android移植
java併發程式設計——四(synchronized\Lock\volatile) 鎖機制原理及關聯
前言 其實標題使用互斥機制更合適,併發中主要兩個問題是:執行緒如何同步以及執行緒如何通訊。 同步主要是通過互斥機制保證的,而互斥機制我們最熟悉的就是鎖,當然也有無鎖的CAS實現。 多執行緒共享資源,比如一個物件的記憶體,怎樣保證多個執行緒不會同時訪問(讀取
OAuth的機制原理講解及開發流程
國內私募機構九鼎控股打造APP,來就送 20元現金領取地址:http://jdb.jiudingcapital.com/phone.html內部邀請碼:C8E245J (不寫邀請碼,沒有現金送)國內私募機構九鼎控股打造,九鼎投資是在全國股份轉讓系統掛牌的公眾公司,股票程式碼為
OAuth1.0/2.0的機制原理講解及開發流程
1、OAuth的簡述 OAuth(Open Authorization,開放授權)是為使用者資源的授權定義了一個安全、開放及簡單的標準,第三方無需知道使用者的賬號及密碼,就可獲取到使用者的授權資訊,並且這是安全的。(我喜歡簡單明瞭,這裡沒看懂,沒關係,接著往下面看) 2、O
Struts2漏洞利用原理及OGNL機制
基本 conf 數據集 fig 然而 example 所有 def 字符串類型 Struts2漏洞利用原理及OGNL機制研究 概述 在MVC開發框架中,數據會在MVC各個模塊中進行流轉。而這種流轉,也就會面臨一些困境,就是由於數據在不同MVC層次中表現出不同的形式和狀態
微信公眾平臺開發教程(二) 基本原理及消息接口
username 普通用戶 縮放 地理位置 cfb 位置 註意 獲得 基本 一、基本原理 在開始做之前,大家可能對這個很感興趣,但是又比較茫然。是不是很復雜?很難學啊? 其實恰恰相反,很簡單。為了打消大家的顧慮,先簡單介紹了微信公眾平臺的基本原理。 微信服務器就相當於一個轉
JVM 及 垃圾回收機制原理
add IE 安全性 mod 銷毀 初始 文件 1.2 com JVM Java 虛擬機 Java 虛擬機(Java virtual machine,JVM)是運行 Java 程序必不可少的機制。JVM實現了Java語言最重要的特征:即平臺無關性。原理:編譯後的 Java
inotify機制監控文件系統事件原理及使用
direct esc his 存儲 cname smo gin related oca 1.基本描述 inotify提供了一種監控文件系統事件的機制,可以用來監控單個的文件以及目錄。當一個目錄被監控,inotify會返回該目錄以及該目錄下面文件的事件。 2.原理以及使用 2
Java核心機制:反射機制的原理及應用方法
一、java的核心機制 java有兩種核心機制:java虛擬機器(JavaVirtual Machine)與垃圾收集機制(Garbage collection): 1、Java虛擬機器:是執行所有Java程式的抽象計算機,是Java語言的執行環境,在其上面執行Java程式碼編譯後的位元組碼程式,
java垃圾回收機制的原理及優缺點
優點:a.不需要考慮記憶體管理, b.可以有效的防止記憶體洩漏,有效的利用可使用的記憶體, c.由於有垃圾回收機制,Java中的物件不再有"作用域"的概念,只有物件的引用才有"作用域" 原
malloc動態記憶體分配機制原理_及_linux/proc/介紹
程序系統資源的使用原理 大部分程序通過glibc申請使用記憶體,但是glibc也是一個應用程式庫,它最終也是要呼叫作業系統的記憶體管理介面來使用記憶體。大部分情況下,glibc對使用者和作業系統是透
Java反射機制的原理及在Android下的簡單應用
package crazypebble.reflectiontest;import java.lang.reflect.Constructor;import java.lang.reflect.Method;publicclass LoadMethod { /** * 在執行時載入指定的類,並呼