iOS遠(yuǎn)程推送

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ù)過程
    注冊流程圖.png

    上述圖片完成了如下流程:

1、安裝了推送功能APP的設(shè)備,攜帶者設(shè)備號和APPid 連接APNs服務(wù)器铣除。

2谚咬、連接成功后,APNs 通過打包和加密等處理生成devicetoken尚粘,返回給注冊的設(shè)備择卦。

3、APP拿到devicetoken后郎嫁,將它發(fā)送給我們自己的服務(wù)器秉继。

4、完成需要被推送的設(shè)備在蘋果服務(wù)器和我們自己服務(wù)器之前的注冊行剂。

  • 推送過程
    推送過程.png

    上述圖片完成如下流程:

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)厉斟。
比較直觀的流程圖:


過程圖.png

信息結(jié)構(gòu)圖:


圖片來自網(wǎng)絡(luò).png

上圖顯示的這個消息體就是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.
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末的止,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子着撩,更是在濱河造成了極大的恐慌诅福,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拖叙,死亡現(xiàn)場離奇詭異氓润,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)薯鳍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門咖气,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挖滤,你說我怎么就攤上這事崩溪。” “怎么了斩松?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵伶唯,是天一觀的道長。 經(jīng)常有香客問我惧盹,道長乳幸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任钧椰,我火速辦了婚禮粹断,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘演侯。我一直安慰自己,他們只是感情好背亥,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布秒际。 她就那樣靜靜地躺著悬赏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪娄徊。 梳的紋絲不亂的頭發(fā)上闽颇,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音寄锐,去河邊找鬼兵多。 笑死,一個胖子當(dāng)著我的面吹牛橄仆,可吹牛的內(nèi)容都是我干的剩膘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼盆顾,長吁一口氣:“原來是場噩夢啊……” “哼怠褐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起您宪,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤奈懒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后宪巨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體磷杏,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年捏卓,在試婚紗的時候發(fā)現(xiàn)自己被綠了极祸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡天吓,死狀恐怖贿肩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情龄寞,我是刑警寧澤汰规,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站物邑,受9級特大地震影響溜哮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜色解,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一茂嗓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧科阎,春花似錦述吸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽道批。三九已至,卻和暖如春入撒,著一層夾襖步出監(jiān)牢的瞬間隆豹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工茅逮, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留璃赡,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓献雅,卻偏偏與公主長得像碉考,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子惩琉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容