I'm building an app for iOS and Android. The app has a JavaScript counter, which updates the time every seconds. When I press the home button in Android and the I come back, the counter is right, it updates when the app is on background, but the iOS doesn't.
Is there a way to make the iOS update the WebView on background?
Sample JS code to update the counter:
setInterval(){
$('#div').html(counter);
counter -= 1;
}, 1000);
I'm afraid tt's not really possible to this in background mode on iOS.
As #Hanh-le mentioned iOS will stop (pause) your app in about 10 seconds from the moment you send it to the background.
Only some native functionality (service) can keep it running (like music playing, bluetooth running in the background, etc).
This services can actually call javascript otherwise Javascript will be stopped.
EDIT (added your function of setinterval) if you set up your interval like this:
window.localStorage.setItem(
'counterInterval',
setInterval({
$('#div').html(counter);
counter -= 1;
}, 1000);
);
You could save the timestamp on the onPause() event and also clear your interval
function onPause() {
clearInterval(window.localStorage.getItem('counterInterval');
window.localStorage.setItem('pauseTime', new Date());
}
and then check for the elapsed time on onResume() event and then update your counter:
function onResume() {
var resumeTime = new Date(),
pauseTime = window.localStorage.getItem('pauseTime'),
elapsedTime = (resumeTime.getTime() - pauseTime.getTime()) / 1000;
//you can round elapsedTime if you'd like
elapsedTime = Math.round(elapsedTime);
//correct the counter
$('#div').html(counter);
counter -= elapsedTime;
//set interval again until the next `Pause` event
window.localStorage.setItem(
'counterInterval',
setInterval({
$('#div').html(counter);
counter -= 1;
}, 1000);
);
}
This would save resources on background mode anyway...
Related
In my application if user lock the mobile.I need to navigate to login screen.I have implemented resume event to navigate login screen if user unlock the device.can anybody tell Why Cordova resume event is not firing in power lock mode / Sleep mode in iOS
is there any other event i need to use in lock mode?
P.S It is working in minimizing the app and maximizing the app
Although in IOS a resume event is fired when minimizing or maximizing an app by pressing the home button, it appears that a resume event is not fired when "closing" or "staring" the app by pressing the power button at least in IOS.
A possible JS-solution might be to check for inactivity. Let's say when an app has not not received any events triggered by an user for some time(30 seconds and if no real pause-event has been fired since) for instance click/touch-events then it can be assumed that the app can still execute some code(so it's still in foreground) and is "paused":
// threshold for inactivity state
var idleTimeout = 30000;
// variable that holds the time in seconds, which indicates how long the app has not received certain events
var timeInSecondsPassed = 0;
// interval instance
var intervalInstance = null;
// variable to handle the transition from "pause" to "resume" state
var inPauseState = false;
function startPauseListener() {
timeInSecondsPassed = 0;
var resetPassedTime = function(){
timeInSecondsPassed = 0;
// has the app reached the "pause" state and
// currently receiving certain events -> the "resume" state is reached
if(inPauseState){
inPauseState = false;
// the "resume" state is reached here
// so the same code might be executed here as it is in the resume-listener
}
};
document.ontouchstart = resetPassedTime;
document.onclick = resetPassedTime;
document.onscroll = resetPassedTime;
document.onkeypress = resetPassedTime;
intervalInstance = setInterval(checkPauseState,1000);
}
function clearPauseListener() {
clearInterval(intervalInstance);
timeInSecondsPassed = 0;
}
function checkPauseState() {
timeInSecondsPassed += 1000;
if (timeInSecondsPassed >= idleTimeout) {
inPauseState = true;
timeInSecondsPassed = 0;
// run further code here to handle "pause" state
// at this point it is assumed as soon as the app receives click/touch-events again a "resume" state is reached.
}
}
function onDeviceReady() {
// handle android devices so that the interval is stopped when a real pause event is fired and started when a real resume event is fired
document.addEventListener("resume", function(){
startPauseListener();
// your actual code to handle real resume events
}, false);
document.addEventListener("pause", function(){
clearPauseListener();
}, false);
}
It has to be noted that when the app is really paused so a pause event is fired the code above is not run in IOS but in android so that's why you might have to handle this szenario in android differently by taking advandage of both resume and pause-Listener for in android when the app is minimized by the home button the interval would still be executed and consumes CPU in the background.
And please also note that it's only a kind of concept-code and not tested in any device!!!
Hope this helps.
There's an iOS-specific event called active that "detects when users disable the Lock button to unlock the device with the app running in the foreground".
Check the documentation at the bottom of the resume doc page:
https://cordova.apache.org/docs/en/5.1.1/cordova/events/events.resume.html
The Timer always jumps right into the function instead of waiting 2000 milliseconds
stop();
var hey:Number= 2000;
function exercise(frame) {
trace("sup");
this.gotoAndStop(frame);
}
setTimeout(exercise(2),hey);
trace(hey);
The setTimeout function (ie. not a Timer) takes the following parameters:
public function setTimeout(closure:Function, delay:Number, ... arguments):uint
Try:
// Save the return value so you can clear it later, otherwise your app for leak memory
var myTimeOut:uint = setTimeout(exercise, hey, 2);
trace(hey);
// the timeout closure object will not be garage collected if you do not clear it
clearTimeout(myTimeOut);
If you are doing this over and over, consider creating a Timer and setting/reseting the repeat count to 1 and then you can just restart the timer when need.
I did this code in swift (2.0)
var timer: dispatch_source_t!
var a = 0
func startTimer() {
let queue = dispatch_queue_create("com.domain.app.timer", nil)
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1 * NSEC_PER_SEC) // every 60 seconds, with leeway of 1 second
dispatch_source_set_event_handler(timer) {
self.a = self.a+1
print( self.a)
}
dispatch_resume(timer)
}
func stopTimer() {
dispatch_source_cancel(timer)
timer = nil
}
the problem is that this code work well on the emulator , I see in the consol a number incrementing every second, and when I go the the home page of the emulator the number is still incrementing ,
On the device, the number stop incrementing as soon as we quit the application context .
so how can I make it keep incrementinf on the device ?
By default iOS applications do not support background execution. You can start execute finite-length background tasks with beginBackgroundTaskWithName method or you have to enable special background modes like voip, location, remote notifications to run in background. (The use of those modes are reviewed by Apple when you submit app to Appstore)
More info: Background execution documentation
I agree that it is quite misleading that it works on simulator, when it is not supposed to.
I am using a timer in my cocos2d-x game (c++) ios. I am using cocos2d-x 2.2 version.
My function for time is as follows
in my init
this->schedule(schedule_selector(HelloWorld::UpdateTimer), 1);
I have defined the function as follows.
void HelloWorld::UpdateTimer(float dt)
{
if(seconds<=0)
{
CCLOG("clock stopped");
CCString *str=CCString::createWithFormat("%d",seconds);
timer->setString(str->getCString());
this->unschedule(schedule_selector(HelloWorld::UpdateTimer));
}
else
{
CCString *str=CCString::createWithFormat("%d",seconds);
timer->setString(str->getCString());
seconds--;
}
}
Everythings is working fine. But i have this timer to keep running even if the game enters background state. I have tried commenting the body of didEnter Background in appdelegate but not successfull. Any help will be appreciated
Thanks
If the app gets in the background, apart from some special background threads, no other thread gets executed.
Best way for you would be to save the unix timestamp in a variable during didEnterBackground, and when app resumes, fetch the current unix timestamp and compare the delta, to get the total time passed and update your timer accordingly.
In my AppDelegate.cpp I wrote the following code in applicationDidEnterBackground function. Here I took a value of time in seconds whenever the app goes background and store it in a CCUserdefault key. And when the app comes to foreground I again took the local system time and subtracted that from the time I stored in the key. Following is my code
void AppDelegate::applicationDidEnterBackground()
{
time_t rawtime;
struct tm * timeinfo;
time (&rawtime);
timeinfo = localtime (&rawtime);
CCLog("year------->%04d",timeinfo->tm_year+1900);
CCLog("month------->%02d",timeinfo->tm_mon+1);
CCLog("day------->%02d",timeinfo->tm_mday);
CCLog("hour------->%02d",timeinfo->tm_hour);
CCLog("minutes------->%02d",timeinfo->tm_min);
CCLog("seconds------->%02d",timeinfo->tm_sec);
int time_in_seconds=(timeinfo->tm_hour*60)+(timeinfo->tm_min*60)+timeinfo->tm_sec;
CCLOG("time in seconds is %d",time_in_seconds);
CCUserDefault *def=CCUserDefault::sharedUserDefault();
def->setIntegerForKey("time_from_background", time_in_seconds);
CCDirector::sharedDirector()->stopAnimation();
// if you use SimpleAudioEngine, it must be pause
// SimpleAudioEngine::sharedEngine()->pauseBackgroundMusic();
}
void AppDelegate::applicationWillEnterForeground()
{
CCUserDefault *def=CCUserDefault::sharedUserDefault();
int time1=def->getIntegerForKey("time_from_background");
time_t rawtime;
struct tm * timeinfo;
time(&rawtime);
timeinfo = localtime (&rawtime);
CCLog("year------->%04d",timeinfo->tm_year+1900);
CCLog("month------->%02d",timeinfo->tm_mon+1);
CCLog("day------->%02d",timeinfo->tm_mday);
CCLog("hour------->%02d",timeinfo->tm_hour);
CCLog("mintus------->%02d",timeinfo->tm_min);
CCLog("seconds------->%02d",timeinfo->tm_sec);
int time_in_seconds=(timeinfo->tm_hour*60)+(timeinfo->tm_min*60)+timeinfo->tm_sec;
int resume_seconds= time_in_seconds-time1;
CCLOG("app after seconds == %d", resume_seconds);
CCDirector::sharedDirector()->startAnimation();
// if you use SimpleAudioEngine, it must resume here
// SimpleAudioEngine::sharedEngine()->resumeBackgroundMusic();
}
You can see and calculate the time the app remained in background.
If I get multiple push notifications whilst app is in foreground. The callback method will execute every push notification one by one.
callback : function(e) {
if (e.inBackground == 1) {
//came from background - do something.
} else {
// Titanium.UI.iPhone.setAppBadge(null);
//check type, if it is chat.
if (type == 'chat') {
//check if window is already opened or not, if so fire event handler
if (currentWindow == '_chatWindow') {
//update view directly after entering app from the background. Fire event handler
Ti.App.fireEvent('_updateChat', {});
} else if (currentWindow == '_messages') {
//refresh messages screen if on messages screen and chat message arrives
//update view directly after entering app from the background. Fire event handler
Ti.App.fireEvent('_updateMessages', {});
} else {
//display local notification
}
}
If the push notification has come from the background it is easy to deal with, as the push notification that is activated is the one the user chooses to swipe. However, if multiple push notifications come into the foreground and say it's chat, it will execute them multiple times.
How can I handle push notifications in the foreground better? Thanks
Update:
tried this code without much luck
Ti.App.addEventListener('_displayNotification', function(e) {
//store all push notifications in array
var pushArray = [];
var countPushNotifications;
//currentTime to cross reference
var currentTime = new Date();
if (currentTime - Alloy.Globals.pushTime < 3000) {
//do something
pushArray.add(e.PushNotificationData);
} else {
//after 3 seconds remove event handler
//fire event to filter array and process notification, reset time for next event
Alloy.Globals.pushTime = null;
Ti.App.removeEventListener('_displayNotification', {});
}
//first push notification, will be the current time
if(Alloy.Globals.pushTime==null){
Alloy.Globals.pushTime = currentTime;
}
});
Trying to get all the push notifications inside the array, for further filtering.
Update 2:
if (Alloy.Globals.countPushNotificationsFlag == 1) {
Alloy.Globals.countPushNotificationsFlag = null;
setTimeout(function() {
Ti.App.fireEvent('_displayNotification', {
PushMessage : message
});
}, 6000);
} else {
Alloy.Globals.countPushNotificationsFlag = 1;
Ti.App.fireEvent('_displayNotification', {
PushMessage : message
});
}
I have tried to execute push notifications alternatively.
1st notification - fires instantly.
2nd notification - fires after 6 seconds.
3rd notification - instantly.
4th notification - fires after 6 seconds.
and so on...
however the code only works for
notification 1 and 2.
Fails when it hits the 3rd notification.
you can check if Push is received in foreground or background using inBackground Property of push here is the documentation
Hope it helps.
I haven't got the Titanium experience to give you actual code, but this is the approach you need to take:
When you receive a notification, check to see if a (global) boolean receivedNotification is false
If it is false, set it to true, store the event into a global and schedule a setTimeout function for, say, 3 seconds, to process the event and reset receivedNotification to false
If receivedNotification is true, update the global event to the newer notification
In the process event method that is triggered via the timer your will do what you currently do in the first section of code.
This will ensure that the event is processed no longer than 3 seconds after it is received and that events will be processed, at most, once every three seconds.
Your code looks pretty close, except you are trying to fire the first event immediately and then subsequent events after a delay. Unfortunately I don't believe that this is possible, because you have no way of seeing if there are events queued immediately. I think that you are always going to have to incur the delay, but you can tune the delay to find a balance between responsiveness and reduced API calls -
if (Alloy.Globals.countPushNotificationsFlag == null) {
Alloy.Globals.countPushNotificationsFlag = 1;
Alloy.Globals.messageToPush=message;
setTimeout(function() {
Alloy.Globals.countPushNotificationsFlag = null;
Ti.App.fireEvent('_displayNotification', {
PushMessage : Alloy.Globals.messageToPush
});
}, 3000);
}
else {
Alloy.Globals.messageToPush=message;
}