Android 8.0 進程拉活 --- 藍牙喚醒


前言:

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或者國內的產商一不開心就......

 

最后,感謝各位大牛的閱讀,如果有不對的地方希望能幫忙提醒改正,在此先謝過。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM