Android藍芽開發流程實踐

weixin_33866037發表於2018-12-14

概述

工作需要用到Android藍芽開發,所以在這裡對Android藍芽開發做一個整理。

先了解下Android藍芽開發的基礎知識:官方文件戳這裡

我們需要知道,現在的藍芽分為經典藍芽和BLE(低功耗藍芽)兩種,經典藍芽和低功耗藍芽的區別戳這裡 ,經典藍芽適合傳輸資料量比較大的傳輸,比如影象、視訊、音樂等,耗電也大;BLE適合耗電小,實時性要求高和資料量小的傳輸,比如智慧穿戴裝置、遙控類、滑鼠、鍵盤、感測裝置如心跳帶,血壓計,溫度感測器等。

對於Android開發者來說,我們要知道 Android 4.3 及以上版本才支援BLE,常說的藍芽單模和雙模指的是僅支援BLE和同時支援BLE和經典藍芽,經典藍芽和BLE之間不能通訊,Android手機同時支援經典藍芽和BLE,但是掃描藍芽的時候,只能掃描其中一種,如果是Android手機跟其他裝置通過藍芽通訊,首先要確認裝置支援的藍芽協議。下面記錄的是經典藍芽開發步驟。

藍芽開發步驟

通常Android藍芽開發包含以下5個步驟:

  • 開啟
  • 掃描
  • 配對
  • 連線
  • 通訊

開啟藍芽

  • 許可權(需要藍芽和GPS許可權,Android 6.0以上要加上執行時許可權授權)

在 AndroidManifest 中宣告許可權:

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

獲取定位授權:

    //要模糊定位許可權才能搜尋到藍芽
    PermissionUtil.requestEach(this, new PermissionUtil.OnPermissionListener() {
        @Override
        public void onSucceed() {
            //授權成功後開啟藍芽
            openBlueTooth();
        }
        @Override
        public void onFailed(boolean showAgain) {

        }
    }, PermissionUtil.LOCATION);

  • 判斷裝置是否支援藍芽
    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
        //如果mBluetoothAdapter為null,該裝置不支援藍芽,不過基本上都支援的
    }

  • 判斷藍芽是否開啟,沒有就開啟
    if (!mBluetoothAdapter.isEnabled()) {
        //若沒開啟則開啟藍芽
        mBluetoothAdapter.enable();
    }

掃描藍芽

  • 可以掃描有指定 UUID 服務的藍芽裝置,UUID 不是裝置的標識,而是某個服務的標識, 什麼是UUID戳這裡
  • 可以掃描全部藍芽裝置
  • 注意:藍芽裝置被某裝置(包括當前的裝置)配對/連線後,可能不會再被掃描到
    //掃描:經典藍芽
    mBluetoothAdapter.startDiscovery();

    //掃描:低功耗藍芽,需要加上停止掃描規則,掃描到指定裝置或者一段時間後,這裡設定10秒後停止掃描
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            mBluetoothAdapter.stopLeScan(MainActivity.this);
            Log.i(TAG, "=== 停止掃描了 === ");
        }
    }, SCAN_PERIOD);

    mBluetoothAdapter.startLeScan(this);

通過廣播來監聽掃描結果:

    //廣播接收器
    BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            switch (action) {
                case BluetoothAdapter.ACTION_DISCOVERY_STARTED:
                    //掃描開始
                    break;
                case BluetoothDevice.ACTION_FOUND:
                    //發現藍芽
                    BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    break;
                case BluetoothAdapter.ACTION_DISCOVERY_FINISHED:
                    //掃描結束
                    break;
            }
        }
    };

配對藍芽

  • 配對與連線之間的區別:配對意味著兩個裝置之間知道彼此的存在,通過配對金鑰,建立一個加密的連線。而連線意味著裝置之間共享一個通訊通道(UUID),能夠彼此傳輸資料
    /**
     * 藍芽配對,配對結果通過廣播返回
     * @param device
     */
    public void pin(BluetoothDevice device) {
        if (device == null || !mBluetoothAdapter.isEnabled()) {
            return;
        }

        //配對之前把掃描關閉
        if (mBluetoothAdapter.isDiscovering()) {
            mBluetoothAdapter.cancelDiscovery();
        }

        //判斷裝置是否配對,沒有就進行配對
        if (device.getBondState() == BluetoothDevice.BOND_NONE) {
            try {
                Method createBondMethod = device.getClass().getMethod("createBond");
                Boolean returnValue = (Boolean) createBondMethod.invoke(device);
                returnValue.booleanValue();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

廣播監聽配對結果:

    case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
        //配對狀態變化廣播
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice
                .EXTRA_DEVICE);
        switch (device.getBondState()) {
            case BluetoothDevice.BOND_NONE:
                Log.i(TAG, "--- 配對失敗 ---");
                break;
            case BluetoothDevice.BOND_BONDING:
                Log.i(TAG, "--- 配對中... ---");
                break;
            case BluetoothDevice.BOND_BONDED:
                Log.i(TAG, "--- 配對成功 ---");
                break;
        }
        break;

連線藍芽

  • 配對成功之後,兩臺裝置之間建立連線,一臺充當client的角色,另一臺充當server的角色,由client發起連線;
  • 通過 UUID 建立 BluetoothSocket 進行連線,兩個端的UUID要一致,client和server都自己開發的話,可以由服務端建立一個UUID
  • 發起連線和監聽連線都要在子執行緒中執行

在client端的子執行緒中發起連線:

/**
 *
 * 發起藍芽連線的執行緒
 * 作者: 程式碼來自於Google官方 -> API指南 -> 藍芽模組
 * 日期: 18/12/14
 */

public class ConnectThread extends Thread {
    private final BluetoothSocket mmSocket;
    private final BluetoothDevice mmDevice;
    private final BluetoothAdapter mBluetoothAdapter;
    private ConnectCallBack callBack;

    public ConnectThread(BluetoothDevice device, BluetoothAdapter bluetoothAdapter, ConnectCallBack callBack) {
        // Use a temporary object that is later assigned to mmSocket,
        // because mmSocket is final
        BluetoothSocket tmp = null;
        mmDevice = device;
        mBluetoothAdapter = bluetoothAdapter;
        this.callBack = callBack;

        // Get a BluetoothSocket to connect with the given BluetoothDevice
        try {
            // MY_UUID is the app's UUID string, also used by the server code
            tmp = device.createRfcommSocketToServiceRecord(UUID.fromString(ServerActivity.uuidStr));
        } catch (IOException e) { }
        mmSocket = tmp;
    }

    public void run() {
        // Cancel discovery because it will slow down the connection
        mBluetoothAdapter.cancelDiscovery();

        try {
            // Connect the device through the socket. This will block
            // until it succeeds or throws an exception
            mmSocket.connect();
        } catch (IOException connectException) {
            // Unable to connect; close the socket and get out
            try {
                mmSocket.close();
            } catch (IOException closeException) { }
            return;
        }

        // Do work to manage the connection (in a separate thread)
//        manageConnectedSocket(mmSocket); //啟動資料傳輸的執行緒
        if(callBack != null) {
            callBack.onConnectSucceed(mmSocket);
        }

    }

    /** Will cancel an in-progress connection, and close the socket */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }

    public interface ConnectCallBack {
        public void onConnectSucceed(BluetoothSocket serverSocket);
    }
}

在server端的子執行緒中監聽連線:

/**
 *
 * 監聽藍芽連線執行緒
 * 作者: 程式碼來自於Google官方 -> API指南 -> 藍芽模組
 * 日期: 18/12/14
 */

public class AcceptThread extends Thread {

    private static final String TAG = "BluetoothDemo";

    private final BluetoothServerSocket mmServerSocket;

    private AcceptCallBack callBack;

    public AcceptThread(BluetoothAdapter bluetoothAdapter, AcceptCallBack callBack) {

        this.callBack = callBack;

        // Use a temporary object that is later assigned to mmServerSocket,
        // because mmServerSocket is final
        BluetoothServerSocket tmp = null;
        try {
            // MY_UUID is the app's UUID string, also used by the client code
            tmp = bluetoothAdapter.listenUsingRfcommWithServiceRecord("bluetoothdemo", UUID
                    .fromString(ServerActivity.uuidStr));
        } catch (IOException e) {
        }
        mmServerSocket = tmp;
    }

    public void run() {
        BluetoothSocket socket = null;
        // Keep listening until exception occurs or a socket is returned
        while (true) {
            Log.i(TAG, "AcceptThread監聽中...");

            try {
                socket = mmServerSocket.accept();
            } catch (IOException e) {
                break;
            }
            // If a connection was accepted
            if (socket != null) {

                try {
                    // Do work to manage the connection (in a separate thread)
//                manageConnectedSocket(socket); //啟動資料傳輸的執行緒
                    if(callBack != null) {
                        callBack.onAcceptSucceed(socket);
                    }


                    Log.i(TAG, "AcceptThread連線成功");
                    mmServerSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
            }
        }
    }

    /**
     * Will cancel the listening socket, and cause the thread to finish
     */
    public void cancel() {
        try {
            mmServerSocket.close();
        } catch (IOException e) {
        }
    }

    public interface AcceptCallBack {
        public void onAcceptSucceed(BluetoothSocket serverSocket);
    }

}

通訊

  • 連線成功後,通過socket得到I/O流,讀取資料和寫入資料,在兩個裝置之間傳輸資料。
/**
 * 傳送和接收資料
 * 作者: 程式碼來自於Google官方 -> API指南 -> 藍芽模組
 * 日期: 18/12/14
 */

public class ConnectedThread extends Thread {
    private static final String TAG = "ConnectedThread";
    private final BluetoothSocket mmSocket;
    private final InputStream mmInStream;
    private final OutputStream mmOutStream;
    private Handler mHandler;

    public ConnectedThread(BluetoothSocket socket, Handler handler) {
        mmSocket = socket;
        InputStream tmpIn = null;
        OutputStream tmpOut = null;
        mHandler = handler;

        // Get the input and output streams, using temp objects because
        // member streams are final
        try {
            tmpIn = socket.getInputStream();
            tmpOut = socket.getOutputStream();
        } catch (IOException e) { }

        mmInStream = tmpIn;
        mmOutStream = tmpOut;
    }

    public void run() {
        byte[] buffer = new byte[1024];  // buffer store for the stream
        int bytes; // bytes returned from read()

        // Keep listening to the InputStream until an exception occurs
        while (true) {
            try {
                // Read from the InputStream
                bytes = mmInStream.read(buffer);
                // Send the obtained bytes to the UI activity
                if(mHandler != null) {
                    mHandler.obtainMessage(ServerActivity.MESSAGE_READ, bytes, -1, buffer)
                            .sendToTarget();
                }

            } catch (IOException e) {
                break;
            }
        }
    }

    /* Call this from the main activity to send data to the remote device */
    public void write(byte[] bytes) {
        try {
            mmOutStream.write(bytes);
        } catch (IOException e) { }
    }

    /* Call this from the main activity to shutdown the connection */
    public void cancel() {
        try {
            mmSocket.close();
        } catch (IOException e) { }
    }
}

ConnectThread、AcceptThread 和 ConnectedThread 都是Google官方文件裡面的示例,我自己加了些回撥方法而已,如果覺得比較亂,可以直接去看官方文件。官方文件戳這裡

程式碼上傳到了GitHub, 功能比較簡單,兩臺手機一個充當client,一個充當server,嚴格按照 開啟 >> 掃描 >> 配對 >> 連線 >> 通訊 5個步驟走才能實現通訊。僅供參考。

相關文章