Android開啟藍牙開關
轉載自Android:Bluetooth 的打開和關閉
檢查系統藍牙是否開啟
BluetoothManager bluetoothManager = (BluetoothManager) this.
getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter mBluetoothAdapter = bluetoothManager.getAdapter();
mBluetoothAdapter.isEnabled();
開啟系統藍牙方式:
靜默打開:
- 注冊權限:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
- 開啟方式:
mBluetoothAdapter.enable();
以上靜默打開 Bluetooth 開關是調用了 BluetoothAdapter.enable() 方法,首先需要獲取 BluetoothAdapter 對象,如果這個對象為 null 的話,說明當前設備不支持 Bluetooth 功能。還有以下幾點需要注意:
1, 在 Nexus 5 Android 4.4.4 原生系統中,在沒有任何其它管理 Bluetooth 權限的應用情況下,調用強制打開 Bluetooth 的方法,沒有任何提示就直接打開 Bluetooth 了。
2,在小米手機 MI 2SC / MIUI-4.7.11 (Android 4.1.1 JRO03L) 上系統自帶的 “安全中心” – “應用權限管理” – “開啟藍牙” 中,有三種設置:
允許:調用強制打開 Bluetooth 代碼,沒有任何提示,Bluetooth 被成功打開。
提示:會彈出提示框,提示安全警告 “ ***應用嘗試開啟藍牙”,可以選擇“拒絕”或“允許”,還有記住此次選擇(備注:如果不記住的話,下次還會彈出同樣的提示框,除非你自己去修改了應用開啟藍牙的權限)。
拒絕:調用強制打開 Bluetooth 代碼,沒有任何提示,Bluetooth 強制打開失敗。
備注:各種手機自帶的權限管理功能或者第三方權限管理應用略有不同。
3,對于 BluetoothAdapter.enable() 這個方法,API 中有以下說明 (備注:初始 API 中,如 Android 2.0 Eclair / API Level 5 中并沒有這段提示)
Bluetooth should never be enabled without direct user consent. If you want to turn on Bluetooth in order to create a wireless connection, you should use the ACTION_REQUEST_ENABLE Intent, which will raise a dialog that requests user permission to turn on Bluetooth. The enable() method is provided only for applications that include a user interface for changing system settings, such as a “power manager” app.
沒有直接的用戶的允許絕不要開啟 Bluetooth。如果你想要打開 Bluetooth 創建一個無線連接,你應當使用 ACTION_REQUEST_ENABLE Intent,這樣會彈出一個提示框提示用戶是否開啟 Bluetooth,enable() 方法僅提供給有 UI 、更改系統設置的應用來使用,例如“電源管理”應用。
從以上官方 API 提示可以看出:不建議你調用此方法來打開 Bluetooth,至少是在沒有任何用戶提醒的前提下!
調用系統彈出框提示用戶打開
- 注冊權限:
<uses-permission android:name="android.permission.BLUETOOTH" />
- 開啟方式:
Intent requestBluetoothOn = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
this.startActivityForResult(requestBluetoothOn, REQUEST_CODE_BLUETOOTH_ON);
對于以上彈出系統彈框提示用戶打開 Bluetooth 的代碼,有以下幾點需要注意:
1,這種調用系統的彈出框提示用戶打開 Bluetooth 的方式,一般不會受到系統或者第三方權限管理應用的阻止。只有當你不提示用戶的情況下,可以理解為“偷偷摸摸”的打開 Bluetooth ,這個是被認為侵犯用戶的知情權,系統或者第三方權限管理應用可能加以阻止:直接禁止不提示用戶的情況下打開 Bluetooth,或者提示用戶,又或者是讓用戶自己選擇哪些應用可以強制開啟 Bluetooth。而在 Nexus 5 / Android 4.4.4 原生系統強制打開 Bluetooth 是沒有任何提示,并且可以成功打開。
2,彈出系統的提示框提醒用戶打開 Bluetooth 的主要代碼:
this.startActivityForResult(requestBluetoothOn, REQUEST_CODE_BLUETOOTH_ON);
注意:這個方法是需要 Activity 的對象來調用的!并且需要在 Activity 中重寫 onActivityResult 方法來獲取用戶操作彈出提示框的結果!
3,這種彈出的系統彈框,根據系統的不同,UI 會有所不同。會導致用戶app視覺不統一。
跳轉到系統設置中讓用戶自己打開:
startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS));
考慮到涉及用戶隱私和用戶體驗,推薦以下方式開啟 Bluetooth :
1,采用強制開啟 Bluetooth 的方式打開 Bluetooth ,但是調用強制開啟 Bluetooth 代碼之前,我們自己在應用中提示用戶,我們的應用需要開啟 Bluetooth ,讓用戶自己選擇是否開啟 Bluetooth 。自己在應用中提示用戶我們需要開啟 Bluetooth 相對于彈出系統的提示框提示用戶當前應用需要開啟 Bluetooth 的優勢在于我們可以控制提示的內容和提示的方式以及 UI。
2,假若用戶選擇了開啟 Bluetooth,但是強制開啟 Bluetooth 失敗,比如系統自帶的權限管理禁止你的應用開啟 Bluetooth ,我們不去提示用戶說當前系統禁止了應用開啟 Bluetooth,讓用戶自己去解除禁止。這樣顯然用戶體驗很差。這種情況下,我們再去調用彈出系統提示框提醒用戶打開 Bluetooth 即可。這種方式一般系統或者第三方應用不會禁止。
3,如果彈出系統提示框提醒用戶打開 Bluetooth 有問題的話,最后采用提示用戶自己去系統 Bluetooth 設置中打開 Bluetooth,跳轉到系統的 Bluetooth 設置界面。
PS:在目前Android手機中,是不支持在飛行模式下開啟藍牙的。如果藍牙已經開啟,那么藍牙的開關狀態會隨著飛行模式的狀態而發生改變。
BLE廣播數據解析
轉載自BLE 廣播數據解析
BLE中peripheral設備處于被發現狀態時會發送廣播包,peripheral設備通過廣播被中心設備發現,廣播中帶有peripheral設備自身的相關信息。
廣播包有兩種: 廣播包 (Advertising Data)和 響應包 (Scan Response),其中廣播包是每個設備必須廣播的,而響應包是可選的。 數據包的格式如下圖所示(圖片來自官方 Spec):
每個包都是 31 字節,數據包中分為有效數據(significant)和無效數據(non-significant)兩部分。
- 有效數據部分 :包含若干個廣播數據單元,稱為 AD Structure 。如圖中所示,AD Structure 的組成是:第一個字節是長度值 Len ,表示接下來的 Len 個字節是數據部分。數據部分的第一個字節表示數據的類型 AD Type ,剩下的 Len - 1 個字節是真正的數據 AD data 。其中 AD type 非常關鍵,決定了 AD Data 的數據代表的是什么和怎么解析,這個在后面會詳細講;
- 無效數據部分 :因為廣播包的長度必須是 31 個 byte,如果有效數據部分不到 31 自己,剩下的就用 0 補全。這部分的數據是無效的,解釋的時候,忽略即可。
AD type
所有的 AD type 的定義在文檔 Core Specification Supplement 中。 AD Type 包括如下類型:
-
Flags: TYPE = 0x01。這個數據用來標識設備 LE 物理連接的功能。DATA 是 0 到多個字節的 Flag 值,每個 bit 上用 0 或者 1 來表示是否為 True。如果有任何一個 bit 不為 0,并且廣播包是可連接的,就必須包含此數據。各 bit 的定義如下:
- bit 0: LE 有限發現模式
- bit 1: LE 普通發現模式
- bit 2: 不支持 BR/EDR
- bit 3: 對 Same Device Capable(Controller) 同時支持 BLE 和 BR/EDR
- bit 4: 對 Same Device Capable(Host) 同時支持 BLE 和 BR/EDR
- bit 5…7: 預留
-
Service UUID: 廣播數據中一般都會把設備支持的 GATT Service 廣播出來,用來告訴外面本設備所支持的 Service。有三種類型的 UUID:16 bit, 32bit, 128 bit。廣播中,每種類型類型有有兩個類別:完整和非完整的。這樣就共有 6 種 AD Type。
- 非完整的 16 bit UUID 列表: TYPE = 0x02;
- 完整的 16 bit UUID 列表: TYPE = 0x03;
- 非完整的 32 bit UUID 列表: TYPE = 0x04;
- 完整的 32 bit UUID 列表: TYPE = 0x05;
- 非完整的 128 bit UUID 列表: TYPE = 0x06;
- 完整的 128 bit UUID 列表: TYPE = 0x07;
-
Local Name: 設備名字,DATA 是名字的字符串。 Local Name 可以是設備的全名,也可以是設備名字的縮寫,其中縮寫必須是全名的前面的若干字符。
- 設備全名: TYPE = 0x08
- 設備簡稱: TYPE = 0x09
-
TX Power Level: TYPE = 0x0A,表示設備發送廣播包的信號強度。DATA 部分是一個字節,表示 -127 到 + 127 dBm。
-
帶外安全管理(Security Manager Out of Band):TYPE = 0x11。DATA 也是 Flag,每個 bit 表示一個功能:
- bit 0: OOB Flag,0 表示沒有 OOB 數據,1 表示有
- bit 1: 支持 LE
- bit 2: 對 Same Device Capable(Host) 同時支持 BLE 和 BR/EDR
- bit 3: 地址類型,0 表示公開地址,1 表示隨機地址
-
外設(Slave)連接間隔范圍:TYPE = 0x12。數據中定義了 Slave 最大和最小連接間隔,數據包含 4 個字節:
- 前 2 字節:定義最小連接間隔,取值范圍:0x0006 ~ 0x0C80,而 0xFFFF 表示未定義;
- 后 2 字節:定義最大連接間隔,同上,不過需要保證最大連接間隔大于或者等于最小連接間隔。
-
服務搜尋:外圍設備可以要請中心設備提供相應的 Service。其數據定義和前面的 Service UUID 類似:
- 16 bit UUID 列表: TYPE = 0x14
- 32 bit UUID 列表: TYPE = 0x??
- 128 bit UUID 列表: TYPE = 0x15
-
Service Data: Service 對應的數據。
- 16 bit UUID Service: TYPE = 0x16, 前 2 字節是 UUID,后面是 Service 的數據;
- 32 bit UUID Service: TYPE = 0x??, 前 4 字節是 UUID,后面是 Service 的數據;
- 128 bit UUID Service: TYPE = 0x??, 前 16 字節是 UUID,后面是 Service 的數據;
-
公開目標地址:TYPE = 0x17,表示希望這個廣播包被指定的目標設備處理,此設備綁定了公開地址,DATA 是目標地址列表,每個地址 6 字節。
-
隨機目標地址:TYPE = 0x18,定義和前一個類似,表示希望這個廣播包被指定的目標設備處理,此設備綁定了隨機地址,DATA 是目標地址列表,每個地址 6 字節。
-
Appearance:TYPE = 0x19,DATA 是表示了設備的外觀。
-
廠商自定義數據: TYPE = 0xFF,廠商自定義的數據中,前兩個字節表示廠商 ID,剩下的是廠商自己按照需求添加,里面的數據內容自己定義。
還有一些其他的數據,我這里就不一一列舉了,有需要的可以從這個文檔查閱 Core Specification Supplement 。
GATT
BLE技術是基于GATT進行通信的,GATT是一種屬性傳輸協議,簡單的講可以認為是一種屬性傳輸的應用層協議。它的結構非常簡單:
你可以把他看成xml來理解:
- 每個GATT由完成不同功能的Service組成;
- 每個Service由不同的Characteristic組成;
- 每個Characteristic由一個value和一個或者多個Descriptor組成;
Service、Characteristic相當于標簽(Service相當于他的類別,Characteristic相當于它的名字),而value才真正的包含數據,Descriptor是對這個value進行的說明和描述,當然我們可以從不同角度來描述和說明,因此可以有多個Descriptor.
這樣子理解可能不夠準確,下面我們來舉一個簡單的例子進行說明:
常見的小米手環是一個BLE設備,(假設)它包含三個Service,分別是提供設備信息的Service、提供步數的Service、檢測心率的Service;
而設備信息的service中包含的characteristic包括廠商信息、硬件信息、版本信息等;而心率Service則包括心率characteristic等,而心率characteristic中的value則真正的包含心率的數據,而descriptor則是對該value的描述說明,比如value的單位啊,描述啊,權限啊等。
作者:小時不識月z
鏈接:https://www.jianshu.com/p/29a730795294
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權并注明出處。
BluetoothAdapter類介紹
摘錄自Android中藍牙的基本使用----BluetoothAdapter類簡介
由于網絡上關于BluetoothAdapter的一些常用API函數都有了介紹,因此,我著重介紹一些BluetoothAdapter類疏忽的地方。
1,BluetoothAdapter STATE 狀態值 , 即開關狀態
int STATE_OFF 藍牙已經關閉
int STATE_ON 藍牙已經打開
int STATE_TURNING_OFF 藍牙處于關閉過程中 ,關閉ing
int STATE_TURNING_O 藍牙處于打開過程中 ,打開ing
2,BluetoothAdapter SCAN_MOD狀態值 ,即掃描狀態
首先說明,可以掃描其他設備的,當然它同時能被其他藍牙設備掃碼。
int SCAN_MODE_CONNECTABLE 表明該藍牙可以掃描其他藍牙設備
int SCAN_MODE_CONNECTABLE_DISCOVERABLE 表明該藍牙設備同時可以掃碼其他藍牙設備,并且可以被其他藍牙設備掃描到。
int SCAN_MODE_NONE 該藍牙不能掃描以及被掃描。
3,獲得藍牙適配器實例
public static synchronized BluetoothAdapter getDefaultAdapter ()
功能:獲得本設備的藍牙適配器實例。
返回值:如果設備具備藍牙功能,返回BluetoothAdapter 實例;否則,返回null對象。
4,掃描藍牙設備
public boolean startDiscovery ()
功能: 掃描藍牙設備
注意: 如果藍牙沒有開啟,該方法會返回false,即不會開始掃描過程。public boolean cancelDiscovery ()
功能: 取消掃描過程。
注意: 如果藍牙沒有開啟,該方法會返回false。public boolean isDiscovering ()
功能: 是否正在處于掃描過程中。
注意: 如果藍牙沒有開啟,該方法會返回false。
5,獲取藍牙相關信息
public String getName ()
功能:獲取藍牙設備Namepublic String getAddress ()
功能:獲取藍牙設備的硬件地址(MAC地址),例如:00:11:22:AA:BB:CC public boolean setName (String name)
功能:設置藍牙設備的Name,public Set<BluetoothDevice> getBondedDevices ()
功能:獲取與本機藍牙所有綁定的遠程藍牙信息,以BluetoothDevice類實例(稍后講到)返回。
注意:如果藍牙為開啟,該函數會返回一個空集合 。public static boolean checkBluetoothAddress (String address)
功能: 驗證藍牙設備MAC地址是否有效。所有設備地址的英文字母必須大寫,48位,形如:00:43:A8:23:10:F1 。
返回值: true 設備地址有效
false 設備地址無效public BluetoothDevice getRemoteDevice (String address)
功能:以給定的MAC地址去創建一個 BluetoothDevice 類實例(代表遠程藍牙實例)。即使該藍牙地址不可見,也會產生一個BluetoothDevice 類實例。
返回:BluetoothDevice 類實例 。注意,如果該藍牙設備MAC地址不能被識別,其藍牙Name為null。
異常:如果MAC address無效,拋出IllegalArgumentException。
Android Bluetooth Low Energy (BLE) Example
轉載自Android Bluetooth Low Energy (BLE) Example
Since BLE was introduced in API 18 and cannot be used on old devices, due to change of Bluetooth specifications. I suggest you to specify the min SDK version to 18 in your app. Next add these permissions and feature tags in the manifest tag of your app manifest:
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
Since I will be printing all the data in logs, there is no need for a layout file, lets have a look at the code for using Bluetooth low energy on Android:
package com.truiton.bleexample;import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.widget.Toast;import java.util.ArrayList;
import java.util.List;@TargetApi(21)
public class MainActivity extends ActionBarActivity {private BluetoothAdapter mBluetoothAdapter;private int REQUEST_ENABLE_BT = 1;private Handler mHandler;private static final long SCAN_PERIOD = 10000;private BluetoothLeScanner mLEScanner;private ScanSettings settings;private List<ScanFilter> filters;private BluetoothGatt mGatt;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mHandler = new Handler();if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {Toast.makeText(this, "BLE Not Supported",Toast.LENGTH_SHORT).show();finish();}final BluetoothManager bluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);mBluetoothAdapter = bluetoothManager.getAdapter();}@Overrideprotected void onResume() {super.onResume();if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);} else {if (Build.VERSION.SDK_INT >= 21) {mLEScanner = mBluetoothAdapter.getBluetoothLeScanner();settings = new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();filters = new ArrayList<ScanFilter>();}scanLeDevice(true);}}@Overrideprotected void onPause() {super.onPause();if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {scanLeDevice(false);}}@Overrideprotected void onDestroy() {if (mGatt == null) {return;}mGatt.close();mGatt = null;super.onDestroy();}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {if (requestCode == REQUEST_ENABLE_BT) {if (resultCode == Activity.RESULT_CANCELED) {//Bluetooth not enabled.finish();return;}}super.onActivityResult(requestCode, resultCode, data);}private void scanLeDevice(final boolean enable) {if (enable) {mHandler.postDelayed(new Runnable() {@Overridepublic void run() {if (Build.VERSION.SDK_INT < 21) {mBluetoothAdapter.stopLeScan(mLeScanCallback);} else {mLEScanner.stopScan(mScanCallback);}}}, SCAN_PERIOD);if (Build.VERSION.SDK_INT < 21) {mBluetoothAdapter.startLeScan(mLeScanCallback);} else {mLEScanner.startScan(filters, settings, mScanCallback);}} else {if (Build.VERSION.SDK_INT < 21) {mBluetoothAdapter.stopLeScan(mLeScanCallback);} else {mLEScanner.stopScan(mScanCallback);}}}private ScanCallback mScanCallback = new ScanCallback() {@Overridepublic void onScanResult(int callbackType, ScanResult result) {Log.i("callbackType", String.valueOf(callbackType));Log.i("result", result.toString());BluetoothDevice btDevice = result.getDevice();connectToDevice(btDevice);}@Overridepublic void onBatchScanResults(List<ScanResult> results) {for (ScanResult sr : results) {Log.i("ScanResult - Results", sr.toString());}}@Overridepublic void onScanFailed(int errorCode) {Log.e("Scan Failed", "Error Code: " + errorCode);}};private BluetoothAdapter.LeScanCallback mLeScanCallback =new BluetoothAdapter.LeScanCallback() {@Overridepublic void onLeScan(final BluetoothDevice device, int rssi,byte[] scanRecord) {runOnUiThread(new Runnable() {@Overridepublic void run() {Log.i("onLeScan", device.toString());connectToDevice(device);}});}};public void connectToDevice(BluetoothDevice device) {if (mGatt == null) {mGatt = device.connectGatt(this, false, gattCallback);scanLeDevice(false);// will stop after first device detection}}private final BluetoothGattCallback gattCallback = new BluetoothGattCallback() {@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {Log.i("onConnectionStateChange", "Status: " + status);switch (newState) {case BluetoothProfile.STATE_CONNECTED:Log.i("gattCallback", "STATE_CONNECTED");gatt.discoverServices();break;case BluetoothProfile.STATE_DISCONNECTED:Log.e("gattCallback", "STATE_DISCONNECTED");break;default:Log.e("gattCallback", "STATE_OTHER");}}@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {List<BluetoothGattService> services = gatt.getServices();Log.i("onServicesDiscovered", services.toString());gatt.readCharacteristic(services.get(1).getCharacteristics().get(0));}@Overridepublic void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristiccharacteristic, int status) {Log.i("onCharacteristicRead", characteristic.toString());gatt.disconnect();}};
}
disconnect() 和close()的區別
在進行BLE開發過程中可能會遇到操作失敗等情況,這個時候可能需要斷開與BLE的連接或者清理相關資源.在BluetoothGatt類中有兩個相關的方法 。
- disconnect()
- close()
那么這個兩個方法有什么區別,又該如何使用呢。
disconnect()方法:如果調用了該方法之后可以調用connect()方法進行重連,這樣還可以繼續進行斷開前的操作。
close()方法:一但調用了該方法, 如果你想再次連接,必須調用BluetoothDevice的connectGatt()方法。 因為close()方法將釋放BluetootheGatt的所有資源。
需要注意的問題:
當你需要手動斷開時,調用disconnect()方法,此時斷開成功后會回調onConnectionStateChange方法,在這個方法中再調用close方法釋放資源。
如果在disconnect后立即調用close,會導致無法回調onConnectionStateChange方法。
常見問題:
BLE連接之后onServicesDiscovered不被調用
摘錄自ble連接之后onServicesDiscovered 不被調用
問題:onServicesDiscovered never called while connecting to GATT Server
Something that has been really useful for me is to wait for about 600ms after the connection has been established and then start the service discovery.
項目中出現藍牙連接上之后,始終不進onServicesDiscovered回調,mBluetoothGatt.discoverServices()做如下延時即可
if (newState == BluetoothProfile.STATE_CONNECTED) {intentAction = ACTION_GATT_CONNECTED;broadcastUpdate(intentAction);Log.i(TAG, "Connected to GATT server.");// Attempts to discover services after successful connection.//有時候發現服務不回調,需延時 https://stackoverflow.com/questions/41434555/onservicesdiscovered-never-called-while-connecting-to-gatt-server#comment70285228_41526267try {Thread.sleep(600);Log.i(TAG, "Attempting to start service discovery:"+ mBluetoothGatt.discoverServices());} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}
}
相關資源:
Android低功耗藍牙的那點事兒