2016年6月17日 星期五

LocalNotification的偵測在前景背景



當按下localnotification之後,會進入這個callback,

可以在這邊處理看是在前景時收到的本地通知(不會有上面跳出的banner),或是在背景時收到的通知,

經過實測發現,其實是三種狀態,但是如果說,你需要的是不管前後景,都跳到一個特定的app的某一個畫面的話,其實,這個callback都會幫你處理跳出,

意思就是說,不管是在background或是inactive,最終,再這個callback內,都會回到forground~下面是一點點code




-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
    UIApplication *app = application;
    app.applicationIconBadgeNumber = notification.applicationIconBadgeNumber -1;
    
    NSDictionary* dic = [[NSDictionary alloc]init];
    //这里可以接受到本地通知中心发送的消息
    dic = notification.userInfo;
    NSLog(@"user info = %@",[dic objectForKey:@"major"]);
    
    

    //前景
    UIApplicationState state = [application applicationState];
    if (state == UIApplicationStateActive) {
        NSLog(@"forforfor");
        
//        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"KNIP"
//                                                        message:notification.alertBody
//                                                       delegate:self cancelButtonTitle:@"Close"
//                                              otherButtonTitles:nil];
//        
//        [alert show];
    }
    if (state == UIApplicationStateBackground) {
        NSLog(@"backbackback");
    }
    if (state == UIApplicationStateInactive) {
        NSLog(@"InactiveInactiveInactive");
    }
    
    if (![AppDelegate runningInForeground]) {
        NSLog(@"AppDelegateBackGround");
        [[NSNotificationCenter defaultCenter] postNotificationName:@"first" object:[dic objectForKey:@"major"] userInfo:@{@"name":@"dean",@"age":@22}];
        
        [self.window.rootViewController presentViewController:self.tabBarController animated:YES completion:nil];
    }
    
    
}


+(BOOL)runningInBackground
{
    NSLog(@"Background");
    UIApplicationState state = [UIApplication sharedApplication].applicationState;
    BOOL result = (state == UIApplicationStateBackground);
    
    return result;
}
+(BOOL)runningInForeground
{
    NSLog(@"Foreground");
    UIApplicationState state = [UIApplication sharedApplication].applicationState;
    BOOL result = (state == UIApplicationStateActive);
    
    return result;

}


當然,這是我所理解的,網路上有大神解釋的更清楚網址是:
http://blog.maxkit.com.tw/2014/04/ios_12.html
以下是他所解釋的:

淺談iOS應用程式背景執行(一) - 在有限時間內在背景執行任意工作


為了能有效率地掌握各個iOS應用程式所使用的資源,及確保iOS應用程式的執行效能,iOS對於應用程式在背景執行的限制相當嚴苛。 一般情況下,只有更新地理位置、下載少量資訊...等輕量級的任務,又或像是音樂播放、VOIP等非得要能在背景運作的任務,才可以在背景執行。

然而,自iOS 4之後,iOS應用程式其實可以向請求在背景執行程式,而不限制執行工作的類型! 只不過相對條件是,在背景執行程式的時間,多半不能超過10分鐘。一旦超過時限,將被iOS強制中止。
但即便如此,10分鐘也或許足以滿足某些特定應用的需求。聊勝於無!

IOS應用程式執行的五種狀態

在開始學習如何在背景執行程式前,應先了解到iOS應用程式執行時的幾種狀態:

上圖參考自Apple官方文件的App States and Multitasking,說明五種狀態之間的變化。

Not running

應用程式尚未被執行的狀態。

Active

應用程式在前景,且正在執行中。

Suspended

應用程式被暫停,無法執行程式。 當使用者按下Home鍵,退出當前應用程式,或是切換至其他應用程式後。 若原應用程式不具有在背景執行的能力,則會立即進入Suspended狀態。
而應用程式中的資訊(如:所使用的變數)...等仍然在記憶體中,只要使用者切換該應用程式, 則該應用程式會接回之前的狀態,繼續執行。
此外,若iOS系統察覺記憶體資源不足時,會清理掉記憶體中Suspended狀態的程式,使應用程式回到Not running的狀態。

Inactive

應用程式在前景,但是可能發生了其他事情(如:接到電話),因此應用程式無法接收任何事件(像是使用者點擊按鈕的UI事件)。 但應用程式仍然在執行中。

Background

當使用者按下Home鍵,退出當前應用程式,或是切換至其他應用程式後。 原應用程式具有在背景執行的能力,因此仍然在執行程式。 如何使應用程式進入此狀態,則是本篇討論的主題。

取得當前應用程式執行狀態

以下API可以取得當前應用程式狀態:
UIApplicationState st = [[UIApplication sharedApplication] applicationState];
UIApplicationState為enum型態,定義如下:
typedef enum : NSInteger {
   UIApplicationStateActive,
   UIApplicationStateInactive,
   UIApplicationStateBackground
} UIApplicationState;
由於Not running與Suspended狀態下無法執行程式,因此自然不會有其定義。

應用程式執行狀態變化時會呼叫的方法

在UIApplicationDelegate中,有幾個方法,會在應用程式執行狀態發生變化時被呼叫:
- (void)applicationWillResignActive:(UIApplication *)application
//當應用程式由Active變為Inactive時會被呼叫。

- (void)applicationDidBecomeActive:(UIApplication *)application
//當應用程式由Inactive變為Active時會被呼叫。


- (void)applicationDidEnterBackground:(UIApplication *)application
//當應用程式由Active變為Background時會被呼叫。

- (void)applicationWillEnterForeground:(UIApplication *)application
//當應用程式由Background回到Active時會被呼叫。
舉幾個例子來說,當應用程式未執行時,點擊應用程式icon開啟後,applicationDidBecomeActive會被呼叫;
當應用程式在前景執行時,按下Home鍵,applicationWillResignActiveapplicationDidEnterBackground會被依序呼叫;
若再度點擊應用程式icon使其回到前景,則applicationWillEnterForegroundapplicationDidBecomeActive會依序被呼叫。
另一種情況是:當應用程式在前景執行時,接到電話或是連按兩下Home鍵進入最近使用的App列表時, 此時,該應用程式還在前景,只是變為Inactive狀態,因此只有applicationWillResignActive會被呼叫。
關於這幾個method被呼叫的時機,也可以參考此篇討論:http://stackoverflow.com/questions/3712979/applicationwillenterforeground-vs-applicationdidbecomeactive-applicationwillre

請求在有限時間內在背景執行任意工作

以下,我們以一個極其簡單的例子,展示如何使iOS應用程式在背景執行工作。
首先,我們將使用一個Timer,每秒在console下印出數字,並累加:
//啟動Timer ...
NSTimer* cntTimer = [NSTimer scheduledTimerWithTimeInterval:1
                             target:self
                             selector:@selector(runCounter)
                             userInfo:nil
                             repeats:YES];

//runCounter 方法
-(void) runCounter {
    NSLog(@"runCounter, count=%i",self.count);
    self.count++;
}
此時,此應用程式還沒有背景執行的能力。
當我們啟動應用程式時,可以看到console下不斷印出數字;但是,一旦按下Home鍵之後,就停止了。
因此,我們需要請求在背景執行。
首先,在AppDelegate類別宣告UIBackgroundTaskIdentifier型態的property。
@property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
接著,在進入背景時,請求在背景執行程式:
- (void)applicationDidEnterBackground:(UIApplication *)application
{      
    self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"Background handler called. Not running background tasks anymore.");
        [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
        self.backgroundTask = UIBackgroundTaskInvalid;
    }];
}
呼叫beginBackgroundTaskWithExpirationHandler方法後,當應用程式退到背景時,就不會進入Suspended狀態,而是在Background狀態。
此時可以看到console也繼續印出訊息。
然而,此方法使應用程式在Background狀態的時間是有限的,一般來說大約為10分鐘。 時間一到,iOS會呼叫當初呼叫beginBackgroundTaskWithExpirationHandler方法時, 所傳入的block,讓應用程式知道即將被停止執行。
應用程式可以在此block中執行最後的處理,並呼叫endBackgroundTask自行中止背景執行。
而倘若不呼叫endBackgroundTask,iOS系統最終還是會終止該應用程式的背景執行。

結語

iOS對於應用程式在背景執行的限制較為嚴苛,但請求在有限時間內執行任意工作的方法並不複雜。 即便只是短短的10分鐘,換個角度思考,或許也足夠滿足部分需求。
此外,在學習使用如何在背景執行程式時,也應了解iOS應用程式執行的五種狀態,以利於掌握應用程式的執行。


stackoverflow上:
When waking up i.e. relaunching an app (either through springboard, app switching or URL) applicationWillEnterForeground: is called. It is only executed once when the app becomes ready for use, after being put into the background, while applicationDidBecomeActive: may be called multiple times after launch. This makes applicationWillEnterForeground: ideal for setup that needs to occur just once after relaunch.
applicationWillEnterForeground: is called:
  • when app is relaunched
  • before applicationDidBecomeActive:
applicationDidBecomeActive: is called:
  • when app is first launched after application:didFinishLaunchingWithOptions:
  • after applicationWillEnterForeground: if there's no URL to handle.
  • after application:handleOpenURL: is called.
  • after applicationWillResignActive: if user ignores interruption like a phone call or SMS.
applicationWillResignActive: is called:
  • when there is an interruption like a phone call.
    • if user takes call applicationDidEnterBackground: is called.
    • if user ignores call applicationDidBecomeActive: is called.
  • when the home button is pressed or user switches apps.
  • docs say you should
    • pause ongoing tasks
    • disable timers
    • pause a game
    • reduce OpenGL frame rates
applicationDidEnterBackground: is called:
  • after applicationWillResignActive:
  • docs say you should:
    • release shared resources
    • save user data
    • invalidate timers
    • save app state so you can restore it if app is terminated.
    • disable UI updates
  • you have 5 seconds to do what you need to and return the method
    • if you dont return within ~5 seconds the app is terminated.
    • you can ask for more time with beginBackgroundTaskWithExpirationHandler:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    /*
     Sent when the application is about to move from active to inactive state. 
     This can occur for certain types of temporary interruptions (such as an 
     incoming phone call or SMS message) or when the user quits the application 
     and it begins the transition to the background state.
     Use this method to pause ongoing tasks, disable timers, and throttle down 
     OpenGL ES frame rates. Games should use this method to pause the game.
     */
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    /*
     Use this method to release shared resources, save user data, invalidate 
     timers, and store enough application state information to restore your 
     application to its current state in case it is terminated later. 
     If your application supports background execution, this method is called 
     instead of applicationWillTerminate: when the user quits.
     */
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    /*
     Called as part of the transition from the background to the inactive state; 
     here you can undo many of the changes made on entering the background.
     */
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    /*
     Restart any tasks that were paused (or not yet started) while the 
     application was inactive. If the application was previously in the 
     background, optionally refresh the user interface.
     */
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    /*
     Called when the application is about to terminate.
     Save data if appropriate.
     See also applicationDidEnterBackground:.
     */
}

沒有留言:

張貼留言