最近一直在思考一个问题,如何写文章?即内容高质量又通俗易懂,让新手既明白其中蕴含的真理又能轻松跑起第一个程序,同时也能让高手温故知新,如获新欢。通过长时间的思索,最终定位为,内容高质量,描述简洁,思路清晰,对读者负责任的文章。初出茅庐,不会高手的底层功力,也不会段子手的套路人心,但,坚持作本身,尽本身所能,为人民服务。android
在Android应用层开发BLE,不懂一些理论和协议也不要紧,照样能够上手开发。本着知其然知其因此然,下面知识点的理解,可以有力支撑使用Android API。git
低功耗蓝牙是不能兼容经典蓝牙的,须要兼容,只能选择双模蓝牙。github
在Android 4.3及更高版本,Android 蓝牙堆栈可提供实现蓝牙低功耗 (BLE) 的功能,在 Android 8.0 中,原生蓝牙堆栈彻底符合蓝牙 5 的要求。也就是说在Android 4.3以上,咱们能够经过Android 原生API和蓝牙设备交互。bash
GAP用来控制蓝牙设备的广播和链接。GAP可使蓝牙设备被其余蓝牙设备发现,并决定是否能够被链接。GAP协议将蓝牙设备分为中心设备和外围设备。ide
外围设备经过广播数据或扫描回复两种方式之一让中心设备发现,而后进行链接,从而达到进行数据交互的前提条件。为了达到低功耗,外围设备并非一直广播,会设定一个广播间隔,每一个广播间隔中,它会从新发送本身的广播数据。广播间隔越长,越省电,同时也不太容易扫描到。post
在Android开发中,常经过蓝牙MAC进行链接,链接成功后就能够进行交互嘹。测试
简单理解为普通属性描述,BLE链接成功后,BLE设备基于该描述进行发送和接收相似“属性”的较短数据。目前大多数BLE属性描述是基于GATT。通常一个Profile表明了一个特殊的功能应用,例如心率或者电量应用。ui
ATT(Attribute Protocol) GATT是基于ATT上实现的,ATT是运行在BLE设备中,它们之间以尽量小的属性在进行交互,而属性则是以Service和Characteristic的形式在ATT上传输。下图是GATT的结构。 this
Service和Characteristic都经过16位或128位的UUID进行识别,16位的UUID须要向官方购买,全球惟一,而120位能够本身定义。通常UUID由硬件部门或者厂商提供。数据的交互都是客户端发起请求,服务端响应,客户端进行读写从而达到全双工。spa
在BLE链接中,定义者两个角色,GATT客户端和Gatt服务端,通常认为,主动发起数据请求的是Client,而响应数据结果的是Server。例如手机和手环。在数据交互的过程当中,永远是Client单方面发起请求,而后读写Server相关属性达到全双工效果。
理论知识就讲到这里了哇,下面进行Android应用层的开发哦。
实战部分的内容,大多数和蓝牙实现聊天功能是一致的。但为了没有看过这边文章的同窗,我就Ctrl+c
和Ctrl-v
一下,顺便修改一下代码。
在AndroidManifest.xml配置下面代码,让APP具备蓝牙访问权限和发现周边蓝牙权限。
//使用蓝牙须要该权限
<uses-permission android:name="android.permission.BLUETOOTH"/>
//使用扫描和设置须要权限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
//Android 6.0以上声明一下两个权限之一便可。声明位置权限,否则扫描或者发现蓝牙功能用不了哦
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
复制代码
为了适配Android 6.0,在主Activity中添加动态申请定位权限代码,不添加扫描不到蓝牙代码哦。
/**
* Android 6.0 动态申请受权定位信息权限,不然扫描蓝牙列表为空
*/
private void requestPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_COARSE_LOCATION)) {
Toast.makeText(this, "使用蓝牙须要受权定位信息", Toast.LENGTH_LONG).show();
}
//请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
REQUEST_ACCESS_COARSE_LOCATION_PERMISSION);
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_ACCESS_COARSE_LOCATION_PERMISSION) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//用户受权
} else {
finish();
}
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
复制代码
避免部分同窗在不支持蓝牙的手机或者设备安装了Demo,或者安装在模拟器了。
/**
* 是否支持BLE
*/
private boolean isSupportBLE() {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = manager.getAdapter();
//设备是否支持蓝牙
if (mBluetoothAdapter == null
//系统是否支持BLE
&& !getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Log.e(TAG, "not support bluetooth");
return true;
} else {
Log.e(TAG, " support bluetooth");
return false;
}
}
/**
* 弹出不支持低功耗蓝牙对话框
*/
private void showNotSupportBluetoothDialog() {
AlertDialog dialog = new AlertDialog.Builder(this).setTitle("当前设备不支持BLE").create();
dialog.show();
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
finish();
}
});
}
复制代码
有了支持BLE的手机,那么要检测手机蓝牙是否打开。若是没有打开则打开蓝牙和监听蓝牙的状态变化的广播。蓝牙打开后,扫描周边蓝牙设备。
//开启蓝牙
private void enableBLE() {
if (mBluetoothAdapter.isEnabled()) {
startScan();
} else {
mBluetoothAdapter.enable();
}
}
//注册监听蓝牙状态变化广播
private void registerBluetoothReceiver() {
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(bluetoothReceiver, filter);
}
BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
int state = mBluetoothAdapter.getState();
if (state == BluetoothAdapter.STATE_ON) {
startScan();
}
}
}
};
复制代码
Android 5.0以上的扫描API和Android 5.0如下的API已经不同了。蓝牙扫描是很是耗电的,Android 默认在手机息屏中止扫描,在手机亮屏后开始扫描。为了更好的下降耗电,正式APP应该主动关闭扫描,不该该循环扫描。BLE扫描速度很是快,咱们根据扫描到的蓝牙设备MAC保存Set集合中,过滤掉重复的设备。
private void startScan() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
//android 5.0以前的扫描方式
mBluetoothAdapter.startLeScan(new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
}
});
} else {
//android 5.0以后的扫描方式
scanner = mBluetoothAdapter.getBluetoothLeScanner();
scanCallback=new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
//中止扫描
if (firstScan){
handler.postDelayed(new Runnable() {
@Override
public void run() {
scanner.stopScan(scanCallback);
}
},SCAN_TIME);
firstScan=false;
}
String mac=result.getDevice().getAddress();
Log.i(TAG,"mac:"+mac);
//过滤重复的mac
if (!macSet.contains(mac)){
macSet.add(result.getDevice().getAddress());
deviceList.add(result.getDevice());
deviceAdapter.notifyDataSetChanged();
}
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
//须要蓝牙芯片支持,支持批量扫描结果。此方法和onScanResult是互斥的,只会回调其中之一
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(TAG,"扫描失败:"+errorCode);
}
};
scanner.startScan(scanCallback);
}
}
复制代码
这里主要实现的Android 5.0后的扫描,经过将扫描到的设备添加到list,并显示到界面上。因为可能扫描到重复的蓝牙设备,经过Set过滤掉重复的设备。
抽象类ScanCallback做为BLE扫描的回调,重写其中三个抽象方法。
ScanResult扫描结果内包含扫描到的周边BLE设备BluetoothDevice。经过BluetoothDevice,咱们能够获取周边BLE的相关信息,例如MAC,链接状态等。
在上一步得到咱们的BLE列表后,选择咱们要链接的BLE设备,进行链接。处理listview 的点击效果,进行链接BLE设备。
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
BluetoothDevice device = deviceList.get(position);
bluetoothGatt = device.connectGatt(MainActivity.this, true, gattCallback);
}
});
复制代码
经过BluetoothDevice的connectGatt()
方法链接周边BLE设备。如今明白为什么要先了解GATT了吧。connectGatt()
方法有三个参数,第二个参数表示当设备可用时,是否自动链接,第三个参数是BluetoothGattCallback类型,经过该回调,咱们能够知道BLE的链接状态和对Service、Charateristic进行操做,从而进行数据交互。connectGatt()
方法会返回类型BluetoothGatt的实例,经过该实例,咱们能够发送请求服务端。
抽象类BluetoothGattCallback有不少方法须要咱们重写,咱们这里说几个比较重要的,其余能够看Demo。咱们经过定义 GattCallback继承BluetoothGattCallback,并在类中重写其方法。这里假设咱们经过手机去链接小米手环,那么手机就是Gatt客户端,小米手环就是Gatt服务端。
onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
该方法手机链接或者断开链接到小米手环会回调该方法。参数一表明当前Gatt客户端,也就是咱们的手机。参数二表示链接或者断开链接的操做是否成功,只有参数二status
值为GATT_SUCCESS
,参数三才有效。参数三会返回STATE_CONNECTED
和STATE_DISCONNECTED
表示当前客户端和服务端的链接状态。链接成功后,咱们经过bluetoothGatt
对象的 discoverServices()
。onServicesDiscovered(BluetoothGatt gatt, int status)
当发现Service就会回调该方法,参数二值为GATT_SUCCESS
表示服务端的全部服务已经被搜索完毕,此时能够调用bluetoothGatt.getServices()
得到Service列表,进而得到全部Characteristic。也能够经过指定的UUID得到Service和Characteristic。
private void updateValue() {
BluetoothGattService service = bluetoothGatt.getService(UUID.fromString(serviceUuid));
if (service == null) return;
BluetoothGattCharacteristic characteristic = service.getCharacteristic(UUID.fromString(charUuid));
enableNotification(characteristic, charUuid);
characteristic.setValue("on");
}
复制代码
这样当咱们修改characteristic成功后,会回调告知咱们。
private void enableNotification(BluetoothGattCharacteristic characteristic,String uuid){
bluetoothGatt.setCharacteristicNotification(characteristic,true);
BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(uuid));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
bluetoothGatt.writeDescriptor(descriptor);
}
复制代码
上面代码设置成功后,会回调BluetoothGattCallback的onCharacteristicChanged()
方法。
若是Characteristic的值被修改,会回调BluetoothGattCallback的onCharacteristicChanged()
方法,在这里咱们能够进一步提升用户体验。须要注意一下,类BluetoothGattCallback有不少方法须要咱们实现,由于Gatt的响应结果都是回调该对象的方法。
小结一下
Gatt客户端经过BluetoothDevice的connectGatt()
方法与服务端链接成功后,利用返回的BluetoothGatt对象,请求Gatt服务端相关数据。Gatt服务端根据请求,将自身的状态经过回调客户端传入的BluetoothGattCallback对象的相关方法,从而告知客户端。
当咱们使用完BLE以后,应该及时关闭,以释放相关资源和下降功耗。
public void close() {
if (bluetoothGatt == null) {
return;
}
bluetoothGatt.close();
bluetoothGatt = null;
}
复制代码
在应用层操做BLE难度不大,由于Android屏蔽了不少蓝牙栈协议的细节。但应用层开发会苦于没有硬件设备支持。经过本文,咱们知道BLE的AP和GATT等等一些概念,了解Android BLE开发的总体流程,对BLE有一个感性的认知。
不知道看完本文,是否对您开文有益?
Star or 点赞本文 是一种鼓励与支持哦,努力坚持写出好文!
Demo的代码地址Github