安卓蓝牙服务端(从端、从设备)编程示例

在网上找了很多安卓蓝牙服务端(从设备)的编程示例,绝大部分都是客户端的示例,偶尔有一两个服务端的盒子,却也不完整,经过一系列折腾,总算是搞通了一个相对完整的例子,写出来给大家参考。

首先说一下作为服务端的几个基本过程:

第一步 申请权限

在MainActivity里面:

    private void myRequetPermission() {
        if (ContextCompat.checkSelfPermission(this, 
                ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH) != PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_ADMIN) != PERMISSION_GRANTED
                || ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{
                            Manifest.permission.ACCESS_COARSE_LOCATION,
                            Manifest.permission.BLUETOOTH,
                            Manifest.permission.BLUETOOTH_ADMIN,
                            Manifest.permission.ACCESS_FINE_LOCATION
                    },
                    1);
        }
    }

把myRequestPermisssion方法在onCreate()方法里调用一下。

同时接收一下权限申请的结果:

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode != 1) {
            return;
        }
        for (int i = 0; i < permissions.length; i++) {
            if (grantResults[i] == PERMISSION_GRANTED) {//选择了“始终允许”
                Toast.makeText(this, "" + "权限" + permissions[i] + "申请成功", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "" + "权限" + permissions[i] + "申请失败", Toast.LENGTH_LONG).show();

                if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i])) {//用户选择了禁止不再询问
                    Log.e("PPPPP", "用户禁用权限并不再询问:" + permissions[i]);
                } else {//选择禁止
                    Log.e("PPPPP", "用户选择了此次禁止权限");
                }
            }
        }
    }

第二步 创建安卓Service,在Service中初始化需要用的一些蓝牙相关成员

注:也不一定要创建安卓的Service,可以直接在Activity中实现下面的步骤,根据需要和习惯来吧。


    private BluetoothAdapter mBluetoothAdapter; //蓝牙适配器对象
    private BluetoothManager bluetoothManager = null; //系统的蓝牙管理器
    private BluetoothLeAdvertiser mBluetoothLeAdvertiser; //作为蓝牙服务设备的广播发送器
    private BluetoothGattServer mBluetoothGattServer; //蓝牙服务器对象
    private BluetoothGattCharacteristic mCharacteristicRead; //蓝牙读数据的特征值
    private BluetoothGattCharacteristic mCharacteristicWrite; //蓝牙写(发送)数据的特征值
    private BluetoothGattService mGattService; //系统蓝牙服务吧?

    //蓝牙服务的ID
    private static UUID UUID_SERVER = UUID.fromString("0000ffe0-0000-1000-8000-00805f9b34fb");
    //蓝牙读特征值的UUID,具体的意义和作用不在这里介绍了,我这写死了。
    private static UUID UUID_CHARREAD = UUID.fromString("0000fff1-0000-1000-8000-00805f9b34fb");
    //蓝牙写特征值的UUID。
    private static UUID UUID_CHARWRITE = UUID.fromString("0000ffe1-0000-1000-8000-00805f9b34fb");
    //蓝牙的描述信息特征值UUID
    private static UUID UUID_DESCRIPTOR = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");

第三步 初始化蓝牙并开启蓝牙广播

初始化蓝牙广播的主要目的是为了让其它设备扫描能发现自己。有些时候可能不要广播也可以由客户端主动来连接,这个情况可能主要用在不想暴露自己的蓝牙信息的场景,我没有试过。

    private void initBluetooth() {
        //获取蓝牙适配器对象
        bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();
        if (mBluetoothAdapter == null) {
            Toast.makeText(context, "蓝牙未启用", Toast.LENGTH_LONG);
            //打开系统蓝牙,此处做法并不是很规范,因为开启蓝牙后其实还是需要再继续往下面进行下面的代码的,这里大家将就一下,认为蓝牙打开成功了吧。
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivity(enableBtIntent);
            //TODO
            // startActivityForResult(enableBtIntent, 1);
        }

        //如果没有启用蓝牙,启用蓝牙,感觉有点奇怪,跟上面有点重复,但是没影响
        if (!mBluetoothAdapter.isEnabled()) {
            Log.e("BBBBB", "启用蓝牙");
            mBluetoothAdapter.enable();
        }

        //设置蓝牙名字,在别人扫描你的时候你显示的名字,如果你不要设置蓝牙名字,那跳过这一步
        Log.e("BBBBB", "蓝牙名字:"+mBluetoothAdapter.getName());
        mBluetoothAdapter.setName("CM-1900-1976");

        //获取广播对象
        mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
        if(mBluetoothLeAdvertiser == null){
            Log.e("BBBBB", "设备不支持广播");
            Toast.makeText(this,"设备不支持蓝牙广播",Toast.LENGTH_SHORT).show();
            return;
        }

        //准备广播的参数,最后这个setTimeout是设置广播 发送的时间间隔吧,0应该是默认值,
        //如果填的太高,可能会导致别人扫描的时候不一定能扫描得到你
        AdvertiseSettings.Builder settingsBuilder=new AdvertiseSettings.Builder();
        settingsBuilder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
                .setConnectable(true)
                .setTimeout(0);
        AdvertiseSettings advertiseSettings = settingsBuilder.build();

        //初始化广播数据
        AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
        dataBuilder.setIncludeDeviceName(true)
                .addServiceUuid(new ParcelUuid(UUID_SERVER))
                .setIncludeTxPowerLevel(true);
        AdvertiseData advertiseData = dataBuilder.build();

        //初始化广播的应答数据(当别人扫描时,我们返回这个数据)
        AdvertiseData scanResponse = new AdvertiseData.Builder()
                //.addManufacturerData(MANUFACTURE_ID_RESPONSE, SPECIFIC_DATA_RESPONSE) //可以加上设备的制造商信息
                .addServiceUuid(new ParcelUuid(UUID_SERVER))
                .build();
        //开始广播, mAdertiseCallback是开启广播时的回调,我们在下面定义它
        mBluetoothLeAdvertiser.startAdvertising(advertiseSettings, advertiseData, scanResponse, mAdertiseCallback);
    }

    //广播成功或失败时的回调处理
    private  AdvertiseCallback mAdertiseCallback = new AdvertiseCallback() {
        @Override
        public void onStartFailure(int errorCode){
            super.onStartFailure(errorCode);
            Log.e("BBBBB","广播失败:"+errorCode);
        }
        @Override
        public void onStartSuccess(AdvertiseSettings settingsInEffect){
            super.onStartSuccess(settingsInEffect);
            Log.e("BBBBB","服务端的广播成功开启");
            //广播开启成功后,我们初始化并蓝牙服务(实现见下面)
            initBleServer(getBaseContext());
        }
    };

第四步 添加启动蓝牙服务

    //添加一个蓝牙服务,该服务下两个特征,一个读特征、一个写特征。
    private void initBleServer(Context context){
        //创建蓝牙服务对象
        mGattService = new BluetoothGattService(UUID_SERVER, BluetoothGattService.SERVICE_TYPE_PRIMARY);

        //创建读特征值,主要用于客户端获取蓝牙的基本信息
        mCharacteristicRead = new BluetoothGattCharacteristic(UUID_CHARREAD, BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ);
        BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(UUID_DESCRIPTOR,
                BluetoothGattCharacteristic.PERMISSION_READ);
        mCharacteristicRead.addDescriptor(descriptor);
        //给特征值添加描述符,我理解相当于告诉客户端这个特征值能干嘛,在这个例子里这个特征值就纯用于发送蓝牙的信息到客户端
        mGattService.addCharacteristic(mCharacteristicRead);

        //创建写特征值,这个特征值有读属性(READ),有写属性(WRITE,发送数据),还可以用于通知客户端接收数据(NOTIFY)
        mCharacteristicWrite = new BluetoothGattCharacteristic(UUID_CHARWRITE,
                BluetoothGattCharacteristic.PROPERTY_WRITE
                        | BluetoothGattCharacteristic.PROPERTY_READ
                        | BluetoothGattCharacteristic.PROPERTY_NOTIFY,
                BluetoothGattCharacteristic.PERMISSION_WRITE
                        |BluetoothGattCharacteristic.PERMISSION_READ);
        Log.e("BBBBB", "写UUID:"+UUID_CHARWRITE);
        //设置描述符的值,此处要注意:如果要用通知方式向客户端发数据,需要加上此两行
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        mCharacteristicWrite.addDescriptor(descriptor);
        //mBluetoothGatt.writeDescriptor(descriptor);

        //mCharacteristicWrite.setCharacteristicNotification(characteristic, enabled);
        //将写特征值加入到服务
        mGattService.addCharacteristic(mCharacteristicWrite);

        //启动蓝牙服务,完成这个步骤后,我们就可以收发数据了
        //注意:此处需要传入gattServerCallback回调,它的实现在下面
        mBluetoothGattServer = bluetoothManager.openGattServer(context, gattServerCallback);
        mBluetoothGattServer.addService(mGattService);
        Log.e("BBBBB","初始化服务成功:initServices ok");
    }

第五步:通过服务端发送数据

服务端(设备端)向客户端发送蓝牙数据时,有两种方式,一种是客户端主动向服务端发一个读的请求,服务端在收到读请求时,在服务的回调处理里去处理这个请求操作(见下面第六步中的回调),向客户端发送其所需要的数据。另一种方式是服务端通过通知方式主动向客户端发送数据,任何时候调用下面代码即可:

String str = "这是要发送的数据";
mCharacteristicWrite.setValue(str.getBytes());
Log.e("BBBBB", "发送数据到:" + bleClientDevice.getAddress());
//bleClientDevice是已连接到服务端的蓝牙客户端设备
mBluetoothGattServer.notifyCharacteristicChanged(bleClientDevice, mCharacteristicWrite, true);

第六步 实现蓝牙服务的回调处理

蓝牙服务的回调处理主要有以下几个,有点多,基本都在这了:

1. 客户端设备连接状态改变

2. 客户端读的请求的处理(向客户端发数据)

3. 客户端写的请求的处理(接收客户端的数据)

4. 客户端查询特征值描述的处理

5. 客户端描述请求被写入的处理

6. 蓝牙服务添加完成结果的处理

7. 向客户端发送通知数据的结果处理

8. 执行写数据的处理(我未用到过)

    //clients是客户端蓝牙设备的MAP,我这个例子里将所有连上来的客户端都放到这个MAP里了
    private ConcurrentHashMap<String, BluetoothDevice> clients = new ConcurrentHashMap<>();
    //服务事件的回调
    private BluetoothGattServerCallback gattServerCallback = new BluetoothGattServerCallback() {
        //1、首先是连接状态的回调
        @Override
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
            super.onConnectionStateChange(device, status, newState);
            Log.e("BBBBB","连接状态发生改变,安卓系统回调onConnectionStateChange:device name="+device.getName()+"address="+device.getAddress()+"status="+status+"newstate="+newState);

            if(newState == BluetoothProfile.STATE_CONNECTED) {
                Log.e("BBBBB", "Add device to clents:"+device.getName()+","+device.getAddress());
                clients.put(device.getAddress(), device);
            } else if(newState == BluetoothProfile.STATE_DISCONNECTED) {
                Log.e("BBBBB", "Remove device from clents:"+device.getName()+","+device.getAddress());
                clients.remove(device.getAddress());
            }
        }

        //处理客户端主动发起的读的操作,通过sendResponse发送数据过去,这个数据可以被多次请求重复发送
        @Override
        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
            Log.e("BBBBB","客户端有读的请求,安卓系统回调该onCharacteristicReadRequest()方法");
            Log.e("BBBBB", "offset="+offset+","+characteristic.getUuid().toString()+",value:"+StringUtils.toHexString(characteristic.getValue()));
            mBluetoothGattServer.sendResponse(device,requestId, BluetoothGatt.GATT_SUCCESS,offset,characteristic.getValue());
        }

        //接收客户端发过来的数据,当有特征被写入时,回调该方法,写入的数据为参数中的value
        @Override
        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
            Log.e("BBBBB","客户端有写的请求,安卓系统回调该onCharacteristicWriteRequest()方法");

            //特征被读取,回复客户端接收数据成功,注意:此处作用为通知客户端已收到该数据,否则客户端可能会认为数据传输失败而断开蓝牙
            mBluetoothGattServer.sendResponse(device,requestId,BluetoothGatt.GATT_SUCCESS,offset,value);

            //客户端发送过来的数据,此处是给客户端又发送了回去
            Log.e("BBBBB","收到"+requestBytes.length+"字节");
            //响应客户端

            mCharacteristicWrite.setValue(requestBytes);
            mBluetoothGattServer.notifyCharacteristicChanged(device, mCharacteristicWrite,false);//用该方法通知characteristicRead数据发生改变
        }

        //特征被读取。当回复相应成功后,客户端胡读取然后触发本方法
        @Override
        public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
            super.onDescriptorReadRequest(device, requestId, offset, descriptor);
            Log.e("BBBBB", "onDescriptorReadRequest调用了"+requestId);
            mBluetoothGattServer.sendResponse(device,requestId,BluetoothGatt.GATT_SUCCESS,offset,null);

        }

        //当有描述请求被写入时,回调该方法,
        @Override
        public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
            super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);

            mBluetoothGattServer.sendResponse(device,requestId,BluetoothGatt.GATT_SUCCESS,offset,value);
            Log.e("BBBBB", "有数据写入");
            // onResponseToClient(value,device,requestId,descriptor.getCharacteristic());
        }

        //蓝牙服务添加成功时的回调
        @Override
        public void onServiceAdded(int status, BluetoothGattService service){
            super.onServiceAdded(status,service);
            Log.e("BBBBB","添加服务成功,安卓系统回调该onServiceAdded()方法");
        }

        //MTU改变时的回调,MTU为每个蓝牙数据包最大的字节数,通常默认是20,
        //如果你有一个数据超过20字节,那需要分成很多个小于或等于20字节的包来发送
        @Override
        public void onMtuChanged(BluetoothDevice device, int mtu) {
            super.onMtuChanged(device, mtu);
            Log.e("BBBBB", "MTU改变:"+mtu);
        }

        //当数据向客户端发送成功时,会调用 此回调
        @Override
        public void onNotificationSent(BluetoothDevice device, int status) {
            super.onNotificationSent(device, status);
            Log.e("BBBBB", "Notification Sent:"+ status);
        }
        //没试,不知道作用
        @Override
        public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
            super.onExecuteWrite(device, requestId, execute);
            Log.e("BBBBB", "执行写");
        }
    };

差不多就这么多吧。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值