iOS推送分為遠(yuǎn)程推送 和 本地推送兩種鹿霸,本地推送此文章先不說,之后會有新的文章更新蚀浆。
遠(yuǎn)程推送將一些重要的信息推送到用戶的相關(guān)APP上挎峦,不管你的APP是否運行香追,或者在后臺掛起 或者 完全殺死,都能收到推送坦胶。并且在iOS10 之后透典,蘋果允許在收到推送內(nèi)容后有30s 的時間對推送的內(nèi)容進(jìn)行修改,或者下載一些圖片(這個在文章的后面會提到),推送會展示一個彈框(alert)顿苇,聲音(sound)峭咒,APP icon 上的紅色標(biāo)志(badge)。
遠(yuǎn)程推送的原理
蘋果推送服務(wù)通知是由自己專門的推送服務(wù)器APNS(Apple push Notification service)來完成的纪岁,其過程是apns 接收到我們自己的應(yīng)用服務(wù)器發(fā)出的被推送消息讹语,將這條消息推送到指定的iOS設(shè)備上,然后再由iOS設(shè)備通知到我們的應(yīng)用程序蜂科,我們將會以通知或者聲音的形式受到推送回來的消息。
我們的APP在啟動的時候短条,會攜帶設(shè)備號和應(yīng)用id 想APNs 注冊推送服務(wù)导匣,成功后APNs 會返回一個標(biāo)識devicetoken,然后我們會將收到的devicetoken發(fā)送到我們自己的服務(wù)器茸时,當(dāng)我們需要推送消息時贡定,我們的服務(wù)器會將推送內(nèi)容payLoad(之前是不超過256字節(jié),現(xiàn)在應(yīng)該比較寬松了可都,具體未定缓待,json 格式)和devicetoken發(fā)送給APNs服務(wù)器。APNs服務(wù)器將新消息推送到iOS設(shè)備上(在有網(wǎng)的情況下設(shè)備會與APNs服務(wù)器建立一個長連接tcp)渠牲。
devicetoken 在以下情況下會發(fā)生改變:
1旋炒、同一款設(shè)備上重新安裝同一款應(yīng)用
2、不同設(shè)備上安裝同一款應(yīng)用
3签杈、設(shè)備重新升級了系統(tǒng)瘫镇,同一個APP對應(yīng)的devicetoken也會發(fā)生改變
-
APP注冊推送服務(wù)過程
上述圖片完成了如下流程:
1、安裝了推送功能APP的設(shè)備,攜帶者設(shè)備號和APPid 連接APNs服務(wù)器铣除。
2谚咬、連接成功后,APNs 通過打包和加密等處理生成devicetoken尚粘,返回給注冊的設(shè)備择卦。
3、APP拿到devicetoken后郎嫁,將它發(fā)送給我們自己的服務(wù)器秉继。
4、完成需要被推送的設(shè)備在蘋果服務(wù)器和我們自己服務(wù)器之前的注冊行剂。
-
推送過程
上述圖片完成如下流程:
1秕噪、首先,我們的設(shè)備安裝了具有推送功能的APP后(APP在啟動的時候會在lunch方法里注冊推送)厚宰,在有網(wǎng)絡(luò)的情況下會連接到apns 推送服務(wù)器腌巾,在連接的過程中apns 會解密設(shè)備的devicetoken進(jìn)行驗證,驗證成功后铲觉,建立tcp 連接澈蝙。
2、Provider(我們自己的應(yīng)用服務(wù)器)將 要被推送的消息結(jié)合接收消息的iOS設(shè)備的devicetoken發(fā)送給apns服務(wù)器
3撵幽、apns 收到Provider 發(fā)送過來的推送消息灯荧,解密devicetoken進(jìn)行驗證,驗證成功后將消息發(fā)送給指定的設(shè)備
4盐杂、iOS設(shè)備收到蘋果推送的消息后逗载,通知我們的APP并顯示和提示用戶(alert,sound链烈,badge)厉斟。
比較直觀的流程圖:
信息結(jié)構(gòu)圖:
上圖顯示的這個消息體就是Provider(我們服務(wù)器)發(fā)送給apns服務(wù)器的消息結(jié)構(gòu),APNs 驗證這個結(jié)構(gòu)正確并提取其中的信息后强衡,再將消息推送給指定的iOS設(shè)備擦秽。這個結(jié)構(gòu)體包括五個部分,第一個部分是命令標(biāo)識符漩勤,第二個部分是devicetoken的長度感挥,第三部分是devicetoken字符串,第四部分是推送消息體(payload)的長度越败,最后一部分也就是真正的消息內(nèi)容了触幼,里面包含了推送的基本信息,比如消息內(nèi)容究飞,應(yīng)用icon右上角顯示多少數(shù)字以及推送消息到達(dá)時所播放的聲音等域蜗。
payload的結(jié)構(gòu):
{
“aps”:{
“alert”:“CSDN給您發(fā)送了新消息”,
“badge”:1,
“sound”:“default”
},
}
這其實就是個json結(jié)構(gòu)體巨双,alert標(biāo)簽的內(nèi)容就是會顯示在用戶手機(jī)上的推送信息,badge 顯示的數(shù)量是會在APP icon右上角顯示的數(shù)量霉祸,提示有多少條未讀消息等筑累,sound就是當(dāng)推送信息送達(dá)時手機(jī)播放的聲音,沒有特殊要求就是default 系統(tǒng)默認(rèn)聲音丝蹭。
使用自己服務(wù)器完成推送
1慢宗、iOS端代碼
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/*
注冊推送服務(wù)
申請APP需要接受來自服務(wù)商提供推送消息
launchOptions:保存了app啟動的原因信息,如果app是因為點擊通知欄啟動的奔穿,可以在launchOptions獲取到通知的具體內(nèi)容
*/
//判斷是不是點擊通知欄啟動
NSDictionary *remoteNotification = [launchOptions objectForKey:@"UIApplicationLaunchOptionsRemoteNotificationKey"];
if (remoteNotification != nil) {
// 點擊通知欄消息啟動
self.isLaunchedByNotification = YES;
}else{
// 不是點擊通知欄消息啟動
self.isLaunchedByNotification = NO;
}
//iOS10以后的注冊方法
if ([[UIDevice currentDevice].systemVersion floatValue] >= 10.0) {
//來自UserNotification框架
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
// center.delegate = self;
// 請求授權(quán)
[center requestAuthorizationWithOptions:UNAuthorizationOptionBadge |UNAuthorizationOptionSound | UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {//授權(quán)成功
[center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
NSLog(@"=======%@", settings);
}];
} else {
//點擊不允許镜沽,注冊失敗
}
}];
}
} else if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
[[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert categories:nil]];
} else {
[[UIApplication sharedApplication]registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert];
}
//最后一定要調(diào)用這個方法,不然收不到apns返回的devicetoken
[[UIApplication sharedApplication]registerForRemoteNotifications];
發(fā)起申請后 會有兩個回調(diào)方法贱田,一個是注冊成功缅茉,返回devicetoken,另一個是注冊失敗的回調(diào)方法
//成功
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSLog(@"deviceToken = %@",deviceToken);
//我們會在收到此方法后男摧,將收到的devicetoken 發(fā)送給我們自己的服務(wù)器蔬墩,我們的服務(wù)器在將消息發(fā)送給apns 時會用到,用于apns 的驗證和識別
}
//注冊apns失敗回調(diào)
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
//Optional
HLLog(@"did Fail To Register For Remote Notifications With Error: %@", error);
}
以上是我們本地代碼完成了在apns 和 Provider 之間的注冊耗拓,以下的方法是在apns 成功推送到我們設(shè)備后的回調(diào)方法拇颅。
//iOS10 在前臺收到通知
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
UNNotificationRequest *request = notification.request; //收到推送的請求
UNNotificationContent *content = request.content;//收到推送的消息內(nèi)容
NSDictionary *userInfo = content.userInfo;
NSNumber *badge = content.badge;//推送的角標(biāo)
NSString *body = content.body;//推送消息體
UNNotificationSound *sound = content.sound;//聲音
NSString *subtitle = content.subtitle;//推送消息的副標(biāo)題
NSString *title = content.title;//推送消息的標(biāo)題
if ([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
//UNPushNotificationTrigger 觸發(fā)器,專門用于遠(yuǎn)程推送乔询,其他一般是本地通知要用到的
} else {
//本地通知
}
UNNotificationPresentationOptions options = UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert;
completionHandler(options);
}
//ios10 點擊推送消息
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
UNNotificationRequest *request = response.notification.request;
UNNotificationContent *content = request.content;
NSDictionary *userInfo = content.userInfo;
NSNumber *badge = content.badge;
NSString *body = content.body;
NSString *subtitle = content.subtitle;
NSString *title = content.title;
if ([request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
//遠(yuǎn)程推送
} else {
//本地通知
}
completionHandler();
}
/*
iOS7及以上樟插,推送字段必須包含content-available = 1 并且Background Modes 中勾選Remote notifications,才能調(diào)用此方法.
如果滿足上述條件竿刁,那么收到推送消息時黄锤,應(yīng)用在前臺和后臺不殺死的情況下還有點擊通知欄消息都會調(diào)用此方法,可以在此方法內(nèi)做一些后臺操作食拜,如下載數(shù)據(jù) 更新UI等
*/
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSDictionary *aps = userInfo[@"aps"];
NSString * storeid = aps[@"order_id"];
if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
}else if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground){
HLLog(@"程序在后臺 或從后臺點擊通知欄 運行 該方法");
}else{
HLLog(@"后臺處于前臺的過度");
}
completionHandler(UIBackgroundFetchResultNewData);
}
現(xiàn)在看一下服務(wù)端的代碼猜扮,大致了解一下,這段也是摘自網(wǎng)上的相關(guān)文章的监婶。
在此之前我們是需要將推送證書導(dǎo)出成p12文件交給服務(wù)器端,不會導(dǎo)出的自行百度齿桃。
java端代碼
import javapns.back.PushNotificationManager;
import javapns.back.SSLConnectionHelper;
import javapns.data.Device;
import javapns.data.PayLoad;
public class pushService {
public static void main(String[] args) {
try {
//這個token 就是客戶端從APNs服務(wù)器獲取到的devicetoken
String deviceToken = "eab6df47eb4f81e0aaa93bb208cffd7dc3884fd346ea0743fcf93288018cfcb6";
//被推送的iphone應(yīng)用程序標(biāo)示符
PayLoad payLoad = new PayLoad();
payLoad.addAlert("測試我的push消息");
payLoad.addBadge(1);
payLoad.addSound("default");
PushNotificationManager pushManager = PushNotificationManager.getInstance();
pushManager.addDevice("iphone", deviceToken);
//測試推送服務(wù)器地址:gateway.sandbox.push.apple.com /2195
//產(chǎn)品推送服務(wù)器地址:gateway.push.apple.com / 2195
String host="gateway.sandbox.push.apple.com"; //測試用的蘋果推送服務(wù)器
int port = 2195;
String certificatePath = "/Users/hsw/Desktop/PushTest/PushTest.p12"; //剛才在mac系統(tǒng)下導(dǎo)出的證書
String certificatePassword= "123456";
pushManager.initializeConnection(host, port, certificatePath,certificatePassword, SSLConnectionHelper.KEYSTORE_TYPE_PKCS12);
//Send Push
Device client = pushManager.getDevice("iphone");
pushManager.sendNotification(client, payLoad); //推送消息
pushManager.stopConnection();
pushManager.removeDevice("iphone");
}
catch (Exception e) {
e.printStackTrace();
System.out.println("push faild!");
return;
}
System.out.println("push succeed!");
}
}
遠(yuǎn)程推送涉及到的方法
1惑惶、 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
2短纵、 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo带污;
3、 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler香到;
4鱼冀、 - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler报破;
5、 - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler千绪;
1充易、會在APP啟動完成后調(diào)用,lunchOptions保存了app啟動的原因信息荸型,如果app是因為點擊通知欄啟動的盹靴,可以在lunchOptions 獲取到通知的具體內(nèi)容(上文已經(jīng)提到如何獲取)
2瑞妇、會在接收到通知的時候調(diào)用稿静,在最新的iOS10中已經(jīng)廢棄,建議不再使用辕狰。
3改备、在iOS7之后新增的方法,可以說是2的升級版本蔓倍,如果APP最低支持iOS7的話可以不用添加2悬钳,其中completionHandler這個block可以填寫的參數(shù)UIBackgroundFetchResult是一個枚舉值。主要是用來在后臺狀態(tài)下進(jìn)行一些操作的柬脸,比如請求數(shù)據(jù)他去,操作完成之后,必須通知系統(tǒng)獲取完成倒堕,可供選擇的結(jié)果:
typedef NS_ENUM(NSUInteger, UIBackgroundFetchResult) {
// 獲取到了新數(shù)據(jù)(此時系統(tǒng)將對現(xiàn)在的UI狀態(tài)截圖并更新APP Switcher中你的應(yīng)用截屏)
UIBackgroundFetchResultNewData,
UIBackgroundFetchResultNoData,//沒有新數(shù)據(jù)
UIBackgroundFetchResultFailed//獲取失敗
}
以上操作的前提是已經(jīng)在Background Modes 里面勾選了Remote notifications(推送喚醒)且推送的消息中包含content-available = 1字段灾测。
4、是iOS10新增的 UNUserNotificationCenterDelegate 代理方法垦巴,在ios10的環(huán)境下媳搪,點擊通知欄都會調(diào)用這個方法。
5骤宣、也是iOS10新增的代理方法秦爆,在iOS10 以前,如果應(yīng)用處于前臺狀態(tài)憔披,接收到推送等限,通知欄是不會有任何提示的,如果開發(fā)者需要展示通知芬膝,需要在3方法中提取到通知內(nèi)容展示望门。在iOS10 中,如果開發(fā)者需要前臺展示通知锰霜,可以再在這個方法completionHandler傳入相應(yīng)的參數(shù)筹误。
typedef NS_OPTIONS(NSUInteger, UNNotificationPresentationOptions) {
UNNotificationPresentationOptionBadge = (1 << 0),
UNNotificationPresentationOptionSound = (1 << 1),
UNNotificationPresentationOptionAlert = (1 << 2),
}
- 當(dāng)程序處于關(guān)閉狀態(tài)的時候收到推送消息,點擊應(yīng)用程序圖標(biāo)無法獲取推送消息癣缅,iOS10環(huán)境下厨剪,點擊通知欄會調(diào)用1哄酝,4,非iOS10的情況下 會調(diào)用1祷膳,3
- 當(dāng)程序處于前臺狀態(tài)下收到推送消息陶衅,iOS10的環(huán)境下如果推送的消息包含content-available字段的話,執(zhí)行方法3钾唬,5万哪,否則只執(zhí)行5,非iOS10的情況會執(zhí)行3
- 當(dāng)程序處于后臺收到推送消息抡秆,如果已經(jīng)在Background Modes 里面勾選了Remote notifications(推送喚醒)且推送消息中包含content-available 字段的話奕巍,都會執(zhí)行3,點擊通知欄iOS10 執(zhí)行4儒士,非iOS執(zhí)行3.