app實現遠程推送是每一個App的必需要求,本公司開發(fā)的XX app是混合開發(fā)第练,采用的Ionic3框架摇予,之前集成過第三方推送解決方案擦囊,效果不太理想,現在直接對接Ios官方接口實現推送刨啸,速度也快些。本人毫無Ios基礎,對ios原生代碼這塊甚至搞不清是Swift語音還是Object-C羽圃,特此記錄此次實現步驟。
環(huán)境:Xcode11, 測試機Ios13
基礎知識
工程的AppDelegate.m文件里提供了如下方法:
//當應用程序啟動后抖剿,可能需要進行其他邏輯處理時調用的方法
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
//當程序被喚醒時調用的方法
- (void)applicationDidBecomeActive:(UIApplication *)application;
/// 用戶同意接收通知后朽寞,會調用此程序
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
/// 注冊失敗調用
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error;
//收到推送后調用的方法(iOS 10 及以上)
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler;
//收到推送后調用的方法(iOS 10 以下)
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;
遠程推送的實現原理:
1.打開App時: 發(fā)送UDID和BundleID給APNs加密后返回deviceToken
2.獲取Token后,App調用接口,將用戶身份信息和deviceToken發(fā)給服務器牙躺,服務器記錄
3.當推送消息時, 服務器按照用戶身份信息找到存儲的deviceToken愁憔,將消息和deviToken發(fā)送給APNs
4.蘋果的APNs通過deviceToken, 找到指定設備的指定程序, 并將消息推送給用戶
第一步,配置應用
1打開推送開關
第二步孽拷,注冊推送吨掌,授權
用戶同意后,會調用此程序脓恕,獲取系統的deviceToken膜宋,應把deviceToken傳給服務器保存,此函數會在程序每次啟動時調用(前提是用戶允許通知):
在工程的AppDelegate.m文件中進行代碼編寫炼幔,在App啟動鉤子中注冊推送服務秋茫,代碼如下
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
self.viewController = [[MainViewController alloc] init];
NSLog(@"app 啟動了....");
//消息推送注冊
UNUserNotificationCenter * center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
UNAuthorizationOptions type = UNAuthorizationOptionBadge|UNAuthorizationOptionSound|UNAuthorizationOptionAlert;
[center requestAuthorizationWithOptions:type completionHandler:^(BOOL granted, NSError * _Nullable error) {
NSLog(@"is register");
if (granted) {
NSLog(@"注冊成功");
}else{
NSLog(@"注冊失敗");
}
}];
//注冊遠程通知
[application registerForRemoteNotifications];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
重寫 didFailToRegisterForRemoteNotificationsWithError 方法,打印出錯誤日志;
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(@"注冊推送失斈诵恪:%@",error);
}
重寫 didRegisterForRemoteNotificationsWithDeviceToken 方法肛著,獲取推送token;
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSLog(@"weboey......");
NSLog(@"token:%@", deviceToken);
if (![deviceToken isKindOfClass:[NSData class]]) return;
const unsigned *tokenBytes = [deviceToken bytes];
NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
NSLog(@"deviceToken:%@",hexToken);
AppDelegate *myDelegate = [[UIApplication sharedApplication] delegate];
myDelegate.token = hexToken;
}
收到消息后處理通知
-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{
NSLog(@"收到推送內容");
NSDictionary * userInfo = notification.request.content.userInfo;
if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
//TODO:處理遠程推送內容
NSLog(@"%@", userInfo);
}
completionHandler(UNNotificationPresentationOptionSound|UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionAlert);
}
App被喚醒后清空通知欄消息
- (void)applicationDidBecomeActive:(UIApplication *)application{
NSLog(@"app 被喚醒....");
[[UNUserNotificationCenter currentNotificationCenter] removeAllDeliveredNotifications];
}
角標計數等要求可以后端接口實現,消息通知合并可以按App分類合并跺讯,也可以在接口推送請求頭中設置apns-collapse-id
編碼結束枢贿,上面代碼已能獲取到推送Token,下一步就可以把日志中打印的Token復制下來刀脏,可以進行測試了局荚。
第三步,配置證書
App調用接口,需要進行用戶身份信息驗證:有2種方式創(chuàng)建身份驗證密鑰(p8證書和p12證書)
創(chuàng)建你的APNs keys 或者 創(chuàng)建推送證書愈污,這兩個創(chuàng)建一個即可實現推送耀态。這兩個創(chuàng)建一個即可實現推送。這兩個創(chuàng)建一個即可實現推送暂雹。重要的事情說三遍首装。并且創(chuàng)建證書和你的app注冊推送無關系,也能獲取到推送token杭跪,只進行身份驗證的簿盅,使服務器具有權限給你的app推送消息挥下。(很多人對此有誤區(qū),認為證書不正確無法獲取推送token)
- 證書驗證 (P12格式身份證書)
- 此方式比較麻煩桨醋,分為生產證書和測試證書棚瘟。
- 還需要生成.pem文件才能測試。
- 有效期一年要換一次喜最。
不建議使用證書驗證偎蘸,推薦使用 token驗證方式(即P8格式證書,永久有效瞬内,只可下載一次記得保存好C匝)
- token驗證
證書配置就不寫了,請參考友盟官方文檔: 證書配置指南
https://developer.umeng.com/docs/67966/detail/66748
常見錯誤及解決辦法:
錯誤一:報錯Code=3000 "未找到應用程序的“aps-environment”的授權字符串"
1:如果你的項目中已經添加了 KeychainAccessGroups.plist 文件虫蝶,在此文件中新增一對鍵值
aps-environment:development
2: 如果沒有此文件則章咧,點擊 capalities 中找到 push notifications
此時應該能看到錯誤信息,還有一個 fixissue 的按鈕能真,點擊即可
3:aps-environment:development 這個鍵值對明顯是針對開發(fā)環(huán)境的赁严。如果到線上環(huán)境的時候還用更改為 aps-environment:production
錯誤二:didRegisterForRemoteNotificationsWithDeviceToken不調用 (獲取不到 devicetoken)
注冊推送后會彈出是否同意,用戶點擊同意后此鉤子方法會自動調用粉铐,如果不被調用可參試如下解決:
設置中沒有打開通知開關疼约; (在配置常看蝙泼,是否允許通知)
程序問題:
看didRegisterForRemoteNotificationsWithDeviceToken這個方法程剥,有沒有被重寫。
通知的配置是否正確汤踏。
看 (void)application didFailToRegisterForRemoteNotificationsWithError 的錯誤信息
重寫didFailToRegisterForRemoteNotificationsWithError這個方法织鲸,查看錯誤信息是什么。
- 卸載重裝溪胶、重啟 App昙沦、關機重啟后測試,反正我是這么解決的T乩蟆!2商摇@廖酢!F瞻臁工扎!
- 換別的IOS 13 系統手機測試 (少部分手機會存在取不到 token 的問題,該問題 第三方推送SDK 無法解決衔蹲,大家可以用原生推送集成后測試是否可以取到 token肢娘,取不到就是系統的問題)
- 切換網絡呈础!不要用內網,VPN 之類的可能會導致不允許連接 Apple 服務器
錯誤三: 推送的 deviceToken 獲取到的格式發(fā)生變化
原本可以直接將 NSData 類型的 deviceToken 轉換成 NSString 字符串橱健,然后替換掉多余的符號即可:
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSString *token = [deviceToken description];
for (NSString *symbol in @[@" ", @"<", @">", @"-"]) {
token = [token stringByReplacingOccurrencesOfString:symbol withString:@""];
}
NSLog(@"deviceToken:%@", token);
}
在 iOS 13 中而钞,這種方法已經失效,NSData類型的 deviceToken 轉換成的字符串變成了:
解決方案:需要進行一次數據格式處理拘荡,參考友盟的做法臼节,可以適配新舊系統,代碼如下
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
if (![deviceToken isKindOfClass:[NSData class]]) return;
const unsigned *tokenBytes = [deviceToken bytes];
NSString *hexToken = [NSString stringWithFormat:@"xxxxxxxx",
ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];
NSLog(@"deviceToken:%@", hexToken);
}
測試工具
https://github.com/onmyway133/PushNotifications#how-to-install
https://github.com/node-apn/node-apn
測試代碼:
參考文章
https://www.raywenderlich.com/8164-push-notifications-tutorial-getting-started