测试应用程序是否从UILocalNotification变为活动状态

时间:2010-11-09 17:00:34

标签: iphone objective-c background uilocalnotification

有没有办法知道应用程序是否通过本地通知变为活动状态?

我知道有一种方法可以测试应用程序是否从本地通知警报启动;但如果只是坐在那里的背景,并收到通知?

当应用程序变为活动状态时,我需要运行不同的代码:

  1. 来自本地通知。
  2. 刚刚变得活跃:)
  3. 有办法吗?

8 个答案:

答案 0 :(得分:63)

我从@naveed的提示中获得了解决方案的线索,在调用didReceiveNotification方法时检查应用程序的状态。 当应用程序从后台恢复时,无需检查变量等。

在iOS7及更低版本上,您可以处理以下通知:

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
    if (application.applicationState == UIApplicationStateInactive ) {
         //The application received the notification from an inactive state, i.e. the user tapped the "View" button for the alert.
         //If the visible view controller in your view controller stack isn't the one you need then show the right one.
    }

    if(application.applicationState == UIApplicationStateActive ) { 
        //The application received a notification in the active state, so you can display an alert view or do something appropriate.
    }
}

iOS 8更新:当通过通知从后台打开应用时,现在会调用以下方法。

- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void(^)())completionHandler {
}

- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void(^)())completionHandler {
}

如果在应用程序位于前台时收到通知,请使用以下方法:

- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *) userInfo {
}

- (void) application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
}

请注意,除非您希望在应用中支持较旧版本的操作系统,否则无需检查应用程序状态。

答案 1 :(得分:29)

我担心赛尔特不正确。当应用从后台进入前景时,无论是通过直接用户操作还是用户对UILocalNotification的响应,它都不会触发applicationDidFinishLaunchingWithOptions。但是,它会调用applicationWillEnterForegroundapplicationDidBecomeActive。这可以通过几个NSLogs进行验证。

所以,问题仍然存在:如果应用程序从后台进入前台,则无法发现应用程序是否进入前台以响应用户对UILocalNotification的响应,或者是否只是进入前景。除了...

应用进入前台后,它会收到方法application:DidReceiveLocalNotification:如果应用进入前台以响应UILocalNotification

问题是,在应用程序已经进入前台后,在application:DidReceiveLocalNotification:方法中响应接收UILocalNotification而进行的任何UI更改都会发生,从而为用户创建令人不安的体验。

有没有人找到解决方案?

答案 2 :(得分:10)

您可以通过以下方式检查应用程序运行与否的情况。

- (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notif {
    if (app.applicationState == UIApplicationStateInactive ) {
        NSLog(@"app not running");
    }else if(app.applicationState == UIApplicationStateActive )  {
        NSLog(@"app running");      
    }

    // Handle the notificaton when the app is running
    NSLog(@"Recieved Notification %@",notif);
}

答案 3 :(得分:7)

我做的是,我测试了两个场景,一个是通过点击图标将应用程序放回到前台,另一个是通过URL sys调用,并比较UIApplication中的所有变量,令人惊讶的是我终于找到了我的内容正在寻找UIApplication.h:

struct {
    unsigned int isActive:1;
    unsigned int isSuspended:1;
    unsigned int isSuspendedEventsOnly:1;
    unsigned int isLaunchedSuspended:1;
    unsigned int calledNonSuspendedLaunchDelegate:1;
    unsigned int isHandlingURL:1;
    unsigned int isHandlingRemoteNotification:1;
    unsigned int isHandlingLocalNotification:1;
    unsigned int statusBarShowsProgress:1;
    unsigned int statusBarRequestedStyle:4;
    unsigned int statusBarHidden:1;
    unsigned int blockInteractionEvents:4;
    unsigned int receivesMemoryWarnings:1;
    unsigned int showingProgress:1;
    unsigned int receivesPowerMessages:1;
    unsigned int launchEventReceived:1;
    unsigned int isAnimatingSuspensionOrResumption:1;
    unsigned int isResuming:1;
    unsigned int isSuspendedUnderLock:1;
    unsigned int isRunningInTaskSwitcher:1;
    unsigned int shouldExitAfterSendSuspend:1;
    unsigned int shouldExitAfterTaskCompletion:1;
    unsigned int terminating:1;
    unsigned int isHandlingShortCutURL:1;
    unsigned int idleTimerDisabled:1;
    unsigned int deviceOrientation:3;
    unsigned int delegateShouldBeReleasedUponSet:1;
    unsigned int delegateHandleOpenURL:1;
    unsigned int delegateOpenURL:1;
    unsigned int delegateDidReceiveMemoryWarning:1;
    unsigned int delegateWillTerminate:1;
    unsigned int delegateSignificantTimeChange:1;
    unsigned int delegateWillChangeInterfaceOrientation:1;
    unsigned int delegateDidChangeInterfaceOrientation:1;
    unsigned int delegateWillChangeStatusBarFrame:1;
    unsigned int delegateDidChangeStatusBarFrame:1;
    unsigned int delegateDeviceAccelerated:1;
    unsigned int delegateDeviceChangedOrientation:1;
    unsigned int delegateDidBecomeActive:1;
    unsigned int delegateWillResignActive:1;
    unsigned int delegateDidEnterBackground:1;
    unsigned int delegateWillEnterForeground:1;
    unsigned int delegateWillSuspend:1;
    unsigned int delegateDidResume:1;
    unsigned int userDefaultsSyncDisabled:1;
    unsigned int headsetButtonClickCount:4;
    unsigned int isHeadsetButtonDown:1;
    unsigned int isFastForwardActive:1;
    unsigned int isRewindActive:1;
    unsigned int disableViewGroupOpacity:1;
    unsigned int disableViewEdgeAntialiasing:1;
    unsigned int shakeToEdit:1;
    unsigned int isClassic:1;
    unsigned int zoomInClassicMode:1;
    unsigned int ignoreHeadsetClicks:1;
    unsigned int touchRotationDisabled:1;
    unsigned int taskSuspendingUnsupported:1;
    unsigned int isUnitTests:1;
    unsigned int requiresHighResolution:1;
    unsigned int disableViewContentScaling:1;
    unsigned int singleUseLaunchOrientation:3;
    unsigned int defaultInterfaceOrientation:3;
} _applicationFlags;

这可能包含程序员希望他们在应用程序返回到前台时可以访问的所有信息,特别是,我想访问标记“isHandlingURL”,如果应用程序被放入前景,则表示为1一个sys调用,如果用户将应用程序置于前台,则为0。

接下来,我查看了“application”和“_applicationFlags”的地址,注意到它们被0x3C偏移,这是60,所以我决定使用地址操作来获取我需要的位:

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    /*
     Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
     */
    id* app = [UIApplication sharedApplication];
    app = app+15; //address increments by long words, don't know if it will be the same on device
    NSLog(@"Test:%x",*app);
}
如果我用完整的长字格式写入,

打印出 test:4a40012 0x04a40012 。 这给了我二进制 0000 0100 1010 0100 0000 0000 0001 0010 。 回顾_applicationFlags,这将从LSB的第6位给我们“isHandlingURL”,这是0.现在,如果我尝试将应用程序置于后台并通过URL sys调用将其带回来,我将打印出 4a40032 二进制文件中 0000 0100 1010 0100 0000 0000 0011 0010 我的isHandlingURL位已打开!所以剩下要做的就是通过位移操作完成语句,最终的代码如下:

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    /*
     Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
     */
    id* app = (id*)[UIApplication sharedApplication]+15;
    BOOL isHandlingURL = ((Byte)*app>>5&0x1);
    if (isHandlingURL) {
        //do whatever I wanna do here
    }
}

我可以继续编写一个完整的函数来解析所有_applicationFlag,但是此时不确定模拟器和目标上的地址增量是否固定为15,我的下一个目标是用一些宏定义或来自系统的值替换幻数'15'所以我可以确定它总是按需要移位0x3C,我需要查看UIApplication头以确保_applicationFlag总是移位0x3C。

现在一切都好!

答案 4 :(得分:4)

在AppDelegate中:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    // Override point for customization after application launch.

    UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];

    if (localNotif) {
        NSLog(@"Recieved Notification %@",localNotif);
    //Do Something
    } else {
    //Do Something else if I didn't recieve any notification, i.e. The app has become active
    }

    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, called instead of applicationWillTerminate: when the user quits.
     */
}


- (void)applicationWillEnterForeground:(UIApplication *)application {
    /*
     Called as part of  transition from the background to the active 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.
     See also applicationDidEnterBackground:.
     */
}

答案 5 :(得分:0)

好的,这是我的最终和优雅的解决方案,您可以访问在UIApplication中声明为私有结构的_applicationFlags。首先创建一个标题“ApplicationFlag.h”:

//
//  ApplicationFlag.h
//  PHPConnectDemo
//
//  Created by Paul on 5/18/11.
//  Copyright 2011 Paul.Poyu.Lu@gmail.com. All rights reserved.
//

#import <Foundation/Foundation.h>
#ifndef APP_FLAG
#define APP_FLAG
#define APP_FLAG_OFFSET 15
#endif

struct appFlag {
    unsigned int isActive:1;
    unsigned int isSuspended:1;
    unsigned int isSuspendedEventsOnly:1;
    unsigned int isLaunchedSuspended:1;
    unsigned int calledNonSuspendedLaunchDelegate:1;
    unsigned int isHandlingURL:1;
    unsigned int isHandlingRemoteNotification:1;
    unsigned int isHandlingLocalNotification:1;
    unsigned int statusBarShowsProgress:1;
    unsigned int statusBarRequestedStyle:4;
    unsigned int statusBarHidden:1;
    unsigned int blockInteractionEvents:4;
    unsigned int receivesMemoryWarnings:1;
    unsigned int showingProgress:1;
    unsigned int receivesPowerMessages:1;
    unsigned int launchEventReceived:1;
    unsigned int isAnimatingSuspensionOrResumption:1;
    unsigned int isResuming:1;
    unsigned int isSuspendedUnderLock:1;
    unsigned int isRunningInTaskSwitcher:1;
    unsigned int shouldExitAfterSendSuspend:1;
    unsigned int shouldExitAfterTaskCompletion:1;
    unsigned int terminating:1;
    unsigned int isHandlingShortCutURL:1;
    unsigned int idleTimerDisabled:1;
    unsigned int deviceOrientation:3;
    unsigned int delegateShouldBeReleasedUponSet:1;
    unsigned int delegateHandleOpenURL:1;
    unsigned int delegateOpenURL:1;
    unsigned int delegateDidReceiveMemoryWarning:1;
    unsigned int delegateWillTerminate:1;
    unsigned int delegateSignificantTimeChange:1;
    unsigned int delegateWillChangeInterfaceOrientation:1;
    unsigned int delegateDidChangeInterfaceOrientation:1;
    unsigned int delegateWillChangeStatusBarFrame:1;
    unsigned int delegateDidChangeStatusBarFrame:1;
    unsigned int delegateDeviceAccelerated:1;
    unsigned int delegateDeviceChangedOrientation:1;
    unsigned int delegateDidBecomeActive:1;
    unsigned int delegateWillResignActive:1;
    unsigned int delegateDidEnterBackground:1;
    unsigned int delegateWillEnterForeground:1;
    unsigned int delegateWillSuspend:1;
    unsigned int delegateDidResume:1;
    unsigned int userDefaultsSyncDisabled:1;
    unsigned int headsetButtonClickCount:4;
    unsigned int isHeadsetButtonDown:1;
    unsigned int isFastForwardActive:1;
    unsigned int isRewindActive:1;
    unsigned int disableViewGroupOpacity:1;
    unsigned int disableViewEdgeAntialiasing:1;
    unsigned int shakeToEdit:1;
    unsigned int isClassic:1;
    unsigned int zoomInClassicMode:1;
    unsigned int ignoreHeadsetClicks:1;
    unsigned int touchRotationDisabled:1;
    unsigned int taskSuspendingUnsupported:1;
    unsigned int isUnitTests:1;
    unsigned int requiresHighResolution:1;
    unsigned int disableViewContentScaling:1;
    unsigned int singleUseLaunchOrientation:3;
    unsigned int defaultInterfaceOrientation:3;
};

@interface ApplicationFlag : NSObject {
    struct appFlag* _flags;
}

@property (nonatomic,assign) struct appFlag* _flags;

@end

然后创建一个implimentation“ApplicationFlag.m”:

//
//  ApplicationFlag.m
//  PHPConnectDemo
//
//  Created by Paul on 5/18/11.
//  Copyright 2011 Paul.Poyu.Lu@gmail.com. All rights reserved.
//

#import "ApplicationFlag.h"

@implementation ApplicationFlag

@synthesize _flags;

- (id)init
{
    self = [super init];
    if (self) {
        // Custom initialization
        _flags = (id*)[UIApplication sharedApplication]+APP_FLAG_OFFSET;
    }
    return self;
}

@end

然后在应用程序委托中进行常规初始化以及属性,合成,包括......无论如何:

applicationFlags = [[ApplicationFlag alloc] init];

然后你可以开始引用标志:​​

- (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.
     */
    if (!applicationFlags._flags->isHandlingURL) {
        //Do whatever you want here
    }
}

答案 6 :(得分:0)

为了更清楚地了解问题,我刚刚测试了从本地通知启动我的应用程序并监控调用应用程序委托方法的顺序。我的测试设备是运行iOS 7.1.1的iPod Touch第5代和运行iOS 7.1.1的iPhone 4S。方法调用的顺序对于两个设备都是相同的。

如果该应用仅转到后台,则点按UILocalNotification以启动应用调用applicationWillEnterForeground:,然后application:didReceiveLocalNotification:,最后点击applicationDidBecomeActive:。请注意,方法调用序列与@ jaredsinclair的答案不同,这是几年前编写的,可能是在不同版本的iOS上测试的。

但是,如果应用程序被终止(通过iOS或用户从多任务侧面滚动条中刷出应用程序),则点击UILocalNotification再次启动应用程序只会调用applicationDidBecomeActive: 。方法application:didReceiveLocalNotification: 未被调用。

我如何测试app委托方法回调序列:在app delegate中,我创建了一个NSMutableArray,并在applicationWillEnterForeground:application:didReceiveLocalNotification:和{{1}时使用字符串填充它} 被称为。然后,我从最后两种方法显示了数组的内容,因为我不确定它们将被调用的顺序。当应用程序来自后台时,只有当我得到两个applicationDidBecomeActive:时才会这样,但这只是因为这两个方法被一个接一个地调用。

无论如何,我还想推断出如果应用程序来自终止状态,则无法跟踪应用程序是否从UIAlertView 启动的结论有人想通过复制测试来帮助确认吗?

答案 7 :(得分:0)

- (void)application:(UIApplication *)application didReceiveLocalNotification:(NSDictionary *)userInfo
{
    if ( application.applicationState == UIApplicationStateActive )
        // app was already in the foreground
    else
        // app was just brought from background to foreground

}