編輯:關於Android編程
在app應用的開發過程中,一般和藍牙接觸的不多,但是隨著智能穿戴設備的發展,穿戴設備和手機關聯的app越來越多,之前也是沒怎麼接觸過這一塊的東西,正好最近需要做一個和藍牙有關的app,所以研究學習下,把應用的東西總結一下。項目源碼已經上傳github。
BLE是Bluetooth Low Energy的縮寫,又叫藍牙4.0,區別於藍牙3.0和之前的技術。BLE前身是NOKIA開發的Wibree技術,主要用於實現移動智能終端與周邊配件之間的持續連接,是功耗極低的短距離無線通信技術,並且有效傳輸距離被提升到了100米以上,同時只需要一顆紐扣電池就可以工作數年之久。
BLE是在藍牙技術的基礎上發展起來的,既同於藍牙,又區別於傳統藍牙。BLE設備分單模和雙模兩種,雙模簡稱BR,商標為Bluetooth Smart Ready,單模簡稱BLE或者LE,商標為Bluetooth Smart。Android是在4.3後才支持BLE,這可以解釋不是所有藍牙手機都支持BLE,而且支持BLE的藍牙手機一般是雙模的。
BLE和普通藍牙之間是有區別的:
優點:
功耗更低
連接速度更快
缺點:
每次發送的數據比較小
ATT
ATT(Attribute Protocol)協議是基礎協議,ATT針對BLE設備進行了特別的優化,它的基礎是屬性,使用一個UUID來定義屬性的類型。
GATT
GATT(Generic Attribute Profile)是所有BLE頂層協議的基礎,它定義了怎麼把一堆ATT屬性分組成為有意義的服務。
services
服務,基礎是UUID的值為0x2800的屬性。所有跟在這個屬性後面的屬性都屬於這個屬性定義的服務,直到另一個0x2800屬性出現。一個BLE設備可以有多個服務。
characteristics
特征,每一個服務都可以包含有多個特征,特征存儲了有用的值以及權限。其中藍牙模塊和app進行通訊主要是通過它來進行。
descriptor
特征的描述,又叫描述符,在一個特征中會有多個描述符。GATT協議已經定義了大多數的標准描述符,這其中有一個特別重要的描述符是:client characteristic configuration其UUID是0x2902,具有一個16bit的可讀寫值。被用來定義通知和暗示,通過設置可以能夠讓設備發送通知,並且被主機端接收到。
Notification
通知,BLE模塊向空中發送消息,可以被主機端的藍牙模塊接收到。包含在characteristics中,但是需要權限打開。
BLE透傳
透傳模式下,所有的串口數據都被看做用戶數據,模塊會將這些數據通過藍牙發送給主機,即是藍牙模塊和手機或者其他控制設備之間的通訊(其實還有另外一種命令模式,但是透傳模式更快速和簡單。),透傳模式即是通過services和characteristics進行。一般廠家或者工程師會給出相應的說明,例如下圖:
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxzdHJvbmc+vcfJq7rN1rDU8Dwvc3Ryb25nPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9wPg0KPHA+QW5kcm9pZMnosbi6zUJMRcnosbi9u7ul09DBvdfpvcfJq6O6PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvcD4NCjxwPtbQ0MTJ6LG4KENlbnRyYWwpus3N4s6nyeixuChQZXJpcGhlcnkpoaPN4s6nyeixuMrHyv2+3czhuanV36Os1tDR68rHyv2+3cq508MvtKbA7dXfoaPSu7j21tDR68nosbi/ydLUzazKscGsvdO24Lj2zeLOp8nosbijrLWrysfSu7j2zeLOp8nosbjNrMqx1rvE3MGsvdPSu7j21tDR68nosbihozwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvcD4NCjxoMiBpZD0="android-使用">Android 使用
准備權限
准備BLE
獲取BluetoothAdapter:BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter = bluetoothManager.getAdapter(); //打開藍牙 方法一 if (!bluetoothAdapter.isEnabled()) { bluetoothAdapter.enable(); } //方法二 推薦 Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enabler, REQUEST_ENABLE);判斷是否支持BLE:
private boolean checkBluetooth() { if (!getPackageManager().hasSystemFeature( PackageManager.FEATURE_BLUETOOTH_LE)) { return false; } return true; }開始掃描設備:
bluetoothAdapter.startLeScan(mLeScanCallback); BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { @Override public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) { runOnUiThread(new Runnable() { @Override public void run() { /* 顯示了一個列表,點擊進入具體設備頁面 BLEDeviceTestActivity 並且將設備的名稱和地址傳遞過去 intent.putExtra(DeviceControlActivity.EXTRAS_DEVICE_NAME, device.getName()); intent.putExtra(DeviceControlActivity.EXTRAS_DEVICE_ADDRESS, device.getAddress()); */ adapter.add("name : " + device.getName() + "\n address : " + device.getAddress()); bluetoothDevices.add(device); } }); } };
連接設備
在BLEDeviceTestActivity中獲取設備,並且連接設備:
//根據地址獲取設備 BluetoothDevice device = bluetoothAdapter.getRemoteDevice(addressStr); //獲取鏈接 這個時候需要實現BluetoothGattCallback BluetoothGatt bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback);
有關device.connectGatt(this, false, bluetoothGattCallback);方法,可以看到它是通過設備建立一個GATT鏈接,任何有關GATT鏈接的操作都將觸發回調。而BluetoothGattCallback會異步處理這些回調結果。
/** * Connect to GATT Server hosted by this device. Caller acts as GATT client. * The callback is used to deliver results to Caller, such as connection status as well * as any further GATT client operations. * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct * GATT client operations. * @param callback GATT callback handler that will receive asynchronous callbacks. * @param autoConnect Whether to directly connect to the remote device (false) * or to automatically connect as soon as the remote * device becomes available (true). * @throws IllegalArgumentException if callback is null */ public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback) { return (connectGatt(context, autoConnect,callback, TRANSPORT_AUTO)); }
所以實現bluetoothGattCallback:
BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() { /** * 返回鏈接狀態 * @param gatt * @param status 鏈接或者斷開連接是否成功 {@link BluetoothGatt#GATT_SUCCESS} * @param newState 返回一個新的狀態{@link BluetoothProfile#STATE_DISCONNECTED} or * {@link BluetoothProfile#STATE_CONNECTED} */ @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); } /** * 獲取到鏈接設備的GATT服務時的回調 * @param gatt * @param status 成功返回{@link BluetoothGatt#GATT_SUCCESS} */ @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); } /** * 讀特征的時候的回調 * @param gatt * @param characteristic 從相關設備上面讀取到的特征值 * @param status */ @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); } /** * 指定特征寫入操作的回調結果 * @param gatt * @param characteristic * @param status */ @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status); } /** * 設備發出通知時會調用到該接口 * @param gatt * @param characteristic */ @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); } /** * 指定描述符的讀操作的回調 * @param gatt * @param descriptor * @param status */ @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorRead(gatt, descriptor, status); } /** * 指定描述符的寫操作 * @param gatt * @param descriptor * @param status */ @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { super.onDescriptorWrite(gatt, descriptor, status); } /** * 當一個寫入事物完成時的回調 * @param gatt * @param status */ @Override public void onReliableWriteCompleted(BluetoothGatt gatt, int status) { super.onReliableWriteCompleted(gatt, status); } @Override public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { super.onReadRemoteRssi(gatt, rssi, status); } @Override public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { super.onMtuChanged(gatt, mtu, status); } };
可以看到BluetoothGattCallback有很多方法,根據名字不難明白其意思,接著根據藍牙的使用過程來一個一個的分析就能清晰明了其中功能。
因為我們前面進行了鏈接,所以首先觸發的回調是onConnectionStateChange()方法:
/** * 返回鏈接狀態 * @param gatt * @param status 鏈接或者斷開連接是否成功 {@link BluetoothGatt#GATT_SUCCESS}即表示操作是否成功 * @param newState 返回一個新的狀態{@link BluetoothProfile#STATE_DISCONNECTED} or 即表示當前的狀態 * {@link BluetoothProfile#STATE_CONNECTED} 因此使用newState參數來做判斷 */ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { super.onConnectionStateChange(gatt, status, newState); if (newState == BluetoothProfile.STATE_CONNECTED) { //連接成功 因為是異步調用的 所以刷新UI的操作要放在主線程中,當然也可以使用hanlder Eventbus等 隨便 runOnUiThread(new Runnable() { @Override public void run() { connectTv.setText("連接成功"); } }); //鏈接成功 gatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { //斷開連接 runOnUiThread(new Runnable() { @Override public void run() { connectTv.setText("連接斷開"); } }); } }
在上面的代碼中發現當連接成功後調用了gatt.discoverServices();方法,這個方法是用來發現遠程設備提供的服務,以及它們包含的特征特性和描述符等等。
因為根據前面的介紹可以知道,要想app和藍牙模塊進行通訊,需要通過這些服務和特征等。這個方法會觸發回調onServicesDiscovered():
@Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); if (BluetoothGatt.GATT_SUCCESS == status) { gatt.getServices(); /* 一個GATT服務表現為一個 BluetoothGattService 對象,我們需要通過適當的UUID從 BluetoothGatt 實例中獲得; 一個GATT特征表示為一個 BluetoothGattCharacteristic 對象,我們可以通過適當的UUID從 BluetoothGattService 中得到; 相當於一個數據類型,它包括一個value和0~n個value的描述(BluetoothGattDescriptor) 一個GATT描述符表現為一個 BluetoothGattDescriptor 對象,我們可以通過適當的UUID從BluetoothGattCharacteristic 對象中獲得: 描述符,對Characteristic的描述,包括范圍、計量單位等 */ gattCharacteristicList.clear(); String uuid = null; ArrayList> gattServiceData = new ArrayList >(); ArrayList>> gattCharacteristicData = new ArrayList>>(); //獲取服務 for (BluetoothGattService gattService : gatt.getServices()) { HashMap currentServiceData = new HashMap (); uuid = gattService.getUuid().toString(); currentServiceData.put("name", SampleGattAttributes.lookup(uuid, "未知服務")); currentServiceData.put("uuid", uuid); gattServiceData.add(currentServiceData); ArrayList > gattCharacteristicGroupData = new ArrayList >(); List gattCharacteristics = gattService .getCharacteristics(); ArrayList charas = new ArrayList (); // 獲取每個服務中包含的特征 for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { charas.add(gattCharacteristic); HashMap currentCharaData = new HashMap (); uuid = gattCharacteristic.getUuid().toString(); currentCharaData.put("name", SampleGattAttributes.lookup(uuid, "未知特征")); currentCharaData.put("uuid", uuid); //當某個特征的描述符UUID為0000ff02-0000-1000-8000-00805f9b34fb時 使能BLE透傳模塊通知功能 if (uuid.equals("0000ff02-0000-1000-8000-00805f9b34fb")) { setCharacteristicNotification(gattCharacteristic, true); } gattCharacteristicGroupData.add(currentCharaData); } gattCharacteristicList.add(charas); gattCharacteristicData.add(gattCharacteristicGroupData); } // 用一個可折疊的列表來展示 這些服務和 特征 final SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter( BLEDeviceTestActivity.this, gattServiceData, android.R.layout.simple_expandable_list_item_2, new String[]{ "name", "uuid"}, new int[]{android.R.id.text1, android.R.id.text2}, gattCharacteristicData, android.R.layout.simple_expandable_list_item_2, new String[]{ "name", "uuid"}, new int[]{android.R.id.text1, android.R.id.text2}); runOnUiThread(new Runnable() { @Override public void run() { listView.setAdapter(gattServiceAdapter); } }); } }
上面一大堆代碼,就是把獲取到的服務和特征用一個列表展示出來並不是關鍵,關鍵代碼在:
if (uuid.equals("0000ff02-0000-1000-8000-00805f9b34fb")) { setCharacteristicNotification(gattCharacteristic, true); }
setCharacteristicNotification()方法如下是啟用一個指定特征的通知權限。這裡被小小的坑了一下,本來以為bluetoothGatt.setCharacteristicNotification(characteristic, enabled);
設置true就夠了的,但是調試的時候發現不能夠接收到通知,查閱了資料才知道還需要給描述符設置通知權限啟用才行,但是具體哪個描述符就不知道了,最後還是根據藍牙模塊廠家發來的demo反編譯,查看了其源碼才知道相應的UUID。不過通過上面的介紹發現此UUID正是0x2902開頭的。由此我猜測應該這個描述符應該是GATT協議當中已經給定義好的一個描述符。
public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) { //僅僅有這一句是不夠的 bluetoothGatt.setCharacteristicNotification(characteristic, enabled); //需要為指定特征的特定的描述符設置啟用才行 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID .fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); if (descriptor != null) { System.out.println("write descriptor"); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); bluetoothGatt.writeDescriptor(descriptor); } }
當設置了通知以後,如果BLE設備通過通知的方式發送數據的話,app端接到通知會觸發onCharacteristicChanged()方法,此方法就可以用來接收BLE模塊通過廣播形式給返回的:
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(BLEDeviceTestActivity.this, "接收到數據", Toast.LENGTH_SHORT).show(); } }); //以字節碼數組的形式接收到數據 final byte[] data = characteristic.getValue(); if (data != null && data.length > 0) { final StringBuilder stringBuilder = new StringBuilder( data.length); StringBuffer test = new StringBuffer(); for (byte byteChar : data) { test.append(byteChar); stringBuilder.append(String.format("%02X ", byteChar));//以兩位16進制輸出 不足的補0 } runOnUiThread(new Runnable() { @Override public void run() { //數據展示 dataTv.setText(new String(data) + "\n" + stringBuilder.toString()); } }); } }
根據前面的介紹知道藍牙的透傳是通過characteristics進行的,所以當然還有讀和寫的操作,在進行操作之前需要先對服務的UUID進行判斷,
可以根據characteristic.getUuid()來得到UUID,或者在知道UUID的情況下,主動獲取對應的特征進行操作。
進行讀操作如下,觸發回調函數onCharacteristicRead():
//對相應的特征進行讀操作 bluetoothGatt.readCharacteristic(characteristic); //讀取成功觸發回調函數 public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicRead(gatt, characteristic, status); //讀取到的數據存在characteristic當中,可以通過characteristic.getValue();函數取出。然後再進行解析操作。 if (BluetoothGatt.GATT_SUCCESS == status) { final byte[] data = characteristic.getValue(); ... //和通知一樣也是通過字節碼的形式傳遞數據 這裡省略不寫 } }
同樣既然有了讀,也可以進行寫的操作,寫操作如下,會觸發回調函數onCharacteristicWrite():
//這裡寫入需要是字節碼數組的形式來進行 characteristic.setValue(byte[] value); bluetoothGatt.writeCharacteristic(characteristic); //這裡沒什麼好說的,就是判斷寫入是否成功 public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { super.onCharacteristicWrite(gatt, characteristic, status); if (BluetoothGatt.GATT_SUCCESS == status) { Log.d("BLEDeviceTestActivity", "寫入成功"); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(BLEDeviceTestActivity.this, "寫入成功", Toast.LENGTH_SHORT).show(); } }); }
如果寫入成功,並且透傳模塊有應答,一般的應答方式是通過通知的方式進行的。所以一般在項目中在鏈接成功以後就要開啟通知的權限,省的後面寫入的時候忘記。至此,手機和BLE模塊之間的通訊基本完成,其他的寫入描述符這些回調過程大同小異。
當然,這只是簡單的完成了通訊,真正使用的時候由於數據包大小的限制還需要分包傳輸,接受等等等等很多需要完善的地方。
很久前也寫過一篇Android數據庫操作相關內容。在正式項目中,我們通常會使用數據庫開源框架如GreenDao來對數據庫進行操作。感覺很久沒有直接使用Sql語句了,這幾天
1. 備忘錄設計模式介紹在不破壞封閉的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,這樣,以後就可將該對象恢復到原先保存的狀態。2. 備忘錄設計模式使用場景
本文實例為大家分享了Android答題器翻頁功能,主要使用ViewFilpper和GestureDetector來實現,供大家參考,具體內容如下1.效果圖2.實現思路把A
Android LayoutInflater.inflate()詳解深入理解LayoutInflater.inflate()由於我們很容易習慣公式化的預置代碼