BLE 扫描在 20 分钟后冻结 UI 个线程

BLE Scan Freezes UI thread after 20 mins

我正在尝试为一个研究项目连续扫描 BLE 设备。我使用 LOW_LATENCY 模式的 BLE 扫描。然而,大约 20 分钟后,我的 UI 冻结了。

基本上我按下按钮它应该开始扫描 BLE 设备,当我再次按下按钮时,它应该停止扫描。我想连续扫描至少 2 小时。以下是我的代码。

 @Override
 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    toggle = (ToggleButton) findViewById(R.id.toggleButton); // initiate a toggle button
    tv = (TextView) findViewById(R.id.tv);

        EasyPermissions.requestPermissions(this, "need permission", 1001, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.ACCESS_COARSE_LOCATION,Manifest.permission.BLUETOOTH,Manifest.permission.BLUETOOTH_ADMIN);
     // Check BLE support for the device
    if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
        Toast.makeText(getApplicationContext(),"no bluetooth LE functionality", Toast.LENGTH_SHORT).show();
        finish();
    }
    btManager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
    btAdapter = btManager.getAdapter();
    bLEScanner = btAdapter.getBluetoothLeScanner();
    settings = new ScanSettings.Builder()
            .setScanMode(SCAN_MODE_LOW_LATENCY)
            .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
            .build();
    toggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
        @TargetApi(Build.VERSION_CODES.N)
        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
            if (isChecked) {
                Toast.makeText(getApplicationContext(), "Scanning started",
                        Toast.LENGTH_SHORT).show();
                // The toggle is enabled
                bFileName = getBluetoothFileName("Bluetooth");
                bLEScanner.startScan(null,settings,bluetoothLeScanCallback);
            } else {
                // The toggle is disabled
                Toast.makeText(getApplicationContext(), "Scanning stopped",
                        Toast.LENGTH_SHORT).show();
                bLEScanner.stopScan(bluetoothLeScanCallback);
            }
        }
    });
}

扫描回调码

private ScanCallback bluetoothLeScanCallback = new ScanCallback() {
 @Override
 public void onScanResult(int callbackType,  android.bluetooth.le.ScanResult result) {
     byte[] scanRecord = result.getScanRecord().getBytes();
     int startByte = 2;
     boolean patternFound = false;
     while (startByte <= 5)
     {
         if (    ((int) scanRecord[startByte + 2] & 0xff) == 0x02 && //Identifies an iBeacon
                 ((int) scanRecord[startByte + 3] & 0xff) == 0x15)
         { //Identifies correct data length
             patternFound = true;
             break;
         }
         startByte++;
     }

     if (patternFound)
     {
         //Convert to hex String
         byte[] uuidBytes = new byte[16];
         System.arraycopy(scanRecord, startByte + 4, uuidBytes, 0, 16);
         String hexString = bytesToHex(uuidBytes);

         //UUID detection
         String uuid =  hexString.substring(0,8) + "-" +
                 hexString.substring(8,12) + "-" +
                 hexString.substring(12,16) + "-" +
                 hexString.substring(16,20) + "-" +
                 hexString.substring(20,32);

         // major
         final int major = (scanRecord[startByte + 20] & 0xff) * 0x100 + (scanRecord[startByte + 21] & 0xff);

         // minor
         final int minor = (scanRecord[startByte + 22] & 0xff) * 0x100 + (scanRecord[startByte + 23] & 0xff);

         Log.i("BLE","UUID: " +uuid + " nmajor : " +major +" nminor " +minor+" time "+getCompleteDate(System.currentTimeMillis())+" rssi "+result.getRssi());
         StringBuilder bStringBuilder = new StringBuilder();
         long sysTime = System.currentTimeMillis();
         bStringBuilder.append(imei+","+sysTime+","+uuid+","+major+","+minor+","+result.getRssi()+","+getCompleteDate(sysTime)+","+"\n");
         String finalString = bStringBuilder.toString();
         exportBluetoothData(finalString, finalString.length(), "Bluetooth");

     }
 }
     @Override
 public void onScanFailed(int errorCode) {
     super.onScanFailed(errorCode);
     Log.i("BLE", "error");
 }
 };

对于这种长时间的操作,使用 AsyncTask 会有帮助吗?任何指南将不胜感激。谢谢

对于这种长时间的操作,使用 AsyncTask 是没有帮助的。 Android 7.0 引入了 BLE 扫描超时,任何扫描 运行 30 分钟或更长时间都会自动有效停止:

此外,BLE扫描会耗尽电池电量,因此您应避免运行长时间进行BLE扫描。

如果您的应用确实需要频繁扫描,您可以考虑在用户操作时停止并重新启动 BLE 扫描。但请记住,Android 7 可防止扫描在 30 秒内启动-停止超过 5 次。

你应该为这个进程使用后台线程,因为当你扫描时,你的ui线程可能被阻塞,这就是ui的原因freezing.make 为此使用任务调度程序或任何其他后台进程。

package com.myapp.services;

import java.util.List;

import android.annotation.TargetApi;
import android.app.Service;
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.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.widget.Toast;

import com.myapp.R;

/**Both RX and RSSI (Received Signal Strength Indication) are indications of the power level being received 
 * by an antenna
 * The difference between RX and RSSI is that RX is measured in milliWatts (mW) or decibel-milliwatts (dBm) 
 * whereas RSSI is a signal strength percentage—the higher the RSSI number, the stronger the signal
 *
 */

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class BeaconService extends Service implements BluetoothAdapter.LeScanCallback{
    private static final String TAG = BeaconService.class.getSimpleName();

    private BluetoothGatt btGatt;
    private BluetoothAdapter mBluetoothAdapter;

    @Override
    public void onCreate() {
        super.onCreate();
        writeLine("Automate service created...");
        getBTService();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        writeLine("Automate service start...");
        if (!isBluetoothSupported()) {
            Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
            stopSelf();
        }else{
            if(mBluetoothAdapter!=null && mBluetoothAdapter.isEnabled()){
                startBLEscan();                     
            }else{
                stopSelf();
            }
        }
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        writeLine("Automate service destroyed...");
        stopBLEscan();
        super.onDestroy();

        if(btGatt!=null){
            btGatt.disconnect();
            btGatt.close();
            btGatt = null;
        }
    }

    @Override
    public boolean stopService(Intent name) {
        writeLine("Automate service stop...");
        stopSelf();
        return super.stopService(name);
    }

    // Initializes a Bluetooth adapter.  For API level 18 and above, get a reference to
    // BluetoothAdapter through BluetoothManager.
    public BluetoothAdapter getBTService(){
        BluetoothManager btManager = (BluetoothManager) getApplicationContext().getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = (BluetoothAdapter) btManager.getAdapter();
        return mBluetoothAdapter;
    }

    public boolean isBluetoothSupported() {
        return this.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
    }

    public void startBLEscan(){
        mBluetoothAdapter.startLeScan(this);
    }

    public void stopBLEscan(){
        mBluetoothAdapter.stopLeScan(this);
    }

    /**
     * 
     * @param enable
     */
    public void scanLeDevice(final boolean enable) {
        if (enable) {
            startBLEscan();
        } else {
            stopBLEscan();
        }
    }

    public static void enableDisableBluetooth(boolean enable){
        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (bluetoothAdapter != null) {
            if(enable) {
                bluetoothAdapter.enable();
            }else{
                bluetoothAdapter.disable();
            }
        }
    }

    @Override
    public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord) {
        if(device!=null && device.getName()!=null){
            //Log.d(TAG + " onLeScan: ", "Name: "+device.getName() + "Address: "+device.getAddress()+ "RSSI: "+rssi);
            if(rssi > -90 && rssi <-1){
                writeLine("Automate service BLE device in range: "+ device.getName()+ " "+rssi);
                if (device.getName().equalsIgnoreCase("NCS_Beacon") || device.getName().equalsIgnoreCase("estimote")) {
                    //This Main looper thread is main for connect gatt, don't remove it
                    // Although you need to pass an appropriate context getApplicationContext(),
                    //Here if you use Looper.getMainLooper() it will stop getting callback and give internal exception fail to register //callback
                    new Handler(getApplicationContext().getMainLooper()).post(new Runnable() {
                        @Override
                        public void run() {
                            btGatt = device.connectGatt(getApplicationContext(), false, bleGattCallback);
                            Log.e(TAG, "onLeScan btGatt value returning from connectGatt "+btGatt);
                        }
                    });
                }
                stopBLEscan();  
            }else{
                //Log.v("Device Scan Activity", device.getAddress()+" "+"BT device is still too far - not connecting");
            }
        }
    }

    BluetoothGattCallback bleGattCallback = new BluetoothGattCallback() {

        @Override
        public void onConnectionStateChange(final BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            writeLine("Automate service connection state: "+ newState);
            if (newState == android.bluetooth.BluetoothProfile.STATE_CONNECTED){
                writeLine("Automate service connection state: STATE_CONNECTED");
                Log.v("BLEService", "BLE Connected now discover services");
                Log.v("BLEService", "BLE Connected");
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        writeLine("Automate service go for discover services");
                        gatt.discoverServices();
                    }
                }).start();
            }else if (newState == android.bluetooth.BluetoothProfile.STATE_DISCONNECTED){
                writeLine("Automate service connection state: STATE_DISCONNECTED");
                Log.v("BLEService", "BLE Disconnected");
            }
        }

        @Override
        public void onServicesDiscovered(final BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                writeLine("Automate service discover service: GATT_SUCCESS");
                Log.v("BLEService", "BLE Services onServicesDiscovered");
                //Get service
                List<BluetoothGattService> services = gatt.getServices();
                writeLine("Automate service discover service imei: " +imei);
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,  int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
        }
    };

    private void writeLine(final String message) {
        Handler h = new Handler(getApplicationContext().getMainLooper());
        // Although you need to pass an appropriate context
        h.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(),message,Toast.LENGTH_SHORT).show();
            }
        });
    }

}

在manifest.xml

<uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" />

    <!-- Permission for bluetooth -->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

 <service
            android:name="com.myapp.services.BeaconService"
            android:enabled="true"
            android:exported="false" />

我用 SCAN_MODE_BALANCED 而不是 SCAN_MODE_LOW_LATENCY 现在它不会冻结线程。谢谢你所有的回答。