1. 程式人生 > >android 藍芽 ble

android 藍芽 ble

    如果不瞭解androd ble就先學ble整個得大致通訊流程,如果大致通訊流程瞭解了,就利用封裝好得 ble庫,應該對你幫助很大。

android ble連線資料大致也沒幾個步驟,但是對於剛涉水藍芽的小夥伴可能會一臉矇蔽,怎麼弄就是不成功,下邊講解,直接從程式碼中講解

1.封裝號的核心ble通訊層,這個可以解決,完整流程通訊和長時間通訊

package com.reformer.bles;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Handler;
import android.os.Message;

import com.reformer.utils.commen.LogUtil;

import java.util.UUID;

public class Ble extends BluetoothGattCallback {
    private UUID UUID_SERVICE = UUID.fromString("0000fde6-0000-1000-8000-00805f9b34fb");
    private UUID UUID_SERVICE2 = UUID.fromString("0000fdeb-0000-1000-8000-00805f9b34fb");//梯控
    private UUID UUID_WRITE = UUID.fromString("0000fde7-0000-1000-8000-00805f9b34fb");
    private UUID UUID_INDICATE = UUID.fromString("0000fde8-0000-1000-8000-00805f9b34fb");
    private UUID UUID_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");

    private Context mCtx;
    private BluetoothAdapter mBtAdapter;
    private String mAddress;
    private OnBleListener mListner;
    private BluetoothGattCharacteristic mWriteChar;
    private BluetoothGatt mBluetoothGatt;
    private byte[] tempCmd2 = null;

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    removeMessages(1);
                    break;
            }
        }
    };

    public Ble(Context ctx) {
        final BluetoothManager bluetoothManager = (BluetoothManager)
                ctx.getSystemService(Context.BLUETOOTH_SERVICE);
        mBtAdapter = bluetoothManager.getAdapter();
        mCtx = ctx;
    }

    public boolean scanStart() {
        LogUtil.d("掃描");
        return mBtAdapter != null && mBtAdapter.startLeScan(null, m18LeScanCallback);
    }

    public void scanStop() {
        if (mBtAdapter != null)//華為:G7-Ul20;CHM-00;G7-TL00;藍芽4.0,會出現空指標異常
            mBtAdapter.stopLeScan(m18LeScanCallback);
    }

    public boolean writeChar(byte[] bytes) {
        LogUtil.d("資料寫入___" + Utils.bytes2String(bytes));
        if (mWriteChar == null || mBluetoothGatt == null || bytes == null)
            return false;
        if (bytes.length <= 20) {
            return writeCharInner(bytes);
        } else {
            byte[] tempCmd1 = new byte[20];
            for (int i = 0; i < 20; i++)
                tempCmd1[i] = bytes[i];

            tempCmd2 = new byte[bytes.length - 20];
            for (int i = 0; i < bytes.length - 20; i++)
                tempCmd2[i] = bytes[i + 20];
            return writeCharInner(tempCmd1);
        }
    }

    private boolean writeCharInner(byte[] bytes) {//寫特徵值
        LogUtil.d("資料寫入_分包__" + Utils.bytes2String(bytes));
        return mWriteChar.setValue(bytes) && mBluetoothGatt.writeCharacteristic(mWriteChar);
    }

    public int connect(String address) {
        LogUtil.d("連線");
        isOk = true;
        return connectGatt(address);
    }

    public void close() {
        LogUtil.d("關閉");
        isOk = false;
        closeGatt();
    }

    private void closeGatt() {
        if (mBluetoothGatt != null) {
            mBluetoothGatt.disconnect();
            if (mBluetoothGatt != null) {
                mBluetoothGatt.close();
                if (mBluetoothGatt != null)
                    mBluetoothGatt = null;
            }
        }
        if (mBtAdapter != null) {
            if (mAddress != null && !mAddress.equals("")) {
                BluetoothDevice device = mBtAdapter.getRemoteDevice(mAddress);
                if (device != null) {
                    if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
                        try {
                            Utils.removeBond(BluetoothDevice.class, device);//適配魅族某款手機
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    private int connectGatt(String address) {
        if (!isOk)
            return 400;
        mAddress = address;
        if (mListner != null)
            mListner.onConnectStatus(1);
        closeGatt();
        if (mBtAdapter == null) {
            return 201;
        }
        BluetoothDevice device = mBtAdapter.getRemoteDevice(mAddress);
        if (device == null) {
            return 202;
        }
        mBluetoothGatt = device.connectGatt(mCtx, false, this);//第一步:連線gatt:獲取gatt管道
        return 400;
    }

    private boolean isOk = false;

    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                        int newState) {
        if (newState == BluetoothProfile.STATE_CONNECTED) {
            LogUtil.d("連線狀態成功");
            gatt.discoverServices();//第二步:gatt連線成功,發現服務
        } else {
            LogUtil.d("連線狀態失敗");
            connect(mAddress);
        }
    }

    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        if (status == 0) {//第三步:發現服務成功;獲取服務,訂閱特徵值
            LogUtil.d("連線服務成功");
            BluetoothGattService service = gatt.getService(UUID_SERVICE);
            mWriteChar = service.getCharacteristic(UUID_WRITE);
            BluetoothGattCharacteristic mIndicateChar = service.getCharacteristic(UUID_INDICATE);
            gatt.setCharacteristicNotification(mIndicateChar, true);//寫入特徵值改變時,通知
            BluetoothGattDescriptor descriptor = mIndicateChar.getDescriptor(UUID_DESCRIPTOR);//獲取修飾
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);//修飾配置
            gatt.writeDescriptor(descriptor);
        } else {
            LogUtil.d("連線服務失敗");
            connect(mAddress);
        }
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        super.onDescriptorWrite(gatt, descriptor, status);
        if (status == 0 && mListner != null) {//第四步:訂閱特徵值成功;傳送資料;交換隨即金鑰
            LogUtil.d("訂閱成功");
            mListner.onReady();
            mListner.onConnectStatus(0);
        } else {
            connect(mAddress);
        }
    }

    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        if (status == 0 && mListner != null) {//第六步:寫入第一包資料後睡眠,傳送第二包資料
            if (tempCmd2 != null) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                byte[] a = tempCmd2;
                tempCmd2 = null;
                writeCharInner(a);
                LogUtil.d("第二包資料寫入" + Utils.bytes2String(a));
            }
        } else {
            connect(mAddress);
        }
    }

    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        LogUtil.d("資料接受成功");
        if (mListner != null)
            mListner.onReceive(characteristic.getValue());
    }

    public void setOnBleListener(OnBleListener bleListener) {
        mListner = bleListener;
    }

    public abstract static class OnBleListener {

        public void onConnectStatus(int status) {

        }

        public void onReady() {

        }

        public abstract void onReceive(byte[] value);

        public abstract void onScanResult(String name, String address, int rssi, byte[] mac);
    }

    private BluetoothAdapter.LeScanCallback m18LeScanCallback = new BluetoothAdapter.LeScanCallback() {

        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] bytes) {
            LogUtil.d("device__" + device.getAddress().toString());
            if (mListner == null)
                return;
            byte[] macTemp = new byte[9];
            System.arraycopy(bytes, 18, macTemp, 0, 9);
            mListner.onScanResult(device.getName(), device.getAddress(), rssi, macTemp);
        }
    };
}
//    private ScanCallback m21ScanCallback = new ScanCallback() {
//        @Override
//        public void onScanResult(int callbackType, ScanResult result) {
//            byte[] scanRecord = result.getScanRecord().getBytes();
//            BluetoothDevice device = result.getDevice();
//            int rssi = result.getRssi();
////            foundNewDevice(scanRecord, device, rssi);
//        }
//    };

2.通過ble傳送資料,這個類,接受資料處理資料

package com.reformer.bles;

import android.content.Context;
import android.os.Handler;
import android.os.Message;

import com.reformer.bles.encrypt.KeyBase;
import com.reformer.bles.encrypt.KeyKeyfree;
import com.reformer.utils.commen.LogUtil;

import java.util.ArrayList;

public class Presenter {
    private byte[] mMac;
    private OnStateListener mOnStateListener;
    private OnScanListener mOnScanListener;
    private ArrayList<BleBean> mScans = new ArrayList<BleBean>();
    private Ble mBle;
    public int mTimeOut = 6000;//超時時間
    private KeyBase mKey;

    private String bytes2HexString(byte[] src) {//位元組陣列轉16進位制字串
        StringBuffer sb = new StringBuffer();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                sb.append(0);
            }
            sb.append(hv);
        }
        return sb.toString();
    }

    public Presenter(Context ctx) {
        mKey = new KeyKeyfree();
        mBle = new Ble(ctx);
        mBle.setOnBleListener(new Ble.OnBleListener() {

            @Override
            public void onReady() {
                mBle.writeChar(mKey.get1040());
            }

            @Override
            public void onReceive(final byte[] receiveDatas) {
                switch (receiveDatas[3]) {
                    case (byte) 4://取開門命令相關裝置引數
                        byte[] devRand = new byte[4];//隨機數
                        System.arraycopy(receiveDatas, 6, devRand, 0, 4);//取出隨機數
                        mBle.writeChar(mKey.get1050(mMac, devRand));
                        break;
                    case (byte) 8:
                    case (byte) 9:
                    case (byte) 5://輸出口控制
                        close();
                        if (mOnStateListener != null)
                            mOnStateListener.onState(receiveDatas[5]);
                        break;
                    case (byte) 6://查詢外設資訊
                        System.arraycopy(receiveDatas, 6, mMac, 0, 9);//MAC_LENGTH=9
                        break;
                    case (byte) 7://設定裝置初始金鑰 (預留,只是配置工具使用)
                        close();
                        if (mOnStateListener != null)
                            mOnStateListener.onState(300);
                        break;
                    default:
                        close();
                        break;
                }
            }

            @Override
            public void onScanResult(String name, String address, int rssi, byte[] mac) {
                if (mOnScanListener != null) {
                    if (!Utils.containBytesFromDevLst(mac, mScans)) {//是否為掃描記錄列表中的裝置
                        final BleBean bean = new BleBean(name, address, rssi, mac);
                        mScans.add(bean);
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                mOnScanListener.onScan(mScans, bean);
                            }
                        });
                    }
                }
            }
        });
    }

    public void setOnScanListener(OnScanListener obdll) {
        this.mOnScanListener = obdll;
    }

    public void setOnStateListener(OnStateListener osl) {
        this.mOnStateListener = osl;
    }

    public void close() {
        mBle.close();
        mHandler.removeMessages(1);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1://超時
                    removeMessages(1);
                    if (mOnStateListener != null)
                        mOnStateListener.onState(200);
                    close();
                    break;
                case 2:
                    scanStop();
                    break;
            }
        }
    };

    public boolean scanStart(int time) {
        scanStop();
        mBle.close();
        mScans.clear();//清空掃描記錄列表
        mHandler.removeMessages(2);
        mHandler.sendEmptyMessageDelayed(2, time);
        return mBle.scanStart();
    }

    public void scanStop() {
        mBle.scanStop();
    }

    public void init(byte[] devPassword, byte[] phone, int time) {
        mKey.init(devPassword, phone, time);
    }

    public void verifyData(byte[] macs) {
        mHandler.sendEmptyMessageDelayed(1, mTimeOut);  //超時
        scanStop();
        mMac = macs;
        //校驗mac
        String address = Utils.getAddressFromMac(macs, mScans);
        if (address.equals("") && mOnStateListener != null) {
            mOnStateListener.onState(102);//mac不再搜尋列表
            return;
        }
        if (mOnStateListener != null) {//設定密碼的動作,無需再校驗其他資料
            mOnStateListener.onState(mBle.connect(address));
        }
    }

}
3.也就是外部呼叫的層,方便其他人呼叫,也可以保證隔離開核心程式碼,尤其涉及保險櫃,門禁,等祕密裝置的加密程式碼,可以完整混淆
package com.reformer.bles;

import android.content.Context;

public class BleKey {
    private Presenter mPresenter;

    public BleKey(Context ctx) {
        mPresenter = new Presenter(ctx);
    }

    public void scanStart() {
        mPresenter.scanStart(5000);
    }

    //掃描開始
    public void scanStart(int time) {
        mPresenter.scanStart(time);
    }

    public void scanStop() {
        mPresenter.scanStop();
    }

    /**
     * 釋放對應樓層許可權
     */
    public void ctrlTK(String mac, String phone, int floor) {
//        mPresenter.verifyData(Utils.mac2Bytes(mac, 18), "", 0, Utils.mac2Bytes("0" + phone, 12), 1080, floor);
    }

    /**
     * 釋放呼梯控制權限  0是up,1是down
     */
    public void ctrlHT(String mac, String phone, int up) {
//        mPresenter.verifyData(Utils.mac2Bytes(mac, 18), "", 0, Utils.mac2Bytes("0" + phone, 12), 1090, up);
    }

    public void openDoor(String mac, int time) {
        byte[] macs = Utils.stringToBytes(mac, 18);
//        byte[] passwords = Utils.mac2Bytes("3131313131313131" + "D67D67966DA21300", 32);
//        int mOutputTime = time != 0x0a ? time : 0x0a; //設定輸出口時間 //不等於預設值時,對其賦值
        mPresenter.init(null,null,time);
        mPresenter.verifyData(macs);
    }

    /**
     * 設定密碼
     */
    public void setPassword(String mac, String password) {
//        mPresenter.verifyData(Utils.mac2Bytes(mac, 18), password, 0, null, 1070, 0);
    }

    /**
     * 開門完成監聽
     */
    public void setOnStateListener(OnStateListener ocl) {
        mPresenter.setOnStateListener(ocl);
    }

    /**
     * 裝置列表實時監聽
     */
    public void setOnScanListener(OnScanListener oblc) {
        mPresenter.setOnScanListener(oblc);
    }

}
4.提供外部的掃描監聽
package com.reformer.bles;

import java.util.ArrayList;


public interface OnScanListener {

    public void onScan(ArrayList<BleBean> mScans, BleBean bean);
}

5.提供給外部的狀態監聽
package com.reformer.bles;

public interface OnStateListener {
    public void onState(int step);
}

6.涉及部分工具類
package com.reformer.bles;

import android.bluetooth.BluetoothDevice;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;


public class Utils {
    public static String bytes2String(byte[] msg) {
        StringBuffer s = new StringBuffer();
        for (int i = 0; i < msg.length; i++) {
            s.append(Integer.toHexString(((int) msg[i])));
            s.append(",");
        }
        return s.toString();
    }

    /**
     * 5      * byte陣列轉換成16進位制字串
     * 6      * @param src
     * 7      * @return
     * 8
     */
    public static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }


    public static byte[] stringToBytes(String outStr, int Length) {
        if (outStr.length() != Length)
            return null;
        int len = outStr.length() / 2;
        byte[] mac = new byte[len];
        String s = null;
        for (int i = 0; i < len; i++) {
            s = outStr.substring(i * 2, i * 2 + 2);
            if (Integer.valueOf(s, 16) > 0x7F) {
                mac[i] = (byte) (Integer.valueOf(s, 16) - 0xFF - 1);
            } else {
                mac[i] = Byte.valueOf(s, 16);
            }
        }
        return mac;
    }

    public static String bytesToString(byte[] mac) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < mac.length; i++) {
            if (mac[i] < 0)
                mac[i] += 256;
            sb.append(padLeft(Integer.toHexString(mac[i]), 2));
        }
        return sb.toString().toUpperCase();
    }

    public static String padLeft(String str, int len) {
        if (str.length() > 2)
            str = str.substring(str.length() - 2);
        String pad = "0000000000000000";
        return len > str.length() && len <= 16 && len >= 0 ? pad.substring(0, len - str.length()) + str : str;
    }

    public static boolean containBytesFromBytesLst(byte[] bytes, ArrayList<byte[]> arrlt) {
        if (arrlt == null)
            return true;
        for (byte[] mac : arrlt) {
            if (Arrays.equals(mac, bytes))
                return true;
        }
        return false;
    }

    public static boolean containBytesFromDevLst(byte[] bytes, ArrayList<BleBean> arrlt) {
        if (arrlt == null)
            return false;
        for (BleBean dev : arrlt) {
            if (Arrays.equals(dev.mac, bytes))
                return true;
        }
        return false;
    }

    public static String getAddressFromMac(byte[] mac, ArrayList<BleBean> list) {
        for (BleBean devContext : list) {
            if (Arrays.equals(mac, devContext.mac))
                return devContext.address;
        }
        return "";
    }

    public static byte[] verifyPhone(String phone) {
        if (phone != null) {
            //校驗電話號碼
            try {
                Integer.parseInt(phone);
            } catch (Exception e) {
                e.printStackTrace();
                return null;//手機號碼不是整數
            }
            StringBuffer sbPhone = new StringBuffer();
            if (phone.length() != 12) {
                if (phone.length() == 11) {
                    sbPhone.append("0").append(phone);
                } else {
                    return null;//手機號碼長度不對
                }
            } else {
                sbPhone.append(phone);
            }
            byte[] bPhone = Utils.stringToBytes(sbPhone.toString(), 12);//校驗手機號長度
            if (bPhone == null) {
                return null;//105手機號碼長度不對
            }
            return bPhone;
        }
        return null;
    }

    public static boolean createBond(Class btClass, BluetoothDevice btDevice) throws Exception {
        Method createBondMethod = btClass.getMethod("createBond");
        Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice);
        return returnValue.booleanValue();
    }

    public static boolean removeBond(Class btClass, BluetoothDevice btDevice) throws Exception {
        Method removeBondMethod = btClass.getMethod("removeBond");
        Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice);
        return returnValue.booleanValue();
    }
}


7.通訊的裝置物件
package com.reformer.bles;

public class BleBean {
    public String name;
    public String address;
    public int rssi = 0;
    public byte[] mac = new byte[9];

    public BleBean() {
    }

    public BleBean(String name, String address, int rssi, byte[] mac) {
        this.name = name;
        this.address = address;
        this.rssi = rssi;
        this.mac = mac;
    }

    public String getMacStr() {
        if (mac == null) {
            return null;
        }
        return Utils.bytesToString(mac);
    }

}