即使设备在信标附近保持静止,应用程序也会在didEnterRegion()和didExitRegion()之间循环

问题描述 投票:1回答:3

我正在使用AltBeacon Android Library(我转载v2.9.2的问题;以及v2.11)与Onyx和kontact.io提供的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);
  • 检测到信标(didRangeBeaconsInRegion()被称为相应的信标)
  • 应用程序切换信标扫描到后台模式: beaconManager.setBackgroundMode(true);
  • 几分钟后,即使设备和信标未移动且应用程序保持相同状态,也会调用didExitRegion()

我发现了两个描述相同问题的Stackoverflow问题:

  1. AltBeacon unstable for OnyxBeacons, cycling through didEnterRegion and didExitRegion repeatedly
  2. http://stackoverflow.com/questions/40835671/altbeacon-reference-app-and-multiple-exit-entry-calls

我目前使用的解决方法是Stackoverflow问题中建议的解决方法:

  • 我已将信标广告频率值从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重现同样的问题。

  1. 你有什么建议吗?
  2. 我错过了使用AltBeacon Android库吗?

谢谢,艾琳

android ibeacon battery altbeacon
3个回答
1
投票

调用didExitRegion()的根本原因在于,在之前的10秒内,Android蓝牙堆栈没有收到与该区域匹配的BLE信标广告包。 (注意:此值可以使用BeaconManager.setRegionExitPeriod(...)进行配置。)

有几件事可能导致这些虚假的didExitRegion()电话:

  1. 信标不经常播放频繁。
  2. 信标通过非常低的无线电信号进行广告宣传。
  3. 附近的无线电噪声太大,无法进行可靠的检测。
  4. 接收设备的蓝牙天线设计较差,导致无法检测到较弱的信号。
  5. 接收设备太远而无法可靠地检测信标。
  6. foregroundScanPeriod或backgroundScanPeriod设置得太短,无法获得有保证的检测

鉴于您所描述的设置,我怀疑当您以1Hz的频率发送信标时,1-4的某些组合会导致问题。您将不得不尝试每个变量,看看是否可以将问题隔离到一个主要问题。但同样,不止一个人可能在同一时间发挥作用。

了解即使在良好的条件下,通过空中传输的信标数据包中只有80-90%是由典型的Android设备接收的。因此,如果你有一个设置,通常在10秒的时间内只收到1-5个信标数据包,如果你运气不好并且连续的几个数据包被无线电噪声破坏,你有时仍会得到退出事件。没有办法保证不会发生这种情况。通过设置系统,你可以在统计上更加不可能,因此在标称条件下,它会在10秒内收到尽可能多的数据包,因此这种可能性更小。

提高广告费率是解决这个问题的最简单方法,因为它可以让您在任何10秒钟内获得更多的统计机会。但正如您所看到的,在电池寿命方面存在权衡。

如果你想保留电池寿命,但不关心获得didExitRegion回调所需的时间,那么你可能想要将BeaconManager.setRegionExitPeriod(...)修改为30,000毫秒或更长时间,直到问题消失为止。

以上讨论特定于Android Beacon Library的配置,相同的理论思想适用于任何信标检测框架,包括iOS Core Location。您有时也会在该框架中看到虚假的退出事件。


0
投票

我认为问题在于:

beaconManager.setForegroundScanPeriod(5000L);
beaconManager.setForegroundBetweenScanPeriod(10000L);

您通常应将scanPeriod设置为5100 ms或更长时间,因为如果广告信标的传输总是在您开始和停止扫描的边界上,那么它很可能会被遗漏。

所以尝试:

beaconManager.setForegroundScanPeriod(5100L);
beaconManager.setForegroundBetweenScanPeriod(10000L);

希望能帮助到你。如果有效,请告诉我。


0
投票

作为这个问题的解决方法,我已经实现了一些额外的逻辑来考虑一个didExitRegion()事件只有在某个时间间隔内没有调用相应的didEnterRegion()(在我的情况下是5分钟,但这可以调整)。

© www.soinside.com 2019 - 2024. All rights reserved.