写在前面:应用场景很重要
如果你的公司有这样一个硬性的需求:用户将APP退出到后台的时候,当该用户收到推送的时候,不让用户感觉到推送过来了,不想打扰用户,但是还想拿到推送内容做一些事情。
在开发人员看来就是要实现如下功能:通知栏没有文字、没有内容、也没有声音,同时还要能实现客户端退出到后台时,还能执行xcode中的某个特定方法,那么此时就可以用到静默推送。
必须知道个推中这些名词的含义,后面再出现这些名词就不做阐述了
离线:APP在后台运行/APP未启动/APP被杀死。
在线:APP在前台运行。
iOS 个推透传机制
iOS消息推送方式只有两种:
APNs的通知栏消息
个推的透传消息
以下说的是普通推送和静默推送的不同之处:
普通推送:当APP离线时,当有推送下达的时候,走的是APNs,所以手机会有铃声、手机顶部出现横幅/通知栏会收到通知。
APP离线时,相关方法什么时候执行,看下面1-4就知道了:
1.只有点开通知栏中的通知进入APP/点开横幅中的通知进入APP,才会执行方法AAA;不点击不执行。
2.点击应用图标进入APP,一定不会执行方法AAA。
3.APP离线收到推送,点击应用图标进入APP/点开通知栏中的通知/点开横幅中的通知进入APP,会执行个推提供的 方法BBB的离线的透传消息
4.APP在线收到推送,就不走APNs,会直接执行个推提供的 方法BBB的在线的透传消息总结:综合1-4,只要点开APNs发来的通知,那么先执行方法AAA,再执行个推的方法BBB
方法AAA
iOS 10之前,点通知,会调用如下方法。
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
...
做你想做的操作。例如更新UI,跳转界面,文字转语音并读出来,顶部弹框,操作userInfo中的内容。
...
}
iOS 10 及以后版本,点击通知,会调用如下方法
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler{
...
做你想做的操作。例如更新UI,跳转界面,文字转语音并读出来,顶部弹框,操作userInfo中的内容。
...
NSLog(@"didReceiveNotification:%@", response.notification.request.content.userInfo);
// [ GTSdk ]:将收到的APNs信息传给个推统计
[GeTuiSdk handleRemoteNotification:response.notification.request.content.userInfo];
completionHandler();
}
方法BBB
- 离线的透传消息:程序离线收到推送时候,当由离线进入在线的时候会执行(offLine为YES)。离线的时候不会执行该方法。
- 在线的透传消息:程序在线收到推送的时候会执行(offLine为NO)。
接收个推推送的透传消息,就会执行如下代理方法
- (void)GeTuiSdkDidReceivePayloadData:(NSData *)payloadData andTaskId:(NSString *)taskId andMsgId:(NSString *)msgId andOffLine:(BOOL)offLine fromGtAppId:(NSString *)appId {
拿到payloadData并转成字符串,然后做你想做的操作 :更新UI,跳转界面,文字转语音并读出来,顶部弹框
if (!offLine) {// offLine为NO表示在线的透传消息。
}else{// offLine为YES表示离线的透传消息。
}
}
普通推送->服务端格式:
$alertmsg=new DictionaryAlertMsg();// 必须有。 声明DictionaryAlertMsg的对象alertmsg
$alertmsg->body=$msgContent;// 必须有。 为body赋值
$alertmsg->title=SYS_ZH_NAME;// 必须有。 为title赋值
$apn = new IGtAPNPayload();// 必须有。 声明IGtAPNPayload的对象apn
$apn->alertMsg=$alertmsg;// 必须有alertmsg,且alertmsg中一定有title以及和body,因为这就是客户端在通知栏/横幅看到的标题和内容。
$apn->contentAvailable=0;// 必须为0
$apn->sound=$client_notice;// 必须有sound
$apn->badge=1;// 角标,可有可无
$apn->add_customMsg("msg",$msgContent);// msg,可有可无
普通推送->客户端格式:
{
"aps" : {
"sound" : "notice_type1.caf",// 必须有sound
"alert" : { // 必须有。 服务器端一定要有alertmsg
"title" : "一秒招聘",// 必须有
"body" : "有一条新的招工信息,点击查看详情" // 必须有
},
"badge" : 1,// 角标,可有可无
},
"msg" : "有一条新的招工信息,点击查看详情"// msg可有可无
}
静默推送(安安静静的、用户感知不到):当APP离线时,当有推送下达的时候,通知栏/横幅中没有文字,同时也不会发出声音)
- 1.应用被杀死或者应用未启动,不会执行如下的代码。
- 2.应用退出到后台,当收到通知的时候(不点开通知,也不打开APP哦),就会立刻执行如下的代码CCC。 所以静默推送的定义也就出现了:应用收到通知后在后台(background)状态下可以执行下面一段代码CCC,可用于从服务器获取内容更新,做你想做的任何操作(跳转界面,文字转语音并读出声音),所以静默推送不同于其他推送,其他推送不能执行代码CCC。
- 3.APP离线收到推送,点击应用图标进入APP/点开通知栏中的通知/点开横幅中的通知进入APP,会执行个推提供的 方法DDD的离线的透传消息
- 4.APP在线收到推送,就不走APNs,会直接执行个推提供的 方法DDD的在线的透传消息
代码CCC
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
...
做你想做的操作:这里我用苹果自带的文字转语音播放userInfo里面的指定内容
...
}
方法DDD
- 离线的透传消息:程序离线收到推送时候,当由离线进入在线的时候会执行(offLine为YES)。离线的时候不会执行该方法。
- 在线的透传消息:程序在线收到推送的时候会执行(offLine为NO)。
接收个推推送的透传消息,就会执行如下代理方法
- (void)GeTuiSdkDidReceivePayloadData:(NSData *)payloadData andTaskId:(NSString *)taskId andMsgId:(NSString *)msgId andOffLine:(BOOL)offLine fromGtAppId:(NSString *)appId {
拿到payloadData并转成字符串,然后做你想做的操作 :更新UI,跳转界面,文字转语音并读出来,顶部弹框
if (!offLine) {// offLine为NO表示在线的透传消息。
}else{// offLine为YES表示离线的透传消息。
}
}
静默推送->服务端格式:
$apn = new IGtAPNPayload();
$apn->alertMsg="";
$apn->sound="com.gexin.ios.silence";
$apn->contentAvailable=1;
$apn->badge=1;
$apn->add_customMsg("msg",$msgContent);
静默推送->客户端格式:
{
"aps" : {
"content-available" : 1,// 必须为1
"badge" : 1 // 角标,可有可无
// 一定不能有alert,因为alert如果有内容,在客户端的通知栏/横幅上会有通知。
},
"msg" : "有一条新的招工信息,点击查看详情" // 可有可无
}
服务端(我们公司是PHP)配置静默推送的格式如下(非常严格,非常严格,非常严格,一项不满足,就不是静默推送,那就变成了普通的有文字有声音的推送):
1.传入的alertMsg对应的值一定为空或者压根就不传alertMsg字段。
$apn->alertMsg="";
- contentAvailable的值一定为1
$apn->contentAvailable=1;
3.sound对应的值一定为com.gexin.ios.silence.改成其他的字符串的话,应用在后台收到推送时,会听到"铛"的一声。 或者sound对应的值为任意常量也可以实现静音(真机测试过一次,发现确实没有声音。测试次数过少,不是太敢断定,如果有想测试的,可以将sound的值设置成常量试一下)。
$apn->sound="com.gexin.ios.silence";
4.其他的倒无关紧要了,不影响静默推送的格式。
综合1.2.3.4,静默推送,php服务端要设置的核心代码必定是下面的这种格式:
$apn->alertMsg="";// alertMsg一定不要有值
$apn->sound="com.gexin.ios.silence";
$apn->contentAvailable=1;// 一定为1
以我们项目中静默推送的实战演练
关键代码如下:
function IGtTransmissionTemplateDemo($appid,$appkey,$msgContent,$keyType,$keyId,$temp_ietm="",$client_notice="default"){
$msg = array(
'keyType' => $keyType,
'keyId' => $keyId,
'msg' => $msgContent,
'nickname' => $temp_ietm
);
$msg = json_encode($msg);
$template = new IGtTransmissionTemplate();
$template->set_appId($appid);//应用appid
$template->set_appkey($appkey);//应用appkey
$template->set_transmissionType(2);//透传消息类型
$template->set_transmissionContent($msg);//透传内容
$apn = new IGtAPNPayload();
$alertmsg=new DictionaryAlertMsg();
$alertmsg->body=$msgContent;
$alertmsg->actionLocKey="ActionLockey";
$alertmsg->locKey=$msgContent;
$alertmsg->locArgs=array("locargs");
$alertmsg->launchImage="launchimage";
// IOS8.2 支持
$alertmsg->title=SYS_ZH_NAME;
$alertmsg->titleLocKey=SYS_ZH_NAME;
$alertmsg->titleLocArgs=array("TitleLocArg");
// $apn->alertMsg=$alertmsg;
$apn->alertMsg="";
$apn->sound="com.gexin.ios.silence";
$apn->contentAvailable=1;
$apn->badge=1;
$apn->add_customMsg("payload","payload");
$apn->add_customMsg("keyType",$keyType);
$apn->add_customMsg("keyId",$keyId);
$apn->add_customMsg("nickname",$nickname);
$apn->add_customMsg("msg",$msgContent);
$apn->category="ACTIONABLE";
$template->set_apnInfo($apn);
return $template;
}
来来来,一起截图圈重点。
以我们公司做的产品为例:雇主在使用iOS客户端发单的时候,后台监听到雇主成功发单后,会访问个推提供的某个SDK,经过一系列的操作。最终iOS客户端在后台收到推送通知的时候,一定会执行xcode中的如下代码
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
NSLog(@"%@",userInfo);
}
经过真机调试,符合静默推送(因为我的iOS真机设备在后台收到推送时,没有声音,没有文字,并且还执行了一段xcode中的方法,方法就在下面),
以下截图是采用静默推送的方式,iOS客户端在后台收到通知时,执行xcode中的下面的方法,打印的userInfo中的内容。
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler;
// userInfo中的内容
{
"_gurl_" : "sdk.open.extension.getui.com:8123",
"_gmid_" : "OSL-0613_EK270rsK7ZA3frD1xut5B5:6b5a07fe4ef9477caa22c01a93aed697:4f561d975e202fcdb57a47068c5b956b",
"keyId" : "1134",
"aps" : {
"content-available" : 1,
"mutable-content" : 1,
"badge" : 1,
"category" : "ACTIONABLE"
},
"keyType" : "7",
"payload" : "payload",
"msg" : "有一条新的招工信息,点击查看详情",
"_ge_" : "1"
}
PS:既然已经走到了这里,那么我们就可以实现这种恶搞的效果了:
用户已经将App退出到后台,此刻来了一个推送,通知栏没有标题和内容,但是用户能听到一段语音。用户很懵逼,不知道是哪个APP发出的声音。
实现方法:按照上面的一模一样的步骤配置成静默推送。然后配置如下方法,因为下面的这个方法是静默推送必定走的方法(我们在里面偷偷地写上了文字转语音的代码,哈哈哈~~~)
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
// 拿到userInfo中的关键内容,然后用苹果自带的功能进行文字转语音
}
静默推送遇到的坑(静默推送硬生生的做成了普通推送)。
下面就来看看由于我们公司的后台,设置静默推送不规范导致的iOS客户端在后台收到推送时,有声音有文字的情况。本质上变成了普通推送(有声音有文字)。
我们公司的后台说是 设置的是静默推送,但实际上从他给我PHP文件来看,没按照静默推送的格式来。导致客户端在后台时,通知栏会有通知的内容展示,同时也会有声音。
这与静默推送(收到推送,没有声音没有文字)的标准相悖了,导致一直在这个问题上卡了很久。
在此记录下来,以备不时之需,希望有遇到和我一样问题的朋友,少走一些坑。
后台设置的有问题的代码
function IGtTransmissionTemplateDemo($appid,$appkey,$msgContent,$keyType,$keyId,$temp_ietm="",$client_notice="default"){
$msg = array(
'keyType' => $keyType,
'keyId' => $keyId,
'msg' => $msgContent,
'nickname' => $temp_ietm
);
$msg = json_encode($msg);
$template = new IGtTransmissionTemplate();
$template->set_appId($appid);//应用appid
$template->set_appkey($appkey);//应用appkey
$template->set_transmissionType(2);//透传消息类型
$template->set_transmissionContent($msg);//透传内容
$apn = new IGtAPNPayload();
$alertmsg=new DictionaryAlertMsg();
$alertmsg->body=$msgContent;
$alertmsg->actionLocKey="ActionLockey";
$alertmsg->locKey=$msgContent;
$alertmsg->locArgs=array("locargs");
$alertmsg->launchImage="launchimage";
// IOS8.2 支持
$alertmsg->title=SYS_ZH_NAME;
$alertmsg->titleLocKey=SYS_ZH_NAME;
$alertmsg->titleLocArgs=array("TitleLocArg");
$apn->alertMsg=$alertmsg;
$apn->contentAvailable=1;
$apn->sound=$client_notice;
$apn->badge=1;
$apn->add_customMsg("payload","payload");
$apn->add_customMsg("keyType",$keyType);
$apn->add_customMsg("keyId",$keyId);
$apn->add_customMsg("nickname",$nickname);
$apn->add_customMsg("msg",$msgContent);
$apn->category="ACTIONABLE";
$template->set_apnInfo($apn);
return $template;
}
截图展示后台设置的有问题的关键代码
分析静默推送格式不规范会出现哪些问题
以下情况满足的前提条件:②的值为1,即静默推送
①是变量,当①有值时,iOS客户端在后台收到推送时,通知栏会有推送标题和内容的展示。
①是变量,当①没有值时,iOS客户端在后台收到推送时,通知栏没有推送标题和内容的展示。
-
③是变量,当③有值时:
- 如果xcode中放置的音频文件和sound对应的值一样,那么iOS客户端在后台收到推送时,就会自动
读出音频文件的声音。
- 的是的撒的 - 如果xcode中放置的音频文件和sound对应的值不一样,或者xcode中根本没有音频文件,那么iOS客户端在后台收到推送时,就会听见铛的一声。
- 如果xcode中放置的音频文件和sound对应的值一样,那么iOS客户端在后台收到推送时,就会自动
③是变量,当③没有值时,那么iOS客户端在后台收到推送时,就会听见铛的一声。
PS:以下①和③不是变量的情况。当然仍满足②是静默推送
PHP后台代码中,将①设置成空字符串,iOS客户端在后台收到推送时,通知栏没有推送标题和内容的展示
PHP后台代码中,将③设置成非com.gexin.ios.silence的任意字符串或者不和xcode中的音频文件重名的,这时iOS客户端在后台收到推送的时候,就会听见铛的一声。
打印的内容如下: 经过测试iOS客户端在后台收到推送时,会读取xcode中存放的音频文件notice_type1.caf,同时通知栏会展示标题(一秒招聘) 和 内容(有一条新的招工信息,点击查看详情)
{
"_gurl_" : "sdk.open.extension.getui.com:8123",
"_gmid_" : "OSL-0613_WzPFJBFf1BAXhORnt7bRn2:e34f8f8517034efa9773415a8d33190f:ada94b80070b9be823f25f8ea4577b92",
"keyId" : "1131",
"aps" : {
"sound" : "notice_type1.caf",
"content-available" : 1,
"alert" : {
"loc-args" : [
"locargs"
],
"title" : "一秒招聘",
"title-loc-args" : [
"TitleLocArg"
],
"title-loc-key" : "一秒招聘",
"action-loc-key" : "ActionLockey",
"body" : "有一条新的招工信息,点击查看详情",
"loc-key" : "有一条新的招工信息,点击查看详情",
"launch-image" : "launchimage"
},
"mutable-content" : 1,
"category" : "ACTIONABLE",
"badge" : 1
},
"keyType" : "7",
"payload" : "payload",
"msg" : "有一条新的招工信息,点击查看详情",
"_ge_" : "1"
}
以上xcode控制台输出的json数据若要符合静默推送,那么必须做如下修改:对应的让服务端改掉对应的内容即可
注意:本地存放的音频文件一定要放在该工作目录下
PS:当然,如果返回的json数据里面sound的值为"1"或者任意字符串就会出现铛的一声,如果sound的值能和xcode的该路径下的音频文件匹配上,那么iOS客户端在后台收到推送时,会自动读出匹配到的音频。
写到这里我在想,如果公司没有强制说要用户在后台的时候,不许在通知栏中展示内容和标题以及发出声音,那么你可以设置成在展示内容和标题以及会发出声音。此时content-available设置为0和1就没有区别了,因为你只要保证如下条件满足即可:
// alert里面有内容 ; 必须保证有值
$apn->alertMsg=$alertmsg;
// 静默推送 1
$apn->contentAvailable=1;
// sound对应的值是字符串com.gexin.ios.silence或者sound对应的值是常量会静音。
// sound对应的值是常量,比如$apn->sound=1;会听见铛的一声。
// sound对应的值是变量,如果xcode中的音频文件名能和变量对应起来,推送来的时候,会自动检索xcode中的音频文件,检索到就会读出来
$apn->sound=$client_notice;
我们项目中有这么个需求,APP中所有的音频要么采用文字转语音,要么用录制好的音频文件,但是因为推送的内容有变量,比如xxx签到了您的家政服务工作,所以不能用录制好的音频文件。所以需求就这么出现了:
当推送过来的时候(在线、离线),有通知、系统推送来的铛的一声没做要求、 文字转语音。明确规定,要对推送过来的通知内容进行文字转语音,不能用录制好的音频文件,
分析(在线走个推,所以肯定能文字转语音,这里就不说了,以下分析的是离线(APP在后台运行/APP未启动/APP被杀死)的情况),不考虑点击通知进入APP的情况:
-
如果单纯采用标准的静默推送,肯定达不到效果:因为只要符合静默推送的格式,来推送的时候,没有通知也没有声音(铛的一声)。
- 当程序在后台的时候,推送来的时候,还能执行一段方法(可以文字转语音读出来)。效果:通知栏没有通知、铛的一声有无(看后台配置的sound对应的值)、文字转语音。所以不符合。
- 当APP被杀死或者APP未启动的时候,推送来的时候,不会执行这段方法(代码都不走了,肯定没法进行文字转语音了)。效果:通知栏没有通知、铛的一声有无(看后台配置的sound对应的值)、没有文字转语音。所以不符合。
-
如果单纯采用标准的普通推送,肯定达不到效果:
- 当程序离线(APP在后台运行/APP未启动/APP被杀死)的时候,推送来的时候,有通知,有声音(铛的一声,不是文字转语音的声音),由于不能用录制好的音频文件,所以我将xcode里面的音频文件删除了,因此不会读音频文件。普通推送不点击通知,离线不会走xcode项目里面的任何代码,所以没法进行文字转语音。效果:通知栏有通知、铛的一声有无(看后台配置的sound对应的值)、没有文字转语音。所以不符合。
采用普通推送+静默推送混合的方式,APP在后台运行能符合要求,APP未启动/APP被杀死不符合要求:
先说一下普通推送+静默推送混合的后台大致格式,具体参考上面的代码:
// alert里面有内容 。普通推送alert中有内容
$apn->alertMsg=$alertmsg;
// 静默推送 1 之所以设置为1,是为了走xcode里面的某个方法
$apn->contentAvailable=1;
// sound对应的值是字符串com.gexin.ios.silence或者sound对应的值是常量会静音。
// sound对应的值是常量,比如$apn->sound=1;会听见铛的一声。
// sound对应的值是变量,如果xcode中的音频文件名能和变量对应起来,推送来的时候,会自动检索xcode中的音频文件,检索到就会读出来
$apn->sound=1;// 有铛的声音
- 当程序在后台的时候,推送来的时候,有通知,铛的一声有无(看后台配置的sound对应的值),同时还能执行xcode里面的一段方法(该段方法里面写了文字转语音的代码)。效果:通知栏有通知、铛的一声有无(看后台配置的sound对应的值)、文字转语音。所以符合。
- 当APP被杀死或者APP未启动的时候,推送来的时候,通知栏有通知、铛的一声有无(看后台配置的sound对应的值)、没有文字转语音。所以不符合。
所以,针对我们公司的要求,只能采用普通推送+静默推送混合的方式,这种方式APP在后台的时候满足公司的需求,APP被杀死或者APP未启动的时候,不满足需求,这是苹果的机制问题,这是最能契合公司需求的做法了,目前只能这么做,如果有其他方法,朋友们麻烦@一下我哦。
................................