1. 目的

基于《软件绿色联盟应用体验标准》中 Bluetooth 资源的定义,对 Bluetooth 后台持续定位的测试apk。旨在触发手机中异常功耗管控机制。

android打开蓝牙并监听蓝牙接收数据 蓝牙监听app_android

2. 测试步骤

H手机和T手机、其他手机进行安装该apk.
所有手机都需要设置应用为白名单。

2.1 手机白名单设置方法:

手机管家->应用启动设置:允许自启动、允许关联启动、允许后台启动

android打开蓝牙并监听蓝牙接收数据 蓝牙监听app_蓝牙功耗异常_02

2.2 测试环境

先把GPS和蓝牙开关打开,本次灭屏BT累积扫描超过5min

2.3 运行本apk

如下权限请先手动赋予:允许扫描附近和定位权限

android打开蓝牙并监听蓝牙接收数据 蓝牙监听app_功耗监控_03

android打开蓝牙并监听蓝牙接收数据 蓝牙监听app_功耗监控_04

2.4 运行本apk

测试:先把GPS和蓝牙开关打开,本次灭屏BT累积扫描超过5min钟,是否会弹出通知栏

android打开蓝牙并监听蓝牙接收数据 蓝牙监听app_测试apk_05

3. apk 源码

本apk作用:后台无限制的蓝牙持续扫描操作

3.1 UI

android打开蓝牙并监听蓝牙接收数据 蓝牙监听app_测试apk_05

3.2 核心逻辑

3.2.1 MainActivity

主要为申请GPS的动态运行时权限和开启前台服务,在此基础上进行持续的bluetoothLeScanner.startScan 蓝牙扫描操作

package com.sufadi.blockbluetoothscan

import android.Manifest
import android.annotation.TargetApi
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothManager
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.content.Context
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.content.Intent
import android.widget.Toast
import android.content.BroadcastReceiver
import android.content.IntentFilter
import android.util.Log
import android.bluetooth.le.ScanSettings
import android.os.Build
import android.os.Handler
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.sufadi.commlib.services.ForegroundService
import com.sufadi.commlib.utils.AlertWakeLock
import java.util.*

/**
 * 方法	                        API	            搜索范围	       效率
BluetoothAdapter.startDiscovery	18	       经典蓝牙和低功耗蓝牙	搜索低功耗蓝牙的效率很低
BluetoothAdapter.startLeScan	18	       低功耗蓝牙	       高
BluetoothLeScanner	            21	       低功耗蓝牙	       高 接口更灵活,如果API支持,最好使用BluetoothLeScanner,否则,应该使用效率更高的startLeScan方法。
 */
class MainActivity : AppCompatActivity() {

    val TAG = MainActivity::class.java.name + "BLUETOOTH_SCAN"
    lateinit var mBluetoothAdapter:BluetoothAdapter
    val PERMISSIONS_REQUEST_CODE_ACCESS = 200

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        AlertWakeLock.acquireCpuWakeLock(this)

        startService(Intent(this, ForegroundService::class.java))
        checkBTPermission()
        initialize()
    }

    private fun checkBTPermission() {
        //清单文件里面也需要配置
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
            ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
            ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
        ) {
            val permList = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.ACCESS_COARSE_LOCATION,
                Manifest.permission.ACCESS_BACKGROUND_LOCATION)
            requestPermissions(permList, PERMISSIONS_REQUEST_CODE_ACCESS)
        }
        checkBackgroundLocationPermissionAPI30(PERMISSIONS_REQUEST_CODE_ACCESS)

    }

    private fun Context.checkSinglePermission(permission: String) : Boolean {
        return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
    }

    @TargetApi(30)
    private fun Context.checkBackgroundLocationPermissionAPI30(backgroundLocationRequestCode: Int) {
        if (checkSinglePermission(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) return
        AlertDialog.Builder(this)
            .setTitle("BT 对话框")
            .setMessage("bt是否一直后台运行对话框")
            .setPositiveButton("Yes") { _,_ ->
                // this request will take user to Application's Setting page
                requestPermissions(arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION), backgroundLocationRequestCode)
            }
            .setNegativeButton("No") { dialog,_ ->
                dialog.dismiss()
            }
            .create()
            .show()

    }


    override fun onDestroy() {
        super.onDestroy()
        mBluetoothAdapter.bluetoothLeScanner.stopScan(mScanCallback)
    }


    fun initialize() {
        if (!packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, "BLE not supported", Toast.LENGTH_SHORT).show()
            return
        }
        val btManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        mBluetoothAdapter = btManager.adapter
        if (null != mBluetoothAdapter) {
            if (!mBluetoothAdapter.isEnabled) {
                val enabler= Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
                startActivityForResult(enabler,1)
            }
        }
    }

    /**
     * startDiscovery是个异步调用,会立即返回。如果不调用cancelDiscovery主动停止扫描的话,最多扫描12s。
     */
    fun bt_scan_click_old(view: View) {
        Log.d(TAG, "bt_scan_click: startDiscovery")
        mBluetoothAdapter.startDiscovery()
    }


    /**
     * startDiscovery是个异步调用,会立即返回。如果不调用cancelDiscovery主动停止扫描的话,最多扫描12s。
     */
    fun bt_scan_click(view: View) {
        Log.d(TAG, "bt_scan_click: startDiscovery")
        // startLeScan()方法的特点:在onLeScan() 中不能做耗时操作,特别是周围的BLE设备多的时候,容易导致底层堵塞
        mBluetoothAdapter.bluetoothLeScanner.startScan(Collections.singletonList(ScanFilter.Builder().build()), buildScanSettings(), mScanCallback)
    }

    fun  buildScanSettings(): ScanSettings{
        val scanSettingBuilder = ScanSettings.Builder()
        //设置蓝牙LE扫描的扫描模式。
        //使用最高占空比进行扫描。建议只在应用程序处于此模式时使用此模式在前台运行
        scanSettingBuilder.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
        //设置蓝牙LE扫描滤波器硬件匹配的匹配模式
        //在主动模式下,即使信号强度较弱,hw也会更快地确定匹配.在一段时间内很少有目击/匹配。
        scanSettingBuilder.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
        //设置蓝牙LE扫描的回调类型
        //为每一个匹配过滤条件的蓝牙广告触发一个回调。如果没有过滤器是活动的,所有的广告包被报告
        scanSettingBuilder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
        return scanSettingBuilder.build()
    }

    val mScanCallback = object : ScanCallback() {
        //当一个蓝牙ble广播被发现时回调
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            super.onScanResult(callbackType, result)
            //扫描类型有开始扫描时传入的ScanSettings相关
            //对扫描到的设备进行操作。如:获取设备信息。
            Log.d(TAG, "ScanResult: $result")
        }

        //批量返回扫描结果
        //@param results 以前扫描到的扫描结果列表。
        override fun onBatchScanResults(results: List<ScanResult>) {
            super.onBatchScanResults(results)
            Log.d(TAG, "onBatchScanResults: $results")

        }

        //当扫描不能开启时回调
        override fun onScanFailed(errorCode: Int) {
            super.onScanFailed(errorCode)
            //扫描太频繁会返回ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED,表示app无法注册,无法开始扫描。
            Log.d(TAG, "onScanFailed: $errorCode")
        }
    }

}
3.3.2 权限配置
<!-- 在6.0版本前,使用蓝牙功能,只需要配置下面的权限即可 蓝牙连接权限-->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <!--打开蓝牙的对话框-->
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

    <!--  在Android 6.0之前的版本中能够完美运行,但换到Android 6.0 及以上系统的手机运行时搜索不到数据。
    这是因为在Android 6.0及以上系统中低功耗蓝牙添加了距离检测功能,所以扫描时需要开启定位功能权限,在更高版本中甚至需要开启精准定位权限 -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
3.3.3 华为异常检测日志

android打开蓝牙并监听蓝牙接收数据 蓝牙监听app_android

2022-06-15 16:32:33.498 2661-2935/com.huawei.iaware D/APwActAnalysis: bt scan app: com.sufadi.blockbluetoothscan level: 2 duration:330300
2022-06-15 16:32:33.498 2661-2935/com.huawei.iaware I/APwActAnalysis: bt scan high power app: com.sufadi.blockbluetoothscan
2022-06-15 16:32:33.501 2661-2935/com.huawei.iaware I/APwActAnalysis: can not process location privider app: com.huawei.lbs uid:1000
2022-06-15 16:32:33.510 2661-2935/com.huawei.iaware D/APwActAnalysis: not process sensor 330310ms, pkg:com.cootek.crazyreader