Android藍牙4.0之玩爆智能穿戴、傢俱(二)【進階篇】

Android藍牙4.0之玩爆智能穿戴、傢俱(二)【進階篇】


閒話中心

這幾天最大的事可能就是美國總統的上任,雙十一,還有樂視股價了,乍一看,好像和我們沒什麼關係,其實肯定是有的了,要不然他也成不了新聞啊,有一點我們得改變,就是我們必須要希望我們自己國家的企業能過強大,我們必須支持他們,哪怕他做的不夠好,這個問題其實就像一個國家一樣,我們都知道許多政策是不合理的,或者說有很多制度是坑人的,但是我們不能因爲這些而不愛我們的國家,那麼企業也是一樣,就拿樂視來說,股價跌了,公司遇到資金問題了,你看看這些媒體都在報道什麼,全是負面消息,馬上倒閉了,或者說是撐不住了,沒有一家媒體是支持樂視,都是在等着看笑話,恨不得把人家祖宗十八代都挖出來,涼一涼,這些都讓我看到這些新聞都不想在點進去,還有就是錘子,你看看國外的品牌對國人的腐蝕度,國內的品牌居然遠不及他,人家一個做手機的,好歹也是國產品牌,我們不想着讓他走出國門,而是想着看他的笑話,媒體恨不得他明天早上就能倒閉,這倒還是一個頭條新聞,其實,要是企業都走出去了,國家也就變得強大了,這個時候,你到別的國家旅遊的時候人家就不會另眼相看了,人家在嘲笑你的時候,你是不是還挺高興的,我們不能這麼恬不知恥,我們要支持我們國家的每一個企業,即使他做的不好,我們也不能嘲笑每一個企業,因爲他有可能就是你以後出去引以爲豪的企業,是你自己國家的。

效果預覽

這是最終效果,也是通過藍牙進行數據傳輸,實時傳輸,可見藍牙傳輸數據之強大

步驟說明

  1. 今天我們要完成的就是框架的搭建,做什麼事首先得有個框架,這樣會事半功倍的
  2. 做藍牙模塊,一般情況下,你的藍牙代碼不可能全寫在activity中吧,這時候我們需要他到後臺去做事情,同時把採集到的數據返回到頁面來
  3. 這時候就是service和activity的通信了,首先得解決掉這個問題,才能進行下一步,也許這很簡單,但你也要堅持看完
  4. 有關於藍牙傳輸數據的問題很多,最重要的也就是在他發送數據的時候,一定要有順序,不能亂,千萬不能亂,也就是不能使用多線程來處理數據,這樣很危險,要麼數據沒有發送出去,要麼發送的順序亂了,影響實在太多,所以,保證了發送數據的順序顯得尤爲重要
  5. 還有發送數據時候的超時問題,這都是我們要考慮的,在這一塊處理問題的方式可能和藍牙協議有些關係,所以得自行處理
  6. 這就是我說的所謂的框架,等這些事都做完了,你會發現,其實很簡單

service和activity之間的通信

首先要說的就是我的處理方式,我是把service和activity之間的通訊當成兩個應用來處理的,爲什麼要這樣做呢?其實是爲了有些時候我們會把藍牙在後臺的service設置成了一個單獨的進程,設置成單獨的進程的好處也是有的,可以單獨的運作,系統會單獨的給他開闢一塊內存出來保證他的運行,因爲這個時候service已經相當於一個單獨的應用了,所以說,這個時候我們要想和service通信,那就是跨進程了,而不是線程了,這個時候大家都會想到aidl,他是專門來處理進程之間的通訊問題的,別逗了,好麼,誰他麼傻,就一個服務,我還得去寫一個aidl,要是這樣的話,我也就不會寫這篇文章了,列位,看大招!

Messenger出場

一. 官方介紹:

它引用了一個Handler對象,以便others能夠向它發送消息。該類允許跨進程間基於Message的通信(即兩個進程間可以通過Message進行通信),在服務端使用Handler創建一個 Messenger,客戶端持有這個Messenger就可以與服務端通信了

二. 爲什麼要使用Messenger

和直接使用AIDL不同的是Messenger利用了Handler處理通信,所以它是線程安全的(不支持併發處理);而我們平時用的AIDL是非線程安全的(支持併發處理)。所以大多數時候我們應用中是不需要處理誇進程併發處理通信的,而且最重要的是藍牙喜歡併發的發送數據,而且還要很有順序,所以這時選擇Messenger會比AIDL更加容易操作。其實Messenger最終還是aidl的實現方式,是不是屌爆了。

三. Messenger使用步驟

① service 內部需要有一個 Handler 的實現,它被用來處理從每一個 client(activity) 發送過的來請求

② 通過這個 Handler ,來生成一個 Messenger

③ 在 service 的onBind() 方法中,需要向 client(activity) 返回由該 Messenger 生成的一個 IBinder 實例

④ client 使用從 service 返回的 IBinder 實例來初始化一個 Messenger, 然後使用該 Messenger 與 service 進行通信

⑤ service 通過它自身內部的 Handler 實現(Handler的handleMessage() 方法中)來處理從 client 發送過來的請求

四. 具體步驟及代碼

1、創建activity中的Messenger

//創建activity中的Messenger
mMessenger = new Messenger(new IncomingHandler(this));
  • 1
  • 2
  • 1
  • 2

2、實現client(activity)端的handler

        // client(activity端) 端 Handler 的實現
        private class IncomingHandler extends Handler {

            private final WeakReference<BaseActivity> mActivity;

            public IncomingHandler(BaseActivity activity) {
                mActivity = new WeakReference<>(activity);
            }

            /*
             * 處理從Service發送至該Activity的消息
             */
            @Override
            public void handleMessage(Message msg) {
                BaseActivity activity = mActivity.get();

                if (activity != null) {
                    switch (msg.what) {

                        case BleService.MSG_BLUETOOTH_ON://檢測到藍牙已開啓
                            ToastUtil.showToast("檢測到藍牙已打開");
                            break;
                        default:
                            super.handleMessage(msg);
                    }
                }

            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

3、client 端 ServiceConnection 的實現
服務和activity綁定,必須實現ServiceConnection

    //與服務的連接回調
    private ServiceConnection serviceConnection = new ServiceConnection() {

        // 當與service的連接建立後被調用
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            // 客戶端(activity) 與 服務 不在同一個進程中的話,是不可以進行顯示強制類型轉換的
            // 使用從Service返回的IBinder來生成一個Messenger
            mService = new Messenger(service);

            // 生成一個Message
            Message msg = Message.obtain();
            if (msg != null) {
                msg.what = MSG_BIND_SUCCESS;
                msg.replyTo = mMessenger;
                // 向Service 發送Message
                sendMessage(msg);
            } else {
                mService = null;
            }
            //activity與service綁定成功
        }

        // 當與service的連接意外斷開時被調用
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

4、client 端 向service發送消息

protected synchronized void sendMessage(Message msg) {
        if (msg != null && mService != null) {
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                unbindService(serviceConnection);
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

5、service端創建Messenger和handler

① 創建Messenger

 //創建service中的Messenger 用來和activity通信
 mHandler = new IncomingHandler(this);
 mMessenger = new Messenger(mHandler);
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

② 創建handler

    // service 端 Handler 的實現
    private static class IncomingHandler extends Handler {
        private final WeakReference<BleService> mService;

        //使用弱引用進行優化
        public IncomingHandler(BleService service) {
            mService = new WeakReference<>(service);
        }

        //用來處理activity發來的消息
        @Override
        public synchronized void handleMessage(Message msg) {

            //拿到service對象
            BleService service = mService.get();
            if (service != null) {
                switch (msg.what) {//根據發送過來消息的種類來處理消息
                    case MSG_REGISTER:
                        service.mClients.add(msg.replyTo);//添加訂閱者
                        LogUtil.fussenLog().d("1008611" + "======註冊service=====");
                        break;

                    case MSG_UNREGISTER:
                        service.mClients.remove(msg.replyTo);//移除此消息
                        LogUtil.fussenLog().d("1008611" + "======註銷service=====");
                        break;
                    default:
                        super.handleMessage(msg);

                }
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

③ 拿到IBinder對象

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

做到這,activity與service的通信框架就基本搭建完成了,這個時候不管service是一個單獨的進程還是服務,那麼都可以和activity就行無縫的通信了,記住不是併發而是有序的

藍牙流程細節處理(重點)

Android藍牙4.0之玩爆智能穿戴、傢俱(一) 一文中,我們已經講述過了一遍藍牙基本的開發流程,下面我們就來說下具體的流程和方法,還有處理方式,列位,不管你是高手還是未來的高手,請聽一聽我的方法,我們都需要共同進步,這篇文章要是說的有理,或者說對你有很大的啓發,那就趕緊關注我的微信公共號,要不然這篇文章的價值體現何在?好吧,可以開始了

1、開啓藍牙,就不說了,現在說掃描藍牙,首先是activity向service發送一個消息,告知service,讓service在後臺處理,掃描成功,向activity發送消息,將掃描到的數據一併帶過去,掃描失敗,告知activity自行處理,不管怎樣給出一個掃描時間,不能一直掃描,我的處理方式,就是5秒後關閉掃描,這時候也有各個手機的兼容問題,所以時間不能太長,浪費資源(耗電),並且會有不能預測的問題出現
① activity向service發消息,掃描藍牙

    /**
     * 向服務發送消息,開始掃描藍牙
     */
    private void startScan() {
        if (!BleUtils.isOpenBle(this)) {
            ToastUtil.showToast("藍牙未開啓,請先開啓藍牙");
            return;
        }

        progressDialog.setMessage("搜索中...");
        progressDialog.show();
        Message msg = Message.obtain(null, BleService.MSG_START_SCAN);
        sendMessage(msg);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

② service收到消息,開始掃描工作

    /**
     * 服務開始掃描所有藍牙設備
     */
    private void startScanBleDevice() {
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mBlueToothAdapter.stopLeScan(mLeScanCallback); // 5秒後結束掃描

                // 通知界面掃描結束 並傳遞數據
                if (mLeDevices != null && mLeDevices.size() > 0) {
                    Message msg = Message.obtain(null, SCAN_BLE_DEVICE_RESULT);
                    msg.arg1 = MSG_SCAN_SUCCESS;
                    msg.obj = mLeDevices;//將掃描到的藍牙設備集合傳遞到activity去
                    sendMessage(msg);
                } else {
                    // 通知沒有掃描到
                    Message msg = Message.obtain(null, SCAN_BLE_DEVICE_RESULT);
                    msg.arg1 = MSG_SCAN_FAIL;
                    sendMessage(msg);
                }
            }
        }, 5 * 1000);

        if (mLeDevices != null && mLeDevices.size() > 0) {
            mLeDevices.clear(); // 清空當前的數據。
        }
        if (mDeviceAddress != null && mDeviceAddress.size() > 0) {
            mDeviceAddress.clear();
        }
        mBlueToothAdapter.startLeScan(mLeScanCallback);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

2、實現BluetoothAdapter.LeScanCallback,在上文中我們在這個回調裏拿到了附近所有的設備信息,但是在實戰中,我們誰都不會去這麼做,其實呢,我們在掃描的階段就應該過濾出自己的設備,不將其他設備展示出來,這樣纔是我們真正的需求呀,如何過濾呢,Demo源碼中已經有很好的方案了,可下載源碼查看,這裏就不講了,有可能,每個設備的過濾方法都不一樣,但是一般情況下都是通過UUID來過濾的,我們採取的是使用UUID和廣播雙層過濾

3、如果說沒有問題的話,你的設備應該已經顯示在你的手機上了,下一步就是連接了,然後又和上面一樣activity發送連接消息向service,然後service收到消息進行處理,代碼不再貼出,現在就是service處理消息的問題了,那麼service收到消息該怎麼處理纔好呢,此時應該開啓異步連接任務,讓他去連接,最好這樣做,要不然很有可能會出現問題,目前各個手機藍牙都已經很少了,偶爾會冒出個毛病來,但是不影響大局

    /**
     * 連接設備
     *
     * @param device
     */
    private void connectDevice(BluetoothDevice device) {

        if (device == null) {
            return;
        }
        mBluetoothDevice = device;
        //開啓異步連接任務
        new ConnectDeviceTask().execute();

    }

    //異步去連接設備
    private class ConnectDeviceTask extends AsyncTask<String, Integer, String> {
        @Override
        protected void onPreExecute() {
        }

        @Override
        protected String doInBackground(String... params) {
            try {
                if (mBluetoothDevice != null) {
                    mBluetoothGatt = mBluetoothDevice.connectGatt(BleService.this, false, mBluetoothCallback);
                }
                return "";
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String result) {
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

4、實現BluetoothGattCallback,這個回調是最主要的啦,剛纔是連接,那麼我們暫時只關心BluetoothGattCallback回調中的onConnectionStateChange,那麼在這裏我們究竟要做些什麼呢?其實這個方法執行了也就意味着藍牙設備和手機連接已經成功了,我們應該先通知activity藍牙設備連接成功,可是並不是這樣的,一般情況下,我們暫時是不通知的,其實通知也是可以的,這不是說了麼,爲了安全麼,所以處理的方式是一旦連接失敗,立即通知,那麼成功呢,別急,我們是和硬件打交道的,我們肯定會和硬件約定一個彼此認識的唯一標示吧,你不能說你拿着你的手機什麼設備都連呀,那恐怕不行吧,這裏我只是拋磚引玉,具體怎麼做,還得看你們怎麼約定的,Demo中是直接通知頁面了

        //連接狀態回調方法
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {

            if (newState == BluetoothProfile.STATE_CONNECTED) {//連接成功
                LogUtil.fussenLog().d("1008611" + "=======設備連接連接成功======");

                //去發現該設備服務
                mBluetoothGatt.discoverServices();
                isDeviceConnection = true;

            } else {//連接失敗
                LogUtil.fussenLog().d("1008611" + "=======設備連接連接失敗======");
                mBluetoothGatt.close();
                mBluetoothGatt = null;
                if (isDeviceConnection) {
                    isDeviceConnection = false;
                }
            }
            sendDeviceConnectionState();//通知頁面
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

5、這個時候,我們要做的就是等這個方法返回成功之後,調用藍牙發現服務的方法,先讓他去尋找這個設備的服務,這個時候讓我們把目光都投向BluetoothGattCallback回調中的onServicesDiscovered方法中來,能不能發現設備中的service的結果都會在這裏出現,在這裏我們要做的事情就是拿到自己設備中的服務,還有像有的數據是要進行訂閱的,在這裏就得訂閱了,這個時候我們就得和設備進行通信了,比如下發配置值,獲取設備電量什麼的,都在這裏執行

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {//已發現該設備的服務

                //尋找服務之後,我們就可以和設備進行通信,比如下發配置值,獲取設備電量什麼的
                LogUtil.fussenLog().d("1008611" + "=======onServicesDiscovered======");

                //通過UUID拿到設備裏的服務service
                mDeviceService = mBluetoothGatt.getService(UUIDUtils.CMD_SERVICE);
                //通過UUID拿到設備裏的Characteristic
                cmdRespondCharacter = mDeviceService.getCharacteristic(UUIDUtils.CMD_READ_CHARACTERISTIC);
                cmdWriteCharacter = mDeviceService.getCharacteristic(UUIDUtils.CMD_WRITE_CHARACTERISTIC);
                btWriteCharacter = mDeviceService.getCharacteristic(UUIDUtils.CMD_BT_WRITE_CHARACTERISTIC);
                btRespondCharacter = mDeviceService.getCharacteristic(UUIDUtils.CMD_BT_READ_CHARACTERISTIC);

                //開啓訂閱事件後 設備可以主動的發送數據給你
                //通知後相應的Characteristic數據會在 onCharacteristicChanged()方法中返回
                //這裏我訂閱了cmdRespondCharacter,btRespondCharacter
                enableNotification(true, mBluetoothGatt, cmdRespondCharacter);
                enableNotification(true, mBluetoothGatt, btRespondCharacter);

                //寫入數據
                nextWrite();

            } else {//未發現該設備的服務
                //這裏我暫時沒有做處理
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

6、訂閱事件:就是當設備需要主動的向你發送數據時,這時候,你應該先訂閱,否則onCharacteristicChanged方法是不會執行的

 private void enableNotification(boolean enable, BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        if (gatt == null || characteristic == null)
            return;

        //這一步必須要有 否則收不到通知
        gatt.setCharacteristicNotification(characteristic, enable);
        BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(UUIDUtils.CCC);
        if (enable) {
            clientConfig.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        } else {
            clientConfig.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
        }
        //準備數據
        BleWriteData bData = new BleWriteData();
        bData.write_type = BleWriteData.DESCRIP_WRITE;//數據種類
        bData.object = clientConfig;
        //將數據加入隊列
        mWriteQueue.add(bData);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

7、寫入數據:這個時候我們寫的數據就是爲了能獲得設備的信息,或者發送一些指令,那要怎麼寫呢?這裏就得注意了

① 寫數據的時候必須是有序的,不能併發,這個時候我們就得用到隊列了,一定不能亂,首先創建一個數據的隊列,並且必須是先進先出

Queue mWriteQueue = new ConcurrentLinkedQueue();//數據隊列

ConcurrentLinkedQueue介紹:

ConcurrentLinkedQueue是一個基於鏈接節點的無界線程安全隊列,它採用先進先出的規則對節點進行排序,當我們添加一個元素的時候,它會添加到隊列的尾部,當我們獲取一個元素時,它會返回隊列頭部的元素。

② 在第6步的時候我們已經準備過數據了,這個時候就開始準備發送數據了,現在給出發送數據的代碼

    /**
     * 寫入數據,方法是同步的
     */
    private synchronized void nextWrite() {
        if (!mWriteQueue.isEmpty() && !isWriting) {
            doWrite(mWriteQueue.poll());//從數據隊列裏取出數據
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

③ 正式寫入數據:這個時候要注意的就是爲了保證在數據很多的情況下都能夠發送成功,必須要有一個超時的判定,一旦超時,要麼重新發送,要麼直接丟掉數據,繼續發送,具體情況得和硬件溝通,發送完之後,就得到onCharacteristicWrite方法中檢查是否寫入成功,但是我第一次調用的是mBluetoothGatt.writeDescriptor(),所以我要在onDescriptorWrite()方法中檢查我發送的數據是否發送完畢

 private void doWrite(BleWriteData data) {

        if (mBluetoothGatt == null) {
            return;
        }
        if (data.write_type == BleWriteData.CMD) { // cmd write
            if (cmdWriteCharacter != null) {
                cmdWriteCharacter.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);//設置爲WRITE_TYPE_NO_RESPONSE,這樣速度會快
                cmdWriteCharacter.setValue(data.write_data);
                mBluetoothGatt.writeCharacteristic(cmdWriteCharacter);
                isWriting = true;
                latSendData = data;
                if (timeOutThread == null) {
                    //開啓超時判斷線程
                    initTimeOutThread();
                }
            }
        } else if (data.write_type == BleWriteData.DESCRIP_WRITE) { //  BluetoothGattDescriptor
            isWriting = true;
            mBluetoothGatt.writeDescriptor((BluetoothGattDescriptor) data.object);
        } else {
            nextWrite();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

④ 檢查數據是否發送完畢,我在這裏 檢查的是我定義的數據隊列一旦數據隊列中沒有數據,那麼我認爲數據已經發送完畢,具體還得看硬件有沒有其他要求,我是這樣做的

        /**
         * 寫入完Descriptor之後調用此方法 應在此檢查數據是否發送完畢
         * @param gatt
         * @param descriptor
         * @param status
         */
        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {

            if (mWriteQueue.isEmpty()) {
                isWriting = false;

                //數據寫入完畢 可以在此處通知頁面或者做其他工作
            } else {

                //如果隊列中還有數據,繼續寫入
                isWriting = false;
                nextWrite();
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

⑤ 超時判斷:在這裏採用的是輪詢方式,但是邏輯的話各個設備都是不一樣的,這裏只是簡單的說下

    /**
     * 指令超時判斷的線程
     */
    class TimeOutThread extends Thread {
        @Override
        public void run() {
            while (timeOutThread_Start) {
                if (isWriting) {
                    if (timeOutTime == 6) {
                        // 超時三秒啦 重發
                        if (timeOutNum == 3) {
                            // 超時3次 放棄當前這條指令 發送下一條
                            isWriting = false;
                            mWriteQueue.clear();
                            timeOutNum = 0;
                            timeOutTime = 0;
                            //發送超時信息
                            sendTimeOutMessage();
                            nextWrite();
                            continue;
                        }
                        if (reSendData != null && latSendData != null && reSendData.write_type == latSendData.write_type && Arrays.toString(reSendData.write_data).equals(Arrays.toString(latSendData.write_data))) {
                            timeOutNum++;
                        } else {
                            timeOutNum = 1;
                        }
                        if (latSendData != null) {
                            doWrite(latSendData);
                            reSendData = latSendData;
                        }
                        timeOutTime = 0;
                    } else {
                        int num = 0;
                        while (isWriting) {
                            SystemClock.sleep(10);
                            num++;
                            if (num == 50) {
                                break;
                            }
                        }
                        timeOutTime++;
                    }
                } else {
                    int num = 0;
                    while (!isWriting) {
                        SystemClock.sleep(10);
                        num++;
                        if (num == 1000) {
                            break;
                        }
                    }
                }
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

8、onCharacteristicChanged方法
我們的業務最主要的就是設備採取數據,然後發送數據到手機上來,所以,關注的數據一般都是在onCharacteristicChanged方法裏,因爲我們需要設備主動發送數據過來,然後驗證和解析,處理的方式就是對於相對重要的數據要進行CRC8校驗,一旦失敗,就得通知頁面,可能你所關心的和我所關心的不一樣,但是道理都是相通的,下面貼出代碼

        /**被訂閱的Characteristic的值要是改變 調用此方法
         * 執行此方法的前提就是要被訂閱
         * @param gatt
         * @param characteristic
         */
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {

            //拿到characteristic的值
            byte[] data = characteristic.getValue();

            // 通過characteristic.getUUID()來判斷是誰發送值給你 來執行相應的操作
            if (characteristic.getUuid().equals(UUIDUtils.CMD_BT_READ_CHARACTERISTIC)) { // BT
                handleBTDataAvailable(data);//拿到數據後解析 併發送給頁面
            } else { // CMD命令的返回
                if (TLUtil.validateCRC8(data)) {//進行CRC8碼驗證 驗證成功後 解析數據
                    LogUtil.fussenLog().e("1008611" + "========BLE CMD命令的返回=========");
                    handleCharacteristicData(data);
                } else {
                    //CRC8碼驗證失敗 通知界面
                    Message msg = Message.obtain(null, MSG_CRC8_ERROR);
                    sendMessage(msg);
                }
            }
        }
  • 2


發佈了37 篇原創文章 · 獲贊 49 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章