Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android開發之藍牙通信(二)

Android開發之藍牙通信(二)

編輯:關於Android編程

接著上篇藍牙通信往下寫,若有不對還請指出,大家共同進步。


先大概了解藍牙掃描連接收發數據的工作流程,這裡只提notify接收數據,read自動忽略。首先藍牙需要動態權限,個人采用動態權限的開源庫permissionsdispatcher,不懂若有需要可以github檢索這裡不在敘述(本篇多為代碼塊,完整接口和類可以自己拷貝到項目+開源庫就可以直接用)

首先是一個接口定義,關於藍牙的使用之前的一些判斷OnBluetoothInspectListener

public interface IBluetoothInspectListener {
    public interface OnBluetoothCheckListener {
        void onOpenBluetoothState();

        void onRefuseOpenBluetoothState();

        void unSupportBluetooth();
    }

    public interface OnBluetoothInspectListener {
        void onInspect();

        void onSupportBluetooth();

        boolean isOpenBluetooth();

        void onOpenBluetooth();
    }
}

實現類OnBluetoothInspectListenerImpl

public class OnBluetoothInspectListenerImpl implements OnBluetoothInspectListener {
    private static final int REQUEST_ENABLE_BT = 1;
    private static OnBluetoothInspectListenerImpl instance;
    private OnBluetoothCheckListener onBluetoothCheckListener;
    BluetoothAdapter mBluetoothAdapter;
    Activity mContext;

    private OnBluetoothInspectListenerImpl(Activity mContext, OnBluetoothCheckListener onBluetoothCheckListener) {
        this.mContext = mContext;
        this.onBluetoothCheckListener = onBluetoothCheckListener;
        this.mBluetoothAdapter = ((BluetoothManager)mContext.getSystemService("bluetooth")).getAdapter();
    }

    public static OnBluetoothInspectListenerImpl init(Activity activity, OnBluetoothCheckListener onBluetoothCheckListener) {
        instance = new OnBluetoothInspectListenerImpl(activity, onBluetoothCheckListener);
        return instance;
    }

    public void onInspect() {
        if(this.mBluetoothAdapter == null) {
            this.onBluetoothCheckListener.unSupportBluetooth();
        } else {
            this.onSupportBluetooth();
        }

    }

    public void onSupportBluetooth() {
        if(this.isOpenBluetooth()) {
            this.onBluetoothCheckListener.onOpenBluetoothState();
        } else {
            this.onOpenBluetooth();
        }

    }

    public boolean isOpenBluetooth() {
        return this.mBluetoothAdapter.isEnabled();
    }

    public void onOpenBluetooth() {
        Intent enableBtIntent = new Intent("android.bluetooth.adapter.action.REQUEST_ENABLE");
        this.mContext.startActivityForResult(enableBtIntent, 1);
    }

    public void performResult(int requestCode, int resultCode) {
        switch(requestCode) {
        case 1:
            if(this.onBluetoothCheckListener != null) {
                if(this.isOpenBluetooth()) {
                    this.onBluetoothCheckListener.onOpenBluetoothState();
                } else {
                    this.onBluetoothCheckListener.onRefuseOpenBluetoothState();
                }
            }
        default:
        }
    }

    public BluetoothAdapter getBluetoothAdapter() {
        return this.mBluetoothAdapter;
    }
}

具體使用這塊的代碼塊(根據動態權限結果,有藍牙的權限後調用onBluetoothInspectListenerImpl.onInspect();方法即可,會自動根據相應函數執行回調,onOpenBluetoothState方法回調時表示可以開始藍牙掃描了)

public class BluetoothActivity extends AppCompatActivity implements IBluetoothInspectListener.OnBluetoothCheckListener{

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //.........略..................

        onBluetoothInspectListenerImpl = OnBluetoothInspectListenerImpl.init(this, this);
        //.......動態權限.........

    }

 @Override
 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        onBluetoothInspectListenerImpl.performResult(requestCode, resultCode);
    }


  @Override
  public void onOpenBluetoothState() {
      //掃描讀寫
      startScan();
  }

 @Override
 public void onRefuseOpenBluetoothState() {
     showInformation("您拒絕了開啟藍牙將影響到功能的使用");
     finish();
 }

 @Override
 public void unSupportBluetooth() {
     showInformation("手機不支持低功耗藍牙通信");
     finish();
 }
}

藍牙掃描代碼個人感覺比較繁瑣,最後選用了小米一哥們的開源庫https://github.com/dingjikerbo/BluetoothKit,由於該庫沒有提供compile路徑,之前blog提到過搭建maven私服,於是下載源碼下來把module上傳到了本地maven私服,根據開發需求,進行了簡單得的封裝

接口定義IBluetoothListener、BluetoothCallback

public interface IBluetoothListener {
    public void startScan();
    public void stopScan();
    public void connect();
    public void read();
    public void write();
    public void registerNotify();
    public void unRegisterNotify();
    public void disconnect();
    public void release();
}


public interface BluetoothCallback {

    public void showInformation(String information);

    public void toastMessage(String message);

    public void readResponse(byte[] bytes);
}

實現類OnBluetootListener ,UUID特別重要,不能隨便copy,最好找硬件那邊要。(如果你拷貝了該類需要自行補充完整UUID)

public class OnBluetootListener implements IBluetoothListener {

    public static final UUID SERVICE_UUID ;

    public static final UUID WRITE_CHARA ;

    public static final UUID READ_CHARA ;

    private byte[] bytes;
    private String deviceMac;
    private SearchRequest request;
    private IBluetoothClient mClient;
    private BluetoothDevice currentBluetoothDevice;
    private BluetoothCallback callback;

    private boolean isStopScan = false;

    public OnBluetootListener(Context context, String deviceMac, byte[] bytes, BluetoothCallback callback) {
        this.deviceMac = deviceMac;
        this.bytes = bytes;
        this.callback = callback;
        mClient = BluetoothClient.getInstance(context);
        request = new SearchRequest.Builder()
                .searchBluetoothLeDevice(3000, 1)   // scan Bluetooth LE device for 3000ms, 3 times
                .searchBluetoothClassicDevice(5000) // then scan Bluetooth Classic device for 5000ms, 1 time
                .searchBluetoothLeDevice(2000)      // at last scan Bluetooth LE device for 2000ms
                .build();
    }

    @Override
    public void startScan() {
        //執行掃描任務
        callback.showInformation("掃描設備..");
        mClient.search(request, new SearchResponse() {
            @Override
            public void onSearchStarted() {

            }

            @Override
            public void onDeviceFounded(SearchResult device) {
                if (device.device.getAddress().equals(deviceMac)&&!isStopScan) {
                    isStopScan=true;
                    //掃描到指定設備後停止掃描
                    callback.showInformation("發現設備");
                    currentBluetoothDevice = device.device;

                    //連接設備
                    connect();

                    stopScan();
                }
            }

            @Override
            public void onSearchStopped() {
                if (currentBluetoothDevice == null) {
                    callback.toastMessage("沒有發現匹配的設備");
                }
            }

            @Override
            public void onSearchCanceled() {

            }
        });
    }

    @Override
    public void stopScan() {
        mClient.stopSearch();
    }

    @Override
    public void connect() {
        mClient.connect(deviceMac, new BleConnectResponse() {
            @Override
            public void onResponse(int code, Bundle data) {
                if (code == REQUEST_SUCCESS) {
                    //連接成功
                    //注冊notify以後發送數據和讀取數據
                    callback.showInformation("連接成功");
                    registerNotify();
                } else {
                    release();
                    callback.toastMessage("連接失敗");
                }
            }
        });
    }

    @Override
    public void read() {
        mClient.read(deviceMac, SERVICE_UUID, READ_CHARA, new BleReadResponse() {
            @Override
            public void onResponse(int code, byte[] data) {
                if (code == REQUEST_SUCCESS) {
                    callback.showInformation(data.toString());
                } else {
                    release();
                    callback.toastMessage("讀取數據失敗");
                }
            }
        });
    }

    @Override
    public void write() {
        mClient.write(deviceMac, SERVICE_UUID, WRITE_CHARA, bytes, new BleWriteResponse() {
            @Override
            public void onResponse(int code) {
                if (code == REQUEST_SUCCESS) {
                    callback.showInformation("發送數據成功");
                    // read();
                } else {
                    release();
                    callback.toastMessage("發送數據失敗");
                }
            }
        });
    }

    public void write(byte[] bytes) {
        mClient.write(deviceMac, SERVICE_UUID, WRITE_CHARA, bytes, new BleWriteResponse() {
            @Override
            public void onResponse(int code) {
                if (code == REQUEST_SUCCESS) {
                } else {
                    release();
                    callback.toastMessage("發送數據失敗");
                }
            }
        });
    }

    @Override
    public void registerNotify() {
        mClient.notify(deviceMac, SERVICE_UUID, READ_CHARA, new BleNotifyResponse() {
            @Override
            public void onNotify(UUID service, UUID character, byte[] value) {
                callback.readResponse(value);
            }

            @Override
            public void onResponse(int code) {
                if (code == REQUEST_SUCCESS) {
                   // callback.showInformation("注冊藍牙通知成功");
                   //write數據需要注意,個別手機有限制一次發送數據最大值,如果超過了需要自己分多次發送
                    callback.showInformation("數據已發送,等待數據響應");
                } else {
                    release();
                    callback.toastMessage("發送失敗,請再試一次");
                }
            }
        });
    }

    @Override
    public void unRegisterNotify() {
        mClient.unnotify(deviceMac, SERVICE_UUID, READ_CHARA, new BleUnnotifyResponse() {
            @Override
            public void onResponse(int code) {

            }
        });
    }

    @Override
    public void disconnect() {
        mClient.disconnect(deviceMac);
    }

    @Override
    public void release() {
        unRegisterNotify();
        disconnect();
        stopScan();
    }

    public BluetoothDevice getCurrentBluetoothDevice(){
        return  currentBluetoothDevice;
    }
}

根據上面的實現類,使用時只需在onCreate獲取實現類實例,在藍牙判斷檢查的回掉函數執行掃描,通過這個實現類代理即可

listener = new OnBluetootListener(this,macAddress,sendMessageBytes,this);

//...........................................
@Override
public void onOpenBluetoothState() {
    //掃描讀寫
    listener.startScan();
}

開啟掃描,掃描到指定設備,開始注冊藍牙通知服務,注冊成功了就開始寫入數據等待返回數據,大致工作流程就這樣的,主要關注回掉函數如下

 @Override
    public void showInformation(String message) {
        toast.setText(message);
     // Logger.e(message);
    }

    @Override
    public void toastMessage(String s) {
        Toast.makeText(getApplicationContext(),s,Toast.LENGTH_SHORT).show();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                finish();
            }
        },1000);
    }

    @Override
    public void readResponse(byte[] bytes) {

    }

再提一點,數據按照字節傳輸,一次接受的字節長度有限制,如果要保證自己接受完整,需要自己采用相應的辦法,如下

    @Override
    public void readResponse(byte[] bytes) {
        saveBytes(bytes, responseBytes);
        if (readCount == responseBytes.length) {
            onResponseReadResult(responseBytes);//解析回傳數據
        }
    }

    /**
     * 拼接byte數據
     *
     * @param src
     * @param ds
     */
    public void saveBytes(byte[] src, byte[] ds) {
        for (int i = 0; i < src.length; i++) {
            ds[i + readCount] = src[i];
        }
        readCount = readCount + src.length;
    }

最後一點,與硬件通信一般都會用到Crc8、Crc16算法相關的,而硬件開發用C與java有所不同,算法轉換這裡提供一個Crc8的一個類(網上多為c char,java的有長度限制直接拷貝到java裡用會有點問題)

public class Crc8Helper {


    /**
     * CRC8算法
     *
     * @param bytes
     * @return
     */
    public static byte CRC8_Table(byte[] bytes)// counter默認為2
    {
        return calcCrc8(bytes);
    }

    static byte[] crc8_tab = {(byte) 0, (byte) 94, (byte) 188, (byte) 226, (byte) 97, (byte) 63, (byte) 221, (byte) 131, (byte) 194, (byte) 156, (byte) 126, (byte) 32, (byte) 163, (byte) 253, (byte) 31, (byte) 65, (byte) 157, (byte) 195, (byte) 33, (byte) 127, (byte) 252, (byte) 162, (byte) 64, (byte) 30, (byte) 95, (byte) 1, (byte) 227, (byte) 189, (byte) 62, (byte) 96, (byte) 130, (byte) 220, (byte) 35, (byte) 125, (byte) 159, (byte) 193, (byte) 66, (byte) 28, (byte) 254, (byte) 160, (byte) 225, (byte) 191, (byte) 93, (byte) 3, (byte) 128, (byte) 222, (byte) 60, (byte) 98, (byte) 190, (byte) 224, (byte) 2, (byte) 92, (byte) 223, (byte) 129, (byte) 99, (byte) 61, (byte) 124, (byte) 34, (byte) 192, (byte) 158, (byte) 29, (byte) 67, (byte) 161, (byte) 255, (byte) 70, (byte) 24,
            (byte) 250, (byte) 164, (byte) 39, (byte) 121, (byte) 155, (byte) 197, (byte) 132, (byte) 218, (byte) 56, (byte) 102, (byte) 229, (byte) 187, (byte) 89, (byte) 7, (byte) 219, (byte) 133, (byte) 103, (byte) 57, (byte) 186, (byte) 228, (byte) 6, (byte) 88, (byte) 25, (byte) 71, (byte) 165, (byte) 251, (byte) 120, (byte) 38, (byte) 196, (byte) 154, (byte) 101, (byte) 59, (byte) 217, (byte) 135, (byte) 4, (byte) 90, (byte) 184, (byte) 230, (byte) 167, (byte) 249, (byte) 27, (byte) 69, (byte) 198, (byte) 152, (byte) 122, (byte) 36, (byte) 248, (byte) 166, (byte) 68, (byte) 26, (byte) 153, (byte) 199, (byte) 37, (byte) 123, (byte) 58, (byte) 100, (byte) 134, (byte) 216, (byte) 91, (byte) 5, (byte) 231, (byte) 185, (byte) 140, (byte) 210, (byte) 48, (byte) 110, (byte) 237,
            (byte) 179, (byte) 81, (byte) 15, (byte) 78, (byte) 16, (byte) 242, (byte) 172, (byte) 47, (byte) 113, (byte) 147, (byte) 205, (byte) 17, (byte) 79, (byte) 173, (byte) 243, (byte) 112, (byte) 46, (byte) 204, (byte) 146, (byte) 211, (byte) 141, (byte) 111, (byte) 49, (byte) 178, (byte) 236, (byte) 14, (byte) 80, (byte) 175, (byte) 241, (byte) 19, (byte) 77, (byte) 206, (byte) 144, (byte) 114, (byte) 44, (byte) 109, (byte) 51, (byte) 209, (byte) 143, (byte) 12, (byte) 82, (byte) 176, (byte) 238, (byte) 50, (byte) 108, (byte) 142, (byte) 208, (byte) 83, (byte) 13, (byte) 239, (byte) 177, (byte) 240, (byte) 174, (byte) 76, (byte) 18, (byte) 145, (byte) 207, (byte) 45, (byte) 115, (byte) 202, (byte) 148, (byte) 118, (byte) 40, (byte) 171, (byte) 245, (byte) 23, (byte) 73, (byte) 8,
            (byte) 86, (byte) 180, (byte) 234, (byte) 105, (byte) 55, (byte) 213, (byte) 139, (byte) 87, (byte) 9, (byte) 235, (byte) 181, (byte) 54, (byte) 104, (byte) 138, (byte) 212, (byte) 149, (byte) 203, (byte) 41, (byte) 119, (byte) 244, (byte) 170, (byte) 72, (byte) 22, (byte) 233, (byte) 183, (byte) 85, (byte) 11, (byte) 136, (byte) 214, (byte) 52, (byte) 106, (byte) 43, (byte) 117, (byte) 151, (byte) 201, (byte) 74, (byte) 20, (byte) 246, (byte) 168, (byte) 116, (byte) 42, (byte) 200, (byte) 150, (byte) 21, (byte) 75, (byte) 169, (byte) 247, (byte) 182, (byte) 232, (byte) 10, (byte) 84, (byte) 215, (byte) 137, (byte) 107, 53};

    /**
     * 計算數組的CRC8校驗值
     *
     * @param data 需要計算的數組
     * @return CRC8校驗值
     */
    public static byte calcCrc8(byte[] data) {
        return calcCrc8(data, 0, data.length, (byte) 0);
    }

    /**
     * 計算CRC8校驗值
     *
     * @param data   數據
     * @param offset 起始位置
     * @param len    長度
     * @return 校驗值
     */
    public static byte calcCrc8(byte[] data, int offset, int len) {
        return calcCrc8(data, offset, len, (byte) 0);
    }

    /**
     * 計算CRC8校驗值
     *
     * @param data   數據
     * @param offset 起始位置
     * @param len    長度
     * @param preval 之前的校驗值
     * @return 校驗值
     */
    public static byte calcCrc8(byte[] data, int offset, int len, byte preval) {
        byte ret = preval;
        for (int i = offset; i < (offset + len); ++i) {
            ret = crc8_tab[(0x00ff & (ret ^ data[i]))];
        }
        return ret;
    }

    /**
     * 計算CRC8校驗值
     *
     * @param data
     *            數據
     * @param offset
     *            起始位置
     * @param len
     *            長度
     * @param preval
     *            之前的校驗值
     * @return 校驗值
     */
    public static byte parseCalcCrc8(byte[] data, int offset, int len, byte preval) {
        byte ret = preval;
        for (int i = offset; i < (offset + len); ++i) {
            ret = crc8_tab[(0x00ff & (ret ^ data[i]))];
        }
        return ret;
    }
}

小結:藍牙通信這塊的開發會遇到高地位的轉換、機型適配的煩惱(小米5.1、4.4手機),個人這塊工作暫時告一段落,用過api直接寫通信模塊,也用過第三方框架,感覺都是麻煩事,還要動態權限一對代碼,希望有一天有位大神閃亮登場,搞個比較牛逼的開源庫讓我一鍵調用就好了。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved