背景
某天深夜,正当我想登录博客的时候,发现居然无法访问。Ping了一下,的确是不通,立即找到主机提供商,问题也很快解决。但由于我也有几天没有登录博客,我具体也不知道这个情况持续了多久,感觉依赖主机提供商的邮件很不靠谱。于是,有了一个念头,就是自己动手丰衣足食。
构想
- 获知网站当前网络是否畅通
- 可以定时自动运行
- 有报告机制,可以及时知道情况
- 最好可以随时随地使用
- 可以有一些更复杂的定制(例如重试次数等)
上述的某些功能用脚本还是可以很好实现的,但是要及时的话,还是移动客户端有优势,而且我也舍不得24小时开着主机(心疼电费)。
技术实现
基本的Ping功能
能Ping通网站是最起码的需求,所以第一个先实现这个基本功能。找了一些资料,原来Apple就有一个SimplePing封装的wrapper,但是比较简陋。我于是找了一些SimplePingHelper来修改,以满足需求。其中我使用了这个Ping,后来增加了一些错误处理和丢失数返回。基本的Ping功能已经具备了,我在考虑以后再完善helper的功能。
后台定时运行
大家一直都认为iOS的多任务比较弱,但其实要做到应用在后台长时间运行还是可行的。最早的时候,大家都是使用后台循环播放无声音乐来实现长时间后台任务。不过,这种方法耗电还是比较厉害的,虽然是无声音乐,但是长时间下来对电量还是有所影响。 于是我还是采用了- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void(^)(void))handler
来实现后台任务,不过这个方法最多只有3分钟的运行时间,所以需要利用一个定时器来定时播放无声音乐,然后重新申请后台运行时间以达到长时间运行的目的。
#import <UIKit/UIKit.h>
@interface PINKLongTimeBackground : NSObject
+ (instancetype)shareInstance;
- (void)start;
- (void)stop;
@end
#import "PINKLongTimeBackground.h"
#import <AVFoundation/AVFoundation.h>
#import <RNTimer/RNTimer.h>
@interface PINKLongTimeBackground ()
@property (nonatomic) UIBackgroundTaskIdentifier taskId;
@property (nonatomic, strong) RNTimer *playerTimer;
@property (nonatomic, strong) AVAudioPlayer *mutePlayer;
@end
@implementation PINKLongTimeBackground
+ (instancetype)shareInstance
{
PINKLongTimeBackground __block *_shareInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_shareInstance = [[PINKLongTimeBackground alloc] init];
});
return _shareInstance;
}
- (instancetype)init
{
self = [super init];
if (self) {
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"mute" ofType:@"wav"];
self.mutePlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:filePath] error:nil];
self.mutePlayer.numberOfLoops = 0;
}
return self;
}
- (void)start
{
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionMixWithOthers error:nil];
[[AVAudioSession sharedInstance] setActive:YES error:nil];
self.taskId =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
self.playerTimer = [RNTimer repeatingTimerWithTimeInterval:60 block:^{
if ([[UIApplication sharedApplication] backgroundTimeRemaining] < 61.0) {
[self.mutePlayer play];
UIBackgroundTaskIdentifier tmpID = self.taskId;
self.taskId =
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
[[UIApplication sharedApplication] endBackgroundTask:tmpID];
}
}];
}
- (void)stop
{
self.playerTimer = nil;
[[UIApplication sharedApplication] endBackgroundTask:self.taskId];
[self.mutePlayer stop];
}
@end
关键在于用定时器每间隔60秒检查剩余的后台任务时间,不足61秒的时候就立即播放无声音乐,然后重新申请后台任务。此方法参照http://my.oschina.net/u/1386081/blog/277380,不过定时器用的是我自己修改了的RNTimer。至于,定时运行也是通过RNTimer这个东西去实现的。
上面的已经把后台定时运行的问题解决了,但是手机也会有重启的时候,每次重启以后都要重新打开应用实在太麻烦了。所以只要在Info.plist
的Required background modes
加上App provides Voice over IP services
,以后每次开机以后,都会重新在后台启动应用了。
报告机制
暂时先使用本地推送,后续打算再加上CocoaLumberjack用作记录。
手机网络情况检测
如果手机离线或者信号不好的时候,还跑Ping去检测服务器也没有任何意义,反而会造成干扰。所以用了AFNetworkReachabilityManager
来做判断,减少了做多余的无用功。
实际使用
使用了几天后,手机的电量榜上也不会出现这个小玩意,说明耗电情况不错。此外,的确能长期在后台运行,不会被系统杀掉。这次实践算是非常成功,既有趣,也解决了问题。
PS
PINKLongTimeBackground和我自己使用的Ping也有打算开源的计划,不过还需要整理一下,其中PINKLongTimeBackground打算支持pod集成。