进入区域时在前台开始信标测距

Start beacon ranging on foreground when entering region

目标

应用程序在后台时的 EddyStone EID 信标检测。 一旦用户进入信标区域,应用程序应开始测距并向我的服务器发出 http 调用,以便我收到检测通知。即使是短期访问。

由于 Android 的后台扫描限制,我正在考虑使用区域 bootstrap 并在进入信标区域后立即启动前台服务。这让我更喜欢直接使用前台服务,所以我不会一直看到通知。

问题

我的应用程序基于 AltBeacon 参考应用程序。我尝试在用户进入该区域后立即启动前台服务。前台服务启动,但测距通知程序未显示任何信标检测。我尝试的替代方法是启动 foregroundservice 并在 didDetermineState 方法回调中进行测距,但这不起作用,因为我必须启用和禁用区域 bootstrap 才能这样做,这将再次触发 didDetermineState 回调方法.

如何在后台检测信标(无延迟)并开始测距而不一直使用前台服务?

代码+日志

public class AppController extends MultiDexApplication implements BootstrapNotifier,
    RangeNotifier, BeaconConsumer {

  private static final String TAG = "BEACON:";
  private static AppComponent appComponent;
  private static AppController instance;

  private RegionBootstrap regionBootstrap;
  private BackgroundPowerSaver backgroundPowerSaver;
  private boolean isScanningOnForeground = false;
  private BeaconManager beaconManager;

  @Override
  public void onCreate() {
    super.onCreate();
    beaconManager = BeaconManager.getInstanceForApplication(this);
    beaconManager.setDebug(true);
    beaconManager.getBeaconParsers().clear();
    beaconManager.getBeaconParsers()
        .add(new BeaconParser().setBeaconLayout(BeaconParser.EDDYSTONE_UID_LAYOUT));
    Region region = new Region("backgroundRegion",
        null, null, null);
    regionBootstrap = new RegionBootstrap(this, region);
    backgroundPowerSaver = new BackgroundPowerSaver(this);
    instance = this;
    this.getAppComponent().inject(this);
  }

  public AppComponent getAppComponent() {
    if (appComponent == null) {
      appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
    }
    return appComponent;
  }

  public static AppController getInstance() {
    return instance;
  }

  @Override
  public void didEnterRegion(Region region) {
    Log.d(TAG, "(didEnterRegion) Beacon detected in region!");
    startMonitoringOnForeground();
  }

  public void disableMonitoring() {
    if (regionBootstrap != null) {
      regionBootstrap.disable();
      regionBootstrap = null;
    }
  }

  public void enableMonitoring() {
    Region region = new Region("backgroundRegion",
        null, null, null);
    regionBootstrap = new RegionBootstrap(this, region);
  }

  private void enableRanging() {

    Log.d(TAG, "Enable ranging");
    beaconManager.removeAllRangeNotifiers();
    beaconManager.addRangeNotifier(this);
    try {
      beaconManager.startRangingBeaconsInRegion(new Region("rangingRegion",
          null, null, null));
      Log.d(TAG, "Ranging started..");
    } catch (RemoteException e) {
      Log.d(TAG, ">>>>>>>>>>> START RANGING EXCEPTION!");
      e.printStackTrace();
    }
  }

  private void disableRanging() {

    Log.d(TAG, "Disable ranging.");
    try {
      beaconManager.stopRangingBeaconsInRegion(new Region("rangingRegion",
          null, null, null));
    } catch (RemoteException e) {
      Log.d(TAG, ">>>>>>>>>>> START RANGING EXCEPTION!");

      e.printStackTrace();
    }
    beaconManager.removeAllRangeNotifiers();
  }

  private void startMonitoringOnForeground() {
    if (isScanningOnForeground) {
      Log.d(TAG, "Ignore method call, already scanning in foreground");
      return;
    }
    isScanningOnForeground = true;

    disableMonitoring();

    Notification.Builder builder = new Notification.Builder(this);
    builder.setSmallIcon(R.drawable.app_icon);
    builder.setContentTitle("Scanning for Beacons");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      NotificationChannel channel = new NotificationChannel("My Notification Channel ID",
          "My Notification Name", NotificationManager.IMPORTANCE_DEFAULT);
      channel.setDescription("My Notification Channel Description");
      NotificationManager notificationManager = (NotificationManager) getSystemService(
          Context.NOTIFICATION_SERVICE);
      notificationManager.createNotificationChannel(channel);
      builder.setChannelId(channel.getId());
    }
    beaconManager.enableForegroundServiceScanning(builder.build(), 456);
    // For the above foreground scanning service to be useful, you need to disable
    // JobScheduler-based scans (used on Android 8+) and set a fast background scan
    // cycle that would otherwise be disallowed by the operating system.
    //
    beaconManager.setEnableScheduledScanJobs(false);
    beaconManager.setBackgroundBetweenScanPeriod(30000);
    beaconManager.setBackgroundScanPeriod(1100);

    enableRanging();
    enableMonitoring();
  }

  private void stopMonitoringOnForeground() {
    if (!isScanningOnForeground) {
      Log.d(TAG, "Not stopping since foreground scanning isn't active.");
      return;
    }
    isScanningOnForeground = false;

    disableMonitoring();
    beaconManager.disableForegroundServiceScanning();
    enableMonitoring();
    disableRanging();
  }

  @Override
  public void didExitRegion(Region region) {
    Log.d(TAG, "(didExitRegion) No beacons anymore");
    stopMonitoringOnForeground();
  }

  @Override
  public void didDetermineStateForRegion(int state, Region region) {
    Log.d(TAG,
        "Determine state for region " + (state == 1 ? "INSIDE" : "OUTSIDE (" + state + ")"));
  }

  @Override
  public void didRangeBeaconsInRegion(Collection<Beacon> collection, Region region) {
    Log.d(TAG, "Did range beacons in region");
    for (Beacon beacon : collection) {
      Log.d(TAG, "Beacon:  " + beacon.getId1().toString());
    }
  }

  @Override
  public void onBeaconServiceConnect() {
    Log.d(TAG, "OnBeaconServiceConnect");
  }
} 
D/BEACON:: Determine state for region OUTSIDE (0)
D/BEACON:: Determine state for region INSIDE
D/BEACON:: (didEnterRegion) Beacon detected in region!
D/BEACON:: Enable ranging
D/BEACON:: Ranging started..
D/BEACON:: Determine state for region OUTSIDE (0)
D/BEACON:: Determine state for region INSIDE
D/BEACON:: (didEnterRegion) Beacon detected in region!
D/BEACON:: Ignore method call, already scanning in foreground
D/BEACON:: (didEnterRegion) Beacon detected in region!
D/BEACON:: Ignore method call, already scanning in foreground

您展示的技术可能接近工作。主要问题是在前台服务 "bound".

之前你无法开始测距

前台服务启动时间较短,启动后才能开始测距。可以尝试的几个选项:

  1. 您可以在禁用 RegionBootstrap 之前开始测距。然后将其余代码保持原样,并且在服务启动时它应该仍然处于打开状态。 (不肯定这会起作用。)

  2. 如果上述方法不起作用,请尝试仅在获得 isScanningOnForeground 为 true 的 didDeermineState 回调后才开始测距。这将确保服务已绑定。

  3. 解决此问题的最好和最干净的方法是停止使用 RegionBootstrap,因为它在绑定服务时不提供直接回调。 BeaconManager.bind(this) 调用确实提供了一个 onBeaconServiceConnect 回调,可用于启动监控和测距。但是,此选项需要最多的代码更改。

您可能还希望设置信标 manager.setDebug(true) 并在测距开始后查找消息,为您提供有关其为何不工作的线索。