即使设备在信标附近保持静止,应用程序也会在 didEnterRegion() 和 didExitRegion() 之间循环
Application cycles between didEnterRegion() and didExitRegion() even when the device stays stationary near the beacon
我正在使用 AltBeacon Android 库(我重现了 v2.9.2 的问题;还有 v2.11)与 Onyx 和 kontact.io.[=25= 提供的 iBeacon 设备集成]
该库似乎工作得很好,但我似乎遇到了一个问题,我找不到可接受的解决方案。
以下是有关我如何使用 AltBeacon 库和问题的更多详细信息:
- 设备在信标附近静止不动
- 蓝牙开启
- 应用程序在前台运行
BeaconManager 配置为使用以下设置在前台模式下扫描:
BeaconManager.setRegionExitPeriod(30000L);
beaconManager.setBackgroundBetweenScanPeriod(120000L);
beaconManager.setForegroundScanPeriod(5000L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
beaconManager.getBeaconParsers().add(
new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
应用程序将 BeaconManager 设置为前台模式
beaconManager.setBackgroundMode(false);
应用程序绑定到 BeaconManager
beaconManager.bind(…)
当onBeaconServiceConnect()
被触发时,应用程序开始监控特定区域的信标(我要监控的信标列表是已知的,静态的;我使用区域列表,一个不同的我要监控的每个信标的区域)
beaconManager.startMonitoringBeaconsInRegion(region);
当设备进入信标区域(调用 didEnterRegion()
)时,应用程序开始对进入的区域进行测距
beaconManager.startRangingBeaconsInRegion(region);
检测到Beacon(didRangeBeaconsInRegion()
调用相应的beacon)
应用程序将信标扫描切换到后台模式:
beaconManager.setBackgroundMode(true);
几分钟后,即使设备和信标未移动并且应用程序保持相同状态,didExitRegion()
也会被调用。
我发现了两个描述相同问题的 Whosebug 问题:
AltBeacon unstable for OnyxBeacons, cycling through didEnterRegion and didExitRegion repeatedly
我目前使用的解决方法是 Whosebug 问题中建议的解决方法:
- 我已将信标 广告频率 值从 1000 毫秒更新为 100 毫秒。
一旦频率增加,似乎一切正常,但是
该解决方案是不可接受的,因为信标的电池寿命是
严重受损。
所有信标扫描都在后台执行(即不使用 Activity):
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import org.altbeacon.beacon.Beacon;
import org.altbeacon.beacon.BeaconConsumer;
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.BeaconParser;
import org.altbeacon.beacon.Identifier;
import org.altbeacon.beacon.MonitorNotifier;
import org.altbeacon.beacon.RangeNotifier;
import org.altbeacon.beacon.Region;
import org.altbeacon.beacon.powersave.BackgroundPowerSaver;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class BeaconDataProvider implements BeaconConsumer, RangeNotifier, MonitorNotifier {
private final Logger LOGGER = LogFactory.get(this);
private final Context applicationContext;
private final BeaconIdentifierFactory beaconIdentifierFactory;
private final BeaconScanningListener beaconScanningListener;
private BeaconManager beaconManager;
private Collection<Region> targetedRegions;
/**
* This field is used for improving battery consumption. Do not remove it.
*/
@SuppressWarnings({"unused", "FieldCanBeLocal"})
private BackgroundPowerSaver backgroundPowerSaver;
public BeaconDataProvider(Context applicationContext, BeaconIdentifierFactory beaconIdentifierFactory,
BeaconScanningListener beaconScanningListener) {
LOGGER.v("BeaconDataProvider - new instance created.");
this.applicationContext = applicationContext;
this.beaconIdentifierFactory = beaconIdentifierFactory;
this.beaconScanningListener = beaconScanningListener;
beaconManager = BeaconManager.getInstanceForApplication(applicationContext);
LOGGER.v("BeaconManager hashCode=%s", beaconManager.hashCode());
BeaconManager.setRegionExitPeriod(30000L);
beaconManager.setBackgroundBetweenScanPeriod(120000L);
beaconManager.setForegroundScanPeriod(5000L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
beaconManager.getBeaconParsers().add(
new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
backgroundPowerSaver = new BackgroundPowerSaver(applicationContext);
}
public void setBackgroundMode() {
LOGGER.i("setBackgroundMode()");
beaconManager.setBackgroundMode(true);
}
public void setForegroundMode() {
LOGGER.i("setForegroundMode()");
beaconManager.setBackgroundMode(false);
}
public boolean checkAvailability() {
return android.os.Build.VERSION.SDK_INT >= 18 && applicationContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
}
public boolean isBluetoothEnabled() {
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
boolean result = mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
LOGGER.i("isBluetoothEnabled() -> %s", result);
return result;
}
public boolean isLocationPermissionGranted(Context context) {
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED);
}
public void startScanning(Collection<BeaconIdentifier> targetedBeacons) {
LOGGER.i("startScanning()");
if (!beaconManager.isBound(this)) {
this.targetedRegions = getRegionsForTargetedBeacons(targetedBeacons);
beaconManager.bind(this);
}
else {
LOGGER.i("Scanning already started.");
}
}
@NonNull
private List<Region> getRegionsForTargetedBeacons(Collection<BeaconIdentifier> beaconIdentifiers) {
List<Region> regions = new ArrayList<>();
for (BeaconIdentifier beaconIdentifier : beaconIdentifiers) {
try {
Region region = new Region(beaconIdentifier.getRegionId(), Identifier.parse(beaconIdentifier.getUuid()),
Identifier.parse(String.valueOf(beaconIdentifier.getMajor())),
Identifier.parse(String.valueOf(beaconIdentifier.getMinor())));
regions.add(region);
}
catch (Exception e) {
LOGGER.e("Caught exception.", e);
LOGGER.w("Failed to create region for beaconIdentifier=%s", beaconIdentifier.getCallParamRepresentation());
}
}
return regions;
}
public void stopScanning() {
LOGGER.i("stopScanning()");
if (beaconManager.isBound(this)) {
for (Region region : targetedRegions) {
try {
beaconManager.stopMonitoringBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Caught exception", e);
}
}
beaconManager.unbind(this);
}
}
@Override
public void didEnterRegion(Region region) {
LOGGER.v("didEnterRegion(region=%s)", region);
beaconScanningListener.onEnterRegion(region.getUniqueId());
try {
beaconManager.startRangingBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Caught Exception", e);
}
}
@Override
public void didExitRegion(Region region) {
LOGGER.v("didExitRegion(region=%s)", region);
beaconScanningListener.onExitRegion(region.getUniqueId());
try {
beaconManager.stopRangingBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Error", e);
}
}
@Override
public void didDetermineStateForRegion(int state, Region region) {
LOGGER.v("didDetermineStateForRegion(state=%s, region=%s)", state, region);
}
@Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
LOGGER.v("didRangeBeaconsInRegion(size=%s, region=%s, regionUniqueId=%s)", beacons.size(), region,
region.getUniqueId());
if (beacons.size() > 0) {
beaconScanningListener.onBeaconsInRange(beaconIdentifierFactory.from(beacons, region.getUniqueId()));
}
}
@Override
public void onBeaconServiceConnect() {
LOGGER.v("onBeaconServiceConnect()");
beaconManager.addRangeNotifier(this);
beaconManager.addMonitorNotifier(this);
for (Region region : targetedRegions) {
try {
beaconManager.startMonitoringBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Caught exception", e);
}
}
}
@Override
public Context getApplicationContext() {
return applicationContext;
}
@Override
public void unbindService(ServiceConnection serviceConnection) {
LOGGER.v("unbindService()");
applicationContext.unbindService(serviceConnection);
}
@Override
public boolean bindService(Intent intent, ServiceConnection serviceConnection, int i) {
LOGGER.v("bindService()");
return applicationContext.bindService(intent, serviceConnection, i);
}
}
public class BeaconIdentifier {
private final String uuid;
private final int major;
private final int minor;
private String regionId;
public BeaconIdentifier(String uuid, int major, int minor) {
this.uuid = uuid;
this.major = major;
this.minor = minor;
}
public int getMinor() {
return minor;
}
public int getMajor() {
return major;
}
public String getUuid() {
return uuid;
}
public String getCallParamRepresentation() {
return (uuid + "_" + major + "_" + minor).toUpperCase();
}
public String getRegionId() {
return regionId;
}
public void setRegionId(String regionId) {
this.regionId = regionId;
}
@Override
public boolean equals(Object o) {
if (o != null) {
if (o instanceof BeaconIdentifier) {
BeaconIdentifier other = (BeaconIdentifier) o;
return this == other || (this.uuid.equalsIgnoreCase(other.uuid)
&& this.major == other.major && this.minor == other.minor);
}
else {
return false;
}
}
else {
return false;
}
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (uuid != null ? uuid.toUpperCase().hashCode() : 0);
result = 31 * result + major;
result = 31 * result + minor;
return result;
}
@Override
public String toString() {
return "BeaconIdentifier{" +
"uuid='" + uuid + '\'' +
", major=" + major +
", minor=" + minor +
", regionId='" + regionId + '\'' +
'}';
}
}
BeaconDataProvider 被用作每个应用程序的单个实例;它在创建 Android 应用程序时由 Dagger 2 实例化。它有@ApplicationScope 生命周期。
信标扫描首先在 前台模式 中从 Android IntentService:
开始
beaconDataProvider.setForegroundMode();
beaconDataProvider.startScanning(targetedBeacons);
一旦设备进入该区域并检测到信标,信标扫描就会切换到后台模式:
beaconDataProvider.setBackgroundMode();
起初我以为我使用的 Onyx Beacons 有问题,但我可以用 Kontact IO Beacons 重现同样的问题。
你有什么建议吗?
我是不是没有使用 AltBeacon Android 库?
谢谢,
阿林
我认为问题出在这里:
beaconManager.setForegroundScanPeriod(5000L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
您通常应将 scanPeriod 设置为 5100 毫秒或更多,因为如果它们的传输始终在您开始和停止扫描的边界上,则广播的信标有轻微的机会被错过。
所以尝试:
beaconManager.setForegroundScanPeriod(5100L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
希望对您有所帮助。让我知道是否有效。
调用didExitRegion()
的根本原因是在前10秒内Android蓝牙堆栈没有收到与该区域匹配的BLE信标广告包。 (注意:这个值可以用BeaconManager.setRegionExitPeriod(...)
配置。)
有几件事可能导致这些虚假的 didExitRegion()
调用:
- 信标的广告频率不够。
- 信标正在用非常低的无线电信号做广告。
- 附近的无线电噪音太大,无法进行可靠检测。
- 接收设备的蓝牙天线设计不佳,导致无法检测到较弱的信号。
- 接收设备距离太远,无法可靠地检测到信标。
- foregroundScanPeriod 或 backgroundScanPeriod 设置得太短,无法保证检测到
鉴于您所描述的设置,我怀疑当您以 1Hz 的频率传输信标时,1-4 的某种组合会导致问题。您将不得不对这些变量中的每一个进行试验,以查看是否可以将问题隔离为一个主要问题。但同样,可能不止一个人同时在玩。
要知道,即使在良好的条件下,典型的 Android 设备也只能接收到空中传输的信标数据包的 80-90%。因此,如果您的设置通常在 10 秒内仅收到 1-5 个信标数据包,那么如果您运气不好,您仍然 有时 会收到退出事件和一些连续的数据包被无线电噪声破坏。没有办法保证这不会发生。您可以通过设置您的系统来使其在统计上更不可能,因此在标称条件下它会在 10 秒内接收尽可能多的数据包,因此这变得更不可能。
提高广告率是解决这个问题最简单的方法,因为它可以让您在任何 10 秒的时间内有更多的统计机会检测到数据包。但正如您所见,在电池寿命方面存在权衡。
如果您想保留电池寿命但不关心获得 didExitRegion 回调所需的时间,那么您可能需要将 BeaconManager.setRegionExitPeriod(...)
修改为 30,000 毫秒或更多,直到问题消失。
以上讨论是针对Android Beacon 库的配置,同样的理论思想适用于任何信标检测框架,包括iOS Core Location。您有时也会看到该框架的虚假退出事件。
作为此问题的解决方法,我实施了一些额外的逻辑来考虑 didExitRegion()
事件,仅当相应的 didEnterRegion()
在特定时间间隔(在我的情况下为 5 分钟)内未被调用时,但这可以调整)。
我正在使用 AltBeacon Android 库(我重现了 v2.9.2 的问题;还有 v2.11)与 Onyx 和 kontact.io.[=25= 提供的 iBeacon 设备集成]
该库似乎工作得很好,但我似乎遇到了一个问题,我找不到可接受的解决方案。
以下是有关我如何使用 AltBeacon 库和问题的更多详细信息:
- 设备在信标附近静止不动
- 蓝牙开启
- 应用程序在前台运行
BeaconManager 配置为使用以下设置在前台模式下扫描:
BeaconManager.setRegionExitPeriod(30000L); beaconManager.setBackgroundBetweenScanPeriod(120000L); beaconManager.setForegroundScanPeriod(5000L); beaconManager.setForegroundBetweenScanPeriod(10000L); beaconManager.getBeaconParsers().add( new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
应用程序将 BeaconManager 设置为前台模式
beaconManager.setBackgroundMode(false);
应用程序绑定到 BeaconManager
beaconManager.bind(…)
当
onBeaconServiceConnect()
被触发时,应用程序开始监控特定区域的信标(我要监控的信标列表是已知的,静态的;我使用区域列表,一个不同的我要监控的每个信标的区域)beaconManager.startMonitoringBeaconsInRegion(region);
当设备进入信标区域(调用
didEnterRegion()
)时,应用程序开始对进入的区域进行测距beaconManager.startRangingBeaconsInRegion(region);
检测到Beacon(
didRangeBeaconsInRegion()
调用相应的beacon)应用程序将信标扫描切换到后台模式:
beaconManager.setBackgroundMode(true);
几分钟后,即使设备和信标未移动并且应用程序保持相同状态,
didExitRegion()
也会被调用。
我发现了两个描述相同问题的 Whosebug 问题:
AltBeacon unstable for OnyxBeacons, cycling through didEnterRegion and didExitRegion repeatedly
我目前使用的解决方法是 Whosebug 问题中建议的解决方法:
- 我已将信标 广告频率 值从 1000 毫秒更新为 100 毫秒。
一旦频率增加,似乎一切正常,但是 该解决方案是不可接受的,因为信标的电池寿命是 严重受损。
所有信标扫描都在后台执行(即不使用 Activity):
import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.RemoteException;
import android.support.annotation.NonNull;
import org.altbeacon.beacon.Beacon;
import org.altbeacon.beacon.BeaconConsumer;
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.BeaconParser;
import org.altbeacon.beacon.Identifier;
import org.altbeacon.beacon.MonitorNotifier;
import org.altbeacon.beacon.RangeNotifier;
import org.altbeacon.beacon.Region;
import org.altbeacon.beacon.powersave.BackgroundPowerSaver;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class BeaconDataProvider implements BeaconConsumer, RangeNotifier, MonitorNotifier {
private final Logger LOGGER = LogFactory.get(this);
private final Context applicationContext;
private final BeaconIdentifierFactory beaconIdentifierFactory;
private final BeaconScanningListener beaconScanningListener;
private BeaconManager beaconManager;
private Collection<Region> targetedRegions;
/**
* This field is used for improving battery consumption. Do not remove it.
*/
@SuppressWarnings({"unused", "FieldCanBeLocal"})
private BackgroundPowerSaver backgroundPowerSaver;
public BeaconDataProvider(Context applicationContext, BeaconIdentifierFactory beaconIdentifierFactory,
BeaconScanningListener beaconScanningListener) {
LOGGER.v("BeaconDataProvider - new instance created.");
this.applicationContext = applicationContext;
this.beaconIdentifierFactory = beaconIdentifierFactory;
this.beaconScanningListener = beaconScanningListener;
beaconManager = BeaconManager.getInstanceForApplication(applicationContext);
LOGGER.v("BeaconManager hashCode=%s", beaconManager.hashCode());
BeaconManager.setRegionExitPeriod(30000L);
beaconManager.setBackgroundBetweenScanPeriod(120000L);
beaconManager.setForegroundScanPeriod(5000L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
beaconManager.getBeaconParsers().add(
new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
backgroundPowerSaver = new BackgroundPowerSaver(applicationContext);
}
public void setBackgroundMode() {
LOGGER.i("setBackgroundMode()");
beaconManager.setBackgroundMode(true);
}
public void setForegroundMode() {
LOGGER.i("setForegroundMode()");
beaconManager.setBackgroundMode(false);
}
public boolean checkAvailability() {
return android.os.Build.VERSION.SDK_INT >= 18 && applicationContext.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
}
public boolean isBluetoothEnabled() {
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
boolean result = mBluetoothAdapter != null && mBluetoothAdapter.isEnabled();
LOGGER.i("isBluetoothEnabled() -> %s", result);
return result;
}
public boolean isLocationPermissionGranted(Context context) {
return (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& context.checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
== PackageManager.PERMISSION_GRANTED);
}
public void startScanning(Collection<BeaconIdentifier> targetedBeacons) {
LOGGER.i("startScanning()");
if (!beaconManager.isBound(this)) {
this.targetedRegions = getRegionsForTargetedBeacons(targetedBeacons);
beaconManager.bind(this);
}
else {
LOGGER.i("Scanning already started.");
}
}
@NonNull
private List<Region> getRegionsForTargetedBeacons(Collection<BeaconIdentifier> beaconIdentifiers) {
List<Region> regions = new ArrayList<>();
for (BeaconIdentifier beaconIdentifier : beaconIdentifiers) {
try {
Region region = new Region(beaconIdentifier.getRegionId(), Identifier.parse(beaconIdentifier.getUuid()),
Identifier.parse(String.valueOf(beaconIdentifier.getMajor())),
Identifier.parse(String.valueOf(beaconIdentifier.getMinor())));
regions.add(region);
}
catch (Exception e) {
LOGGER.e("Caught exception.", e);
LOGGER.w("Failed to create region for beaconIdentifier=%s", beaconIdentifier.getCallParamRepresentation());
}
}
return regions;
}
public void stopScanning() {
LOGGER.i("stopScanning()");
if (beaconManager.isBound(this)) {
for (Region region : targetedRegions) {
try {
beaconManager.stopMonitoringBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Caught exception", e);
}
}
beaconManager.unbind(this);
}
}
@Override
public void didEnterRegion(Region region) {
LOGGER.v("didEnterRegion(region=%s)", region);
beaconScanningListener.onEnterRegion(region.getUniqueId());
try {
beaconManager.startRangingBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Caught Exception", e);
}
}
@Override
public void didExitRegion(Region region) {
LOGGER.v("didExitRegion(region=%s)", region);
beaconScanningListener.onExitRegion(region.getUniqueId());
try {
beaconManager.stopRangingBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Error", e);
}
}
@Override
public void didDetermineStateForRegion(int state, Region region) {
LOGGER.v("didDetermineStateForRegion(state=%s, region=%s)", state, region);
}
@Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
LOGGER.v("didRangeBeaconsInRegion(size=%s, region=%s, regionUniqueId=%s)", beacons.size(), region,
region.getUniqueId());
if (beacons.size() > 0) {
beaconScanningListener.onBeaconsInRange(beaconIdentifierFactory.from(beacons, region.getUniqueId()));
}
}
@Override
public void onBeaconServiceConnect() {
LOGGER.v("onBeaconServiceConnect()");
beaconManager.addRangeNotifier(this);
beaconManager.addMonitorNotifier(this);
for (Region region : targetedRegions) {
try {
beaconManager.startMonitoringBeaconsInRegion(region);
}
catch (RemoteException e) {
LOGGER.e("Caught exception", e);
}
}
}
@Override
public Context getApplicationContext() {
return applicationContext;
}
@Override
public void unbindService(ServiceConnection serviceConnection) {
LOGGER.v("unbindService()");
applicationContext.unbindService(serviceConnection);
}
@Override
public boolean bindService(Intent intent, ServiceConnection serviceConnection, int i) {
LOGGER.v("bindService()");
return applicationContext.bindService(intent, serviceConnection, i);
}
}
public class BeaconIdentifier {
private final String uuid;
private final int major;
private final int minor;
private String regionId;
public BeaconIdentifier(String uuid, int major, int minor) {
this.uuid = uuid;
this.major = major;
this.minor = minor;
}
public int getMinor() {
return minor;
}
public int getMajor() {
return major;
}
public String getUuid() {
return uuid;
}
public String getCallParamRepresentation() {
return (uuid + "_" + major + "_" + minor).toUpperCase();
}
public String getRegionId() {
return regionId;
}
public void setRegionId(String regionId) {
this.regionId = regionId;
}
@Override
public boolean equals(Object o) {
if (o != null) {
if (o instanceof BeaconIdentifier) {
BeaconIdentifier other = (BeaconIdentifier) o;
return this == other || (this.uuid.equalsIgnoreCase(other.uuid)
&& this.major == other.major && this.minor == other.minor);
}
else {
return false;
}
}
else {
return false;
}
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (uuid != null ? uuid.toUpperCase().hashCode() : 0);
result = 31 * result + major;
result = 31 * result + minor;
return result;
}
@Override
public String toString() {
return "BeaconIdentifier{" +
"uuid='" + uuid + '\'' +
", major=" + major +
", minor=" + minor +
", regionId='" + regionId + '\'' +
'}';
}
}
BeaconDataProvider 被用作每个应用程序的单个实例;它在创建 Android 应用程序时由 Dagger 2 实例化。它有@ApplicationScope 生命周期。
信标扫描首先在 前台模式 中从 Android IntentService:
开始 beaconDataProvider.setForegroundMode();
beaconDataProvider.startScanning(targetedBeacons);
一旦设备进入该区域并检测到信标,信标扫描就会切换到后台模式:
beaconDataProvider.setBackgroundMode();
起初我以为我使用的 Onyx Beacons 有问题,但我可以用 Kontact IO Beacons 重现同样的问题。
你有什么建议吗?
我是不是没有使用 AltBeacon Android 库?
谢谢, 阿林
我认为问题出在这里:
beaconManager.setForegroundScanPeriod(5000L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
您通常应将 scanPeriod 设置为 5100 毫秒或更多,因为如果它们的传输始终在您开始和停止扫描的边界上,则广播的信标有轻微的机会被错过。
所以尝试:
beaconManager.setForegroundScanPeriod(5100L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
希望对您有所帮助。让我知道是否有效。
调用didExitRegion()
的根本原因是在前10秒内Android蓝牙堆栈没有收到与该区域匹配的BLE信标广告包。 (注意:这个值可以用BeaconManager.setRegionExitPeriod(...)
配置。)
有几件事可能导致这些虚假的 didExitRegion()
调用:
- 信标的广告频率不够。
- 信标正在用非常低的无线电信号做广告。
- 附近的无线电噪音太大,无法进行可靠检测。
- 接收设备的蓝牙天线设计不佳,导致无法检测到较弱的信号。
- 接收设备距离太远,无法可靠地检测到信标。
- foregroundScanPeriod 或 backgroundScanPeriod 设置得太短,无法保证检测到
鉴于您所描述的设置,我怀疑当您以 1Hz 的频率传输信标时,1-4 的某种组合会导致问题。您将不得不对这些变量中的每一个进行试验,以查看是否可以将问题隔离为一个主要问题。但同样,可能不止一个人同时在玩。
要知道,即使在良好的条件下,典型的 Android 设备也只能接收到空中传输的信标数据包的 80-90%。因此,如果您的设置通常在 10 秒内仅收到 1-5 个信标数据包,那么如果您运气不好,您仍然 有时 会收到退出事件和一些连续的数据包被无线电噪声破坏。没有办法保证这不会发生。您可以通过设置您的系统来使其在统计上更不可能,因此在标称条件下它会在 10 秒内接收尽可能多的数据包,因此这变得更不可能。
提高广告率是解决这个问题最简单的方法,因为它可以让您在任何 10 秒的时间内有更多的统计机会检测到数据包。但正如您所见,在电池寿命方面存在权衡。
如果您想保留电池寿命但不关心获得 didExitRegion 回调所需的时间,那么您可能需要将 BeaconManager.setRegionExitPeriod(...)
修改为 30,000 毫秒或更多,直到问题消失。
以上讨论是针对Android Beacon 库的配置,同样的理论思想适用于任何信标检测框架,包括iOS Core Location。您有时也会看到该框架的虚假退出事件。
作为此问题的解决方法,我实施了一些额外的逻辑来考虑 didExitRegion()
事件,仅当相应的 didEnterRegion()
在特定时间间隔(在我的情况下为 5 分钟)内未被调用时,但这可以调整)。