一、申請位置權限
在Android6.0以後要掃描藍牙設備,還需要請求位置權限:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
位置權限屬於危險權限,因此需要動態獲取:
//判斷是否有權限
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
//請求權限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},MY_PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION);
//判斷是否需要 向用戶解釋,爲什麼要申請該權限
if(ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.READ_CONTACTS)) {
Toast.makeText(this,"shouldShowRequestPermissionRationale",
Toast.LENGTH_SHORT).show();
}
}
//權限申請結果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
在實際開發的過程中你也可以設置只有支持藍牙的設備才能運行該APP:
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="true" />
這裏設置required爲true就是只有在支持藍牙的設備上才能運行,但是如果想讓你的app提供給那些不支持BLE的設備,需要在manifest中包括上面代碼並設置required="false"
,然後在運行時可以通過使用PackageManager.hasSystemFeature()
確定BLE的可用性。
// 使用此檢查確定BLE是否支持在設備上,然後你可以有選擇性禁用BLE相關的功能
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
二、掃描方式
Android官方提供的藍牙掃描方式有三種,分別是:
- BluetoothAdapter.startDiscovery()//可以掃描經典藍牙和ble藍牙兩種
- BluetoothAdapter.startLeScan()//掃描低功耗藍牙,在api21已經棄用,不過還是可以使用
- BluetoothLeScanner.startScan()//新的ble掃描方法
startDiscovery()方法在大多數手機上是可以同時發現經典藍牙和低功耗藍牙(BLE)的,但是startDiscovery()的回調無法返回BLE的廣播,所以無法通過廣播識別設備,而且startDiscovery()掃描BLE效率比startLeScan()低很多。因此需要根據具體的需求去做適配,才能更高效的搜尋藍牙。PS: startLeScan()和startScan()有重載方法可以指定規則,參數去搜索。
市面上的藍牙設備,現在一般都是低功耗的藍牙設備,因此只需要用下面兩種掃描方式就OK了
2.1.startLeScan方式
官方提供的事例代碼正是使用了這種方式,startLeScan方法有兩個:
public boolean startLeScan(LeScanCallback callback)
public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback)
查看源碼會發現一個參數的方法是調用了兩個參數的方法,只是serviceuuid[]傳入爲null,也就是過濾條件爲空。這裏我使用的是沒有過濾條件的方法:
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 10000;
...
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
...
}
// Device scan callback.
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
//掃描到的設備信息,將字符數組轉成16進制字符串
String scanRecordStr = SysConvert.bytesToHex(scanRecord);
//在這裏可以做一下簡單的操作,避免多操作造成性能問題
}
};
因爲在掃描藍牙是一個非常非常耗能的操作,因此一定要掃描一段時間就關閉,如果需要循環掃描,可以通過定時器實現掃描的開關操作
注意:打開和關閉所使用的回調必須是同一個,雖然關閉一個沒有打開的回調沒有錯誤返回。
2.2.startScan方式
我在項目中是使用startScan()掃描的,因爲可以通過設置一些參數來靈活控制掃描的頻率等。
同樣的有三個重載方法:
public void startScan(final ScanCallback callback)
public void startScan(List<ScanFilter> filters, ScanSettings settings,final ScanCallback callback)
public int startScan(@Nullable List<ScanFilter> filters, @Nullable ScanSettings settings,
@NonNull PendingIntent callbackIntent)
這裏我以第二種爲例:
/**
* 掃描設備
*/
public void scanDevices() {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mProgressDialog.dismiss();
if (mBluetoothLeScanner != null) {
mBluetoothLeScanner.stopScan(scanCallback);
}
}
}, 1000);
mBluetoothLeScanner.startScan(null, createScanSetting(), scanCallback);
}
/**
* 掃描廣播數據設置
*
* @return
*/
public ScanSettings createScanSetting() {
ScanSettings.Builder builder = new ScanSettings.Builder();
builder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
//builder.setReportDelay(100);//設置延遲返回時間
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES);
}
return builder.build();
}
/**
* 回調
*/
private ScanCallback scanCallback=new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
byte[] scanData=result.getScanRecord().getBytes();
//把byte數組轉成16進制字符串,方便查看
}
@Override
public void onBatchScanResults(List<ScanResult> results) {
super.onBatchScanResults(results);
}
@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
}
};
ScanSettings :
setScanMode:有三種模式分別是
SCAN_MODE_LOW_POWER--------耗電最少,掃描時間間隔最短
SCAN_MODE_BALANCED---------平衡模式,耗電適中,掃描時間間隔一般,我使用這種模式來更新設備狀態
SCAN_MODE_LOW_LATENCY---------最耗電,掃描延遲時間短,打開掃描需要立馬返回結果可以使用
setReportDelay:設置掃描返回延遲時間,一般是大於零的毫秒值
如果設置了該屬性,掃描結果會以list形式在onBatchScanResults方法返回,在onScanResult中沒有返回,如果不設置則在onScanResult中返回,返回的信息包含在result中,可以通過getBytes返回每一個設備信息的字符數組,getDevice獲取BluetoothDevice設備,在通過字符數組和藍牙設備對象獲取你想要的信息
本人也是一個藍牙開發新手,如果上面有什麼地方有問題,請指出,共同探討,共同進步,不勝感激!