本文只講述實(shí)際開(kāi)發(fā)中的最基本的概念、用法及代碼,不過(guò)多深入概念及源碼。
什么是周邊設(shè)備
- BLE(藍(lán)牙4.0+) 有兩種狀態(tài)模式:中心(center)及peripheral(周邊)。
- 大多數(shù)中心設(shè)備的扮演者是手機(jī)、電腦等能主動(dòng)去連接別人的設(shè)備,而大多數(shù)周邊設(shè)備就等著這些中心設(shè)備連接,如手環(huán)、血糖儀等。
- Android從 Lolipop 開(kāi)始支持了BLE Peripheral(周邊設(shè)備)開(kāi)發(fā)。
- 如果你希望使Android設(shè)備開(kāi)啟為周邊設(shè)備模式,請(qǐng)往下看。
配置
在 AndroidManifest.xml 中 添加以下權(quán)限:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- 6.0之后藍(lán)牙還需要地理位置權(quán)限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 只掃描ble設(shè)備 -->
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
開(kāi)啟藍(lán)牙
// 是否支持ble
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(mContext, "hasSystemFeature == false", Toast.LENGTH_SHORT).show();
return false;
}
// 是否能獲取到藍(lán)牙服務(wù)
mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Toast.makeText(mContext, "mBluetoothManager == null", Toast.LENGTH_LONG).show();
return false;
}
// 獲取藍(lán)牙適配器
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Toast.makeText(mContext, "BluetoothAdapter == null", Toast.LENGTH_LONG).show();
return false;
}
// 藍(lán)牙是否打開(kāi)
if (!mBluetoothAdapter.isEnabled()) {
Toast.makeText(mContext, "BluetoothAdapter.isEnabled == false", Toast.LENGTH_LONG).show();
return false;
}
// 獲取廣播者
mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
if (mBluetoothLeAdvertiser == null) {
Toast.makeText(mContext, "BluetoothLeAdvertiser == null ", Toast.LENGTH_LONG).show();
return false;
}
到這里如果 mBluetoothLeAdvertiser 為空,請(qǐng)換設(shè)備再繼續(xù)(國(guó)內(nèi)部分機(jī)型不支持)
建立服務(wù)
// 給個(gè)風(fēng)騷的廣播名稱(chēng),默認(rèn)是手機(jī)設(shè)置里藍(lán)牙的名稱(chēng)
mBluetoothAdapter.setName("Bleoo");
// 這個(gè)Callback 是設(shè)備廣播成功后所有狀態(tài)的回調(diào),包括讀寫(xiě)等
mGattServerCallback = new PeriServerCallBack();
// 打開(kāi)GattServer
mGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback);
// 創(chuàng)建一個(gè)特征通道用來(lái)寫(xiě)
mWriteCharacter = new BluetoothGattCharacteristic(
UUID.fromString(Constants.CHARACTERISTIC_WRITEABLE),
BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
// 創(chuàng)建一個(gè)特征通道用來(lái)讀
mReadCharacter = new BluetoothGattCharacteristic(
UUID.fromString(Constants.CHARACTERISTIC_READABLE),
BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
// 創(chuàng)建一個(gè)Gatt服務(wù)
mGattService = new BluetoothGattService(
UUID.fromString(Constants.GATT_SERVICE_PRIMARY),
BluetoothGattService.SERVICE_TYPE_PRIMARY);
// 添加讀寫(xiě)通道
mGattService.addCharacteristic(mWriteCharacter);
mGattService.addCharacteristic(mReadCharacter);
// 添加服務(wù)
if (mGattServer != null && mGattService != null)
mGattServer.addService(mGattService);
開(kāi)啟廣播
成功開(kāi)啟廣播后,中心設(shè)備能夠掃描到你的設(shè)備,當(dāng)然前提是中心設(shè)備支持ble設(shè)備,并且在掃描ble設(shè)備。
中心設(shè)備能獲取到你ble設(shè)備的所有信息,包括 GattService 及其 BluetoothGattCharacteristic。
public void startAdvertising() {
// 這里的Callback是是否開(kāi)啟成功的回調(diào)
mBluetoothLeAdvertiser.startAdvertising(createAdvSettings(true, 0), createAdvertiseData(), mAdvCallback);
}
private AdvertiseSettings createAdvSettings(boolean connectable, int timeoutMillis) {
AdvertiseSettings.Builder builder = new AdvertiseSettings.Builder();
//設(shè)置廣播的模式,跟功耗相關(guān)
builder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED);
builder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH);
builder.setConnectable(connectable);
builder.setTimeout(timeoutMillis);
return builder.build();
}
//設(shè)置廣播數(shù)據(jù)(可以攜帶廣播數(shù)據(jù),這里沒(méi)有攜帶)
private AdvertiseData createAdvertiseData() {
AdvertiseData.Builder builder = new AdvertiseData.Builder();
builder.setIncludeDeviceName(true);
return builder.build();
}
廣播回調(diào)
//發(fā)送廣播的回調(diào)
private AdvertiseCallback mAdvCallback = new AdvertiseCallback() {
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
mOnCallBackListener.advertisingStatus(true);
if (settingsInEffect != null) {
LogUtil.e("onStartSuccess TxPowerLv=" + settingsInEffect.getTxPowerLevel()
+ " mode=" + settingsInEffect.getMode() + " timeout=" + settingsInEffect.getTimeout());
} else {
LogUtil.e("onStartSuccess, settingInEffect is null");
}
}
public void onStartFailure(int errorCode) {
mOnCallBackListener.advertisingStatus(false);
LogUtil.e("onStartFailure errorCode=" + errorCode);
}
};
Gatt服務(wù)回調(diào)
private class PeriServerCallBack extends BluetoothGattServerCallback {
//當(dāng)添加一個(gè)GattService成功后會(huì)回調(diào)改接口。
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
if (status == BluetoothGatt.GATT_SUCCESS) {
LogUtil.e("onServiceAdded status=GATT_SUCCESS service=" + service.getUuid().toString());
} else {
LogUtil.e("onServiceAdded status!=GATT_SUCCESS");
}
}
//BLE連接狀態(tài)改變后回調(diào)的接口
@Override
public void onConnectionStateChange(android.bluetooth.BluetoothDevice device, int status, int newState) {
mClientDevice = device;
LogUtil.e("BLE連接狀態(tài)改變 status=" + status + "->" + newState + " ==== Address: " + device.getAddress());
}
//當(dāng)有客戶(hù)端來(lái)讀數(shù)據(jù)時(shí)回調(diào)的接口
@Override
public void onCharacteristicReadRequest(android.bluetooth.BluetoothDevice device,
int requestId, int offset, BluetoothGattCharacteristic characteristic) {
mClientDevice = device;
LogUtil.e("客戶(hù)端讀數(shù)據(jù) requestId=" + requestId + " offset=" + offset);
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
}
//當(dāng)有客戶(hù)端來(lái)寫(xiě)數(shù)據(jù)時(shí)回調(diào)的接口
@Override
public void onCharacteristicWriteRequest(android.bluetooth.BluetoothDevice device, int requestId,
BluetoothGattCharacteristic characteristic, boolean preparedWrite,
boolean responseNeeded, int offset, byte[] value) {
mClientDevice = device;
try {
String msg = new String(value, "UTF-8");
mOnCallBackListener.writeRequest(msg);
LogUtil.e("客戶(hù)端寫(xiě)數(shù)據(jù) + message= " + msg + " requestId= " + requestId + " offset= " + offset);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// 必須sendResponse用于響應(yīng)(具體原因也不清楚,似乎是為了保持連接)
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
}
//當(dāng)有客戶(hù)端來(lái)寫(xiě)Descriptor時(shí)回調(diào)的接口
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor,
boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
mClientDevice = device;
LogUtil.e("onDescriptorWriteRequest === ");
// now tell the connected device that this was all successfull
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
}
}
寫(xiě)出數(shù)據(jù)
與中心設(shè)備的寫(xiě)出方法不同,周邊設(shè)備通過(guò) notifyCharacteristicChanged 方法,類(lèi)似通知的方法寫(xiě)出數(shù)據(jù)。
當(dāng)然,sendResponse 也能用于返回?cái)?shù)據(jù)。
public boolean write(byte[] value) {
if (mWriteCharacter == null)
return false;
if (mGattServer == null)
return false;
mWriteCharacter.setValue(value);
return mGattServer.notifyCharacteristicChanged(mClientDevice, mWriteCharacter, false);
}