Android 蓝牙4.0入门开发

本文针对一对一的蓝牙进行通讯,适合没有开发过蓝牙的同学来看,也适合大部分物联网简单开发,没有深入蓝牙的开发。对于没有开发过蓝牙的来说,我先说下逻辑。比如先拿自己手机的蓝牙来说,打开蓝牙,列出列表,包含已经配对过的和可用设备, 点击其中一个进行配对,配对完成就可以进行文件传输等通信功能了。所以对于蓝牙开发,大致以下步骤

1.打开蓝牙

2.蓝牙扫描,列出可用设备

3.关闭蓝牙扫描(不关闭会一直扫描)

4.找到目标蓝牙设备进行连接

5.连接成功,进行通信

6.关闭蓝牙释放资源

接下来我们要根据上面6个步骤进行API的说明,在说明前,我先说明一下

(1)Service蓝牙功能集合,每一个Service都有一个UUID,

(2)Characteristic 在service中也有好多个Characteristic 独立数据项,其中也有独立UUID

上面的两个uuid需要从硬件工程师中获取,这样你才能匹配到你要的。

(3)BluetoothAdapter 蓝牙的打开关闭等基本操作

(4)BluetoothDevice 蓝牙设备,扫描到的

(5)BluetoothGatt 蓝牙连接重连断开连接等操作的类

(6)BluetoothGattCharacteristic 数据通信操作类,读写等操作

1.打开蓝牙

需要权限

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

BluetoothAdapter 这个类就是蓝牙的基本操作,比如打开关闭等

初始化蓝牙,得到BluetoothAdapter

private void initBlueTooth() {
    BluetoothManager manager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);
    if (manager != null) {
        bluetoothAdapter = manager.getAdapter();
        if (bluetoothAdapter != null) {
            //蓝牙没有打开
            if (!bluetoothAdapter.isEnabled()) {
                openBle();
            } else {
                Toast.makeText(MainActivity.this, "蓝牙已打开", Toast.LENGTH_SHORT).show();
                scanLeDevice(true);
            }
        } else {
            openBle();
        }
    }
}

打开蓝牙

   private void openBle() {
   //以下两种方式 第二种方式在onActivityResult处理回调
//        boolean enable = bluetoothAdapter.enable();//打开蓝牙'直接打开,用户不知权,用于定制系统'
//        Toast.makeText(MainActivity.this, "正在打开蓝牙", Toast.LENGTH_SHORT).show();
//        if (enable) {
//            Log.e("open",enable+"");
//            new Handler().postDelayed(new Runnable() {
//                @Override
//                public void run() {
//                    scanLeDevice(true);
//                }
//            },2000);
//
//        }
​
        //提示用户正在打开蓝牙
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
​
​
    }

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode == RESULT_OK && requestCode == REQUEST_ENABLE_BT) {
        scanLeDevice(true);
    }
}

2.3.打开或者停止扫描,放在同一个方法中

/**
 * 打开或者停止扫描
 *
 * @param enable
 */
private void scanLeDevice(final boolean enable) {
​
    if (enable) {
        mScanning = true;
        // 定义一个回调接口供扫描结束处理
        bluetoothAdapter.startLeScan(mLeScanCallback);
        // 预先定义停止蓝牙扫描的时间(因为蓝牙扫描需要消耗较多的电量)
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                mScanning = false;
                bluetoothAdapter.stopLeScan(mLeScanCallback);
            }
        }, SCAN_PERIOD);
​
    } else {
        mScanning = false;
        bluetoothAdapter.stopLeScan(mLeScanCallback);
    }
}

扫描回调,回调之后得到 BluetoothDevice 的集合,可以放到列表中去

/**
 * 扫描回调
 */
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice bluetoothDevice, int i, byte[] bytes) {
        if (bluetoothDevice.getName() != null) {
            if (!bluetoothDeviceArrayList.contains(bluetoothDevice)) {//去下重
                bluetoothDeviceArrayList.add(bluetoothDevice);
            }
            Log.e(TAG, "scan--" + bluetoothDevice.getName());
        }
    }
};

4.5.进行蓝牙连接

/**
 * 连接蓝牙 参数为目标设备
/
public void connectBle(BluetoothDevice bluetoothDevice) {
    mBluetoothDevice = bluetoothDevice;
    if (bluetoothDevice != null) {
        //第二个参数 是否重连
        mBluetoothGatt = bluetoothDevice.connectGatt(MainActivity.this, false, bluetoothGattCallback);
    }
​
}

连接回调

  /**
     * 蓝牙连接成功回调
     */
​
    private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
        @Override
        public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
            super.onPhyUpdate(gatt, txPhy, rxPhy, status);
        }
​
        @Override
        public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
            super.onPhyRead(gatt, txPhy, rxPhy, status);
        }
​
        //不要执行耗时操作
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            if (newState == BluetoothProfile.STATE_CONNECTED) {//连接成功
                Log.e(TAG, "onConnectionStateChange 蓝牙连接");
                //这里要执行以下方法,会在onServicesDiscovered这个方法中回调,如果在                        //onServicesDiscovered方法中回调成功,设备才真正连接起来,正常通信
                gatt.discoverServices();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                Log.e(TAG, "onConnectionStateChange 蓝牙断连");
                if (mBluetoothDevice != null) {
                    //关闭当前新的连接
                    gatt.close();
                    characteristic = null;
                 
                }
​
            }
​
        }
​
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            //回调之后,设备之间才真正通信连接起来
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.e(TAG, "onServicesDiscovered 蓝牙连接正常");
                BluetoothGattService service = gatt.getService(UUID.fromString(BleConstantValue.serverUuid));//uuid从硬件工程师获取
                characteristic = service.getCharacteristic(UUID.fromString(BleConstantValue.charaUuid));
                gatt.readCharacteristic(characteristic);//执行之后,会执行下面的                onCharacteristicRead的回调方法
                //设置通知,一般设备给手机发送数据,需要以下监听
                setCharacteristicNotification(characteristic, true);
                //耗时操作,如果有ui操作,需要用到handler
                adapterFreshHandler.sendEmptyMessage(0);
            } else {
                Log.e(TAG, "onServicesDiscovered 蓝牙连接失败");
            }
​
        }
​
        //这个方法一般用不到
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            Log.e(TAG, "callback characteristic read status " + status
                    + " in thread " + Thread.currentThread());
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.e(TAG, "read value: " + characteristic.getValue());
            }
​
​
        }
​
        //这个方法是写入数据时的回调,可以和你写入的数据做对比
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            Log.e(TAG, "write value: " + FormatUtil.bytesToHexString(characteristic.getValue()));
        }
​
        //设备发出通知时会调用到该接口,蓝牙设备给手机发送数据,在这个方法接收
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            Log.e(TAG, "接收:" + FormatUtil.bytesToHexString(characteristic.getValue()));//byte[]转为16进制字符串
            bleWriteReceiveCallback();
        }
    };
​
/**
* 设置通知
/
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
        if (bluetoothAdapter == null || mBluetoothGatt == null) {
            return;
        }
        mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
    }

参考写入指令

/**
 * 写入命令
 */
private void write(byte[] cmd) {
    if (characteristic != null) {
        // 发出数据
        characteristic.setValue(cmd);
        if (mBluetoothGatt.writeCharacteristic(characteristic)) {
            Log.e(TAG, "写入成功");
        } else {
            Log.e(TAG, "写入失败");
        }
    } else {
        Toast.makeText(MainActivity.this, "蓝牙未连接", Toast.LENGTH_SHORT).show();
    }
}

6.断开连接 释放资源

/**
 * 断开蓝牙设备
 */
public void bleDisConnectDevice(BluetoothDevice device) {
    if (mBluetoothGatt != null) {
        mBluetoothGatt.disconnect();
    }
}
​
 /**
     * 释放资源 
     */
​
    private void releaseResource() {
        Log.e(TAG, "断开蓝牙连接,释放资源");
        if (mBluetoothGatt != null) {
            mBluetoothGatt.disconnect();
            mBluetoothGatt.close();
        }
    }

最后别忘了蓝牙广播:

 /**
     * 注册蓝牙监听广播
     */
    private void registerBleListenerReceiver() {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED);
        intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
        registerReceiver(bleListenerReceiver, intentFilter);
    }
 /**
     * 蓝牙监听广播接受者
     */
    private BroadcastReceiver bleListenerReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
        //连接的设备信息
        BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        Log.e(TAG, "蓝牙广播" + action);

        if (mBluetoothDevice != null && mBluetoothDevice.equals(device)) {
            Log.e(TAG, "收到广播-->是当前连接的蓝牙设备");

            if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
                Log.e(TAG,"广播 蓝牙已经连接");

            } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
                Log.e(TAG,"广播 蓝牙断开连接");
            }
        } else {
            Log.e(TAG, "收到广播-->不是当前连接的蓝牙设备");
        }

        if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
            int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
            switch (state) {
                case BluetoothAdapter.STATE_OFF:
                    Log.e(TAG, "STATE_OFF 蓝牙关闭");
                    adapter.clear();
                    releaseResource();
                    break;
                case BluetoothAdapter.STATE_TURNING_OFF:
                    Log.e(TAG, "STATE_TURNING_OFF 蓝牙正在关闭");
                    //停止蓝牙扫描
                    scanLeDevice(false);
                    break;
                case BluetoothAdapter.STATE_ON:
                    Log.d(TAG, "STATE_ON 蓝牙开启");
                    //扫描蓝牙设备
                    scanLeDevice(true);
                    break;
                case BluetoothAdapter.STATE_TURNING_ON:
                    Log.e(TAG, "STATE_TURNING_ON 蓝牙正在开启");
                    break;
            }
        }
        }
    };

所有完成,基本的蓝牙操作及通信功能,通信协议需要和蓝牙硬件厂商工程师获取。本文适合蓝牙入门开发,如有错误请指正 https://github.com/Leaderpaking/ble

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353