-
Notifications
You must be signed in to change notification settings - Fork 836
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Android O Background Scanning #484
Conversation
Updates include:
The new JobScheduler strategy is much more stable and less resource intensive than the previous strategy. This resolves the first two open issues regarding failure to detect in the background between scan cycles on Android O, and the serialization being to heavy-weight for rapid scan cycles. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only a minor review. I'm still trying to parse through the major changes with the JobScheduler
but won't be able to do that for a bit.
LogManager.d(TAG, "Not starting beacon scanning service. Using scheduled jobs"); | ||
consumer.onBeaconServiceConnect(); | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy paste error duplicating these blocks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, other one removed
@@ -476,6 +493,24 @@ public void setBackgroundMode(boolean backgroundMode) { | |||
} | |||
} | |||
} | |||
public void setEnableScheduledScanJobs(boolean enabled) { | |||
this.mScheduledScanJobsEnabled = enabled; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any particular reason for the this.
here? Just asking as it's use has been mixed but it seems the majority of cases don't included it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since we are prefixing members with m
per Android style, I agree that this
adds nothing.
@@ -476,6 +493,24 @@ public void setBackgroundMode(boolean backgroundMode) { | |||
} | |||
} | |||
} | |||
public void setEnableScheduledScanJobs(boolean enabled) { | |||
this.mScheduledScanJobsEnabled = enabled; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if someone enables this after the manager has already been started?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can throw an IllegalStateException
if the BeaconService has already been started
} | ||
Message msg = Message.obtain(null, BeaconService.MSG_STOP_MONITORING, 0, 0); | ||
msg.setData(new StartRMData(region, callbackPackageName(), this.getScanPeriod(), this.getBetweenScanPeriod(), this.mBackgroundMode).toBundle()); | ||
serviceMessenger.send(msg); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since there are four places related to beacon updates where you needed to make this change, maybe a single internal helper would be good to have to centralize this logic?
Maybe something like:
@TargetApi(18)
private void beaconServiceUpdate(int type, @NonNull Region region) throws RemoteException {
if (mScheduledScanJobsEnabled) {
ScanJob.applySettingsToScheduledJob(mContext, this);
return;
}
if (serviceMessenger == null) {
throw new RemoteException("The BeaconManager is not bound to the service. Call beaconManager.bind(BeaconConsumer consumer) and wait for a callback to onBeaconServiceConnect()");
}
Message msg = Message.obtain(null, type, 0, 0);
msg.setData(new StartRMData(region, callbackPackageName(), getScanPeriod(), getBetweenScanPeriod(), mBackgroundMode).toBundle());
serviceMessenger.send(msg);
}
packageManager.queryIntentServices(intent, | ||
PackageManager.MATCH_DEFAULT_ONLY); | ||
if (resolveInfo.size() == 0) { | ||
if (resolveInfo != null && resolveInfo.size() == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts on resolveInfo.isEmpty()
instead of resolveInfo.size() == 0
? isEmpty
feels a bit more expressive of the intent.
While I haven't had a chance to get ahold of a Android O preview device to test directly, I was curious why the original bound service would be affected. The following is from the Android O behavior changes:
From what I can tell it seems that only if the app / service is initially started in the background (i.e. a device reboot or power connection event) would there be a potential issue. I saw the report from #504 which seems to indicate an issue, but I didn't see anything in the actual documentation which states this should be the case. Is there something I'm missing with regards to this? |
Android O plans to kill long running background services and also prevent them from starting up on phone boot using I'm not 100% sure what the "Bound services are not affected" clause means. My guess is that it means foreground apps can use bound services provided by background apps, and if they do, the background service is allowed to run indefinitely. My guess is also that background apps using their own bound services will be subject to the same restrictions of non-bound services. If this interpretation is true, this does not apply to this library because the service is not exported for other apps to use -- it is only used internally. Testing will ultimately show which is true. Regardless, the |
Here the logs that are generated on Android 7+ |
My understanding of https://github.com/evernote/android-job is that it provides a shim for using the JobScheduler on Android versions that do not support (pre API 21). My plan on using the JobScheduler is to do so only and Android O and above. The reason is that prior to Android O, there is no way to get callbacks for BLE detections if your app is not running. This means a service must be continually running to scan for and detect beacons in low power mode, effectively making it not possible to use a JobScheduler as intended. It means the long-running BeaconService will continue to be used for devices running OS versions earlier than Android O. Yes, you are correct that the minimum time for the JobScheduler is now 15 minutes. What this means for Android O is that:
|
…-beacon-library into scheduled-job-scanning
This PR has been rolled into a beta release below so that @Ch3D and others may use it for testing apps targeting Android O.
See updated release beta 2 below. |
New beta2 release fixes crashes when ranging callbacks are made:
See updated beta 3 release below. |
@davidgyoung every background scan I see the following error message in logcat: |
@Ch3D, see #547 for a description of this issue. I will post a beta update to fix this here: See updated beta 4 release below. |
- use BeaconLocalBroadcastProcessor instead of BeaconIntentProcessor when ScanJob is used
@davidgyoung yeah, I saw it, thanks David. Will try it out later today |
Hi, everyone. I want to report an issue with the 2.12-beta3 version #551 |
Reported another issue here: #550 |
… older Android devices
This may happen if the scans are processed before the CycledScanner is created.
Here is a beta 4 release that fixes:
https://github.com/AltBeacon/android-beacon-library/tree/2.12-beta4 |
@davidgyoung Pardon me if this is dumb but any help/documentation in how to implement the new ScanJob class? Do I use it just like any other job in a JobScheduler? |
The ScanJob class is designed to be an internal component of the library that you don't have to worry about. If you are running the library in an app on Android 8.0, the library will use it by default to schedule scans for beacons. If you are running the library in an app on Android 4.3-7.x, it will keep using a long running BeaconService to do the scanning. |
Thanks for the quick response! Thats pretty convenient. So just so I understand you correctly my current implementation with a service implementing the RangeNotifier and persisting in the background will not need to be refactored? |
You may need to refactor for changes in Android 8, even if not for changes in this library, as Android 8 blocks long-running background services. Read more here: http://www.davidgyoungtech.com/2017/08/07/beacon-detection-with-android-8 |
This fixes #627 which is a regression of #523 (AltBeacon/android-beacon-library-reference#30). It was introduced in commit f084042 (PR #484) where the `RunningAverageRssiFilter` has it's value constantly reset after every cycle in [`RangedBeacon#commitMeasurements`](f084042#diff-65311818bc092d4192549ca6a7932a8aR50).
This fixes #627 which is a regression of #523 (AltBeacon/android-beacon-library-reference#30). It was introduced in commit f084042 (PR #484) where the `RunningAverageRssiFilter` has it's value constantly reset after every cycle in [`RangedBeacon#commitMeasurements`](f084042#diff-65311818bc092d4192549ca6a7932a8aR50).
@davidgyoung would it be feasible to schedule an exact job using JobScheduler and scheduling same job within the scheduled exact job? |
@chintan-mishra are you suggesting this as a way of getting around the 15 minute limit on Android 8? |
Yes, @davidgyoung that's exactly what I am trying to say. |
This adds an alternate means of background scanning compatible with Android O that does not rely on long running background services (newly prohibited by Android O). The new technique relies on the JobScheduler which allows an app that isn't in the foreground to schedule periodic activities.
The goals of this design are as follows:
This change retains the legacy use of a long-running BeaconService on Android 7 and earlier, but instead uses a ScanJob triggered by the JobScheduler on devices with Android O. The ScanJob may also be manually configured on Android 5+, but it is not made the default on these devices because it does not allow detecting beacons with low-power scans when the ScanJob is not running without new scanning APIs introduced in Android O.
This change works as follows when ScanJobs are enabled on Android O:
In the foreground or when the
betweenScanPeriod
is 0, aScanJob
will be scheduled immediately, and run for 5 minutes, at which time it will end and a new one will immediately begin. The ScanJob uses aCycledLeScanner
in much the same way that theBeaconService
does.In the background, or when the
betweenScanPeriod
is nonzero, aScanJob
will be scheduled to periodically run everyscanPeriod+betweenScanPeriod
milliseconds. It will run for thescanPeriod
then stop.Due to OS limits, periodic jobs may only be scheduled at a maximum frequency of 15 minutes, meaning the default
backgroundBetweenScanPeriod
of 30000ms (5 minutes) will not be achieved. The consequence of this is minor due the following point:When the
betweenScanPeriod
is nonzero and aScanJob
completes, a low-power filtered scan will be started with the results to be delivered viaIntent
using new APIs available in Android O. TheseIntents
, if received, will trigger the library'sStartupBroadcastReceiver
which will then schedule an immediateScanJob
to process the beacon detections. In testing on a Nexus Player, this ensures that a newly detected beacon causes adidEnterRegion
callback within seconds of being received, even if theScanJob
is not running.If the
ScanJob
is enabled on pre-Android O devices, points 1-3 apply, but point 4 does not, as these APIs are unavailable.The main difference in the library behavior on Android O is as follows:
In the background, full high-power scans will not be scheduled more than once every 15 minutes, regardless of what the
betweenScanPeriod
is set to.These high-power scans will not run exactly every 15 minutes. On pre-Android O devices using the BeaconService, timers are used to schedule the high-power scans at nearly exact intervals of 310000 ms using default background scan settings. But because the JobScheduler allows
In overnight tests, the variation in the actual time the job was scheduled ranged from 10.2 to 25.7 minutes, with an average of 15.5 minutes. If for some reason a device running Android O fails to detect a beacon with a low-powered filtered scan (e.g. all the bluetooth hardware filters are used) then it may take up to 25 minutes to detect the beacon based on these tests.
If beacons have been detected recently with the app in the background, no low-powered filtered scans will be performed, because they will immediately yield results. Instead, periodic high-powered scans are used. This is the same as when using a long-running
BeaconService
. But because these high powered scans will be less frequent and less regular, it will typically take longer on Android O devices for backgrounddidExitRegion
events to fire, or fordidRangeBeaconsInRegion
events to fire when the app is in the background. These will come only after a period of 10-25 minutes.