本文只講述實際開發(fā)中的最基本的概念、用法及代碼,不過多深入概念及源碼。
什么是周邊設(shè)備
- BLE(藍牙4.0+) 有兩種狀態(tài)模式:中心(center)及peripheral(周邊)。
- 大多數(shù)中心設(shè)備的扮演者是手機、電腦等能主動去連接別人的設(shè)備,而大多數(shù)周邊設(shè)備就等著這些中心設(shè)備連接,如手環(huán)、血糖儀等。
- Android從 Lolipop 開始支持了BLE Peripheral(周邊設(shè)備)開發(fā)。
- 如果你希望使Android設(shè)備開啟為周邊設(shè)備模式,請往下看。
配置
在 AndroidManifest.xml 中 添加以下權(quán)限:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!-- 6.0之后藍牙還需要地理位置權(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" />
開啟藍牙
// 是否支持ble
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(mContext, "hasSystemFeature == false", Toast.LENGTH_SHORT).show();
return false;
}
// 是否能獲取到藍牙服務(wù)
mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Toast.makeText(mContext, "mBluetoothManager == null", Toast.LENGTH_LONG).show();
return false;
}
// 獲取藍牙適配器
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Toast.makeText(mContext, "BluetoothAdapter == null", Toast.LENGTH_LONG).show();
return false;
}
// 藍牙是否打開
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 為空,請換設(shè)備再繼續(xù)(國內(nèi)部分機型不支持)
建立服務(wù)
// 給個風(fēng)騷的廣播名稱,默認是手機設(shè)置里藍牙的名稱
mBluetoothAdapter.setName("Bleoo");
// 這個Callback 是設(shè)備廣播成功后所有狀態(tài)的回調(diào),包括讀寫等
mGattServerCallback = new PeriServerCallBack();
// 打開GattServer
mGattServer = mBluetoothManager.openGattServer(mContext, mGattServerCallback);
// 創(chuàng)建一個特征通道用來寫
mWriteCharacter = new BluetoothGattCharacteristic(
UUID.fromString(Constants.CHARACTERISTIC_WRITEABLE),
BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
// 創(chuàng)建一個特征通道用來讀
mReadCharacter = new BluetoothGattCharacteristic(
UUID.fromString(Constants.CHARACTERISTIC_READABLE),
BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ,
BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
// 創(chuàng)建一個Gatt服務(wù)
mGattService = new BluetoothGattService(
UUID.fromString(Constants.GATT_SERVICE_PRIMARY),
BluetoothGattService.SERVICE_TYPE_PRIMARY);
// 添加讀寫通道
mGattService.addCharacteristic(mWriteCharacter);
mGattService.addCharacteristic(mReadCharacter);
// 添加服務(wù)
if (mGattServer != null && mGattService != null)
mGattServer.addService(mGattService);
開啟廣播
成功開啟廣播后,中心設(shè)備能夠掃描到你的設(shè)備,當(dāng)然前提是中心設(shè)備支持ble設(shè)備,并且在掃描ble設(shè)備。
中心設(shè)備能獲取到你ble設(shè)備的所有信息,包括 GattService 及其 BluetoothGattCharacteristic。
public void startAdvertising() {
// 這里的Callback是是否開啟成功的回調(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ù),這里沒有攜帶)
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)添加一個GattService成功后會回調(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)有客戶端來讀數(shù)據(jù)時回調(diào)的接口
@Override
public void onCharacteristicReadRequest(android.bluetooth.BluetoothDevice device,
int requestId, int offset, BluetoothGattCharacteristic characteristic) {
mClientDevice = device;
LogUtil.e("客戶端讀數(shù)據(jù) requestId=" + requestId + " offset=" + offset);
mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
}
//當(dāng)有客戶端來寫數(shù)據(jù)時回調(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("客戶端寫數(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)有客戶端來寫Descriptor時回調(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);
}
}
寫出數(shù)據(jù)
與中心設(shè)備的寫出方法不同,周邊設(shè)備通過 notifyCharacteristicChanged 方法,類似通知的方法寫出數(shù)據(jù)。
當(dāng)然,sendResponse 也能用于返回數(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);
}