赵工的个人空间


网络课堂部分转编程技巧部分转编程演练

 Android编程技巧

首页 > 网络课堂 > Android编程技巧 > 蓝牙低功耗设备搜索
蓝牙低功耗设备搜索
蓝牙是一种短距离的无线通信技术,开始是为了传输语音,现在已经是手机、平板电脑、笔记本电脑等移动电子产品的标准配置之一。蓝牙有多个版本,从4.2版本之后,开始支持蓝牙低功耗BLE(Bluetooth Low Energy),目的是与外设之间建立一种数据传输链路,用于传输较短的数据包,一般用于数据采集、监测、操控等。
目前,手机等移动设备的蓝牙模块大都可以支持传输语音的经典蓝牙和低功耗蓝牙BLE两种模式,其实相当于将两种模块集成在一起,经典蓝牙只能与经典蓝牙进行通信,而低功率蓝牙只与低功耗蓝牙进行通信。而且市场上也有一些蓝牙模块只支持蓝牙低功耗,相对成本比较低,主要用于数据传输,而不能传输语音,手机可以搜索到这些蓝牙低功耗设备,并实现数据通信。
1)安卓手机搜索蓝牙低功耗的配置:
因为国外用户对个人隐私的重视程度比较高,因此安卓系统对app的限制也越来越多。早期版本在manifest.xml中只需要配置蓝牙设备许可即可搜索附近的蓝牙设备,较新的版本还需要设置定位许可,因为一些应用可以据此实现室内定位。
一个典型的manifest.xml许可配置部分为:
  <!--加入蓝牙权限许可-->
   <uses-permission android:name="android.permission.BLUETOOTH" />
   <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
   <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
   <!--加入低功耗蓝牙权限许可-->
   <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
   <uses-feature android:name="android.bluetooth_le" android:required="true"/>
  
   <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
   <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

即使这样配置,在较新的Android版本中还是不够,还需要进行动态配置,也就是app打开后用代码配置,系统会提示用户获取许可:
  if (Build.VERSION.SDK_INT >= 23) {
     int checkCallPhonePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION);
     int checkCallPhonePermission2 = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
     if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED || checkCallPhonePermission2 != PackageManager.PERMISSION_GRANTED) {
       if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_COARSE_LOCATION)) {
         ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1);
         ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
       }
       return;
     }
  }

此外,还需要在在Android Studio中的gradle中配置sdk版本:
  compileSdkVersion 28
虽然使用更低的编译版本受到的限制会更少,但因为版本兼容性问题往往会出现编译错误。经测试,版本28是最低可以通过编译的版本。如果使用较新的版本编译,就难以搜索到蓝牙低功耗设备,因为缺乏较新版本的详细资料,不清楚哪些设置会限制其使用。
因为不同厂商使用的安卓版本有一些差异,如果搜索软件还得不到结果,还需要检查安卓系统的蓝牙设置及app的蓝牙权限设置,相应的蓝牙低功耗权限都打开才能正常使用。
2)获取本地蓝牙适配器:
在Android代码中,需要先检查本设备是否支持蓝牙低功耗BLE:
  if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
     sta.setText("不支持BLE");
     finish();
  }

虽然现在不支持蓝牙低功耗的Android产品非常稀少,但这种检查往往也是必要的。
然后就要获取蓝牙适配器,主要有两种方法。第一种:
  final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
  BluetoothAdapter btAdapter = bluetoothManager.getAdapter();

第二种更简单:
  BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
下面就要进行判断,并提醒用户使能相关功能:
  if (btAdapter == null || !btAdapter.isEnabled()) {
     Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
     startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
  }

前面提到的动态获得用户许可的代码需要放在其后,如果获取成功,要在界面中进行适当提示,否则就要退出了。
3)扫描控制:
可以在界面中放置一个按钮,点击此按钮就开始进行BLE设备的扫描:
  @Override
  public void onClick(View v) {
     switch (v.getId()) {
       case R.id.idScan:
         if (scan_flag) {
           bleDevices.clear();
           ble_list.clear();
           mAryAapter.notifyDataSetChanged();
           scanLeDevice(true);  //开始扫描
         } else {
           scanLeDevice(false);  //停止扫描
         }
         break;
  ......

这里,将扫描获取的BLE存入ArrayList()列表bleDevices中,而将一些用于显示的信息存入ArrayList()列表ble_list中,而界面中设置了一个ListView列表项lv,使用一个ArrayAdapter类型的变量mAryAapter将ble_list与界面中的ListView联系起来:
  mAryAapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, ble_list);
  lv.setAdapter(mAryAapter);

在启动扫描前,需要将之前的bleDevices和ble_list中的内容清除,以便存入新获得的数据,并使用mAryAapter.notifyDataSetChanged()将列表ble_list内容的变动及时在对应的ListView界面显现出来。
代码中还设置了一个是否正在扫描的标记scan_flag,如果没有扫描,点击此按钮就启动扫描,如果正在扫描,点击按钮就停止扫描,为此,也需要根据这个标志来改变按钮上显示的文字。
4)启动/停止低功耗蓝牙扫描:
上面代码中有个自定义方法用于启动/停止蓝牙低功耗设备扫描,代码为:
  public void scanLeDevice(final boolean enable) {
     BluetoothLeScanner btLeScanner = btAdapter.getBluetoothLeScanner();
     if (enable) {
       scan_flag = false;
       btLeScanner.startScan(mScanCallback);
     } else {
       btLeScanner.stopScan(mScanCallback);
       scan_flag = true;
     }
  }

现在较新的版本要使用BluetoothLeScanner类的startScan()和stopScan()来对蓝牙低功耗设备启动扫描及停止扫描,而Build.VERSION_CODES.M之前的版本则是使用BluetoothAdapter类的startLeScan()和stopLeScan(),如果app要对两者都支持就要根据版本分支处理,但因为旧版本的Android占比已经比较少,也可以只使用新版本方式。
为了使扫描在一定时间内自动停止,而不会无限延续下去,可以加入以下代码:
  Handler mHandler = new Handler();
  Runnable runnable = () -> {
     scan_flag = true;
     btAdapter.stopLeScan(mLeScanCallback);
  };

并把以下代码加入到启动扫描前:
  mHandler.postDelayed(runnable, SCAN_PERIOD);
其中的时间SCAN_PERIOD可以根据需要来预设,比如10000ms,即10秒。
5)蓝牙低功耗扫描的核心代码:
  private ScanCallback mScanCallback = new ScanCallback() {
     @Override
     public void onScanResult(int callbackType, ScanResult result) {
       super.onScanResult(callbackType, result);   //当发现一个外设时回调此方法。
       BluetoothDevice device = result.getDevice();
       if (!bleDevices.contains(device)) {  //判断是否重复
         bleDevices.add(device);  //未重复则加入列表
         ble_list.add(device.getName() + "\n" + device.getAddress()+ "\n");
       }
       mAryAapter.notifyDataSetChanged();
     }
     @Override
     public void onBatchScanResults(List<ScanResult> results) {
       super.onBatchScanResults(results);   //在此返回一个包含所有扫描结果的列表集,包括以往扫描到的结果
     }
     @Override
     public void onScanFailed(int errorCode) {
       super.onScanFailed(errorCode);   //扫描失败后的处理
     }
  };

代码中将扫描到的结果通过result.getDevice()方法获取到对应的设备,存入bleDevices列表中,并使用device.getName()及device.getAddress()获取到对应设备名及地址信息,存入ble_list列表中,然后使用mAryAapter.notifyDataSetChanged()将信息显示到界面的ListView上面。
这是较新版本Android的蓝牙低功耗设备的扫描方法,如果使用旧版本,则需要用BluetoothAdapter.LeScanCallback回调,使用onLeScan()方法扫描。
附近的蓝牙低功耗设备扫描显示后,用户就可以选择其一建立连接,完成数据传输。
Copyright@dwenzhao.cn All Rights Reserved   备案号:粤ICP备15026949号
联系邮箱:dwenzhao@163.com  QQ:1608288659