美文网首页为了更好的活着
iOS后台运行计时器

iOS后台运行计时器

作者: 青鸟evergreen | 来源:发表于2016-06-30 11:41 被阅读2106次

    最近做的一个项目,主要关于效率类的,涉及到得主要功能也就是计时器。如果只是一个简单的计时器,应该是不费事的,但是这次项目中的计时器有几个必须实现的部分:一个是长时间后台运行,另一个是有周期,到时提醒。这些刚开始做的时候感觉很简单,计时器直接套用一个MZTimerLabel这个类,实现正反计时很方便,代码如下:

    
    //计时器
    
    timer1= [[MZTimerLabelalloc]initWithFrame:CGRectMake(0,kScreenHeight*.24,self.view.frame.size.width,40)];
    
    [selfsetTime:timer1];
    
    [self.viewaddSubview:timer1];
    
    - (void)setTime:(MZTimerLabel*)time{
    
    time.timerType=MZTimerLabelTypeTimer;
    
    time.timeLabel.backgroundColor= [UIColorclearColor];
    
    time.timeLabel.textColor= [UIColorwhiteColor];
    
    time.timeLabel.font= [UIFontfontWithName:Gongtisize:28];
    
    time.timeFormat=@"mm : ss";
    
    time.timeLabel.textAlignment=NSTextAlignmentCenter;
    
    time.resetTimerAfterFinish=YES;
    
    }
    
    

    接下来就要实现周期循环,计时提醒的功能,因为之前对计时器了解不多,用起来不是很得心应手。最开始涉及循环这块,我直接采用了

    dispatch_async(dispatch_get_global_queue(0,0), ^{
            dispatch_apply([self.count integerValue], dispatch_get_main_queue(), myBlock);
            [[NSRunLoop currentRunLoop]run];
        });
    

    但是这个方法无法实现计时器的重复运行,这一点让我很废神,但因为赶的紧,所以直接用了NSTimer的一个方法:

    [NSTimer scheduledTimerWithTimeInterval:time target:self selector:@selector(runTime) userInfo:nil repeats:YES];
    

    这个方法的缺陷在于周期必须固定,如果一旦某一段任务时间执行超出,那么就会产生代码执行混乱。这里产生的问题,让我一直再想有没有更好的方法,后来就采用了更简单粗暴的方式,方法里套用方法,每执行一次,计数加一,直到指定的周期停止。到这里,循环的问题解决了,后来我就加了声音提醒,手机震动之类的,这里插播一下系统手机声音,震动的方法:先导入AudioToolBox.framework;然后在需要调用的文件里#import <AudioToolbox/AudioToolbox.h>,再添加以下两句代码就可以实现提示音播放和震动了

      AudioServicesPlaySystemSound(1005);
      AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
    

    如果想自己添加音乐,可以看下如下链接http://www.blogjava.net/writegull/archive/2012/04/16/374729.html

    本来以为到这应该就结束了,结果出现了如下问题:一是无法长时间运行,二是只要计时器置于后台,声音,震动都无法执行。这些问题本来就是iOS自身的问题,因为应用置于后台后,不久就会挂起,大概时间是10分钟。刚开始没想到什么比较好的方法,就在网上查了一些后台长时间运行的方法,尝试了添加后台音频模式,位置更新,但这两种一是没有成功,再个是应用审查可能会被挂掉。一筹莫展,等到第二天起床的时候,闹铃响起,我看了看手机,才想到可以用本地通知的方式啊,怎么之前没想到,应该是从来没用过的原因。本地通知用起来很方便,到时会有提醒之类的,简单写下相关代码

    //设置本地通知
    + (void)registerLocalNotification:(NSInteger)alertTime words:(NSString *)words{
        UILocalNotification * notification  = [[UILocalNotification alloc]init];
        //设置触发通知的时间
        NSDate * fireDate = [NSDate dateWithTimeIntervalSinceNow:alertTime];
        notification.fireDate = fireDate;
        //时区
        notification.timeZone = [NSTimeZone defaultTimeZone];
        //设置重复的间隔
      //  notification.repeatInterval = kCFCalendarUnitSecond;
        
        //通知内容
        notification.alertBody = words;
        notification.applicationIconBadgeNumber = 1;
        //通知触发播放的声音
        notification.soundName = UILocalNotificationDefaultSoundName;
        //通知参数
        NSDictionary * userDict= [NSDictionary dictionaryWithObject:words forKey:@"key"];
        notification.userInfo = userDict;
        
        // ios8后,需要添加这个注册,才能得到授权
        if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {
            UIUserNotificationType type =  UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
            UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:type
                                                                                     categories:nil];
            [[UIApplication sharedApplication] registerUserNotificationSettings:settings];
            // 通知重复提示的单位,可以是天、周、月
            notification.repeatInterval = NSCalendarUnitDay;
        } else {
            // 通知重复提示的单位,可以是天、周、月
            notification.repeatInterval = NSCalendarUnitDay;
        }
        
        // 执行通知注册
        [[UIApplication sharedApplication] scheduleLocalNotification:notification];
     
    }
    // 取消某个本地推送通知
    + (void)cancelLocalNotificationWithKey:(NSString *)key {
        // 获取所有本地通知数组
        NSArray *localNotifications = [UIApplication sharedApplication].scheduledLocalNotifications;
        for (UILocalNotification *notification in localNotifications) {
            NSDictionary *userInfo = notification.userInfo;
            if (userInfo) {
                // 根据设置通知参数时指定的key来获取通知参数
                NSString *info = userInfo[key];
                // 如果找到需要取消的通知,则取消
                if (info != nil) {
                    [[UIApplication sharedApplication] cancelLocalNotification:notification];
                    break;
                }
            }
        }
    }
    //取消特定通知
    [jishiVC cancelLocalNotificationWithKey:@"key"];
    //取消全部
    [[UIApplication sharedApplication]cancelAllLocalNotifications];
    
    // 本地通知回调函数,当应用程序在前台时调用
    - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
        // 获取通知所带的数据
        NSString *notMess = [notification.userInfo objectForKey:@"key"];
        UIAlertController * alert=[UIAlertController alertControllerWithTitle:nil message:notMess preferredStyle:UIAlertControllerStyleAlert];
        [self.window.rootViewController presentViewController:alert animated:YES completion:^{
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
            });
        }];
        // 更新显示的徽章个数
        NSInteger badge = [UIApplication sharedApplication].applicationIconBadgeNumber;
        badge--;
        badge = badge >= 0 ? badge : 0;
        [UIApplication sharedApplication].applicationIconBadgeNumber = badge;
        // 在不需要再推送时,可以取消推送
        [jishiVC cancelLocalNotificationWithKey:@"key"];
    }
    

    但是时间长了以后,即使提醒,计时器还是挂掉了,页面也不在了...
    唯有重写计时器页面,每次点进去都要刷新一遍计时,但是这样也会导致一些问题,因为有两个计时页面,后台挂起后,应用重新进入要判断哪个计时页面,计时部分也要做大量的判断,因为会被分成不同阶段,然后有不同的提示。刚开始这么写的时候,很乱。后来就想试试看苹果的计时器是怎么做的,然后用手机自带的计时器设置了半小时的时长,半小时后再打开计时器,页面还在,无论中途打开也依旧如此,这个里边一定有鬼,于是在网上查了相关的资料,发现了如下两段代码,加进去之后,有如神来之笔,计时器可以一段时间内运行,页面不会挂掉:

    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(appGoToBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
    
     [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(appHasGoneInForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
    

    查了一下原因:
    UIApplicationDidEnterBackgroundNotification(通知名称)----->应用程序在此方法中释放所有可在以后重新创建的资源,保存所有用户数据,关闭网络连接等。如果需要,也可以在这里请求在后台运行更长时间。如果在这里花费了太长时间(超过5秒),系统将断定应用程序的行为异常并终止他。
    UIApplicationWillEnterForegroundNotification(通知名称) ---->当应用程序在applicationDidEnterBackground:花费了太长时间,终止后,应该实现此方法来重新创建在applicationDidEnterBackground中销毁的内容,比如重新加载用户数据、重新建立网络连接等。

    这是项目中仅仅计时器这块出现的问题,虽然某些问题解决了,但是深层次的原因还是不清楚,希望有了解此类问题的童鞋能多多指教。。。

    相关文章

      网友评论

      • 翻滚的炒勺2013:你好 [NSNotificationCenter defaultCenter]addObserver:self selector:@selector(appGoToBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];

        [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(appHasGoneInForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];


        这两句怎么用 ?
        翻滚的炒勺2013:@青鸟evergreen 关键计时器也会被杀死
        青鸟evergreen:@翻滚的炒勺2016 :直接写在计时页面的viewdidload中

      本文标题:iOS后台运行计时器

      本文链接:https://www.haomeiwen.com/subject/acnxjttx.html