7
votes

I am working on a bluetooth app using the AltBeacon library. It seems that only on instance of the BeaconManager is allowed per application. The problem I am facing is this: I want a continuously running background service that constantly does bluetooth ranging and sends notifications. If I open my app (bring it to foreground) I was the service to pause ranging. The foreground activity will then do ranging and show content on the screen.

The problem is that the beacons manager (from BeaconManager beaconManager = BeaconManager.getInstanceForApplication(this);) in the activity and service is the same instance. So when the activity gets closed, beaconManager.unbind(this); gets called and the range notifier in the service no longer fires.

Is it possible to get two separate instances of beacon manager? If not, how can I do ranging in a continuous running service and an activity?

RangingActivity

@Override
protected void onCreate(Bundle savedInstanceState) {
...
regionEstimote = new Region("estimote", null, null, null);
beaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));
beaconManager.bind(this);
}
@Override
protected void onDestroy() {
    super.onDestroy();
    notificationManager.cancel(NOTIFICATION_ID);
    //beaconManager.unbind(this);
}
@Override
public void onBeaconServiceConnect() {
    beaconManager.setRangeNotifier(new RangeNotifier() {
        ....
    });
    try {
        beaconManager.startRangingBeaconsInRegion(regionEstimote);
    } catch (RemoteException e) {
        Log.e(TAG, "RangingActivity", e);
    }
}

BeaconService.java

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if(beaconHistory == null)
        beaconHistory = new HashMap<Integer, Date>();

    mBeaconManager = BeaconManager.getInstanceForApplication(this);
    mBeaconManager.getBeaconParsers().add(new BeaconParser().
            setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));

    return START_STICKY;
}

@Override
public void onDestroy() {
    super.onDestroy();
    beaconHistory = null;
    mBeaconManager.unbind(this);
}
@Override
public void onBeaconServiceConnect() {
    mBeaconManager.setRangeNotifier(new RangeNotifier() {
        @Override
        public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
            if(ActivityBase.isActivityVisible()) {   //don't do ranging logic if any activity from my app is in the foreground
                return;
            }
            ...
        }
    });

    try {
        mBeaconManager.startRangingBeaconsInRegion(regionMint);
    } catch (RemoteException e) {
        Log.e(TAG, "BeaconService", e);
    }
}
2

2 Answers

6
votes

This is a case where a custom android.app.Application class is very useful. The BeaconManager is a singleton so only one is allowed to exist at the same time. Similarly, the Application class has a single instance per active Android application. If you want to do beacon detection in an Activity and a Service simultaneously, use the centralized Application class to do the binding to BeaconManager and then forward the callbacks to both your Activity and your Service.

You can see an example of binding to the BeaconManager in an Application class and then passing callbacks to an Activity in the reference application here: https://github.com/AltBeacon/android-beacon-library-reference/blob/master/app/src/main/java/org/altbeacon/beaconreference/BeaconReferenceApplication.java

1
votes

You should extend the Applicaton class and start region background monitoring like it is explained here (see "Starting an App in the Background").

In order to do the ranging, in the same class implement RangeNotifier:

public class AndroidApp extends Application implements BootstrapNotifier, RangeNotifier {...

Start the ranging in didEnterRegion:

@Override
public void didEnterRegion(Region region) {
    try {
        mBeaconManager.startRangingBeaconsInRegion(region);
    }
    catch (RemoteException e) {
        if (BuildConfig.DEBUG) Log.d(Const.TAG, "Can't start ranging");
    }
}
  1. Implement didRangeBeaconsInRegion method:

    @Override
    public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
        if (beacons.size() > 0) {
            Beacon firstBeacon = beacons.iterator().next();
            if (BuildConfig.DEBUG) Log.d(Const.TAG, "Beacon ranged: UUID: "
                    + firstBeacon.getId1().toString() + " Major: "
                    + firstBeacon.getId2().toString() + " Minor: "
                    + firstBeacon.getId3().toString());
    
            // Do something with the result
    
            // Stop ranging
            try {
                mBeaconManager.stopRangingBeaconsInRegion(region);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }