Android上BLE功能的逐步演進
藍牙在Android發(fā)展過程如下:
- Android4.3開始龙填,開始支持BLE功能周荐,但只支持Central Mode(中心模式)
- Android5.0開始,開始支持Peripheral Mode(外圍模式)
中心模式和外圍模式是什么意思?
- Central Mode:Android端作為中心設(shè)備,連接其他外圍設(shè)備。
- Peripheral Mode:Android端作為外圍設(shè)備疆股,被其他中心設(shè)備連接。在Android5.0支持外圍模式之后倒槐,才實現(xiàn)了兩臺Android手機通過BLE進行相互通信。
BLE通信基礎(chǔ)
- ATT:全稱 Attribute Protocol附井,中文名"屬性協(xié)議"讨越。它是BLE通信的基礎(chǔ)。ATT把數(shù)據(jù)封裝永毅,向外暴露為"屬性"把跨,提供"屬性"的為服務(wù)端,獲取"屬性"的為客戶端沼死。ATT是專門為低功耗藍牙設(shè)計的着逐,結(jié)構(gòu)非常簡單,數(shù)據(jù)長度很短意蛀。
- GATT:全稱 Generic Attribute Profile耸别,中文名"通用屬性配置文件"。它是在ATT的基礎(chǔ)上县钥,對ATT進行的進一步邏輯封裝秀姐,定義數(shù)據(jù)的交互方式和含義。GATT是我們做BLE開發(fā)的時候直接接觸的概念若贮。
-
GATT層級:GATT按照層級定義了4個概念:配置文件(profile)省有、服務(wù)(Service)痒留、特征(Characteristic)和描述(Descriptor)。他們的關(guān)系是這樣的蠢沿,profile定義了一個實際的應(yīng)用場景伸头,一個profile包含若干個service,一個service包含若干個characteristic舷蟀,一個characteristic可以包含若干個descriptor恤磷。
GATT層級
Profile
Profile 并不是實際存在于 BLE 外設(shè)上的,它只是一個被 Bluetooth SIG 或者外設(shè)設(shè)計者預(yù)先定義的 Service 的集合雪侥。例如心率Profile(Heart Rate Profile)就是結(jié)合了 Heart Rate Service 和 Device Information Service碗殷。所有官方通過 GATT Profile 的列表可以從這里找到。Service
Service 是把數(shù)據(jù)分成一個個的獨立邏輯項速缨,它包含一個或者多個 Characteristic锌妻。每個 Service 有一個 UUID 唯一標識。 UUID 有 16 bit 的旬牲,或者 128 bit 的仿粹。16 bit 的 UUID 是官方通過認證的,需要花錢購買原茅,128 bit 是自定義的吭历,這個就可以自己隨便設(shè)置。官方通過了一些標準 Service擂橘,完整列表在這里晌区。以 Heart Rate Service為例,可以看到它的官方通過 16 bit UUID 是0x180D
通贞,包含 3 個 Characteristic:Heart Rate Measurement, Body Sensor Location 和 Heart Rate Control Point朗若,并且定義了只有第一個是必須的,它是可選實現(xiàn)的昌罩。Characteristic
需要重點提一下Characteristic哭懈, 它定義了數(shù)值和操作,包含一個Characteristic聲明茎用、Characteristic屬性遣总、值、值的描述(Optional)轨功。通常我們講的 BLE 通信旭斥,其實就是對 Characteristic 的讀寫或者訂閱通知。比如在實際操作過程中夯辖,我對某一個Characteristic進行讀琉预,就是獲取這個Characteristic的value。-
UUID
Service蒿褂、Characteristic 和 Descriptor 都是使用 UUID 唯一標示的圆米。UUID 是全局唯一標識卒暂,它是 128bit 的值,為了便于識別和閱讀娄帖,一般以 “8位-4位-4位-4位-12位”的16進制標示也祠,比如“12345678-abcd-1000-8000-123456000000”。
但是近速,128bit的UUID 太長诈嘿,考慮到在低功耗藍牙中,數(shù)據(jù)長度非常受限的情況削葱,藍牙又使用了所謂的 16 bit 或者 32 bit 的 UUID奖亚,形式如下:“0000XXXX-0000-1000-8000-00805F9B34FB”。除了 “XXXX” 那幾位以外析砸,其他都是固定昔字,所以說,其實 16 bit UUID 是對應(yīng)了一個 128 bit 的 UUID首繁。這樣一來作郭,UUID 就大幅減少了,例如 16 bit UUID只有有限的 65536(16的四次方) 個弦疮。與此同時夹攒,因為數(shù)量有限,所以 16 bit UUID 并不能隨便使用胁塞。藍牙技術(shù)聯(lián)盟已經(jīng)預(yù)先定義了一些 UUID咏尝,我們可以直接使用,比如“00001011-0000-1000-8000-00805F9B34FB”就一個是常見于BLE設(shè)備中的UUID啸罢。當然也可以花錢定制自定義的UUID状土。
作為外圍設(shè)備開發(fā)實現(xiàn)
1.聲明權(quán)限
<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" />
- android.permission.BLUETOOTH:這個權(quán)限允許程序連接到已配對的藍牙設(shè)備
- android.permission.BLUETOOTH_ADMIN:這個權(quán)限允許程序發(fā)現(xiàn)和配對藍牙設(shè)備
- android.permission.ACCESS_COARSE_LOCATION和android.permission_ACCESS_FINE_LOCATION這兩個權(quán)限是Android6.0后必須添加的,藍牙掃描周圍設(shè)備需要獲取模糊的位置信息伺糠。這兩個權(quán)限屬于同一組危險權(quán)限,在AndroidManifest文件聲明后還需要在運行時動態(tài)獲取斥季。
2.開發(fā)流程
1.獲取藍牙設(shè)備管理類
mBluetoothManager =(BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE)
2.獲取藍牙適配器
mAdapter = mBluetoothManager.getAdapter();
3.獲取服務(wù)
mService = new BluetoothGattService(UUID.fromString(SERVICE_WIFI), BluetoothGattService.SERVICE_TYPE_PRIMARY);
4.獲取一個特征
charWifiNameAndPassword = new BluetoothGattCharacteristic(UUID.fromString(CHARACTERISTIC_SET_WIFI_NAME_AND_PASSWORD), BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
5.獲取一個描述
BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(UUID.fromString(CHARACTERISTIC_CONFIG), BluetoothGattDescriptor.PERMISSION_READ);
6.將描述加入到特征中
charWifiNameAndPassword.addDescriptor(descriptor);
7.將特征加入到服務(wù)中
mService.addCharacteristic(charWifiNameAndPassword);
8.獲取Server
mBluetoothGattServer = mBluetoothManager.openGattServer(mContext, new BluetoothGattServerCallback() {....}
9.將服務(wù)加入到Server
mBluetoothGattServer.addService(mService);
10.開啟ble廣播
mAdvertiser.startAdvertising(settings, data, new AdvertiseCallback() {......}
3.代碼實現(xiàn)
public class BleManager {
public static final String TAG = BleManager.class.getName();
private Context mContext;
private static BleManager manager;
//藍牙設(shè)備管理類
private BluetoothManager mBluetoothManager;
//藍牙設(shè)配器
private BluetoothAdapter mAdapter;
private BluetoothLeAdvertiser mAdvertiser;
//藍牙Server
private BluetoothGattServer mBluetoothGattServer;
//藍牙服務(wù)
private BluetoothGattService mService;
//藍牙特征
private BluetoothGattCharacteristic charWifiNameAndPassword;
//服務(wù)UUID
public static final String SERVICE_WIFI = "00001111-0000-1000-8000-00805f9b34fb";
//特征UUID
public static final String CHARACTERISTIC_SET_WIFI_NAME_AND_PASSWORD = "00008888-0000-1000-8000-00805f9b34fb";
//描述UUID
public static final String CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";
public static BleManager getInstance(Context context) {
if (manager == null) {
manager = new BleManager(context);
}
return manager;
}
public BleManager(Context context) {
mContext = context;
//1.獲取管理類
mBluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
//判斷設(shè)備是否支持藍牙
if (mBluetoothManager == null) {
return;
}
//2.獲取藍牙適配器
mAdapter = mBluetoothManager.getAdapter();
if (!mAdapter.isEnabled()) {
mAdapter.enable();
}
}
/**
* 開啟藍牙并開啟ble廣播
*/
public synchronized void openBle() {
//如果是調(diào)用closeBle來關(guān)閉藍牙的训桶,會將bluetoothAdapter,bluetoothReceiver置為null,需要重新賦值
if (mAdapter == null) {
mAdapter = mBluetoothManager.getAdapter();
}
if (!mAdapter.isEnabled()) {
mAdapter.enable();
}
if (Build.VERSION.SDK_INT >= 21 && mAdapter.isMultipleAdvertisementSupported()) {
if (initService()) {
//10.啟動ble廣播
startAdvertise();
} else {
Log.d(TAG, "開啟ble失敗");
}
} else {
Log.d(TAG, "開啟ble失敗");
if (!mAdapter.isMultipleAdvertisementSupported()) {
Log.d(TAG, "您的設(shè)備不支持藍牙從模式");
}
}
}
/**
* 開啟ble廣播
*/
private void startAdvertise() {
mAdvertiser = mAdapter.getBluetoothLeAdvertiser();
AdvertiseSettings settings = new AdvertiseSettings.Builder()
.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
.setConnectable(true)
.setTimeout(0)
.build();
ParcelUuid parcelUuid = new ParcelUuid(UUID.fromString(SERVICE_WIFI));
AdvertiseData data = new AdvertiseData.Builder()
.setIncludeDeviceName(true)
.addServiceUuid(parcelUuid)
.build();
mAdvertiser.startAdvertising(settings, data, new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
super.onStartSuccess(settingsInEffect);
Log.d(TAG, "開啟ble廣播成功");
}
@Override
public void onStartFailure(int errorCode) {
super.onStartFailure(errorCode);
Log.d(TAG, "開啟ble廣播失敗");
}
});
}
private boolean initService() {
//3.獲取服務(wù)
mService = new BluetoothGattService(UUID.fromString(SERVICE_WIFI), BluetoothGattService.SERVICE_TYPE_PRIMARY);
//4.獲取一個特征
charWifiNameAndPassword = new BluetoothGattCharacteristic(UUID.fromString(CHARACTERISTIC_SET_WIFI_NAME_AND_PASSWORD), BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_WRITE | BluetoothGattCharacteristic.PERMISSION_READ);
//5.獲取一個描述
BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(UUID.fromString(CHARACTERISTIC_CONFIG), BluetoothGattDescriptor.PERMISSION_READ);
descriptor.setValue("WIFI ACCOUNT".getBytes(Charset.forName("UTF-8")));
//6.將描述加入到特征中
charWifiNameAndPassword.addDescriptor(descriptor);
//7.將特征加入到服務(wù)中
mService.addCharacteristic(charWifiNameAndPassword);
//8.獲取周邊
mBluetoothGattServer = mBluetoothManager.openGattServer(mContext, new BluetoothGattServerCallback() {
@Override
public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
super.onConnectionStateChange(device, status, newState);
//連接狀態(tài)發(fā)生變化回調(diào)
Log.d(TAG, "連接狀態(tài)發(fā)生改變:" + newState);
}
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
super.onServiceAdded(status, service);
//當周邊添加到服務(wù)成功時的回調(diào)
Log.d(TAG, "服務(wù)添加成功");
}
@Override
public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
//當遠程設(shè)備請求讀取本地特征時回調(diào)
//必須調(diào)用BluetoothGattServer.sendResponse
Log.d(TAG, "遠程設(shè)備讀取本地特征");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
}
@Override
public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);
//當遠程設(shè)備請求寫入本地特征時回調(diào)
//通常我們講的BLE通信酣倾,其實就說對characteristic的讀寫或者訂閱
//必須調(diào)用BluetoothGattServer.sendResponse
try {
Log.d(TAG, "遠程設(shè)備寫入本地特征:" + new String(value, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
}
@Override
public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
super.onDescriptorReadRequest(device, requestId, offset, descriptor);
//當遠程設(shè)備請求讀取本地描述時回調(diào)
//必須調(diào)用BluetoothGattServer.sendResponse
Log.d(TAG, "遠程設(shè)備讀取本地描述");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
}
@Override
public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);
//當遠程設(shè)備請求寫入本地描述時回調(diào)
//必須調(diào)用BluetoothGattServer.sendResponse
Log.d(TAG, "遠程設(shè)備寫入本地描述:" + value);
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
}
@Override
public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
super.onExecuteWrite(device, requestId, execute);
//執(zhí)行本地設(shè)備所有掛起的寫操作
//必須調(diào)用BluetoothGattServer.sendResponse
Log.d(TAG, "執(zhí)行所有掛起的寫操作");
mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);
}
@Override
public void onNotificationSent(BluetoothDevice device, int status) {
super.onNotificationSent(device, status);
//當通知發(fā)送到遠程設(shè)備時的回調(diào)
Log.d(TAG, "通知發(fā)送成功");
}
/**
* MTU(Maxximum Transmission Unit)最大傳輸單元:指在一個協(xié)議數(shù)據(jù)單元中(PDU,Protocol Data Unit)有效的最大傳輸Byte
* AndroidMTU一般為23舵揭,發(fā)送長包需要更改MTU(5.1(API21)開始支持MTU修改)或者分包發(fā)送
* core spec里面定義了ATT的默認MTU為23bytes,除去ATT的opcode一個字節(jié)以及ATT的handle2個字節(jié)后躁锡,剩余20個bytes留給GATT
* MTU是不可以協(xié)商的午绳,只是通知對方,雙方在知道對方的極限后會選擇一個較小的值作為以后的MTU
*/
@Override
public void onMtuChanged(BluetoothDevice device, int mtu) {
super.onMtuChanged(device, mtu);
//MTU更改時的回調(diào)
Log.d(TAG, "MTU發(fā)生更改:" + mtu);
}
/**
* PHY(Physical):物理接口收發(fā)器映之,實現(xiàn)OSI模型的物理層
* 當調(diào)用BluetoothGattServer.setPreferredPhy時拦焚,或者遠程設(shè)備更改了PHY時回調(diào)
* 低功耗藍牙5.0協(xié)議中蜡坊,定義了兩種調(diào)制方案。這兩種方案都采用了GFSK調(diào)制赎败。區(qū)別在于symbol rate不同秕衙,一種1 Msym/s,另一種2Msym/s僵刮。
* 其中1 Msym/s是符合低功耗藍牙5.0協(xié)議的設(shè)備所必須支持的据忘。
* 在1 Msym/s調(diào)制下,低功耗藍牙5.0協(xié)議定義了兩種PHY:(1)LE 1MPHY ,即信息數(shù)據(jù)不變嗎怨愤,信息數(shù)據(jù)的傳輸速率就為1Mb/s
* (2)LE Coded PHY嘶摊,即信息數(shù)據(jù)編碼方式署辉,信息數(shù)據(jù)的傳輸速率為125kb/s或者500kb/s
* 在2 Msym/s調(diào)制下,低功耗藍牙5.0協(xié)議僅定義了一種PHY:LE 2MPHY,即信息數(shù)據(jù)不編碼汉规,信息數(shù)據(jù)的傳輸速率就為2Mb/s
*/
@Override
public void onPhyUpdate(BluetoothDevice device, int txPhy, int rxPhy, int status) {
super.onPhyUpdate(device, txPhy, rxPhy, status);
//當調(diào)用了BluetoothGattServer.setPreferredPhy時,或者遠程設(shè)備更改了PHY時回調(diào)
Log.d(TAG, "onPhyUpdate");
}
@Override
public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
super.onPhyRead(device, txPhy, rxPhy, status);
//當調(diào)用了BluetoothGattServer.readPhy時回調(diào)
Log.d(TAG, "onPhyRead");
}
});
//9.將服務(wù)加入到周邊
return mBluetoothGattServer.addService(mService);
}
}