【问题标题】:Test if app did become active from a UILocalNotification测试应用程序是否确实从 UILocalNotification 变为活动状态
【发布时间】:2011-05-07 09:15:06
【问题描述】:

有没有办法通过本地通知知道应用程序是否已激活?

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

当应用激活时,我需要运行不同的代码:

  1. 来自本地通知。
  2. 刚刚变得活跃:)

有办法吗?

【问题讨论】:

    标签: iphone objective-c background uilocalnotification


    【解决方案1】:

    在您的 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:.
         */
    }
    

    【讨论】:

    • 在iPhone 4中应用从后台恢复时是否调用了这个方法(applicationDidFinishLaunchingWithOptions)?
    • 不,应该是 applicationWillEnterForeground,然后是 applicationDidBecomeActive。
    • 对!您必须使用 applicationWillEnterForeground 和 applicationDidBecomeActive:在这些方法中,您可以撤消在进入后台时所做的许多更改,或者您可以刷新 UI
    • 是的,但是我如何知道应用程序是否从本地通知进入前台?
    • 如果应用程序从本地通知进入前台,它会调用 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 方法,所以使用我发布的代码你可以知道一切: - applicationDidEnterBackground:应用程序在后台进入 - applicationWillEnterForeground/applicationDidBecomeActive:应用程序变为活动状态,因此用户可以使用它。 - - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions: 应用启动时,所以即使它是由本地通知启动的。
    【解决方案2】:

    恐怕 Sylter 不正确。当应用程序从后台进入前台时,无论是通过直接用户操作还是通过用户对UILocalNotification 的响应,它都不会触发applicationDidFinishLaunchingWithOptions。但是,它确实调用了applicationWillEnterForegroundapplicationDidBecomeActive。这可以通过几个NSLogs 来验证。

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

    一旦应用进入前台,它将收到application:DidReceiveLocalNotification方法:如果应用进入前台响应UILocalNotification

    问题在于,在application:DidReceiveLocalNotification: 方法中为响应接收 UILocalNotification 而做出的任何 UI 更改都发生在应用程序已经进入前台之后,从而给用户带来了令人不安的体验。 p>

    有人找到解决办法了吗?

    【讨论】:

      【解决方案3】:

      您可以通过以下方式在收到应用程序时检查任一应用程序是否正在运行。

      - (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);
      }
      

      【讨论】:

        【解决方案4】:

        我做的是,我测试了两个场景,一个是通过点击图标将应用程序放回前台,另一个是通过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系统调用,如果用户将应用置于前台,则为 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:4a400120x04a40012(如果我以完整的长字格式编写)。 这给了我二进制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。

        到此为止!

        【讨论】:

        • 这是你在编写代码时要保存调试和避免的那种 C 知识。请,请,请不要在您无法控制的库 (UIKit) 中针对确切的内存偏移量进行编码,该库不能保证这些私有成员的 ABI 稳定性。
        【解决方案5】:

        好的,这是我最终的优雅解决方案,让您能够访问在 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
        

        然后创建一个实现“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];
        

        然后你就可以开始参考flags了:

        - (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
            }
        }
        

        【讨论】:

        • 我的东西苹果不允许你访问_applicationFlags,如果你想通过它的指针访问它也没关系。
        • 你提交到应用商店了吗?批准了吗?
        【解决方案6】:

        我从@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 {
        }
        

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

        【讨论】:

        • 宾果游戏!!!!!!!!!! (需要感叹号才能达到最小评论长度...)
        • 我目前正在 iOS 8 beta 5 上对此进行测试,似乎行为已经改变。当应用程序在后台运行并且您收到本地通知并从中打开应用程序时,didReceiveLocalNotification: 不会在 applicationDidBecomeActive: 之后调用。
        • 我还观察到 Markus 所看到的......如果应用程序在后台并且您收到本地通知并从中打开应用程序,didReceiveLocalNotification: 永远不会被调用。我也在 iOS 8 beta 5 上。
        • 哦!但是,我正在实现新的 iOS 8 可操作通知,我注意到 application:handleActionWithIdentifier:forLocalNotification:completionHandler: 被调用。 (不仅仅是点击了一个动作,而是点击了通知本身。)希望这可以帮助其他人在未来阅读这篇文章。
        • 只是跟进一下:我发现在 iOS 8 GM 上,行为恢复到原始行为。也就是说,didReceiveLocalNotification: 被调用以响应通知。
        【解决方案7】:

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

        如果应用程序只是进入后台,点击UILocalNotification 启动应用程序调用applicationWillEnterForeground:,然后是application:didReceiveLocalNotification:,最后是applicationDidBecomeActive:。请注意,方法调用的顺序与@jaredsinclair 的答案不同,后者是几年前编写的,可能在不同版本的 iOS 上进行了测试。

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

        我如何测试应用程序委托方法回调序列:在应用程序委托中,我创建了一个NSMutableArray,并在每次调用applicationWillEnterForeground:application:didReceiveLocalNotification:applicationDidBecomeActive: 时用一个字符串填充它。然后,我显示了最后两个方法的数组内容,因为我不确定它们会以什么顺序被调用。当应用程序来自后台时,只有当我得到两个UIAlertViews,但这只是因为这两个所说的方法是一个接一个地调用的。

        无论如何,我还想提出这样一个结论,即如果应用程序处于终止状态,则无法跟踪应用程序是否是从UILocalNotification 启动的。 > 有人想通过重现测试来帮助确认?

        【讨论】:

        • 我在 ios8 上使用 UIMutableUserNotificationActiion 对其进行了测试。如果我单击通知文本而不是本地通知中的操作按钮,在 didFinishLaunchingWithOptions 方法中,我能够提取通知信息 UILocalNotification *localNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];这意味着我可以看出它是通过本地通知启动的。但是,如果我单击操作按钮,则无法分辨。 PS UIMutableUserNotificationAction 在 iOS 10 中不起作用。
        【解决方案8】:
        - (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
        
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-20
          • 1970-01-01
          • 2010-11-11
          相关资源
          最近更新 更多