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