APP连接BLE4.0具体步骤

借助BLE4.0来做手机APP与硬件设备的通信,与经典蓝牙的步骤是不一样的。下面详细介绍如何在编写一个与BLE设备通信的app。

APP对BLE的操作主要可分为四步:准备、搜索、连接、交互

 

第一步:准备(请求权限和打开蓝牙)

这一步可分为三小步:声明权限,判断设备是否支持BLE,打开蓝牙

①声明权限

在AndroidManifest.xml文件下添加一下代码:

    <!-- 获取蓝牙相关权限 -->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

    <!-- 获取模糊定位权限,Android6.0以后使用蓝牙所需 -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"
        android:protectionLevel="dangerous"/>

需要注意的是,我们不仅需要获取有关蓝牙的两个权限,还要请求一个模糊定位权限,这是Android6.0以上BLE搜索必需的权限。另外,由于定位权限属于危险权限,不但需要在Manifest.xml文件中添加,还要在代码中进行申请才可以,后面会提到如何申请定位权限并处理用户反馈结果。

判断设备是否支持BLE

        /* 判断本机设备是否支持BLE */
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, R.string.not_support, Toast.LENGTH_SHORT).show();
            finish();  // 不支持就直接退出本页面
        }

        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mBluetoothAdapter == null) {
            Toast.makeText(this, R.string.not_support, Toast.LENGTH_SHORT).show();
            finish();  //不支持就直接退出本页面
        }

其中,mBluetoothAdapter是BluetoothAdapter的实例,有两种实例化方式,一种是上述代码中的

mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

另一种是通过BluetoothManager来得到

private BluetoothManager bluetoothManager;
bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();

③打开蓝牙

打开蓝牙我们一般采用用户交互式的打开方式。

        /* 以询问的方式,打开蓝牙 */
        if (!mBluetoothAdapter.isEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }

这里使用的是startActivityForResult来向用户询问是否打开蓝牙,并定义了一个final的常数量 REQUEST_ENABLE_BT作为请求标志位,即requestcode ,这个标志位是用来在onActivityResult方法中读取反馈时判断时什么请求,在下面会用到。

private final int REQUEST_ENABLE_BT = 1;

我们重写onActivityResult方法来获得用户的反馈

     /**
     * 系统自动回调函数,处理其他activity通信的返回结果
     * @param requestCode:接收到的请求
     * @param resultCode:用户做出的应答
     * @param data:
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        // 接收到了打开蓝牙的请求,但是用户拒绝打开蓝牙
        if (requestCode == REQUEST_ENABLE_BT && resultCode == RESULT_CANCELED) {
            Toast.makeText(this, R.string.open_fail, Toast.LENGTH_SHORT).show();

            // 用户打开蓝牙
        } else if (requestCode == REQUEST_ENABLE_BT && resultCode == RESULT_OK) {
            Toast.makeText(this, R.string.open_success, Toast.LENGTH_SHORT).show();
        }
    }

onActivityResult方法是通过判断requestcode来判断处理的是什么请求的。所以我们定义一个常数来做标志位。

在上面的方法中,我们在响应中写了一个Toast,输出蓝牙是否打开成功。你也可以直接写上自己的执行代码。

④申请模糊定位权限

这一步与申请蓝牙是否打开有相似的操作步骤。先进行权限申请,向系统发出请求;然后系统会生成弹窗让用户进行选择是否授权;最后在回调函数中对用户的选择进行处理。

权限申请:

    /* Android6.0以上版本需要授权位置信息 */
    if (BUILD_VERSION >= Build.VERSION_CODES.M) {
        String[] permissions = new String[]{Manifest.permission.ACCESS_COARSE_LOCATION};
        int check = ContextCompat.checkSelfPermission(activity, permissions[0]);
        // GRANTED---授权  DENIED---拒绝
        if (check == PackageManager.PERMISSION_DENIED) {
            // 请求权限,在回调方法onRequestPermissionsResult中写下一步
            requestPermissions(permissions, REQUEST_ENABLE_LOCATION);
        }else{
                
        }
    }else{
            
    }

这里,我们把要申请的权限放入是字符串数组permissions中,并使用了 requestPermissions 方法来申请权限,这个方法会在用户给出选择以后调用 onRequestPermissionsResult 方法,所以我们需要在这个方法中写后面的代码:

     /**
     * 请求权限的回调方法
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        // 成功获取位置权限
        if (requestCode == REQUEST_ENABLE_LOCATION &&
                grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            
            //code

            // 没有获取到权限
        } else {
            showToast(activity, R.string.no_location, Toast.LENGTH_SHORT);
        }
    }

在回调方法中,在成功获取位置权限是可以加入自己的代码,比如开始下一步搜索设备。

以上,准备工作就完成了,下一步就要搜索BLE设备了。

 

第二步、 搜索

搜索设备也可以分为几个小部分:开始搜索,搜索回调,停止搜索。逻辑上很简单明了,代码也很简单,就是各部分功能有点混。我们详细来看。

BLE开始搜索在API18时有一种方式,是直接通过BluetoothAdapter进行开始搜索和停止搜索的,并使用BluetoothAdapter.LeScanCallback作为搜索到设备后的回调。

mBLEAdapter.startLeScan(mLeScanCallback);
mBLEAdapter.stopLeScan(mLeScanCallback);

但是这种方式在API21时被弃用,现在的方法是使用一个新的类:BluetoothLeScanner 来进行设备的搜索,当然回调也换新的类了:ScanCallback。

来看完整代码。

    private BluetoothLeScanner mBLEScanner;

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    private void searchDevice() {
        // Android5.0以上系统用这个
        if (BUILD_VERSION >= Build.VERSION_CODES.LOLLIPOP) {
            mBLEScanner = mBLEAdapter.getBluetoothLeScanner();
            if (mBLEScanner != null) {
                list.clear();
                dialog.show();
                mBLEScanner.startScan(scanCallback);
                // 10秒后停止搜索
                mHandle.postDelayed(new Runnable() {
                    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
                    @Override
                    public void run() {
                        mBLEScanner.stopScan(scanCallback);
                        dialog.cancel();
                        showToast(activity, getString(R.string.search_finished), Toast.LENGTH_SHORT);
                    }
                }, 10000);
            }
        }
    }


     /**
     * Android5.0以上,搜索到设备后的回调
     */
    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    ScanCallback scanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            // 在扫描到未添加的设备时,将其添加
            if (result != null) {
                BluetoothDevice device = result.getDevice();
                if (device.getAddress() != null) {
                    if (!list.contains(device)) {
                        list.add(device);
                        listAdapter.notifyDataSetChanged();
                    }
                }
            }
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            super.onBatchScanResults(results);
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
            showToast(activity, "搜索失败,错误代码:" + errorCode, Toast.LENGTH_SHORT);
        }
    };

在searchDevice方法中,同时写了开始搜索和停止搜索的代码,其实也就两行。用mBLEScanner.startScan(scanCallback)开始搜索,并使用handle来延时10秒,用mBLEScanner.stopScan(scanCallback)停止搜索。

当然,要注意一点,在searchDevice这个方法中,首先进行的是API版本的判断,BluetoothLeScanner和ScanCallback这两个类是在API21才引入的,若是系统版本低于Android 5.0,这段代码就会出错,所以首先要判断一下系统的版本,用if语句将这段代码包起来。

searchDevice中的其他代码是关于UI的,用于用户交互,主要有两个,一个是搜索时显示了一个dialog提示用户正在搜索,一个是用来显示搜索结果的ListView。另外showToast是我网上找的一个可以实时显示Toast的方法。功能都很简单,就不把代码列出来了。

说一下回调方法scanCallback,这个类需要我们重写其中的三个方法,其中onScanResult是每搜索到一个设备就回调,onBatchScanResults是搜索到一堆设备的回调,onScanFailed是开始搜索失败的回调。在这里我用的是onScanResult,每搜索到一个设备就将它添加到ListView中去。

至于可不可以直接在onBatchScanResults令 list = results,将搜索到的设备集合直接赋值给list,答案是行不通。我也还没搞清楚是因为什么。测试了一下发现,在搜索过程中,系统回调了无数次的onScanResult,但是一次都没有onBatchScanResults,搜索结束都没有。

好了,搜索完成了,下一步要实现的是点击ListView中的item进行设备的连接。

第三步、连接

连接主要有两个部分:连接,回调。而且同搜索一样,这部分的关键代码仅有几句,只是处理的相关代码有些繁琐。

先说关键代码,连接有两个方式:

//第一种 : 发起连接。获得一个BluetoothGatt实例,并关联回调gattCallback
mBluetoothGatt = device.connectGatt(this, false, gattCallback);

//第二种 : 意外终端连接后重新发起连接
mBluetoothGatt.connect()

说是两种方式,但其实二者并不是独立的。第一种方式connectGatt()在第一次请求连接时执行的,这个方法是BluetoothDevice类下的一个方法,故需知道要连接的设备device。其中gattCallback是一个回调方法,它的回调范围比较广,是整个连接过程中(mBluetoothGatt不为空)的唯一回调,当连接状态改变或者接收到消息等等都会去调用这个回调。connectGatt()有一个返回值就是一个BluetothGatt实例,有了这个实例,在断开连接以后,就可以直接用它来调用connect(address)方法发起连接,这正是我们第二种连接方式。

OK,上完整代码。(我设定了点击ListView中的item就发起连接,这部分代码就不列出来了)

    /**
     * 连接设备,return: true是连接失败,false是连接成功
     */
    public boolean connect(String address) {
        if (mBLEAdapter == null) {
            return true;
        }
        if (address == null) {
            return true;
        }
        // reconnect, 原来连接过了,但是断开了,尝试再次连接
        if (mAddress != null && address.equals(mAddress) && mBluetoothGatt != null) {
            if (mBluetoothGatt.connect()) {
                // 连接完成
                Log.i(TAG, "connect: ~~connect");
                return false;
            }
        }
        // connect,第一次连接
        BluetoothDevice device = mBLEAdapter.getRemoteDevice(address);
        if (device == null) {
            return true;
        }
        mBluetoothGatt = device.connectGatt(this, false, gattCallback);
        if (mBluetoothGatt != null) {
            Log.i(TAG, "connect: ~~Gatt");
            mBluetoothGatt.connect();
            mAddress = address;
            return false;
        } else {
            return true;
        }
    }

(代码来自一个开源的BLE连接APP,但是已经不清楚是哪里的了,我只改了一点点)

可以看到在这个连接的代码中,关键代码只有几行,其他的都是一些判断语句。

可能有些人看到了在【第一次连接】代码中我不止调用了connectGatt(),完了还调用了connect()。这个解释一下,这是个未解决的bug,connectGatt是可以实现连接的,但是我在最近的程序的程序里发现,在connectGatt并不会连接上设备,只能再调connect()才能连接,至于为什么我还没想出来。之前的程序并没有这个问题。

好了,说一下回调gattCallback。代码如下。

    private BluetoothGattCharacteristic mCharacWrite;
    /**
     * 回调
     */
    private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        /* 连接状态改变时调用 */
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            // 连接状态
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                mBluetoothGatt.discoverServices();
                Log.i(TAG, "onConnectionStateChange: ~~1");

                // 不连接状态
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                showToast(activity, getString(R.string.disconnected), Toast.LENGTH_SHORT);
            }
        }

        /* discoverServices()的回调,一旦有服务被发现,就会回调 */
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            Log.i(TAG, "onServicesDiscovered: ~~发现服务");
            if (status == BluetoothGatt.GATT_SUCCESS) {
                BluetoothGattService service = mBluetoothGatt.getService(SERVICE_UUID_CC41);
                mCharacWrite = service.getCharacteristic(CHARA_UUID_CC41);
                mBluetoothGatt.setCharacteristicNotification(mCharacWrite, true);
            }

        }

        /* 接收通知数据;BLE端有数据传来,数据由characteristic携带 */
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {

            Log.i(TAG, "onCharacteristicChanged: ~~收到数据");
            /* 从characteristic中取出数据 */
            if (characteristic != null) {
                int charaProp = characteristic.getProperties();
                // 可读的uuid
                if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
                    String data = new String(characteristic.getValue());
                    tvReceive.setText(data);
                }
            }
        }
    };

在回调中,我们可以复写很多方法,其中主要是有三种。

第一个是onConnectionStateChange,当连接状态改变时回调,就是说原来是连接的现在断开了,原来是断开的现在连上了。在这个方法中我们可以根据连接状态的改变写一些执行代码,但是注意不能写改变UI的代码。因为这已经不属于主线程了,而UI只能在主线程中改变。

第二个方法是onServicesDiscovered,这个方法其实是在我们在第一个方法中执行的mBluetoothGatt.discoverServices();的回调,表示发现了服务。我们可以在其中选择可写的、可通知的Characteristic或者Service进行通信。在这里,我是通过一个BLE调试器把硬件设备的UUID获取了以后直接写上的,我的设备的读、写、通知的Characteristic是一个,所以也就只有一个UUID。有些设备的Characteristic是不一样的,就需要分别获取了。方法都是一样的。

第三个比较重要的方法是onCharacteristicChanged,这个方法是接收有BLE发来的消息的,当有消息发来的时候回调,消息由一个Characteristic携带,可以通过getValue取出来,注意取出来的是一个byte数组,想要字符串的可以通过new String这种方式来转换,不要用toString(),会出现一堆乱码。

其他的可复写的方法还有很多,有需要的可以直接添加补充,这几个做简单通信已经足够了。

 

第四步、通信

通信分发送接收两部分。这部分与刚刚的回调类是有很大关系的。

先说接收,其实刚刚在回调的第三种方法中已经说到了接收,就是这么简单。因为系统在收到BLE的信息后会自动回调onCharacteristicChanged()方法,所以我们只需要在回调方法中写执行代码就够了。

再说发送,发送也很简单。仅两行关键代码:

    String str = etSend.getText().toString();
    if (mCharacWrite != null) {
       mCharacWrite.setValue(str);
       mBluetoothGatt.writeCharacteristic(mCharacWrite);
       showToast(activity, "发送成功", Toast.LENGTH_SHORT);
    }

这里我用的是一个EditText输入要发送的信息,然后把信息装入了一个可写的Characteristic,再把这个Characteristic发送出去,就OK了。

 

最后

写完这些还不算完,还有个收尾。

主动断开连接

APP上必然是要加入主动断开连接的功能的。emm,一行代码,如下。

     /**
     * 断开连接
     */
    public void disconnect() {
        if (mBLEAdapter == null || mBluetoothGatt == null) {
            return;
        }
        mBluetoothGatt.disconnect(); 
    }

    /**
     * 关闭BluetoothGatt,否则会占用连接数目
     */
    public void close() {
        if (mBluetoothGatt == null) {
            return;
        }
        mBluetoothGatt.close();
        mBluetoothGatt = null;
    }

最后的最后

不知道各位看懂了没。。。另外推荐一篇很好的博文,看懂没看懂的都推荐去看看。

Android BLE 蓝牙开发入门-JBD-简书

(文中代码有些是自己写的,有些来自一个开源的BLE连接APP,但是已经不清楚是哪里的了,只能这么感谢一下作者了,我的大部分BLE的只是都是从那里学的)

发布了12 篇原创文章 · 获赞 7 · 访问量 1万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 精致技术 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览