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

时间:2017-08-03 08:55:46

标签: android ibeacon battery altbeacon

我正在使用AltBeacon Android Library(我转载v2.9.2的问题;以及v2.11)与Onyx和kontact.io提供的iBeacon设备集成。

该库似乎运行良好,但我似乎遇到了一个问题,我找不到合适的解决方案。

以下是有关我如何使用AltBeacon Library以及该问题的更多详细信息:

  • 设备在信标附近静止
  • 蓝牙
  • 应用程序在前台运行
  • 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

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

    • 我已将信标广告频率值从1000毫秒更新为 100毫秒

    一旦频率增加,一切似乎都很好,但是    解决方案是不可接受的,因为信标的电池寿命是    严重受损

    所有信标扫描都在后台执行(即不使用任何活动):

    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库吗?

    3. 谢谢, 阿林

3 个答案:

答案 0 :(得分:1)

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

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

  1. 灯塔的播放频率不够高。
  2. 信标的广播信号非常低。
  3. 附近的无线电噪音太大,无法进行可靠的检测。
  4. 接收设备的蓝牙天线设计较差,导致无法检测到较弱的信号。
  5. 接收设备太远而无法可靠地检测到信标。
  6. foregroundScanPeriod或backgroundScanPeriod设置得太短,无法获得有保证的检测
  7. 鉴于您所描述的设置,我怀疑当您以1Hz的频率发送信标时,1-4的某些组合会导致问题。您将不得不尝试每个变量,看看是否可以将问题隔离到一个主要问题。但同样,不止一个人可能会在同一时间发挥作用。

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

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

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

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

答案 1 :(得分:0)

我认为问题在于:

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

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

所以试试:

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

希望它有所帮助。如果有效,请告诉我。

答案 2 :(得分:0)

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