通知與消息機(jī)制_遠(yuǎn)程推送(推送周知)

和本地通知不同,推送通知(遠(yuǎn)程推送)是由應(yīng)用服務(wù)提供商發(fā)起的,通過蘋果的APNs(Apple Push Notification Server)發(fā)送到應(yīng)用客戶端

屏幕快照 2016-03-26 下午8.43.33.png
  • Provider:就是為指定iOS設(shè)備應(yīng)用程序提供Push的服務(wù)器,(如果iOS設(shè)備的應(yīng)用程序是客戶端的話,那么Provider可以理解為服務(wù)端[消息的發(fā)起者]);

  • APNS:Apple Push Notification Service[蘋果消息推送服務(wù)器];

  • iPhone:用來接收APNS下發(fā)下來的消息;

  • Client App:iOS設(shè)備上的應(yīng)用程序,用來接收iphone傳遞APNS下發(fā)的消息到制定的一個(gè)客戶端 app[消息的最終響應(yīng)者];

推送通知可以分為三個(gè)階段

階段一:Provider[服務(wù)端]把要發(fā)送的消息,目的iOS設(shè)備標(biāo)識打包,發(fā)送給APNS;

階段二:APNS在自身的已注冊Push服務(wù)的iOS設(shè)備列表中,查找有相應(yīng)標(biāo)識的iOS設(shè)備,并將消息發(fā)送到iOS設(shè)備;

階段三:iOS設(shè)備把發(fā)送的消息傳遞給對應(yīng)的應(yīng)用程序,并且按照設(shè)定彈出Push通知。

具體過程,如下圖

屏幕快照 2016-03-26 下午8.43.39.png

1、[Client App]注冊消息推送;

  • 只有注冊過的應(yīng)用才有可能接收到消息,程序中通常通過UIApplication的registerUserNotificationSettings:方法注冊,iOS8中通知注冊的方法發(fā)生了改變,如果是iOS7及之前版本的iOS請參考其他代碼。

  • 注冊之前有兩個(gè)前提條件必須準(zhǔn)備好:開發(fā)配置文件(provisioning profile,也就是.mobileprovision后綴的文件)的App ID不能使用通配ID必須使用指定APP ID并且生成配置文件中選擇Push Notifications服務(wù),一般的開發(fā)配置文件無法完成注冊;應(yīng)用程序的Bundle Identifier必須和生成配置文件使用的APP ID完全一致。

2、[Client App]跟[APNS Service]要deviceToken, Client App接收到APNs 分配的deviceToken

  • 在UIApplication的-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken代理方法中獲取令牌,此方法發(fā)生在注冊之后。

  • 如果無法正確獲得device token可以在UIApplication的-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error代理方法中查看詳細(xì)錯(cuò)誤信息,此方法發(fā)生在獲取device token失敗之后。

  • 必須真機(jī)調(diào)試,模擬器無法獲取device token。

3、[Client App]將deviceToken發(fā)送給[Provider]Push服務(wù)端程序,告訴服務(wù)器端當(dāng)前設(shè)備允許接收消息。

  • device token的生成算法只有Apple掌握,為了確保算法發(fā)生變化后仍然能夠正常接收服務(wù)器端發(fā)送的通知,每次應(yīng)用程序啟動都重新獲得device token(device token的獲取不會造成性能問題,蘋果官方已經(jīng)做過優(yōu)化)。

  • 通??梢詣?chuàng)建一個(gè)網(wǎng)絡(luò)連接發(fā)送給應(yīng)用程序提供商的服務(wù)器端, 在這個(gè)過程中最好將上一次獲得的device token存儲起來,避免重復(fù)發(fā)送,一旦發(fā)現(xiàn)device token發(fā)生了變化最好將原有的device token一塊發(fā)送給服務(wù)器端,服務(wù)器端刪除原有令牌存儲新令牌避免服務(wù)器端發(fā)送無效消息。

4、當(dāng)Push服務(wù)端程序滿足發(fā)送消息條件了,[Provider]向[APNS Service]發(fā)送消息;

  • 發(fā)送時(shí)指定device token和消息內(nèi)容,并且完全按照蘋果官方的消息格式組織消息內(nèi)容,通常情況下可以借助其他第三方消息推送框架來完成。

5、[APNS Service]根據(jù)消息中的device token查找已注冊的設(shè)備推送消息,將消息發(fā)送給[Client App].

  • 正常情況下可以根據(jù)device token將消息成功推送到客戶端設(shè)備中,但是也不排除用戶卸載程序的情況,此時(shí)推送消息失敗,APNs會將這個(gè)錯(cuò)誤消息通知服務(wù)器端以避免資源浪費(fèi)(服務(wù)器端此時(shí)可以根據(jù)錯(cuò)誤刪除已經(jīng)存儲的device token,下次不再發(fā)送)

推送通知的流程代碼:

//
//  AppDelegate.m
//  pushnotification
//

#import "AppDelegate.h"
#import "LTYViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate

#pragma mark - 應(yīng)用程序代理方法
#pragma mark 應(yīng)用程序啟動之后
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    _window=[[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    
    _window.backgroundColor =[UIColor colorWithRed:249/255.0 green:249/255.0 blue:249/255.0 alpha:1];
    
    //設(shè)置全局導(dǎo)航條風(fēng)格和顏色
    [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1]];
    [[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];
    
    LTYViewController *mainController=[[LTYViewController alloc]init];
    _window.rootViewController=mainController;
    
    [_window makeKeyAndVisible];
    
    //注冊推送通知(注意iOS8注冊方法發(fā)生了變化)
    [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil]];
    [application registerForRemoteNotifications];
    
    return YES;
}
#pragma mark 注冊推送通知之后
//在此接收設(shè)備令牌
-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
    [self addDeviceToken:deviceToken];
    NSLog(@"device token:%@",deviceToken);
}

#pragma mark 獲取device token失敗后
-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
    NSLog(@"didFailToRegisterForRemoteNotificationsWithError:%@",error.localizedDescription);
    [self addDeviceToken:nil];
}

#pragma mark 接收到推送通知之后
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
    NSLog(@"receiveRemoteNotification,userInfo is %@",userInfo);
}

#pragma mark - 私有方法
/**
 *  添加設(shè)備令牌到服務(wù)器端
 *
 *  @param deviceToken 設(shè)備令牌
 */
-(void)addDeviceToken:(NSData *)deviceToken{
    NSString *key=@"DeviceToken";
    NSData *oldToken= [[NSUserDefaults standardUserDefaults] objectForKey:key];
    //如果偏好設(shè)置中的已存儲設(shè)備令牌和新獲取的令牌不同則存儲新令牌并且發(fā)送給服務(wù)器端
    if (![oldToken isEqualToData:deviceToken]) {
        [[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:key];
        [self sendDeviceTokenWidthOldDeviceToken:oldToken newDeviceToken:deviceToken];
    }
}

-(void)sendDeviceTokenWidthOldDeviceToken:(NSData *)oldToken newDeviceToken:(NSData *)newToken{
    //注意一定確保真機(jī)可以正常訪問下面的地址
    NSString *urlStr=@"http://192.168.1.101/RegisterDeviceToken.aspx";
    urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url=[NSURL URLWithString:urlStr];
    NSMutableURLRequest *requestM=[NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:10.0];
    [requestM setHTTPMethod:@"POST"];
    NSString *bodyStr=[NSString stringWithFormat:@"oldToken=%@&newToken=%@",oldToken,newToken];
    NSData *body=[bodyStr dataUsingEncoding:NSUTF8StringEncoding];
    [requestM setHTTPBody:body];
    NSURLSession *session=[NSURLSession sharedSession];
    NSURLSessionDataTask *dataTask= [session dataTaskWithRequest:requestM completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            NSLog(@"Send failure,error is :%@",error.localizedDescription);
        }else{
            NSLog(@"Send Success!");
        }
        
    }];
    [dataTask resume];
}
@end
  • iOS客戶端代碼的代碼比較簡單,注冊推送通知,獲取device token存儲到偏好設(shè)置中,并且如果新獲取的device token不同于偏好設(shè)置中存儲的數(shù)據(jù)則發(fā)送給服務(wù)器端,更新服務(wù)器端device token列表。
  • 由于device token需要發(fā)送給服務(wù)器端,這里使用一個(gè)Web應(yīng)用作為服務(wù)器端接收device token,這里使用了ASP.NET程序來處理令牌接收注冊工作,當(dāng)然你使用其他技術(shù)同樣沒有問題。下面是對應(yīng)的后臺代碼:
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using CMJ.Framework.Data;

namespace WebServer
{
    public partial class RegisterDeviceToken : System.Web.UI.Page
    {
        private string _appID = @"com.cmjstudio.pushnotification";
        private SqlHelper _helper = new SqlHelper();
        protected void Page_Load(object sender, EventArgs e)
        {
            try
            {
                string oldToken = Request["oldToken"] + "";
                string newToken = Request["newToken"] + "";
                string sql = "";
                //如果傳遞舊的設(shè)備令牌則刪除舊令牌添加新令牌
                if (oldToken != "")
                {
                    sql = string.Format("DELETE FROM dbo.Device WHERE AppID='{0}' AND DeviceToken='{1}';", _appID, oldToken);
                }
                sql += string.Format(@"IF NOT EXISTS (SELECT ID FROM dbo.Device WHERE AppID='{0}' AND DeviceToken='{1}')
                                        INSERT INTO dbo.Device ( AppID, DeviceToken ) VALUES ( N'{0}', N'{1}');", _appID, newToken);
                _helper.ExecuteNonQuery(sql);
                Response.Write("注冊成功!");
            }
            catch(Exception ex)
            {
                Response.Write("注冊失敗,錯(cuò)誤詳情:"+ex.ToString());
            }
        }
    }
}

這個(gè)過程主要就是保存device token到數(shù)據(jù)庫中,當(dāng)然如果同時(shí)傳遞舊的設(shè)備令牌還需要先刪除就的設(shè)備令牌,這里簡單的在數(shù)據(jù)庫中創(chuàng)建了一張Device表來保存設(shè)備令牌,其中記錄了應(yīng)用程序Id和設(shè)備令牌。

  • 第三步就是服務(wù)器端發(fā)送消息,如果要給APNs發(fā)送消息就必須按照Apple的標(biāo)準(zhǔn)消息格式組織消息內(nèi)容。但是好在目前已經(jīng)有很多開源的第三方類庫供我們使用,具體消息如何包裝完全不用自己組織,這里使用一個(gè)開源的類庫Push Sharp來給APNs發(fā)送消息 ,除了可以給Apple設(shè)備推送消息,Push Sharp還支持Android、Windows Phone等多種設(shè)備,更多詳細(xì)內(nèi)容大家可以參照官方說明。前面說過如果要開發(fā)消息推送應(yīng)用不能使用一般的開發(fā)配置文件,這里還需要注意:如果服務(wù)器端要給APNs發(fā)送消息其秘鑰也必須是通過APNs Development iOS類型的證書來導(dǎo)出的,一般的iOS Development 類型的證書導(dǎo)出的秘鑰無法用作服務(wù)器端發(fā)送秘鑰。下面通過在一個(gè)簡單的WinForm程序中調(diào)用Push Sharp給APNs發(fā)送消息,這里讀取之前Device表中的所有設(shè)備令牌循環(huán)發(fā)送消息:
using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using PushSharp;
using PushSharp.Apple;
using CMJ.Framework.Data;
using CMJ.Framework.Logging;
using CMJ.Framework.Windows.Forms;

namespace PushNotificationServer
{
    public partial class frmMain : PersonalizeForm
    {
        private string _appID = @"com.cmjstudio.pushnotification";
        private SqlHelper _helper = new SqlHelper();
        public frmMain()
        {
            InitializeComponent();
        }

        private void btnClose_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            List<string> deviceTokens = GetDeviceToken();
            SendMessage(deviceTokens, tbMessage.Text);
        }

        #region 發(fā)送消息
        /// <summary>
        /// 取得所有設(shè)備令牌
        /// </summary>
        /// <returns>設(shè)備令牌</returns>
        private List<string> GetDeviceToken()
        {
            List<string> deviceTokens = new List<string>();
            string sql = string.Format("SELECT DeviceToken FROM dbo.Device WHERE AppID='{0}'",_appID);
            DataTable dt = _helper.GetDataTable(sql);
            if(dt.Rows.Count>0)
            {
                foreach(DataRow dr in dt.Rows)
                {
                    deviceTokens.Add((dr["DeviceToken"]+"").TrimStart('<').TrimEnd('>').Replace(" ",""));
                }
            }
            return deviceTokens;
        }
        
        /// <summary>
        /// 發(fā)送消息
        /// </summary>
        /// <param name="deviceToken">設(shè)備令牌</param>
        /// <param name="message">消息內(nèi)容</param>
        private void SendMessage(List<string> deviceToken, string message)
        {
            //創(chuàng)建推送對象
            var pusher = new PushBroker();
            pusher.OnNotificationSent += pusher_OnNotificationSent;//發(fā)送成功事件
            pusher.OnNotificationFailed += pusher_OnNotificationFailed;//發(fā)送失敗事件
            pusher.OnChannelCreated += pusher_OnChannelCreated;
            pusher.OnChannelDestroyed += pusher_OnChannelDestroyed;
            pusher.OnChannelException += pusher_OnChannelException;
            pusher.OnDeviceSubscriptionChanged += pusher_OnDeviceSubscriptionChanged;
            pusher.OnDeviceSubscriptionExpired += pusher_OnDeviceSubscriptionExpired;
            pusher.OnNotificationRequeue += pusher_OnNotificationRequeue;
            pusher.OnServiceException += pusher_OnServiceException;
            //注冊推送服務(wù)
            byte[] certificateData = File.ReadAllBytes(@"E:\KenshinCui_Push.p12");
            pusher.RegisterAppleService(new ApplePushChannelSettings(certificateData, "123"));
            foreach (string token in deviceToken)
            {
                //給指定設(shè)備發(fā)送消息
                pusher.QueueNotification(new AppleNotification()
                    .ForDeviceToken(token)
                    .WithAlert(message) 
                    .WithBadge(1)
                    .WithSound("default"));
            }
        }

        void pusher_OnServiceException(object sender, Exception error)
        {
            Console.WriteLine("消息發(fā)送失敗,錯(cuò)誤詳情:" + error.ToString());
            PersonalizeMessageBox.Show(this, "消息發(fā)送失敗,錯(cuò)誤詳情:" + error.ToString(), "系統(tǒng)提示");
        }

        void pusher_OnNotificationRequeue(object sender, PushSharp.Core.NotificationRequeueEventArgs e)
        {
            Console.WriteLine("pusher_OnNotificationRequeue");
        }

        void pusher_OnDeviceSubscriptionExpired(object sender, string expiredSubscriptionId, DateTime expirationDateUtc, PushSharp.Core.INotification notification)
        {
            Console.WriteLine("pusher_OnDeviceSubscriptionChanged");
        }

        void pusher_OnDeviceSubscriptionChanged(object sender, string oldSubscriptionId, string newSubscriptionId, PushSharp.Core.INotification notification)
        {
            Console.WriteLine("pusher_OnDeviceSubscriptionChanged");
        }

        void pusher_OnChannelException(object sender, PushSharp.Core.IPushChannel pushChannel, Exception error)
        {
            Console.WriteLine("消息發(fā)送失敗,錯(cuò)誤詳情:" + error.ToString());
            PersonalizeMessageBox.Show(this, "消息發(fā)送失敗,錯(cuò)誤詳情:" + error.ToString(), "系統(tǒng)提示");
        }

        void pusher_OnChannelDestroyed(object sender)
        {
            Console.WriteLine("pusher_OnChannelDestroyed");
        }

        void pusher_OnChannelCreated(object sender, PushSharp.Core.IPushChannel pushChannel)
        {
            Console.WriteLine("pusher_OnChannelCreated");
        }

        void pusher_OnNotificationFailed(object sender, PushSharp.Core.INotification notification, Exception error)
        {
            Console.WriteLine("消息發(fā)送失敗,錯(cuò)誤詳情:" + error.ToString());
            PersonalizeMessageBox.Show(this, "消息發(fā)送失敗,錯(cuò)誤詳情:"+error.ToString(), "系統(tǒng)提示");
        }

        void pusher_OnNotificationSent(object sender, PushSharp.Core.INotification notification)
        {
            Console.WriteLine("消息發(fā)送成功!");
            PersonalizeMessageBox.Show(this, "消息發(fā)送成功!", "系統(tǒng)提示");
        }
        #endregion
    }
}

服務(wù)器端消息發(fā)送應(yīng)用運(yùn)行效果:

170829115313261.png

iOS客戶端接收的消息的效果:

170829135941093.png

到目前為止通過服務(wù)器端應(yīng)用可以順利發(fā)送消息給APNs并且iOS應(yīng)用已經(jīng)成功接收推送消息。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 概述 在多數(shù)移動應(yīng)用中任何時(shí)候都只能有一個(gè)應(yīng)用程序處于活躍狀態(tài),如果其他應(yīng)用此刻發(fā)生了一些用戶感興趣的那么通過通知...
    莫離_焱閱讀 6,717評論 1 8
  • 來源:崔江濤的博客 概述在多數(shù)移動應(yīng)用中任何時(shí)候都只能有一個(gè)應(yīng)用程序處于活躍狀態(tài),如果其他應(yīng)用此刻發(fā)生了一些用戶感...
    李棲桐閱讀 978評論 0 0
  • ![ 1.應(yīng)用程序注冊APNs推送消息。說明:a.只有注冊過的應(yīng)用才有可能接收到消息,程序中通常通過UIAppli...
    Crazy2015閱讀 533評論 0 2
  • 說在前面 iOS中的推送分為:Local Notification(本地消息通知)和Remote Notifica...
    UncleChen閱讀 1,847評論 0 5
  • 明天你是否會想起 昨天你寫的日記 明天你是否會想起 曾經(jīng)年少的你 走過學(xué)校旁的老街道,熟悉的歌詞再次抨擊著我那脆弱...
    何小白子閱讀 330評論 0 4

友情鏈接更多精彩內(nèi)容