【161219丨Android學習筆記·藍牙通訊

一、藍牙概述

藍牙協議分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手機藍牙模塊連接。 一開始用了其他的,就連接不上了(┐「ε:)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容