我正在使用AltBeacon Android Library(我转载v2.9.2的问题;以及v2.11)与Onyx和kontact.io提供的iBeacon设备集成。
该库似乎运行良好,但我似乎有一个问题,我无法找到一个可接受的解决方案。
以下是有关我如何使用AltBeacon库以及该问题的更多详细信息:
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.setBackgroundMode(false);
beaconManager.bind(…)
onBeaconServiceConnect()
时,应用程序开始监视特定区域中的信标(我要监视的信标列表是已知的,静态的;我使用区域列表,我想要监视的每个信标的一个不同区域)
beaconManager.startMonitoringBeaconsInRegion(region);
didEnterRegion()
)时,应用程序开始输入区域的范围
beaconManager.startRangingBeaconsInRegion(region);
didRangeBeaconsInRegion()
被称为相应的信标)beaconManager.setBackgroundMode(true);
didExitRegion()
。我发现了两个描述相同问题的Stackoverflow问题:
我目前使用的解决方法是Stackoverflow问题中建议的解决方法:
一旦频率增加,一切似乎工作正常,但解决方案是不可接受的,因为信标的电池寿命严重受损。
所有信标扫描都在后台执行(即没有使用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重现同样的问题。
谢谢,艾琳
调用didExitRegion()
的根本原因在于,在之前的10秒内,Android蓝牙堆栈没有收到与该区域匹配的BLE信标广告包。 (注意:此值可以使用BeaconManager.setRegionExitPeriod(...)
进行配置。)
有几件事可能导致这些虚假的didExitRegion()
电话:
鉴于您所描述的设置,我怀疑当您以1Hz的频率发送信标时,1-4的某些组合会导致问题。您将不得不尝试每个变量,看看是否可以将问题隔离到一个主要问题。但同样,不止一个人可能在同一时间发挥作用。
了解即使在良好的条件下,通过空中传输的信标数据包中只有80-90%是由典型的Android设备接收的。因此,如果你有一个设置,通常在10秒的时间内只收到1-5个信标数据包,如果你运气不好并且连续的几个数据包被无线电噪声破坏,你有时仍会得到退出事件。没有办法保证不会发生这种情况。通过设置系统,你可以在统计上更加不可能,因此在标称条件下,它会在10秒内收到尽可能多的数据包,因此这种可能性更小。
提高广告费率是解决这个问题的最简单方法,因为它可以让您在任何10秒钟内获得更多的统计机会。但正如您所看到的,在电池寿命方面存在权衡。
如果你想保留电池寿命,但不关心获得didExitRegion回调所需的时间,那么你可能想要将BeaconManager.setRegionExitPeriod(...)
修改为30,000毫秒或更长时间,直到问题消失为止。
以上讨论特定于Android Beacon Library的配置,相同的理论思想适用于任何信标检测框架,包括iOS Core Location。您有时也会在该框架中看到虚假的退出事件。
我认为问题在于:
beaconManager.setForegroundScanPeriod(5000L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
您通常应将scanPeriod设置为5100 ms或更长时间,因为如果广告信标的传输总是在您开始和停止扫描的边界上,那么它很可能会被遗漏。
所以尝试:
beaconManager.setForegroundScanPeriod(5100L);
beaconManager.setForegroundBetweenScanPeriod(10000L);
希望能帮助到你。如果有效,请告诉我。
作为这个问题的解决方法,我已经实现了一些额外的逻辑来考虑一个didExitRegion()
事件只有在某个时间间隔内没有调用相应的didEnterRegion()
(在我的情况下是5分钟,但这可以调整)。