android bluetooth——藍芽的開啟、搜尋、配對與連線

夜輝疾風發表於2016-09-30

android 的blt僅僅支援api 18 android4.3以上,有的功能甚至需要api 19 android4.4;
所以我們在做blt專案之前一定要清楚可用的版本範圍。

我要講述的是開啟blt大門的操作。這些操作就是如何開啟blt、如何搜尋到其他裝置、如何配對選中裝置、如何通過mac地址連線之前已經配對過的裝置以及連線成功的兩個(或一對多個)裝置如何通訊。

在學習blt知識前要先搞清楚,blt是如何讓兩個裝置之間通訊的。我們先來看一下基本步驟:

1,開啟blt。
——1)許可權
——2)監聽裝置是否開啟blt
——3)操作,開啟或者關閉blt

2,搜尋附近裝置&讓附近裝置搜尋到
——1)讓自身裝置可以被附近搜尋到,時間最長300s;(隨著api的逐漸升級可能不止300s,做了一定的優化處理)
——2)搜尋附近可連線的裝置;過時的api在介面中回撥搜尋結果,新的api則要求用廣播接收搜尋結果。(我們這裡使用廣播接收結果)

3,與目標裝置配對
——1)對於目標裝置進行配對,android系統本身會完成配對動作,並且將已經成功配對的裝置的mac地址儲存起來,以供下次進行自動配對使用。
——2)對進行配對過的裝置自動配對。這個動作也是系統幫我們完成的,我們只需要根據系統給我們的狀態來判斷這個是否已經配對就行了。

4,與成功配對的裝置進行連線
——1)如果要對成功配對的裝置進行連線,則必須先建立伺服器端。伺服器端建立只有會執行緒阻塞等待客戶端的連線。
——2)確保建立了伺服器端之後再建立客戶端。客戶端的連線過程也是執行緒阻塞的。知道連線成功後,伺服器端收到訊息,則表示配對裝置已連線成功。

5,注意事項:
——1)配對成功過的裝置無需再次配對,只需要從系統中拿到mac地址進行連線。這個根據系統返回的狀態值去判斷。
——2)搜尋附近裝置的操作是一個消耗記憶體的動作,我們務必在連線裝置‘之前‘停止搜尋。
——3)裝置的配對成功不代表連線成功。配對和連線是兩回事且配對在連線動作之前。
——4)當我們的程式明確指出“不需要blt操作了”的時候,及時反註冊blt廣播,停止blt連線,登出藍芽物件。而這個操作例如:程式退出、使用者無操作超時、邏輯不需要blt的連線了。

以上就是一個藍芽連線成功之後可以正常傳輸資訊的步驟。
接下來我們再來看看相應的步驟在程式碼邏輯中的代表。

1,許可權。

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

2,獲得藍芽管理物件。BluetoothManager的主要作用是從中得到我們需要的物件

//@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
//首先獲取BluetoothManager
 BluetoothManager bluetoothManager=(BluetoothManager) context.getService(Context.BLUETOOTH_SERVICE);

3,獲得藍芽介面卡。藍芽介面卡是我們操作藍芽的主要物件,可以從中獲得配對過的藍芽集合,可以獲得藍芽傳輸物件等等

//獲取BluetoothAdapter
if (bluetoothManager != null)
    BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();

3,註冊廣播來接收藍芽配對資訊。在藍芽開啟前呼叫

// 用BroadcastReceiver來取得搜尋結果
        IntentFilter intent = new IntentFilter();
        intent.addAction(BluetoothDevice.ACTION_FOUND);//搜尋發現裝置
        intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);//狀態改變
        intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);//行動掃描模式改變了
        intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//動作狀態發生了變化
        context.registerReceiver(searchDevices, intent);
 /**
     * 藍芽接收廣播
     */
    private BroadcastReceiver searchDevices = new BroadcastReceiver() {
        //接收
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            Bundle b = intent.getExtras();
            Object[] lstName = b.keySet().toArray();

            // 顯示所有收到的訊息及其細節
            for (int i = 0; i < lstName.length; i++) {
                String keyName = lstName[i].toString();
                Log.e("bluetooth", keyName + ">>>" + String.valueOf(b.get(keyName)));
            }
            BluetoothDevice device;
            // 搜尋發現裝置時,取得裝置的資訊;注意,這裡有可能重複搜尋同一裝置
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                onRegisterBltReceiver.onBluetoothDevice(device);
            }
            //狀態改變時
            else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
                device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                switch (device.getBondState()) {
                    case BluetoothDevice.BOND_BONDING://正在配對
                        Log.d("BlueToothTestActivity", "正在配對......");
                        onRegisterBltReceiver.onBltIng(device);
                        break;
                    case BluetoothDevice.BOND_BONDED://配對結束
                        Log.d("BlueToothTestActivity", "完成配對");
                        onRegisterBltReceiver.onBltEnd(device);
                        break;
                    case BluetoothDevice.BOND_NONE://取消配對/未配對
                        Log.d("BlueToothTestActivity", "取消配對");
                        onRegisterBltReceiver.onBltNone(device);
                    default:
                        break;
                }
            }
        }
    };

4,反註冊廣播和清楚藍芽連線。在不需要使用藍芽的時候呼叫

/**
     * 反註冊廣播取消藍芽的配對
     *
     * @param context
     */
    public void unregisterReceiver(Context context) {
        context.unregisterReceiver(searchDevices);
        if (mBluetoothAdapter != null)
            mBluetoothAdapter.cancelDiscovery();
    }

5,判斷當前裝置是否支援藍芽,如果支援則開啟藍芽

   /**
     * 判斷是否支援藍芽,並開啟藍芽
     * 獲取到BluetoothAdapter之後,還需要判斷是否支援藍芽,以及藍芽是否開啟。
     * 如果沒開啟,需要讓使用者開啟藍芽:
     */
    public void checkBleDevice(Context context) {
        if (mBluetoothAdapter != null) {
            if (!mBluetoothAdapter.isEnabled()) {
                Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                enableBtIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                context.startActivity(enableBtIntent);
            }
        } else {
            Log.i("blueTooth", "該手機不支援藍芽");
        }
    }

6,搜尋藍芽。搜尋後新增到自定義的集合中以供自身程式的邏輯使用。注意:搜尋到的結果會在廣播中回撥過來,這裡不做回撥。若使用過時的介面,則在當前邏輯裡回撥結果。我們這裡使用廣播,不建議使用過時的方法。

/**
     * 搜尋藍芽裝置
     * 通過呼叫BluetoothAdapter的startLeScan()搜尋BLE裝置。
     * 呼叫此方法時需要傳入 BluetoothAdapter.LeScanCallback引數。
     * 因此你需要實現 BluetoothAdapter.LeScanCallback介面,BLE裝置的搜尋結果將通過這個callback返回。
     * <p/>
     * 由於搜尋需要儘量減少功耗,因此在實際使用時需要注意:
     * 1、當找到對應的裝置後,立即停止掃描;
     * 2、不要迴圈搜尋裝置,為每次搜尋設定適合的時間限制。避免裝置不在可用範圍的時候持續不停掃描,消耗電量。
     * <p/>
     * 如果你只需要搜尋指定UUID的外設,你可以呼叫 startLeScan(UUID[], BluetoothAdapter.LeScanCallback)方法。
     * 其中UUID陣列指定你的應用程式所支援的GATT Services的UUID。
     * <p/>
     * 注意:搜尋時,你只能搜尋傳統藍芽裝置或者BLE裝置,兩者完全獨立,不可同時被搜尋。
     */
    private boolean startSearthBltDevice(Context context) {
        //開始搜尋裝置,當搜尋到一個裝置的時候就應該將它新增到裝置集合中,儲存起來
        checkBleDevice(context);
        //如果當前發現了新的裝置,則停止繼續掃描,當前掃描到的新裝置會通過廣播推向新的邏輯
        if (getmBluetoothAdapter().isDiscovering())
            stopSearthBltDevice();
        Log.i("bluetooth", "本機藍芽地址:" + getmBluetoothAdapter().getAddress());
        //開始搜尋
        mBluetoothAdapter.startDiscovery();
        //這裡的true並不是代表搜尋到了裝置,而是表示搜尋成功開始。
        return true;
    }

7,停止搜尋藍芽裝置

public boolean stopSearthBltDevice() {
        //暫停搜尋裝置
        if(mBluetoothAdapter!=null)
        return mBluetoothAdapter.cancelDiscovery();
    }

8,連線藍芽。注意:連線藍芽的前提是我們已經建立好了藍芽伺服器端。所以,這裡我們先建立藍芽伺服器
——1)先建立藍芽伺服器端

/**
     * 這個操作應該放在子執行緒中,因為存線上程阻塞的問題
     */
    public void run(Handler handler) {
        //伺服器端的bltsocket需要傳入uuid和一個獨立存在的字串,以便驗證,通常使用包名的形式
        BluetoothServerSocket  bluetoothServerSocket = tmBluetoothAdapter.listenUsingRfcommWithServiceRecord("com.bluetooth.demo", BltContant.SPP_UUID);
        while (true) {
            try {
                //注意,當accept()返回BluetoothSocket時,socket已經連線了,因此不應該呼叫connect方法。
                //這裡會執行緒阻塞,直到有藍芽裝置連結進來才會往下走
                socket = getBluetoothServerSocket().accept();
                if (socket != null) {
                    BltAppliaction.bluetoothSocket = socket;
                    //回撥結果通知
                    Message message = new Message();
                    message.what = 3;
                    message.obj = socket.getRemoteDevice();
                    handler.sendMessage(message);
                    //如果你的藍芽裝置只是一對一的連線,則執行以下程式碼
                    getBluetoothServerSocket().close();
                    //如果你的藍芽裝置是一對多的,則應該呼叫break;跳出迴圈
                    //break;
                }
            } catch (IOException e) {
                try {
                    getBluetoothServerSocket().close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
                break;
            }
        }
    }

——2)在藍芽伺服器建立之後,再進行連線藍芽的操作。

 /**
     * 嘗試連線一個裝置,子執行緒中完成,因為會執行緒阻塞
     *
     * @param btDev 藍芽裝置物件
     * @param handler 結果回撥事件
     * @return
     */
    private void connect(BluetoothDevice btDev, Handler handler) {
        try {
            //通過和伺服器協商的uuid來進行連線
            mBluetoothSocket = btDev.createRfcommSocketToServiceRecord(BltContant.SPP_UUID);
            if (mBluetoothSocket != null)
                //全域性只有一個bluetooth,所以我們可以將這個socket物件儲存在appliaction中
                BltAppliaction.bluetoothSocket = mBluetoothSocket;
            //通過反射得到bltSocket物件,與uuid進行連線得到的結果一樣,但這裡不提倡用反射的方法
            //mBluetoothSocket = (BluetoothSocket) btDev.getClass().getMethod("createRfcommSocket", new Class[]{int.class}).invoke(btDev, 1);
            Log.d("blueTooth", "開始連線...");
            //在建立之前呼叫
            if (getmBluetoothAdapter().isDiscovering())
                //停止搜尋
                getmBluetoothAdapter().cancelDiscovery();
            //如果當前socket處於非連線狀態則呼叫連線
            if (!getmBluetoothSocket().isConnected()) {
                //你應當確保在呼叫connect()時裝置沒有執行搜尋裝置的操作。
                // 如果搜尋裝置也在同時進行,那麼將會顯著地降低連線速率,並很大程度上會連線失敗。
                getmBluetoothSocket().connect();
            }
            Log.d("blueTooth", "已經連結");
            if (handler == null) return;
            //結果回撥
            Message message = new Message();
            message.what = 4;
            message.obj = btDev;
            handler.sendMessage(message);
        } catch (Exception e) {
            Log.e("blueTooth", "...連結失敗");
            try {
                getmBluetoothSocket().close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }
    }

9,自動連線以往配對成功的裝置。注意:連線的前提是伺服器端已開啟,若沒開啟則進行8.1的操作

/**
     * 嘗試配對和連線
     *
     * @param btDev
     */
    public void createBond(BluetoothDevice btDev, Handler handler) {
        if (btDev.getBondState() == BluetoothDevice.BOND_NONE) {
            //如果這個裝置取消了配對,則嘗試配對
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                btDev.createBond();
            }
        } else if (btDev.getBondState() == BluetoothDevice.BOND_BONDED) {
            //如果這個裝置已經配對完成,則嘗試連線
            connect(btDev, handler);
        }
    }
 /**
     * 獲得系統儲存的配對成功過的裝置,並嘗試連線
     */
    public void getBltList() {
        if (getmBluetoothAdapter() == null) return;
        //獲得已配對的遠端藍芽裝置的集合
        Set<BluetoothDevice> devices = getmBluetoothAdapter().getBondedDevices();
        if (devices.size() > 0) {
            for (Iterator<BluetoothDevice> it = devices.iterator(); it.hasNext(); ) {
                BluetoothDevice device = it.next();
                //自動連線已有藍芽裝置
                createBond(device, null);
            }
        }
    }

注意:外界只需要呼叫getBltList();方法即可進行自動連線。

10,輸入mac地址自動連線裝置。前提是系統原來連線過該地址。

 /**
     * 輸入mac地址進行自動配對
     * 前提是系統儲存了該地址的物件
     *
     * @param address
     */
    public void autoConnect(String address, Handler handler) {
        if (getmBluetoothAdapter().isDiscovering()) getmBluetoothAdapter().cancelDiscovery();
        BluetoothDevice btDev = getmBluetoothAdapter().getRemoteDevice(address);
        connect(btDev, handler);
    }

11,藍芽連線狀態。用於我們判斷該藍芽裝置是出於:未配對還是配對未連線還是已連線狀態

public String bltStatus(int status) {
        String a = "未知狀態";
        switch (status) {
            case BluetoothDevice.BOND_BONDING:
                a = "連線中";
                break;
            case BluetoothDevice.BOND_BONDED:
                a = "連線完成";
                break;
            case BluetoothDevice.BOND_NONE:
                a = "未連線/取消連線";
                break;
        }
        return a;
    }

11,藍芽點選事件。包括藍芽的開啟,關閉,被搜尋,斷開連線

/**
     * 藍芽操作事件
     *
     * @param context
     * @param status
     */
    public void clickBlt(Context context, int status) {
        switch (status) {
            case BltContant.BLUE_TOOTH_SEARTH://搜尋藍芽裝置,在BroadcastReceiver顯示結果
                startSearthBltDevice(context);
                break;
            case BltContant.BLUE_TOOTH_OPEN://本機藍芽啟用
                if (getmBluetoothAdapter() != null)
                    getmBluetoothAdapter().enable();//啟用
                break;
            case BltContant.BLUE_TOOTH_CLOSE://本機藍芽禁用
                if (getmBluetoothAdapter() != null)
                    getmBluetoothAdapter().disable();//禁用
                break;
            case BltContant.BLUE_TOOTH_MY_SEARTH://本機藍芽可以在300s內被搜尋到
                Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
                discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
                context.startActivity(discoverableIntent);
                break;
            case BltContant.BLUE_TOOTH_CLEAR://本機藍芽關閉當前連線
                try {
                    if (getmBluetoothSocket() != null)
                        getmBluetoothSocket().close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;
        }
    }

到此,藍芽從開啟到連線的方法都寫完了。最後我們來總結一下。
當我們獲得到了bluetoothsocket物件的時候,我們就可以像使用socket程式設計一樣,讓兩個藍芽之間傳輸資料。甚至可以在程式內部監聽藍芽耳機的暫停/播放/音量鍵等的點選事件。

具體的藍芽操作,我將放在demo裡供大家學習。

demo下載

相關文章