I have an application that monitors for iBeacons. When the application terminates from a suspended state and then enters a beacon region that it is monitoring for, it can sometimes take a very long time (sometimes up to 1 minute) for the application to wake up (call didEnterRegion or didExitRegion). Is there anything I can do about this? Here is the code I am using when the application enters the background
- (void)extendBackgroundRunningTime {
if (_backgroundTask != UIBackgroundTaskInvalid) {
// if we are in here, that means the background task is already running.
// don't restart it.
return;
}
// Attempt to extend background time by initializing a placeholder background thread
__block Boolean self_terminate = YES;
_backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:kBISBeaconManagerKeyBackgroundTask expirationHandler:^{
// Background task expired, completion
if (self_terminate) {
NSLog(#"Application Terminated");
[[UIApplication sharedApplication] endBackgroundTask:_backgroundTask];
_backgroundTask = UIBackgroundTaskInvalid;
}
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Background task started
});
}
A few thoughts:
The code shown in the question extends background beacon ranging time. It does not do anything to affect beacon monitoring (monitoring APIs are what provide didEnterRegion and didExitRegion callbacks).
Monitoring response times can vary quite a bit. Best case scenario, didEnterRegion callbacks will fire within a second or two of the beacon being in range. The didExitRegion event is always delayed by an additional 30 seconds (that's how long iOS waits before deciding a beacon is no longer visible.)
The best case scenario times are not met if all 30 Bluetooth hardware acceleration slots are taken. These slots are across all apps on the phone. If your app tries to monitor a beacon region when all slots are taken, detection times fall back to software backup cycles and can take 15 minutes.
See here for more info:
http://developer.radiusnetworks.com/2014/03/12/ios7-1-background-detection-times.html
http://developer.radiusnetworks.com/2015/04/21/max-beacon-regions-ios.html
Related
I'm making a ibeacon region monitoring app with location updates when the user enter into this region (app not in foreground). This location updates must be configured as kCLLocationAccuracyBestForNavigation accuracy (because I have to make a tracking while the use remain in the region,
subscribe to me significant changes is not enough). Everything works well, but after 20 seconds (sometimes 1 minute o more) I stop receiving locations updates. I put all the keys in info.plist for always location usage, I include the background modes in capabilities section and locations updates on background.
I configure the locationManager with different configurations and always the SO stops my locations updates. I'm using IOS 12 and Iphone 7 for testing.
The way I configure CLLocationManager:
self.locationManager.desiredAccuracy
=kCLLocationAccuracyBestForNavigation
self.locationManager.activityType = .automotiveNavigation
self.locationManager.distanceFilter = kCLDistanceFilterNone;
self.locationManager.delegate = self
self.locationManager.allowsBackgroundLocationUpdates = true
self.locationManager.pausesLocationUpdatesAutomatically = false
Start location updates (when user enter in Ibeacon Region):
func beaconManager(_ manager: KTKBeaconManager, didEnter region:
KTKBeaconRegion) {
self.locationManager.startUpdatingLocation()
}
And finally, in didUpdate locations i call a web service:
func locationManager(_ manager: CLLocationManager,
didUpdateLocations locations: [CLLocation]) {
//Call web service using alamofire
}
I ask for your help to know if I am performing the settings correctly for the purpose I want to perform and any clue that lets me know why the operating system kills my process to get locations updates
Getting regular location updates in the background on iOS is tricky. The operating system is designed to keep apps from constantly running in the background to optimize battery usage, and it suspends them after a period of time unless you have several things exactly right.
You need to do three things:
You must get obtain always location permission from the user (as you say you've done).
You must add the following entry to your Info.plist. This will allow your app to run indefinitely in the background, however if you wish to submit your app to the App Store, this entry will also declare to reviewers that it is a background location app, and you will need to convince them that it provides a location-based benefit to the user, and that the user is aware of this benefit.
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
You must maintain a background thread to keep your app alive. It doesn't matter if you actually do anything in this background thread. Just having it be active keeps iOS from suspending your app.
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid
func extendBackgroundRunningTime() {
if (backgroundTask != UIBackgroundTaskIdentifier.invalid) {
// already started
return
}
NSLog("Attempting to extend background running time")
self.backgroundTask = UIApplication.shared.beginBackgroundTask(withName: "DummyTask", expirationHandler: {
NSLog("Background task expired by iOS. Cannot run again until a new beacon region event")
UIApplication.shared.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskIdentifier.invalid
})
DispatchQueue.global().async {
while (true) {
let backgroundTimeRemaining = UIApplication.shared.backgroundTimeRemaining
// This will be a very large number if you have proper permissions
// If not, it will generally count down from 10 seconds once you are in the
// background until iOS suspends your app.
NSLog("Thread background time remaining: \(backgroundTimeRemaining)")
Thread.sleep(forTimeInterval: 1.0)
}
}
}
In my iOS app I turned on location services in background and also set on always. I also set up region monitoring for every 500 meters so if iOS kills my app in background then it will wake up using region monitoring.
But I found one major issue in updating location. I disabled location services of iOS and re enable it, my app is still in background but it's location icon showing disable and other app which is not even in background shows enable. Please see attached screenshot.
If any one knows about this then please guide me.
I think you have wrong concept, the arrow unfilled means that the app is using geofences, if you scroll to the bottom in that list you can read the legend as you can se in this screenshot
Hope this helps you
In app delegate.m,
- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions
{
[self setUpLocationManager];
return YES;
}
#pragma mark - CLLocationManager Delegate
-(void)setUpLocationManager
{
LocationManager=[[CLLocationManager alloc]init];
LocationManager.delegate=self;
LocationManager.desiredAccuracy=kCLLocationAccuracyBest;
[LocationManager requestWhenInUseAuthorization];
[LocationManager startUpdatingLocation];
}
-(void)locationManager:(CLLocationManager )manager didUpdateLocations:(NSArray<CLLocation > *)locations
{
CLGeocoder *GioCoder=[[CLGeocoder alloc]init];
[GioCoder reverseGeocodeLocation:LocationManager.location completionHandler:^(NSArray<CLPlacemark > Nullable placemarks, NSError * Nullable error)
{
if (error !=nil)
{
}
else
{
CLPlacemark *place=[placemarks lastObject];
NSLog(#"%#",place);
NSString *Path=[[NSBundle mainBundle]pathForResource:#"countries" ofType:#"json"];
NSError *Error;
NSData *JsonData=[[NSData alloc]initWithContentsOfFile:Path options:NSDataReadingMappedAlways error:&Error];
NSMutableArray *DataArray=[[NSMutableArray alloc]init];
if (Error==nil)
{
NSError *err;
DataArray=[NSJSONSerialization JSONObjectWithData:JsonData options:NSJSONReadingMutableContainers error:&err];
}
for (int i=0; i<DataArray.count;i++)
{
if ([[[DataArray objectAtIndex:i]valueForKey:#"code"] isEqualToString:place.ISOcountryCode])
{
// Insert your code here
[LocationManager stopUpdatingLocation];
break;
}
}
}
}];
}
-(void)locationManager:(CLLocationManager )manager didFailWithError:(NSError )error
{
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusRestricted)
{
NSLog(#"Location not enabled in your device");
}
else
{
}
}
Just as Reiner said in his answer, your understanding is wrong. If you have an empty purple arrow it means your geofence is set correctly.
If another app is staying filled purple. It could be because of multiple reasons:
It has pausesLocationUpdatesAutomatically set to false and is always tracking.
It has pausesLocationUpdatesAutomatically set to true but hasn't yet paused long enough at a location to trigger a pause which would technically turn that filled purple into either gray or empty purple (which means the app is continuing tracking using significant location changes or regionMonitoring or visits monitoring)
AFAIK if you region is setup for 500meters then it's better to use SignificantLocation Changes. Because you can use cellTower information. For regionMonitoring app turns it's location Tracking on and off every few minutes to make sure it's been exited the app or not.
VERY IMPORTANT NOTE
Apple documents on this are very confusing. It took me a whole week to figure it out!
Region Monitoring or significant location Tracking or visit Monitoring will only launch app in the background if and only if your app was terminated. If your app is launched again then right from the DidFinishLaunching you can startLocationUpdates.
If your app was suspended or for some reason still in the background you would only get the didExitRegion callback. And AFIAK from the callback you're not able to startUpdateLocations. This is a very vague text from WWDC.
Finally, you must start your location updates while in the
foreground. If you don’t start your updates in the foreground, you
won’t get this special behavior. So what happens if you do start your
updates in the background? Well, first off, your app will probably
need AlwaysAuthorization since your app won’t be considered in use at
that time. Furthermore, Core Location won’t take any action to
ensure your app continues to run. So if you have background runtime
for some reason, and you decide to start a location session, you
might get some updates, but you might also get suspended before you receive all the information you had hoped to receive.
WWDC 2016: Session 716
I don't know why Apple is saying might...it could be something that varies based on battery/cell coverage/ usage patterns / iOS version. It's obviously something they don't want us developers know how. They want to force us to go for other ways.
The only way for you to get your app into terminated state is either if you user manually kills the app or you wait for a very very long time ie wait for any or all other background tasks to finish + then once app is suspended wait for it to become terminated and then exit the region to launch app and be able to start tracking Locations
Is there any way to run NSTimer for more than 3 mins in background?
I have to create simple app that uses
`locationManager(manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], inRegion region: CLBeaconRegion)`
for scan my beacons. I have run into problem when I needed to check if user is exactly 10 seconds near closest beacon. I created
var timer: NSTimer?
var lastClosestBeacon: CLBeacon? {
didSet {
timer?.invalidate()
timer = NSTimer.scheduledTimerWithTimeInterval(10, target: self, selector: "showLocalNotification", userInfo: nil, repeats: false)
// NSRunLoop.mainRunLoop().addTimer(timer!, forMode: NSDefaultRunLoopMode)
}
}
When closest beacon changes lastClosestBeacon is set and timer is scheduled. It is working in App and for 3 mins when user enters background(locks phone etc.) with help from Scheduled NSTimer when app is in background?
Is there any possibility to check that for more than 3 mins ?
PS. I have already added Background Modes with location updates.
You can do it by following way:
1) First include required background mode keys into your Info.plist
2) Check and add following line of code for adding background working of location manager in iOS 9 (update: also works in iOS 10):
if ([self.locationManager respondsToSelector:#selector(setAllowsBackgroundLocationUpdates:)]) {
[self.locationManager setAllowsBackgroundLocationUpdates:YES];
[self.locationManager pausesLocationUpdatesAutomatically:NO];
}
3)Then create a new timer with repeated continuously with every 1 sec.
4)In that timer method add these two line of code.
[self.locationManager stopUpdatingLocation];
[self.locationManager startUpdatingLocation];
This make your app run in background more than 3 mins. To be aware, the battery usage may be costly.
As of iOS 9, apps are allowed a maximum of 180 seconds (3 minutes) of background execution time upon request. This can be extended indefinitely if you put location updates in the “Required background modes” in your Info.plist. I have verified that doing so lets the background execution run forever. Having this in your Info.plist, however, will require you to get approval from Apple for this background mode before putting your app in the App Store.
If you don't want to request location background mode, there are some other tricks you can do to get around Apple's restrictions, which you can read about here: https://gooddevbaddev.wordpress.com/2013/10/22/ios-7-running-location-based-apps-in-the-background/
A word of caution about using those tricks, though -- they are subject to change in any iOS upgrade, and using them might get your app rejected by reviewers.
In my apple watch application I am making a request to location services and to a web service. Performing both these tasks on the iPhone takes roughly 2 seconds, at most. However, when I am making the same requests from the Apple watch it takes more than 10 seconds before I get a reply...unless the iPhone application is already running in which case the request takes about 2 seconds.
I figure there is something wrong with my background task in application:handleWatchKitExtensionRequest:reply: but I cannot figure out what. All hints are appreciated.
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
__block UIBackgroundTaskIdentifier identifier = UIBackgroundTaskInvalid;
dispatch_block_t endBlock = ^ {
if (identifier != UIBackgroundTaskInvalid) {
[application endBackgroundTask:identifier];
}
identifier = UIBackgroundTaskInvalid;
};
identifier = [application beginBackgroundTaskWithExpirationHandler:endBlock];
reply = ^(NSDictionary *replyInfo) {
reply(replyInfo);
endBlock();
};
// Call location services with completion block ^{
// Use the location and make request to web service
// with completion ^{
// reply(#{#"info" : #"here be dragons"});
// };
//};
}
In order to save power, the Apple watch limits the update frequency for location services (a major power hog). When you open the iPhone application, you prioritize the task you're running, and the Apple watch will update its location more often.
You can try to force the application to update faster by sending a "wake up" ping: send an empty/dummy call to the application and reply with a dummy reply. This should wake up the connection for a while, and your second call might be processed faster, although I am unsure of whether this "wake up" ping will prioritize location services.
Last thing, IMHO 10 seconds isn't really an "absurd" amount of time.
this is how I start a background task when application goes background ,
void applicationDidEnterBackground:(UIApplication *)application
{
btId = UIBackgroundTaskInvalid;
UIApplication* cuiApplication = [UIApplication sharedApplication];
void (^backgroundTimeRemainingExtenderHandler)() = ^() {
NSTimeInterval timeRemaining1 = [cuiApplication backgroundTimeRemaining];
if(btId != UIBackgroundTaskInvalid){
[proximityEngine StopEngine];
[cuiApplication endBackgroundTask:btId];
btId = UIBackgroundTaskInvalid;
}
};
btId= [cuiApplication beginBackgroundTaskWithExpirationHandler:backgroundTimeRemainingExtenderHandler];
if(bgmanager != nil){
[bgmanager BeginBackgroundTaskMainLoop];
}
}
My problem is that when my background task calls :
NSURLConnection sendSynchronousRequest
The expiration block is being called even though there is more time remaning , how can I prevent this ?
Regards ,
James
EDIT :
After reading the answer below : I still have 596 seconds left when querying the amount of time left and yet still IOS calls the expiration block handler.
beginBackgroundTaskWithExpirationHandler: is the means by which apps request a little extra background time to do some tidying up as a result of going into the background. However iOS reserves the right to decide how much time it will offer you, if any at all, and to kill your process if you fail to end within the required amount of time.
You don't get to execute in the background indefinitely and you don't get to pick your own time limit. You can query what you've been allocated via backgroundTimeRemaining but that's pretty much the full extent.
Per the documentation the handler is called "shortly before the application’s remaining background time reaches 0". So you should expect backgroundTimeRemaining not quite to be zero.
That being said, if your URL connection hasn't yet completed then you're just meant to note somewhere that it didn't complete and deal with the error next time you come back from the background, usually by trying again. That's what your expiration handler should do, and it needs to do it fast.
The extra time allotted to your app is non-negotiable however.
In my particular case - which I do not quite sure why it behaved the way it behaved , I performed the task on a different thread than the beginbackgroundtask thread , after sendsync returned in that thread the backgroundtask was interrupted by the OS .
When calling sendsync in the beginbackground original thread , it does not occur.
Not sure if its something logical and I did something wrong or an OS bug.