前言:
IPhone 可以通過 ibeacon 設備發出的藍牙廣播來喚醒應用,但android有沒有類似的機制來進行喚醒app呢?
很開心的告訴你,在 android 8.0(android 0) 以上的系統已經支持了!!!
說明:
在android 8.0 的 API中,藍牙庫中的android.bluetooth.le.BluetoothLeScanner類增加了一個新方法,看下圖
注:api level 26 即 android 8.0
該方法是用於掃描手機周邊的藍牙設備。在8.0以前,google提供的藍牙掃描方法都是需要app進程還活。但該方法只要調用成功,無論app進程是否還活着,系統都會在后台持續執行藍牙掃描。如果手機靠近指定的藍牙設備附近,app就能被喚醒接收藍牙的掃描結果。
用途:
該機制雖然只能在特定的區域對app進行喚醒,但在很多業務場景上非常實用,舉幾個栗子:
1.室內定位
2.進入商場用戶立馬能收到商場的活動信息
3.運動手環需要即時上報數據(如:電量不足等)
....
開發:
支持該機制的拉活,實際上就是讓app去掃描一個特定的藍牙廣播,等待系統返回結果。相信有弄過藍牙開發的小伙伴都知道藍牙開發這個坑有多深,而下面是一個跳坑的教程。
一.權限:
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
注:ACCESS_FINE_LOCATION在android 6.0以上需要運行獲取(見6.0以上運行時權限申請)。
app除獲取了上述權限,還需要確保藍牙開啟,以下是代碼開啟藍牙的方法:
//判斷如果藍牙沒有開啟的話,則進行提示用戶開啟 BluetoothManager bluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }
二.API調用
@TargetApi(26) public void onOpen(View view){ //BluetoothManager是向藍牙設備通訊的入口 BluetoothManager bluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); //指定需要識別到的藍牙設備 List<ScanFilter> scanFilterList = new ArrayList<>(); ScanFilter.Builder builder = new ScanFilter.Builder(); builder.setServiceUuid(ParcelUuid.fromString(UUID_SERVICE)); ScanFilter scanFilter = builder.build(); scanFilterList.add(scanFilter); //指定藍牙的方式,這里設置的ScanSettings.SCAN_MODE_LOW_LATENCY是比較高頻率的掃描方式 ScanSettings.Builder settingBuilder = new ScanSettings.Builder(); settingBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY); settingBuilder.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE); settingBuilder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES); settingBuilder.setLegacy(true); ScanSettings settings = settingBuilder.build(); //指定掃描到藍牙后是以什么方式通知到app端,這里將以可見服務的形式進行啟動 PendingIntent callbackIntent = PendingIntent.getForegroundService( this, 1, new Intent("com.hungrytree.receiver.BleService").setPackage(getPackageName()), PendingIntent.FLAG_UPDATE_CURRENT ); //啟動藍牙掃描 bluetoothAdapter.getBluetoothLeScanner().startScan(scanFilterList,settings,callbackIntent); }
這是開啟藍牙掃描的代碼,此處不做過多的說明,有想對android藍牙開發有更深入了解的小伙伴,可以前往這里的傳送門。不過需要提醒一下大家要注意兩個點:
1.builder.setServiceUuid(ParcelUuid.fromString("填入您需要掃描的設備uuid"));
此處的uuid就相當於是你藍牙設備的標志,是一定要與你的設備匹配上的,不能隨便亂填。
2.PendingIntent建議以前台服務(getForegroundService)方式create。經過測試,如果以getService,getBroadcast方法進行創建PendingIntent的話在拉活方面存在比較多的兼容問題.而getForegroundService方式在國內主流機型上可正常拉活進程(經過測試的機型有:華為,小米,oppo,vivo等)
PendingIntent.getForegroundService( this, 1, new Intent("com.hungrytree.receiver.BleService").setPackage(getPackageName()), PendingIntent.FLAG_UPDATE_CURRENT );
上面代碼已經實現藍牙掃描觸發,而下面是實現藍牙廣播的接收:
//AndroidManifest中的服務聲明
<service android:name=".TestService"> <intent-filter> <action android:name="com.hungrytree.receiver.BleService"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </service>
@TargetApi(26) public class TestService extends Service { @Override public void onCreate() { super.onCreate(); //以前台服務的方式啟動,要調用startForeground,否則會出現arn異常 Notification notification = new Notification.Builder(this, NotificationChannel.DEFAULT_CHANNEL_ID) .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)) .build(); startForeground(110, notification); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent.getAction() == null) { return super.onStartCommand(intent, flags, startId); } //獲取返回的錯誤碼 int errorCode = intent.getIntExtra(BluetoothLeScanner.EXTRA_ERROR_CODE, -1);//ScanSettings.SCAN_FAILED_* //獲取到的藍牙設備的回調類型 int callbackType = intent.getIntExtra(BluetoothLeScanner.EXTRA_CALLBACK_TYPE, -1);//ScanSettings.CALLBACK_TYPE_* if (errorCode == -1) { //掃描到藍牙設備信息 List<ScanResult> scanResults = (List<ScanResult>) intent.getSerializableExtra(BluetoothLeScanner.EXTRA_LIST_SCAN_RESULT); if (scanResults != null) { for (ScanResult result : scanResults) { //打印所有設備的地址 String address = result.getDevice().getAddress(); Log.i("haha", "device address " + address); } } } else { //此處為掃描失敗的錯誤處理 } return super.onStartCommand(intent, flags, startId); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { super.onDestroy(); } }
此處是一個的ForegroundService的創建,他和普通服務的創建沒什么大區別,不同的地方是在服務創建后要即時調用 startForeground() 方法,否則會出現anr異常。正常調用了 startForeground() 方法,手機通知欄會出現相應的通知提示,但國內很多機型是不會顯示通知欄的,所以按這種方式很多用戶也看不到進程被拉活了( ̄▽ ̄)。
通過intent回調過來的參數見下表:
常量(BluetoothLeScanner.java中) |
值 | 描述 |
EXTRA_CALLBACK_TYPE | 見 android.bluetooth.le.ScanSettings.CALLBACK_TYPE_* | 回調的內容類型 |
EXTRA_ERROR_CODE | 見 android.bluetooth.le.ScanCallback.SCAN_FAILED_* | 掃描失敗的錯誤碼,若成為,則該字段為空 |
EXTRA_LIST_SCAN_RESULT | 見 List<android.bluetooth.le.ScanResult> | 掃描成功之后獲得的藍牙設備息 |
有啟動掃描,當然有關閉方法,見以下代碼:
@TargetApi(26) public void onClose(View view){ PendingIntent callbackIntent = PendingIntent.getBroadcast( this, 1, new Intent("com.hungrytree.receiver.BleReceiver") .setPackage(getPackageName()), PendingIntent.FLAG_UPDATE_CURRENT ); BluetoothManager bluetoothManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); bluetoothAdapter.getBluetoothLeScanner().stopScan(callbackIntent); }
三、注意事項
以上的api調用在主流機型親測是沒有問題,但如果遇到以下問題可償試用以下方式解決
1.藍牙無法拉活
大多數國內的手機里面都有一個很神奇的權限----自啟動權限,它的意思不僅僅包含手機啟動的時候啟動app,同時還有其它應用(包含系統應用)能否拉活你的應用。簡單的說,如果發現不能被拉活,那就可能是系統的限制,試一下打開app的自啟動權限。
2.無論怎么樣都無法掃描到藍牙
在android 6.0以上的系統,部分手機如果想掃描到藍牙設備,還要檢查位置服務或定位服務是否開啟!!!在位置服務打開之后是有其它選項要求的。一般有三種選項,分別是 1.高精確度(GPS+網絡)2.低耗電量(網絡) 3.權限設備(GPS)。需要用到藍牙掃描的話就只能選1或者2,選3是沒有用的。以下小米的位置服務為例,看下圖。
3.用戶關閉藍牙后再打開,會停止原先所有的藍牙掃描,也意味着終止了app喚醒
假設進程還活着的時候,監測藍牙開關再觸發藍牙掃描是沒有問題的。但如果進程死后,用戶關閉藍牙,那暫時還沒找到合適的辦法。
參考資料:
https://developer.android.com/reference/android/bluetooth/le/ScanSettings(需要科學上網)
結論:
android 8.0以上也可以支持類似像ios ibeacon方式的喚醒,同時不會像蘋果局限於只能用ibeacon,能給許多的業務帶來更好的擴展。
如果該拉活方式只是為了提供給用戶在特定的場景有更好的體驗,那我是建議的。但如果要用來讓app持續性后台運行,這種方式就有點濫用了。哪天google或者國內的產商一不開心就......
最后,感謝各位大牛的閱讀,如果有不對的地方希望能幫忙提醒改正,在此先謝過。