一、藍牙概述
藍牙協議分4層:核心協議層,電纜替代協議層,電話控制協議層,采納的其他協議層。最重要的是核心協議,包括基帶,鏈路管理(LMP 負責藍牙組件間連接的建立 設備的搜索配對連接),邏輯鏈路控制和適應協議(L2CAP 位于基帶協議層上,屬于數據鏈路層,是一個為高層傳輸和應用層協議屏蔽基帶協議的適配協議。
二、打開和關閉藍牙設備
方式1:請求打開
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, 1);
會出現藍牙請求權限對話框
方式2:靜默打開
要先在配置文件里添加權限
<users-permission android:name = "android.permission.BLUETOOTH" />
<users-permission android:name = "android.permission.BLUETOOTH_ADMIN" />
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
// 設備不支持藍牙
}
adapter.enable(): //打開
adapter.disable(); //關閉
三、搜索藍牙設備
前提:必須先打開藍牙模塊
PS. 被動搜索:還要打開可見性
/* 使自己的藍牙設備可見 */
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
// 可自定義可見時間(s),最大長度為3600。最小為0,表示始終可見。默認120,超過3600會被設為120
// 請求可見性的方法
startActivityForResult(discoverableIntent);
3.1. 查詢已配對設備
在搜索設備之前,要查詢已配對的設備集,看看想要連接的設備是否已經配對。
// 獲取已配對的設備集合
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// 如果有已配對的設備
if (pairedDevices.size() > 0) {
// 遍歷已配對設備
for (BluetoothDevice device : pairedDevices) {
// 把他們的名字和地址加入數組
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
3.2. 發現設備
發現藍牙設備,執行startDiscovery()方法。該過程是異步的,該方法將會立刻返回一個布爾值表明搜索是否已經開始。通常情況下,該搜索的過程調用12秒鐘的查詢,隨后返回找到的設備。搜索到的設備通過廣播回傳
/* stap1. 定義和注冊廣播接收器 */
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
// FOUND: 找到一個設備,會發送一個廣播
this.registerReceiver(receiver, filter);
filter = new IntentFilter(BluetoothAdapter.ACTION_FINISHED);
// FINISHED: 搜索結束,發送一個廣播
this.registerReceiver(receiver, filter);
//記得在onDestroy()中反注銷
/* step2. 開啟搜索 */
public void onClick_Search(View view){
if(myBluetoothAapter.isDiscovering()){
//如果正在搜索,要停止。因為startDiscovery()不能重復調用
myBluetoothAapter.cancelDiscovery();
}
myBluetoothAapter.startDiscovery();
}
/* step3. 重寫接收器的onReceive方法 */
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@override
public void onReceiver(Context context, Intent intent) {
String action = intent.getAction();
//當發現一個設備時
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
// 判斷設備是否已存在于已配對的設備集中
if(BluetoothDevice.ACTION_FOUND.equals(action)){
if(device.getBondState() != BluetoothDevice.BOND_BONDED){
//將設備名稱和地址添加到數組
mArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)){
//...搜索完成的 其他處理...
}
}
}
}
四、連接藍牙設備
使用BluetoothSocket和BluetoothServerSocket實現。
服務器端和客戶端通過不同的方式獲得BluetoothSocket。當一個連接接受(accept)的時候服務器端接收BluetoothSocket。而客戶端則通過打開服務器端的RFCOMM通道得到BluetoothSocket。
一種實現技術是,應用程序同時實現客戶端和服務器端。因此,每一個服務器端的程序擁有一個server socket并監聽連接。
另一種是在一個應用中實現服務器端的功能,而另一個應用中實現客戶端的部分。
PS. 如果兩個設備之前并沒有配對過,那么Android的框架將會自動進行配對的請求通知。因此當嘗試進行連接時,你的應用并不需要關心兩臺設備是否已經配對。你的RFCOMM連接將會被阻塞,直到用戶成功配對,或因為用戶拒絕配對而取消,或者配對失敗以及超時等。
UUID說明
UUID(全局唯一標識符 Universally Unique Identifier)實際上是一個格式8-4-4-4-12的字符串
UUID相當于Socket的端口,藍牙地址相當于Socket的IP
/* 定義需要的組件 */
private BluetoothAdapter myBluetoothAapter;
private List<String> bluetoothDevice = new ArrayList<String>();
private final UUID MY_UUID = UUID.fromString("a8a5f732-5fc5-49f6-b2e7-bc472e8797db");
// 字符串符合UUID的格式即可
private final String NAME = "Bluetooth_Socket";
private BluetoothSocket clientSocket;
private BluetoothDevice device;
private OutputStream os; //客戶端往服務端的輸出流
4.1. 客戶端的實現
/* 客戶端 列表單擊事件為例 */
public void onItemClick(AdapterView<?> parent, View view, int position, long id){
String s = mArrayAdapter.getItem(position);
//解析藍牙地址
String address = s.substring(s.indexOf(":") + 1).trim();
try{
// 依舊 如果正在搜索則取消搜索
if (myBluetoothAapter.isDiscovering()) {
myBluetoothAapter.cancelDiscovery();
}
try{
if(device == null){
device = myBluetoothAapter.getRemoteDevice(address);
}
if(clientSocket == null){
clientSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
// 開始連接
clientSocket.connect();
// 輸出流
os = clientSocket.getOutputStream();
}
}catch(Exception e){
// 異常處理
}
if(os != null){
// 成功獲取輸出流
os.write("發送信息".getBytes("utf-8"));
}
}
}
4.2. 服務端的實現
/* 服務端 監聽客戶端的線程類 */
private Handler handler = new Handler(){
public void handleMessage(massage msg){
Toast.makeText(Main.this, String.valueOf(msg.obj), Toast.LENGTH_LONG).show();
super.handleMessage(msg);
}
};
private class AcceptThread extends Thread{
private BluetoothServerSocket serverSocket;
private BluetoothSocket socket;
private InputStream is;
private OutputStream os;
public AcceptThread(){
try{
serverSocket = myBluetoothAapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID)
}catch(Exception e){
// 異常處理
}
}
//在run()方法中具體截獲藍牙消息
public void run(){
try{
socket = serverSocket.accept();
is = socket.getInputStream();
os = socket.getOutputStream();
// 無限從客戶端接受
while(true){
byte[] buffer = new byte[128];
int count = is.read(buffer);
Message msg = new Message();
msg.obj = new String(buffer, count, 0, "utf-8");
handler.sendMessage(msg);
}
}catch(Exception e){
// 異常處理
}
}
}
/* 在onCreat()里啟動服務 */
acceptThread = new AcceptThread();
acceptThread.start();
四、問題總結
在實際項目里還是遇到了一些問題,記錄如下:
4.1. 關于UUID
UUID的值必須是00001101-0000-1000-8000-00805F9B34FB。因為這個是android的API上面說明的,用于普通藍牙適配器和android手機藍牙模塊連接。 一開始用了其他的,就連接不上了(┐「ε:)