一般来说, IOS很少给App后台运行的权限. 仅有的方式就是 VoIP. IOS少有的为VoIP应用提供了后台socket连接,定期唤醒并且随开机启动的权限.而这些就是IOS上实现VoIP App的关键. 苹果官方文档对于的描述就短短的一页(点击这里),很多细节没有提及. 这篇微博通过具体实现和查阅资料,补充了这些细节.并且列举出了在实现过程中可能遇到的问题, 作为参考.


  • 博客: http://www.cnblogs.com/jhzhu
  • 邮箱: jhzhuustc@gmail.com
  • 作者: 知明所以
  • 时间: 2013-11-10

官方文档描述如是:

PS:此节纯用来占座.如果你你E文不好或者想直接切入正题, 请看下一标题.

There are several requirements for implementing a VoIP app:

  1. Add the UIBackgroundModes key to your app’s Info.plist file. Set the value of this key to an array that includes the voip string.
  2. Configure one of the app’s sockets for VoIP usage.
  3. Before moving to the background, call the setKeepAliveTimeout:handler: method to install a handler to be executed periodically. Your app can use this handler to maintain its service connection.
  4. Configure your audio session to handle transitions to and from active use.
  5. To ensure a better user experience on iPhone, use the Core Telephony framework to adjust your behavior in relation to cell-based phone calls; see Core Telephony Framework Reference.
  6. To ensure good performance for your VoIP app, use the System Configuration framework to detect network changes and allow your app to sleep as much as possible.

我的翻译:

关于IOS为VoIP应用提供的特殊权限和实现方法,我的描述如下. 我尽可能的涉及到voip实现的各种细节, 这样你能对这个运作机制有一个更好的理解,我觉得这远比单单贴几行代码有意义. 因为一个开发者在实际实现过程中遇到的千难险阻很少会体现在最终代码上, 就如你永远不知道台上的角儿在台下的挫折.

  1. IOS允许App的一个Socket在App切换到后台后仍然保持连接. 这样,当有通话请求的时候,App能及时处理. 这个socket需要在应用第一次启动的时候创建, 并标记为"此socket用于VoIP服务". 这样当App切换到后台的时候,IOS会接管这个标记为"用于VoIP服务"的socket. 这个socket的响应函数(比如,一个delegate,或者是个block)会正常的响应, 就像App还在前台一样.
  2. 10s魔咒. 当socket有任何数据从服务端传来, 你在app里为socket写的响应函数都会做处理.但是, 你只有最多10s的时间来干你想干的事情. 也就意味着你在响应函数里新建一个大于10s的timer是没有意义的. 并且IOS并不保证给你足够10s的时间,视系统情况而定.
  3. socket的响应函数里, 你能通过NSLocalNotification来通知用户"电话来了". 除此之外, 你没法做其他任何视觉上的动作来提醒用户, 因为你的app还处于某个不知道的次元, 甚至连window都还没创建.
  4. 你永远也没有办法知道或者决定NSLocalNotification的样式是banner还是alert. 你也许钟爱后者, 但是决定权在用户手里.
  5. 允许在后台定期执行一段代码. 你可以设定一个大于等于10分钟的时间t, 和一个定期执行的handler, IOS系统会在每次经过t时间的时候调用一次这个handler. 但是IOS不保证这个handler会准时运行, 只保证在时间t范围内的某个点会执行一次.
  6. 我们通常用楼上的handler处理socket的断线重连操作. 因为网络不稳定, 或者用户开启飞行模式等原因, 我们用于voip服务的socket会断开连接. 在这个handler里,如果发现连接断开,我们只需要跟条目1一样的创建socket,设置一样的socket响应函数,一切又会恢复正常.
  7. 不建议这个handler做太多事情, 因为它也有10s魔咒.(据不完全统计,苹果所有的后台处理都有这个10s限制. 不保证绝对正确哈, 仅供参考)
  8. 自启服务. 当IOS重新启动, 或者你的app因为其他原因退出时, IOS会马上启动你注册为voip的app, 你可以很迅速的恢复socket连接. 但是, 从底部多任务栏中手动关闭应用除外.更"code"的说明是:当程序退出的exitcode != 0,IOS会重启你的app.经试验发现,从底部多任务栏关闭的时候,程序的exitcode == 0.
  9. 如果你亲爱的用户是一个典型的"app终结者",那么你还剩最后一条路来通知来电提醒:NSRemoteNotification. 你也许会被NSRemoteNotification的可靠性和实时性折腾的抓狂, 但是, 谁让你选了IOS? 你享受了封闭带来的传世体验, 也得承受封闭的限制.
  10. 当条目8描述的情况发生之后,app的application:didFinishLaunchingWithOptions:会被调用. 但是,请时刻提醒自己我们的app仍然处于后台. 我们以前总在这里创建window创建rootController, 但现在不必了. 现在我们需要的就是把可爱的socket连上, 像在条目1里一样,让一切回归正常(我不去写歌词真浪费了^_^).
  11. application:didFinishLaunchingWithOptions:里你能通过[application applicationState] == UIApplicationStateBackground来判断是正常启动应用还是系统自动启动, 然后决定该创建window还是创建voip的socket.

非越狱情况下实现:

开机启动:App安装到IOS设备设备之后,无论App是否开启过,只要IOS设备重启,App就会随之启动;

无限后台运行:应用进入后台状态,可以无限后台运行,不被系统kill;

监听进程:可获IOS设备运行除系统外的App(包括正在运行和后台运行);

配置项目 plist文件

添加:

<key>UIBackgroundModes</key>

<array>

<string>voip</string>

</array>

功能类:ProccessHelper

  1. #import <Foundation/Foundation.h>
  2. @interface ProccessHelper : NSObject
  3. + (NSArray *)runningProcesses;
  4. @end
  5. [cpp] view plaincopyprint?
  6. #import "ProccessHelper.h"
  7. //#include<objc/runtime.h>
  8. #include <sys/sysctl.h>
  9. #include <stdbool.h>
  10. #include <sys/types.h>
  11. #include <unistd.h>
  12. #include <sys/sysctl.h>
  13. @implementation ProccessHelper
  14. //You can determine if your app is being run under the debugger with the following code from
  15. static bool AmIBeingDebugged(void)
  16. // Returns true if the current process is being debugged (either
  17. // running under the debugger or has a debugger attached post facto).
  18. {
  19. int                 junk;
  20. int                 mib[4];
  21. struct kinfo_proc   info;
  22. size_t              size;
  23. // Initialize the flags so that, if sysctl fails for some bizarre
  24. // reason, we get a predictable result.
  25. info.kp_proc.p_flag = 0;
  26. // Initialize mib, which tells sysctl the info we want, in this case
  27. // we're looking for information about a specific process ID.
  28. mib[0] = CTL_KERN;
  29. mib[1] = KERN_PROC;
  30. mib[2] = KERN_PROC_PID;
  31. mib[3] = getpid();
  32. // Call sysctl.
  33. size = sizeof(info);
  34. junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
  35. assert(junk == 0);
  36. // We're being debugged if the P_TRACED flag is set.
  37. return ( (info.kp_proc.p_flag & P_TRACED) != 0 );
  38. }
  39. //返回所有正在运行的进程的 id,name,占用cpu,运行时间
  40. //使用函数int   sysctl(int *, u_int, void *, size_t *, void *, size_t)
  41. + (NSArray *)runningProcesses
  42. {
  43. //指定名字参数,按照顺序第一个元素指定本请求定向到内核的哪个子系统,第二个及其后元素依次细化指定该系统的某个部分。
  44. //CTL_KERN,KERN_PROC,KERN_PROC_ALL 正在运行的所有进程
  45. int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL ,0};
  46. size_t miblen = 4;
  47. //值-结果参数:函数被调用时,size指向的值指定该缓冲区的大小;函数返回时,该值给出内核存放在该缓冲区中的数据量
  48. //如果这个缓冲不够大,函数就返回ENOMEM错误
  49. size_t size;
  50. //返回0,成功;返回-1,失败
  51. int st = sysctl(mib, miblen, NULL, &size, NULL, 0);
  52. struct kinfo_proc * process = NULL;
  53. struct kinfo_proc * newprocess = NULL;
  54. do
  55. {
  56. size += size / 10;
  57. newprocess = realloc(process, size);
  58. if (!newprocess)
  59. {
  60. if (process)
  61. {
  62. free(process);
  63. process = NULL;
  64. }
  65. return nil;
  66. }
  67. process = newprocess;
  68. st = sysctl(mib, miblen, process, &size, NULL, 0);
  69. } while (st == -1 && errno == ENOMEM);
  70. if (st == 0)
  71. {
  72. if (size % sizeof(struct kinfo_proc) == 0)
  73. {
  74. int nprocess = size / sizeof(struct kinfo_proc);
  75. if (nprocess)
  76. {
  77. NSMutableArray * array = [[NSMutableArray alloc] init];
  78. for (int i = nprocess - 1; i >= 0; i--)
  79. {
  80. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  81. NSString * processID = [[NSString alloc] initWithFormat:@"%d", process[i].kp_proc.p_pid];
  82. NSString * processName = [[NSString alloc] initWithFormat:@"%s", process[i].kp_proc.p_comm];
  83. NSString * proc_CPU = [[NSString alloc] initWithFormat:@"%d", process[i].kp_proc.p_estcpu];
  84. double t = [[NSDate date] timeIntervalSince1970] - process[i].kp_proc.p_un.__p_starttime.tv_sec;
  85. NSString * proc_useTiem = [[NSString alloc] initWithFormat:@"%f",t];
  86. NSString *startTime = [[NSString alloc] initWithFormat:@"%ld", process[i].kp_proc.p_un.__p_starttime.tv_sec];
  87. NSString * status = [[NSString alloc] initWithFormat:@"%d",process[i].kp_proc.p_flag];
  88. NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
  89. [dic setValue:processID forKey:@"ProcessID"];
  90. [dic setValue:processName forKey:@"ProcessName"];
  91. [dic setValue:proc_CPU forKey:@"ProcessCPU"];
  92. [dic setValue:proc_useTiem forKey:@"ProcessUseTime"];
  93. [dic setValue:proc_useTiem forKey:@"ProcessUseTime"];
  94. [dic setValue:startTime forKey:@"startTime"];
  95. // 18432 is the currently running application
  96. // 16384 is background
  97. [dic setValue:status forKey:@"status"];
  98. [processID release];
  99. [processName release];
  100. [proc_CPU release];
  101. [proc_useTiem release];
  102. [array addObject:dic];
  103. [startTime release];
  104. [status release];
  105. [dic release];
  106. [pool release];
  107. }
  108. free(process);
  109. process = NULL;
  110. //NSLog(@"array = %@",array);
  111. return array;
  112. }
  113. }
  114. }
  115. return nil;
  116. }
  117. @end

实现代码:

  1. systemprocessArray = [[NSMutableArray arrayWithObjects:
  2. @"kernel_task",
  3. @"launchd",
  4. @"UserEventAgent",
  5. @"wifid",
  6. @"syslogd",
  7. @"powerd",
  8. @"lockdownd",
  9. @"mediaserverd",
  10. @"mediaremoted",
  11. @"mDNSResponder",
  12. @"locationd",
  13. @"imagent",
  14. @"iapd",
  15. @"fseventsd",
  16. @"fairplayd.N81",
  17. @"configd",
  18. @"apsd",
  19. @"aggregated",
  20. @"SpringBoard",
  21. @"CommCenterClassi",
  22. @"BTServer",
  23. @"notifyd",
  24. @"MobilePhone",
  25. @"ptpd",
  26. @"afcd",
  27. @"notification_pro",
  28. @"notification_pro",
  29. @"syslog_relay",
  30. @"notification_pro",
  31. @"springboardservi",
  32. @"atc",
  33. @"sandboxd",
  34. @"networkd",
  35. @"lsd",
  36. @"securityd",
  37. @"lockbot",
  38. @"installd",
  39. @"debugserver",
  40. @"amfid",
  41. @"AppleIDAuthAgent",
  42. @"BootLaunch",
  43. @"MobileMail",
  44. @"BlueTool",
  45. nil nil] retain];
    1. - (void)applicationDidEnterBackground:(UIApplication *)application
    2. {
    3. while (1) {
    4. sleep(5);
    5. [self postMsg];
    6. }
    7. [cpp] view plaincopyprint?
    8. [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{
    9. NSLog(@"KeepAlive");
    10. }];
    11. }
    12. - (void)applicationWillResignActive:(UIApplication *)application
    13. {
    14. }
    15. - (void)applicationWillEnterForeground:(UIApplication *)application
    16. {
    17. }
    18. - (void)applicationDidBecomeActive:(UIApplication *)application
    19. {
    20. }
    21. - (void)applicationWillTerminate:(UIApplication *)application
    22. {
    23. }
    24. #pragma mark -
    25. #pragma mark - User Method
    26. - (void) postMsg
    27. {
    28. //上传到服务器
    29. NSURL *url = [self getURL];
    30. NSURLRequest *request = [[NSURLRequest alloc]initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
    31. NSError *error = nil;
    32. NSData *received = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error];
    33. if (error) {
    34. NSLog(@"error:%@", [error localizedDescription]);
    35. }
    36. NSString *str = [[NSString alloc]initWithData:received encoding:NSUTF8StringEncoding];
    37. NSLog(@"%@",str);
    38. }
    39. - (NSURL *) getURL
    40. {
    41. UIDevice *device = [UIDevice currentDevice];
    42. NSString* uuid = @"TESTUUID";
    43. NSString* manufacturer = @"apple";
    44. NSString* model = [device model];
    45. NSString* mobile = [device systemVersion];
    46. NSString *msg = [NSString stringWithFormat:@"Msg:%@  Time:%@", [self processMsg], [self getTime]];
    47. CFShow(msg);
    48. /  省略部分代码  /
    49. NSString *urlStr = [strUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    50. NSURL *url = [NSURL URLWithString:urlStr];
    51. return url;
    52. }
    53. - (BOOL) checkSystemProccess:(NSString *) proName
    54. {
    55. if ([systemprocessArray containsObject:proName]) {
    56. return YES;
    57. }
    58. return NO;
    59. }
    60. - (BOOL) checkFirst:(NSString *) string
    61. {
    62. NSString *str = [string substringToIndex:1];
    63. NSRange r = [@"ABCDEFGHIJKLMNOPQRSTUVWXWZ" rangeOfString:str];
    64. if (r.length > 0) {
    65. return YES;
    66. }
    67. return NO;
    68. }
    69. - (NSString *) processMsg
    70. {
    71. NSArray *proMsg = [ProccessHelper runningProcesses];
    72. if (proMsg == nil) {
    73. return nil;
    74. }
    75. NSMutableArray *proState = [NSMutableArray array];
    76. for (NSDictionary *dic in proMsg) {
    77. NSString *proName = [dic objectForKey:@"ProcessName"];
    78. if (![self checkSystemProccess:proName] && [self checkFirst:proName]) {
    79. NSString *proID = [dic objectForKey:@"ProcessID"];
    80. NSString *proStartTime = [dic objectForKey:@"startTime"];
    81. if ([[dic objectForKey:@"status"] isEqualToString:@"18432"]) {
    82. NSString *msg = [NSString stringWithFormat:@"ProcessName:%@ - ProcessID:%@ - StartTime:%@ Running:YES", proName, proID, proStartTime];
    83. [proState addObject:msg];
    84. } else {
    85. NSString *msg = [NSString stringWithFormat:@"ProcessName:%@ - ProcessID:%@ - StartTime:%@ Running:NO", proName, proID, proStartTime];
    86. [proState addObject:msg];
    87. }
    88. }
    89. }
    90. NSString *msg = [proState componentsJoinedByString:@"______"];
    91. return msg;
    92. }
    93. // 获取时间
    94. - (NSString *) getTime
    95. {
    96. NSDateFormatter *formatter =[[[NSDateFormatter alloc] init] autorelease];
    97. formatter.dateStyle = NSDateFormatterMediumStyle;
    98. formatter.timeStyle = NSDateFormatterMediumStyle;
    99. formatter.locale = [NSLocale currentLocale];
    100. NSDate *date = [NSDate date];
    101. [formatter setTimeStyle:NSDateFormatterMediumStyle];
    102. NSCalendar *calendar = [[[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar] autorelease];
    103. NSDateComponents *comps = [[[NSDateComponents alloc] init] autorelease];
    104. NSInteger unitFlags = NSYearCalendarUnit |
    105. NSMonthCalendarUnit |
    106. NSDayCalendarUnit |
    107. NSWeekdayCalendarUnit |
    108. NSHourCalendarUnit |
    109. NSMinuteCalendarUnit |
    110. NSSecondCalendarUnit;
    111. comps = [calendar components:unitFlags fromDate:date];
    112. int year = [comps year];
    113. int month = [comps month];
    114. int day = [comps day];
    115. int hour = [comps hour];
    116. int min = [comps minute];
    117. int sec = [comps second];
    118. NSString *time = [NSString stringWithFormat:@"%d-%d-%d %d:%d:%d", year, month, day, hour, min, sec];
    119. return time;
    120. }
    121. @end

IOS高级开发~开机启动&无限后台运行&监听进程的更多相关文章

  1. IOS开发~开机启动&无限后台运行&监听进程

    非越狱情况下实现: 开机启动:App安装到IOS设备设备之后,无论App是否开启过,只要IOS设备重启,App就会随之启动: 无限后台运行:应用进入后台状态,可以无限后台运行,不被系统kill: 监听 ...

  2. 安卓开机启动service后台运行

    安卓开机启动service后台运行 Android开机启动时会发送一个广播android.intent.action.BOOT_COMPLETED,捕捉到这个广播,然后可以进行相应的操作,比如:通过捕 ...

  3. iOS 11开发教程(三)运行第一个iOS 11程序

    iOS 11开发教程(三)运行第一个iOS 11程序 运行iOS11程序 创建好项目之后,就可以运行这个项目中的程序了.单击运行按钮,如果程序没有任何问题的话,会看到如图1.6和1.7的运行效果. 图 ...

  4. (转发)IOS高级开发~Runtime(四)

    用C代替OC: #import <objc/runtime.h> #import <objc/message.h> #import <stdio.h> extern ...

  5. (转发)IOS高级开发~Runtime(三)

    11.系统类的方法实现部分替换 - (void) methodExchange { Method m1 = class_getInstanceMethod([NSStringclass],@selec ...

  6. (转发)IOS高级开发~Runtime(二)

    一些公用类: @interface ClassCustomClass :NSObject{ NSString *varTest1; NSString *varTest2; NSString *varT ...

  7. (转发)IOS高级开发~Runtime(一)

    IOS高级开发-Runtime(一) IOS高级开发-Runtime(二) IOS高级开发-Runtime(三) IOS高级开发-Runtime(四) 一些公用类: @interface Custom ...

  8. Linux下java nohup 后台运行关闭后进程停止的原因,不挂断后台运行命令

    Linux下java nohup 后台运行关闭后进程停止的原因,不挂断后台运行命令 今天写sh脚本发现一终止命令程序就停止运行了,检查了很久才发现后面少了个&字符导致的!错误写法:nohup ...

  9. Android实训案例(六)——四大组件之一BroadcastReceiver的基本使用,拨号,短信,SD卡,开机,应用安装卸载监听

    Android实训案例(六)--四大组件之一BroadcastReceiver的基本使用,拨号,短信,SD卡,开机,应用安装卸载监听 Android中四大组件的使用时重中之重,我这个阶段也不奢望能把他 ...

随机推荐

  1. 子窗口url调整导致父窗口刷新

    2014年3月19日 10:22:38 如题: 在弹窗里搜索时,url发生改变,导致父窗口的div消失.为何? 之前的逻辑是隐藏div 现在修改为插入节点 .可是还是刷新字窗口后,父窗口里面的div节 ...

  2. 使用原始XML资源——使用原始XML文件

    下面为示例程序添加一个原始的XML文件,将该XML文件放到/res/xml目录下,该XML文件的内容很简单.XML资源的内容如下. 程序清单:  \res\xml\books.xml文件 <?x ...

  3. MySQL 替换部分电话号码为000

    要做敏感信息剔除,要求又不能全换成同一个号码影响测试,想了几个方法,最终采用替换部分电话号码为000来做到敏感信息覆盖. mysql>update phone setb=replace(b,su ...

  4. ImageView及其子类(一)

    ImageView继承自View组件,它的主要功能是用于显示图片——实际上这个书法不太严谨因为他能显示的不仅是图片,任何Drawable对象都可以使用ImageView来显示.除此之外,ImageVi ...

  5. quick-cocos2d-x添加到Pomelo的支持

    https://github.com/luoxinliang/pomelo_quick_x/tree/master/pomelo_quick_x

  6. 用SWF来代替传统的帧动画

    一般的帧动画是有两大缺点: 1.资源浪费,包大 2.很难实现平滑过渡 特别对于GIF,还会存在噪点问题,但是SWF利用自身的优势,不仅有现成的编辑器,而且还有矢量动画,补间动画等,大大 降低了资源的大 ...

  7. 去除 MyEclipse updating index

    去除 MyEclipse updating index http://zhidao.baidu.com/link?url=OfHjTTxnNRoijnsaweBl3K3UTlnlFGdtHEQIvEW ...

  8. Activity生命周期完全解析

    **转载请注明出处:http://www.cnblogs.com/landptf/p/6309108.html** 生命周期是个老生常谈的问题了,今天做个汇总,全当是记个笔记,以后查找起来方便一些.下 ...

  9. 使用SpringMvc调用POI jar导出excel的源码

    @RequestMapping(value = "/result/export") public String export(ResultIn in,HttpServletRequ ...

  10. 详解googe Chrome浏览器(理论篇)

    开篇概述 1详解google Chrome浏览器,这个标题似乎抽象了一些,我想应该把它拆分成如下几个问题,也许会更加理解一些. 问题1:目前开发中,主选浏览器有Google Chrome,IE,Fir ...