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
现在它不会冻结线程。谢谢你所有的回答。
我正在尝试为一个研究项目连续扫描 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
现在它不会冻结线程。谢谢你所有的回答。