前言
最近在做Android藍(lán)牙這部分內(nèi)容,所以查閱了很多相關(guān)資料,在此總結(jié)一下。
基本概念
Bluetooth是一種短距離(10米)的無(wú)線通信技術(shù)標(biāo)準(zhǔn),藍(lán)牙協(xié)議分為4層,即核心協(xié)議層、電纜替代協(xié)議層、電話控制協(xié)議層和采納的其它協(xié)議層。這4種協(xié)議中最重要的是核心協(xié)議。藍(lán)牙的核心協(xié)議包括基帶、鏈路管理、邏輯鏈路控制和適應(yīng)協(xié)議四部分。其中鏈路管理(LMP)負(fù)責(zé)藍(lán)牙組件間連接的建立。邏輯鏈路控制與適應(yīng)協(xié)議(L2CAP)位于基帶協(xié)議層上,屬于數(shù)據(jù)鏈路層,是一個(gè)為高層傳輸和應(yīng)用層協(xié)議屏蔽基帶協(xié)議的適配協(xié)議。
安卓平臺(tái)提供對(duì)藍(lán)牙的通訊棧的支持,允許設(shè)別和其他的設(shè)備進(jìn)行無(wú)線傳輸數(shù)據(jù)。應(yīng)用程序?qū)油ㄟ^(guò)安卓API來(lái)調(diào)用藍(lán)牙的相關(guān)功能,這些API使程序無(wú)線連接到藍(lán)牙設(shè)備,并擁有P2P或者多端無(wú)線連接的特性。
- 藍(lán)牙的功能:
- 掃描其他藍(lán)牙設(shè)備
- 為可配對(duì)的藍(lán)牙設(shè)備查詢藍(lán)牙適配器
- 建立RFCOMM通道(其實(shí)就是尼瑪?shù)恼J(rèn)證)
- 通過(guò)服務(wù)搜索來(lái)鏈接其他的設(shè)備
- 與其他的設(shè)備進(jìn)行數(shù)據(jù)傳輸
- 管理多個(gè)連接
- 藍(lán)牙建立連接必須要求:
- 打開(kāi)藍(lán)牙
- 查找附近已配對(duì)或可用設(shè)備
- 連接設(shè)備
- 設(shè)備間數(shù)據(jù)交互
藍(lán)牙API
代碼分布
packages/apps/Bluetooth/
- 藍(lán)牙應(yīng)用,主要是關(guān)于藍(lán)牙應(yīng)用協(xié)議的表現(xiàn)代碼,包括opp、hfp、hdp、a2dp、pan等等
frameworks/base/core/Java/android/server/
- 4.2以后這個(gè)目錄雖然還有,但里面代碼已經(jīng)轉(zhuǎn)移到應(yīng)用層了,就是前面那個(gè)目錄,所以4.2.2上的藍(lán)牙這里可以忽略。
framework/base/core/java/android/bluetooth
- 這個(gè)目錄里的代碼更像一個(gè)橋梁,里面有供java層使用一些類,也有對(duì)應(yīng)的aidl文件聯(lián)系C、C++部分的代碼,還是挺重要的。
kernel\drivers\bluetoothBluetooth
- 具體協(xié)議實(shí)現(xiàn)。包括hci,hid,rfcomm,sco,SDP等協(xié)議
kernel\net\bluetooth Linux kernel
- 對(duì)各種接口的Bluetoothdevice的驅(qū)動(dòng)。例如:USB接口,串口等,上面kernel這兩個(gè)目錄有可能看不到的,但一定會(huì)有的。
external\bluetooth\bluedroid
- 官方藍(lán)牙協(xié)議棧
system\bluetoothBluetooth
- 適配層代碼,和framework那個(gè)作用類似,是串聯(lián)framework與協(xié)議棧的工具。
關(guān)鍵類
/frameworks/base/core/java/android/bluetooth/
-
BluetoothAdapter
代表本地藍(lán)牙適配器(藍(lán)牙發(fā)射器),是所有藍(lán)牙交互的入口。通過(guò)它可以搜索其它藍(lán)牙設(shè)備,查詢已經(jīng)配對(duì)的設(shè)備列表,通過(guò)已知的MAC地址創(chuàng)建BluetoothDevice,創(chuàng)建BluetoothServerSocket監(jiān)聽(tīng)來(lái)自其它設(shè)備的通信。 -
BluetoothDevice
代表了一個(gè)遠(yuǎn)端的藍(lán)牙設(shè)備, 使用它請(qǐng)求遠(yuǎn)端藍(lán)牙設(shè)備連接或者獲取 遠(yuǎn)端藍(lán)牙設(shè)備的名稱、地址、種類和綁定狀態(tài)。 (其信息是封裝在 bluetoothsocket 中) 。 -
BluetoothSocket
代表了一個(gè)藍(lán)牙套接字的接口(類似于 tcp 中的套接字) ,他是應(yīng)用程 序通過(guò)輸入、輸出流與其他藍(lán)牙設(shè)備通信的連接點(diǎn)。 -
BluetoothServerSocket
代表打開(kāi)服務(wù)連接來(lái)監(jiān)聽(tīng)可能到來(lái)的連接請(qǐng)求 (屬于 server 端) , 為了連接兩個(gè)藍(lán)牙設(shè)備必須有一個(gè)設(shè)備作為服務(wù)器打開(kāi)一個(gè)服務(wù)套接字。 當(dāng)遠(yuǎn)端設(shè)備發(fā)起連 接連接請(qǐng)求的時(shí)候,并且已經(jīng)連接到了的時(shí)候,Blueboothserversocket 類將會(huì)返回一個(gè) bluetoothsocket。 -
BluetoothClass
描述了一個(gè)設(shè)備的特性(profile)或該設(shè)備上的藍(lán)牙大致可以提供哪些服務(wù)(service),但不可信。比如,設(shè)備是一個(gè)電話、計(jì)算機(jī)或手持設(shè)備;Blueboothserversocket 設(shè)備可以提供audio/telephony服務(wù)等。可以用它來(lái)進(jìn)行一些UI上的提示。 -
BluetoothProfile
藍(lán)牙協(xié)議 -
BluetoothHeadset
提供手機(jī)使用藍(lán)牙耳機(jī)的支持。這既包括藍(lán)牙耳機(jī)和免提(V1.5)模式。 -
BluetoothA2dp
定義高品質(zhì)的音頻,可以從一個(gè)設(shè)備傳輸?shù)搅硪粋€(gè)藍(lán)牙連接。 “A2DP的”代表高級(jí)音頻分配模式。 -
BluetoothHealth
代表了醫(yī)療設(shè)備配置代理控制的藍(lán)牙服務(wù) -
BluetoothHealthCallback
一個(gè)抽象類,使用實(shí)現(xiàn)BluetoothHealth回調(diào)。你必須擴(kuò)展這個(gè)類并實(shí)現(xiàn)回調(diào)方法接收更新應(yīng)用程序的注冊(cè)狀態(tài)和藍(lán)牙通道狀態(tài)的變化。 -
BluetoothHealthAppConfiguration
代表一個(gè)應(yīng)用程序的配置,藍(lán)牙醫(yī)療第三方應(yīng)用注冊(cè)與遠(yuǎn)程藍(lán)牙醫(yī)療設(shè)備交流。 -
BluetoothProfile.ServiceListener
當(dāng)他們已經(jīng)連接到或從服務(wù)斷開(kāi)時(shí)通知BluetoothProfile IPX的客戶時(shí)一個(gè)接口(即運(yùn)行一個(gè)特定的配置文件,內(nèi)部服務(wù))。
\packages\apps\Settings\src\com\android\settings\bluetooth
-
BluetoothEnabler
界面上藍(lán)牙開(kāi)啟、關(guān)閉的開(kāi)關(guān)就是它了, -
BluetoothSettings
主界面,用于管理配對(duì)和連接設(shè)備 -
LocalBluetoothManager
提供了藍(lán)牙API上的簡(jiǎn)單調(diào)用接口,這里只是開(kāi)始。 -
CachedBluetoothDevice
描述藍(lán)牙設(shè)備的類,對(duì)BluetoothDevice的再封裝 -
BluetoothPairingDialog
那個(gè)配對(duì)提示的對(duì)話框
/packages/apps/Phone/src/com/android/phone/
-
BluetoothPhoneService
在phone的目錄肯定和電話相關(guān)了,藍(lán)牙接聽(tīng)掛斷電話會(huì)用到這個(gè)
/packages/apps/Bluetooth/src/com/android/bluetooth/
說(shuō)到這里不能不說(shuō)4.2藍(lán)牙的目錄變了,在4.1及以前的代碼中packages層的代碼只有opp協(xié)議相關(guān)應(yīng)用的代碼,也就是文件傳輸那部分,而4.2的代碼應(yīng)用層的代碼則豐富了許多,按具體的藍(lán)牙應(yīng)用協(xié)議來(lái)區(qū)別,分為以下文件夾(這里一并對(duì)藍(lán)牙一些名詞作個(gè)簡(jiǎn)單解釋)
-
btservice
這個(gè)前面AdapterService.java的描述大家應(yīng)該能猜到一些,關(guān)于藍(lán)牙基本操作的目錄,一切由此開(kāi)始。- AdapterService (4.2后才有的代碼)藍(lán)牙打開(kāi)、關(guān)閉、掃描、配對(duì)都會(huì)走到這里,其實(shí)更準(zhǔn)確的說(shuō)它替代了4.1之前的BluetoothService.java,原來(lái)的工作就由這個(gè)類來(lái)完成了。
a2dp
(Advanced Audio Distribution Profile)高級(jí)音頻傳輸模式,藍(lán)牙立體聲,和藍(lán)牙耳機(jī)聽(tīng)歌有關(guān)那些。avrcp
音頻/視頻遠(yuǎn)程控制配置文件,是用來(lái)聽(tīng)歌時(shí)暫停,上下歌曲選擇的。hdp
(Health Device Profile)藍(lán)牙醫(yī)療設(shè)備模式,可以創(chuàng)建支持藍(lán)牙的醫(yī)療設(shè)備,使用藍(lán)牙通信的應(yīng)用,例如心率監(jiān)視器,血液,溫度計(jì)和秤。hfp
(Hands-free Profile)讓藍(lán)牙設(shè)備可以控制電話,如接聽(tīng)、掛斷、拒接、語(yǔ)音撥號(hào)等,拒接、語(yǔ)音撥號(hào)要視藍(lán)牙耳機(jī)及電話是否支持。pbap
(Phonebook Access Profile)電話號(hào)碼簿訪問(wèn)協(xié)議hid
(The Human Interface Device)人機(jī)交互接口,藍(lán)牙鼠標(biāo)鍵盤(pán)什么的就是這個(gè)了。該協(xié)議改編自USB HID Protocol。opp
(Object Push Profile)對(duì)象存儲(chǔ)規(guī)范,最為常見(jiàn)的,文件的傳輸都是使用此協(xié)議。pan
(Personal Area Network)描述了兩個(gè)或更多個(gè)藍(lán)牙設(shè)備如何構(gòu)成一個(gè)即時(shí)網(wǎng)絡(luò),和網(wǎng)絡(luò)有關(guān)還有串行端口功能(SPP),撥號(hào)網(wǎng)絡(luò)功能(DUN)
android 4.2的藍(lán)牙應(yīng)用層部分代碼更豐富了,雖然有些目錄還沒(méi)具體代碼,不過(guò)說(shuō)不準(zhǔn)哪個(gè)版本更新就有了,就像4.0添加了hdp醫(yī)療那部分一樣。另外原本在framework的JNI代碼也被移到packages/apps/bluetooth當(dāng)中。
主要方法
-
BluetoothAdapter
(藍(lán)牙本地適配器)-
getDefaultAdapter()
得到本地藍(lán)牙適配器 -
setName(String name)
設(shè)置藍(lán)牙名稱 -
disable()
關(guān)閉藍(lán)牙 -
enable()
打開(kāi)藍(lán)牙 -
isEnabled()
判斷藍(lán)牙是否打開(kāi) -
getName()
得到本地藍(lán)牙的名稱 -
getAddress()
得到本地藍(lán)牙適配器的地址 -
getBondedDevices()
得到已經(jīng)綁定的藍(lán)牙的設(shè)備 -
getRemoteDevice(byte[] address)
得到遠(yuǎn)程藍(lán)牙設(shè)備 -
getRemoteDevice(String address)
得到遠(yuǎn)程藍(lán)牙設(shè)備 -
startDiscovery()
開(kāi)始搜多附近藍(lán)牙 -
cancelDiscovery()
停止當(dāng)前搜索藍(lán)牙的 Task -
listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid)
創(chuàng)建 BluetoothServerSocket
-
-
BluetoothDevice
(藍(lán)牙設(shè)備)-
createBond()
藍(lán)牙配對(duì) (低版本不支持,>=api19) -
createRfcommSocketToServiceRecord(UUID uuid)
創(chuàng)建 BluetoothSocket -
getBondState()
得到配對(duì)的狀態(tài) -
getAddress()
得到遠(yuǎn)程藍(lán)牙適配器的地址 -
getName()
得到遠(yuǎn)程藍(lán)牙的名稱
-
-
BluetoothServerSocket
(數(shù)據(jù)傳輸服務(wù)端)
這個(gè)類一共只有三個(gè)方法兩個(gè)重載的。兩個(gè)重載的區(qū)別在于后面的方法指定了過(guò)時(shí)時(shí)間,需要注意的是,執(zhí)行這兩個(gè)方法的時(shí)候,直到接收到了客戶端的請(qǐng)求(或是過(guò)期之后),都會(huì)阻塞線程,應(yīng)該放在新線程里運(yùn)行!-
close()
關(guān)閉 -
connect()
連接 -
isConnected()
判斷當(dāng)前的連接狀態(tài) -
accept()
接收請(qǐng)求 -
accept(int timeout)
接收請(qǐng)求
-
-
BluetoothSocket
(數(shù)據(jù)傳輸客戶端)-
close()
關(guān)閉 -
connect()
連接 -
getInptuStream()
獲取輸入流 -
getOutputStream()
獲取輸出流 -
getRemoteDevice()
獲取遠(yuǎn)程設(shè)備,這里指的是獲取bluetoothSocket指定連接的那個(gè)遠(yuǎn)程藍(lán)牙設(shè)備
-
藍(lán)牙操作
打開(kāi)和關(guān)閉藍(lán)牙
開(kāi)啟藍(lán)牙有兩種方法:
一、直接調(diào)用系統(tǒng)對(duì)話框啟動(dòng)藍(lán)牙:
在AndroidManifest.xml
文件中添加需要的權(quán)限,高版本也不需要?jiǎng)討B(tài)授權(quán):
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
然后,在代碼中執(zhí)行:
startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), 1);
如果不想讓用戶看到對(duì)話框,那么我們還可以選擇第二種方法,進(jìn)行靜默開(kāi)啟藍(lán)牙。
二、靜默開(kāi)啟,不會(huì)有方法一的對(duì)話框:
照樣在AndroidManifest.xml
文件中添加需要的權(quán)限:
<!-- 已適配Android6.0 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
由于藍(lán)牙所需要的權(quán)限包含Dangerous Permissions,所以我們需要在Java代碼中進(jìn)行動(dòng)態(tài)授權(quán)處理:
private static final int REQUEST_BLUETOOTH_PERMISSION=10;
private void requestBluetoothPermission(){
//判斷系統(tǒng)版本
if (Build.VERSION.SDK_INT >= 23) {
//檢測(cè)當(dāng)前app是否擁有某個(gè)權(quán)限
int checkCallPhonePermission = ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION);
//判斷這個(gè)權(quán)限是否已經(jīng)授權(quán)過(guò)
if(checkCallPhonePermission != PackageManager.PERMISSION_GRANTED){
//判斷是否需要 向用戶解釋,為什么要申請(qǐng)?jiān)摍?quán)限
if(ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_COARSE_LOCATION))
Toast.makeText(this,"Need bluetooth permission.",
Toast.LENGTH_SHORT).show();
ActivityCompat.requestPermissions(this ,new String[]
{Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_BLUETOOTH_PERMISSION);
return;
}else{
}
} else {
}
}
接下來(lái)我們就可以靜默開(kāi)啟藍(lán)牙了:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.enable(); //開(kāi)啟
關(guān)閉藍(lán)牙
if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
mBluetoothAdapter.disable();
}
搜索藍(lán)牙設(shè)備
搜索分為主動(dòng)搜索和被動(dòng)搜索:
一、被動(dòng)搜索
if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE)
{
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
// 設(shè)置被發(fā)現(xiàn)時(shí)間,最大值是3600秒,0表示設(shè)備總是可以被發(fā)現(xiàn)的(小于0或者大于3600則會(huì)被自動(dòng)設(shè)置為120秒)
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 120);
activity.startActivity(discoverableIntent);
}
二、主動(dòng)搜索
創(chuàng)建BluetoothAdapter
對(duì)象
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
我們先獲取并顯示一下已經(jīng)配對(duì)的藍(lán)牙設(shè)備列表
/*
* 已配對(duì)設(shè)備列表
*/
private ListView mBoundDevicesLv;
/**
* 顯示已配對(duì)的設(shè)備列表
*/
private void showBoundDevices() {
List<Map<String, String>> mBoundDevicesList = new ArrayList<>();
Set<BluetoothDevice> boundDeviceSet = mBluetoothAdapter.getBondedDevices();
for (BluetoothDevice boundDevices : boundDeviceSet) {
Map<String, String> mBoundDevicesMap = new HashMap<>();
mBoundDevicesMap.put("name", boundDevices.getName());
mBoundDevicesMap.put("address", boundDevices.getAddress());
mBoundDevicesList.add(mBoundDevicesMap);
}
SimpleAdapter mSimpleAdapter = new SimpleAdapter(MainActivity.this, mBoundDevicesList,
android.R.layout.simple_list_item_2,
new String[]{"name", "address"},
new int[]{android.R.id.text1, android.R.id.text2});
mBoundDevicesLv.setAdapter(mSimpleAdapter);
}
開(kāi)始搜索
if (mBluetoothAdapter == null) {
LogUtil.e(TAG, "設(shè)備不支持藍(lán)牙");
}
// 打開(kāi)藍(lán)牙
if (!mBluetoothAdapter.isEnabled()) {
BluetoothAdapter.enable();
mBluetoothAdapter.cancelDiscovery();
}
// 尋找藍(lán)牙設(shè)備,android會(huì)將查找到的設(shè)備以廣播形式發(fā)出去
while (!mBluetoothAdapter.startDiscovery()) {
LogUtil.e(TAG, "嘗試失敗");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
定義搜索結(jié)果的廣播接收器
// 設(shè)置廣播信息過(guò)濾
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);//每搜索到一個(gè)設(shè)備就會(huì)發(fā)送一個(gè)該廣播
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//當(dāng)全部搜索完后發(fā)送該廣播
filter.setPriority(Integer.MAX_VALUE);//設(shè)置優(yōu)先級(jí)
registerReceiver(receiver, filter);// 注冊(cè)藍(lán)牙搜索廣播接收者,接收并處理搜索結(jié)果
搜索藍(lán)牙設(shè)備的廣播接收器如下:
/**
* 搜索出的設(shè)備集合
*/
private List<Map<String, String>> devices = new ArrayList<>();
/**
* 發(fā)現(xiàn)的設(shè)備列表
*/
private ListView mDevicesLv;
/**
* 定義廣播接收器
*/
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
ToastUtil.showToast(MainActivity.this, "Showing Devices");
// 從Intent中獲取設(shè)備對(duì)象
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// 定義一個(gè)裝載藍(lán)牙設(shè)備名字和地址的Map
Map<String, String> deviceMap = new HashMap<>();
// 過(guò)濾已配對(duì)的和重復(fù)的藍(lán)牙設(shè)備
if ((device.getBondState() != BluetoothDevice.BOND_BONDED) && isSingleDevice(device)) {
deviceMap.put("name", device.getName() == null ? "null" : device.getName());
deviceMap.put("address", device.getAddress());
devices.add(deviceMap);
}
// 顯示發(fā)現(xiàn)的藍(lán)牙設(shè)備列表
mDevicesLv.setVisibility(View.VISIBLE);
// 加載設(shè)備
showDevices();
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
//已搜素完成
}
}
};定義服務(wù)端線程類:
/**
* 判斷此設(shè)備是否存在
*/
private boolean isSingleDevice(BluetoothDevice device) {
if (devices == null) {
return true;
}
for (Map<String, String> mDeviceMap : devices) {
if ((device.getAddress()).equals(mDeviceMap.get("address"))) {
return false;
}
}
return true;
}
/**
* 顯示搜索到的設(shè)備列表
*/
private void showDevices() {
SimpleAdapter mSimpleAdapter = new SimpleAdapter(MainActivity.this, devices,
android.R.layout.simple_list_item_2,
new String[]{"name", "address"},
new int[]{android.R.id.text1, android.R.id.text2});
mDevicesLv.setAdapter(mSimpleAdapter);
}
藍(lán)牙配對(duì)
當(dāng)我們搜索到了藍(lán)牙的之后,就需要配對(duì),因?yàn)橹挥性谂鋵?duì)之后才能連接。
在上面的搜索到的設(shè)備列表的點(diǎn)擊事件中,進(jìn)行配對(duì)。
BluetoothDevice device = (BluetoothDevice) adapter.getItem(i);
if (device.getBondState() == BluetoothDevice.BOND_BONDED) {//是否已配對(duì)
connect(device);
} else {
try {
Method boned=device.getClass().getMethod("createBond");
boolean isok= (boolean) boned.invoke(device);
if(isOk) {
connect(device);
}
} catch (Exception e) {
e.printStackTrace();
}
}
這里需要說(shuō)明的是,這個(gè)配對(duì)Android在API19
之后對(duì)外提供了createBond()
這個(gè)方法。但是在API19
以前并沒(méi)有這個(gè)方法,所以用反射兼容性比較好。
藍(lán)牙的UUID
在進(jìn)行藍(lán)牙連接之前,先介紹一下一個(gè)關(guān)鍵的東西:兩個(gè)藍(lán)牙設(shè)備進(jìn)行連接時(shí)需要使用同一個(gè)UUID。但很多讀者可能發(fā)現(xiàn),有很多型號(hào)的手機(jī)(可能是非Android系統(tǒng)的手機(jī))之間使用了不同的程序也可以使用藍(lán)牙進(jìn)行通訊。從表面上看,它們之間幾乎不可能使用同一個(gè)UUID。
UUID的格式如下:
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
UUID的格式被分成5段,其中中間3段的字符數(shù)相同,都是4,第1段是8個(gè)字符,最后一段是12個(gè)字符。所以UUID實(shí)際上是一個(gè)8-4-4-4-12的字符串。
實(shí)際上,UUID和TCP的端口一樣,也有一些默認(rèn)的值。例如,將藍(lán)牙模擬成串口的服務(wù)就使用了一個(gè)標(biāo)準(zhǔn)的UUID:
00001101-0000-1000-8000-00805F9B34FB
除此之外,還有很多標(biāo)準(zhǔn)的UUID,如下面就是兩個(gè)標(biāo)準(zhǔn)的UUID:
信息同步服務(wù):00001104-0000-1000-8000-00805F9B34FB
文件傳輸服務(wù):00001106-0000-1000-8000-00805F9B34FB
藍(lán)牙設(shè)備間的數(shù)據(jù)傳輸
藍(lán)牙傳輸數(shù)據(jù)與Socket類似。在網(wǎng)絡(luò)中使用Socket和ServerSocket控制客戶端和服務(wù)端的數(shù)據(jù)讀寫(xiě)。而藍(lán)牙通訊也由客戶端和服務(wù)端Socket來(lái)完成。藍(lán)牙客戶端Socket是BluetoothSocket
,藍(lán)牙服務(wù)端Socket是BluetoothServerSocket
。這兩個(gè)類都在android.bluetooth包中。
無(wú)論是BluetoothSocket
,還是BluetoothServerSocket
,都需要一個(gè)UUID(全局唯一標(biāo)識(shí)符,Universally Unique Identifier),UUID相當(dāng)于Socket的端口,而藍(lán)牙地址相當(dāng)于Socket的IP。
下面,我們開(kāi)始進(jìn)行模擬一個(gè)藍(lán)牙數(shù)據(jù)的傳輸:
一、首先來(lái)看客戶端:
定義全局常量變量:
private ListView mDevicesLv;
private BluetoothAdapter mBluetoothAdapter;
private List<Map<String, String>> devices = new ArrayList<>();
//隨便定義一個(gè)UUID
private final UUID MY_UUID = UUID.fromString("abcd1234-ab12-ab12-ab12-abcdef123456");
private BluetoothSocket clientSocket;
private BluetoothDevice device;
private OutputStream os;//輸出流
接下來(lái)我們?cè)O(shè)置設(shè)備列表的點(diǎn)擊事件
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Map<String, String> s = devices.get(i);
String address = s.get("address");//把地址解析出來(lái)
//主動(dòng)連接藍(lán)牙服務(wù)端
try {
// 如果當(dāng)前正在搜索,則取消搜索。
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
try {
if (device == null) {
//獲得遠(yuǎn)程設(shè)備
device = mBluetoothAdapter.getRemoteDevice(address);
}
if (clientSocket == null) {
//創(chuàng)建客戶端藍(lán)牙Socket
clientSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
//開(kāi)始連接藍(lán)牙,如果沒(méi)有配對(duì)則彈出對(duì)話框提示我們進(jìn)行配對(duì)
clientSocket.connect();
//獲得輸出流(客戶端指向服務(wù)端輸出文本)
os = clientSocket.getOutputStream();
}
} catch (Exception e) {
}
if (os != null) {
//往服務(wù)端寫(xiě)信息
os.write("藍(lán)牙信息來(lái)了".getBytes("utf-8"));
}
} catch (Exception e) {
}
}
二、接下來(lái)看服務(wù)端:
服務(wù)端使用的是另一部手機(jī),接受上面手機(jī)通過(guò)藍(lán)牙發(fā)送過(guò)來(lái)的信息并顯示。
定義全局常量變量:
private BluetoothAdapter mBluetoothAdapter;
private AcceptThread acceptThread;
// 和客戶端相同的UUID
private final UUID MY_UUID = UUID.fromString("abcd1234-ab12-ab12-ab12-abcdef123456");
private final String NAME = "Bluetooth_Socket";
private BluetoothServerSocket serverSocket;
private BluetoothSocket socket;
private InputStream is;//輸入流
定義服務(wù)端線程類:
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
Toast.makeText(getApplicationContext(), String.valueOf(msg.obj),
Toast.LENGTH_LONG).show();
super.handleMessage(msg);
}
};
// 服務(wù)端監(jiān)聽(tīng)客戶端的線程類
private class AcceptThread extends Thread {
public AcceptThread() {
try {
serverSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (Exception e) {
}
}
public void run() {
try {
socket = serverSocket.accept();
is = socket.getInputStream();
while(true) {
byte[] buffer =new byte[1024];
int count = is.read(buffer);
Message msg = new Message();
msg.obj = new String(buffer, 0, count, "utf-8");
handler.sendMessage(msg);
}
}
catch (Exception e) {
}
}
}
在onCreate方法中初始化線程類并開(kāi)啟:
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
acceptThread = new AcceptThread();
acceptThread.start();
注意,使用socket.getInputStream接收到的數(shù)據(jù)是字節(jié)流,這樣的數(shù)據(jù)是沒(méi)法分析的,所以很多情況需要一個(gè)byte轉(zhuǎn)十六進(jìn)制String的函數(shù):
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
藍(lán)牙協(xié)議
藍(lán)牙協(xié)議簡(jiǎn)介
從Android 3.0開(kāi)始,Bluetooth API就包含了對(duì)Bluetooth profiles的支持。
Bluetooth profile是基于藍(lán)牙的設(shè)備之間通信的無(wú)線接口規(guī)范。
你在你的類里可以完成BluetoothProfile接口來(lái)支持某一Bluetooth profile。
Android Bluetooth API完成了下面的Bluetooth profile:
-
Headset profile
提供了移動(dòng)電話上的Bluetooth耳機(jī)支持。Android提供了BluetoothHeadset類,它是一個(gè)協(xié)議,用來(lái)通過(guò)IPC(interprocess communication)控制Bluetooth Headset Service。BluetoothHeadset既包含Bluetooth Headset profile
也包含Hands-Free profile
,還包括對(duì)AT命令
的支持。 -
HFP (Hands-free Profile)
,免提模式,讓藍(lán)牙設(shè)備可以控制電話,如接聽(tīng)、掛斷、拒接、語(yǔ)音撥號(hào)等,拒接、語(yǔ)音撥號(hào)要視藍(lán)牙耳機(jī)及電話是否支持。 -
HDP(Health Device Profile.)
,藍(lán)牙醫(yī)療設(shè)備模式,可以創(chuàng)建支持藍(lán)牙的醫(yī)療設(shè)備,使用藍(lán)牙通信的應(yīng)用,例如心率監(jiān)視器,血液,溫度計(jì)和秤。 -
AVRCP
,音頻/視頻遠(yuǎn)程控制配置文件,是用來(lái)聽(tīng)歌時(shí)暫停,上下歌曲選擇的。 -
A2DP(Advanced Audio Distribution Profile)
,高級(jí)音頻傳輸模式。Android提供了BluetoothA2dp類,這是一個(gè)通過(guò)IPC來(lái)控制Bluetooth A2DP的協(xié)議。 -
HID (The Human Interface Device)
,人機(jī)交互接口,藍(lán)牙鼠標(biāo)鍵盤(pán)什么的就是這個(gè)了。該協(xié)議改編自USB HID Protocol。 -
OPP (Object Push Profile)
,對(duì)象存儲(chǔ)規(guī)范,最為常見(jiàn)的,文件的傳輸都是使用此協(xié)議。 -
PAN (Personal Area Network)
,描述了兩個(gè)或更多個(gè)藍(lán)牙設(shè)備如何構(gòu)成一個(gè)即時(shí)網(wǎng)絡(luò),和網(wǎng)絡(luò)有關(guān)還有串行端口功能(SPP),撥號(hào)網(wǎng)絡(luò)功能(DUN)。 -
PBAP (Phonebook Access Profile)
,電話號(hào)碼簿訪問(wèn)協(xié)議。
藍(lán)牙協(xié)議的使用
下面是使用profile的基本步驟:
- 獲取默認(rèn)的Bluetooth適配器。
- 使用getProfileProxy()來(lái)建立一個(gè)與profile相關(guān)的profile協(xié)議對(duì)象的連接。在下面的例子中,profile協(xié)議對(duì)象是BluetoothHeadset的一個(gè)實(shí)例。
- 設(shè)置BluetoothProfile.ServiceListener。該listener通知BluetoothProfile IPC客戶端,當(dāng)客戶端連接或斷連服務(wù)器的時(shí)候
- 在BluetoothProfile.ServiceListener的onServiceConnected()內(nèi),得到一個(gè)profile協(xié)議對(duì)象的句柄。
- 一旦擁有了profile協(xié)議對(duì)象,就可以用它來(lái)監(jiān)控連接的狀態(tài),完成于該profile相關(guān)的其他操作。
例如,下面的代碼片段顯示如何連接到一個(gè)BluetoothHeadset協(xié)議對(duì)象,用來(lái)控制Headset profile:
BluetoothHeadset mBluetoothHeadset;
// 獲取默認(rèn)的Bluetooth適配器
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 連接Headset profile
mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener(){
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = (BluetoothHeadset) proxy;
}
}
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET) {
mBluetoothHeadset = null;
}
}
};
// ... 使用 mBluetoothHeadset
// 使用之后,關(guān)閉Proxy
mBluetoothAdapter.closeProfileProxy(mBluetoothHeadset)
以上,就先分析到這兒吧。