通知與消息機制

概述

在多數移動應用中任什麼時候候都只能有一個應用程序處於活躍狀態,若是其餘應用此刻發生了一些用戶感興趣的那麼經過通知機制就能夠告訴用戶此時發生的事情。iOS中通知機制又叫消息機制,其包括兩類:一類是本地通知;另外一類是推送通知,也叫遠程通知。兩種通知在iOS中的表現一致,能夠經過橫幅或者彈出提醒兩種形式告訴用戶,而且點擊通知能夠會打開應用程序,可是實現原理卻徹底不一樣。今天就和你們一塊去看一下如何在iOS中實現這兩種機制,而且在文章後面會補充通知中心的內容避免初學者對兩種概念的混淆。html

  1. 本地通知
  2. 推送通知
  3. 補充--iOS開發證書、祕鑰
  4. 補充--通知中心
  5. 目 錄

本地通知

本地通知是由本地應用觸發的,它是基於時間行爲的一種通知形式,例如鬧鐘定時、待辦事項提醒,又或者一個應用在一段時候後不使用一般會提示用戶使用此應用等都是本地通知。建立一個本地通知一般分爲如下幾個步驟:git

  1. 建立UILocalNotification。 
  2. 設置處理通知的時間fireDate。 
  3. 配置通知的內容:通知主體、通知聲音、圖標數字等。 
  4. 配置通知傳遞的自定義數據參數userInfo(這一步可選)。 
  5. 調用通知,可使用scheduleLocalNotification:按計劃調度一個通知,也可使用presentLocalNotificationNow當即調用通知。

下面就以一個程序更新後用戶長期沒有使用的提醒爲例對本地通知作一個簡單的瞭解。在這個過程當中並無牽扯太多的界面操做,全部的邏輯都在AppDelegate中:進入應用後若是沒有註冊通知,須要首先註冊通知請求用戶容許通知;一旦調用完註冊方法,不管用戶是否選擇容許通知此刻都會調用應用程序的- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings代理方法,在這個方法中根據用戶的選擇:若是是容許通知則會按照前面的步驟建立通知並在必定時間後執行。github

AppDelegate.m算法

//
//  AppDelegate.m
//  LocalNotification
//
//  Created by Kenshin Cui on 14/03/28.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "AppDelegate.h"
#import "KCMainViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate

#pragma mark - 應用代理方法
- (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];
    
    //設置全局導航條風格和顏色
    [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1]];
    [[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];
    
    KCMainViewController *mainController=[[KCMainViewController alloc]init];
    _window.rootViewController=mainController;
    
    [_window makeKeyAndVisible];

    //若是已經得到發送通知的受權則建立本地通知,不然請求受權(注意:若是不請求受權在設置中是沒有對應的通知設置項的,也就是說若是歷來沒有發送過請求,即便經過設置也打不開消息容許設置)
    if ([[UIApplication sharedApplication]currentUserNotificationSettings].types!=UIUserNotificationTypeNone) {
        [self addLocalNotification];
    }else{
        [[UIApplication sharedApplication]registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound  categories:nil]];
    }
    
    return YES;
}

#pragma mark 調用過用戶註冊通知方法以後執行(也就是調用完registerUserNotificationSettings:方法以後執行)
-(void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings{
    if (notificationSettings.types!=UIUserNotificationTypeNone) {
        [self addLocalNotification];
    }
}

#pragma mark 進入前臺後設置消息信息
-(void)applicationWillEnterForeground:(UIApplication *)application{
    [[UIApplication sharedApplication]setApplicationIconBadgeNumber:0];//進入前臺取消應用消息圖標
}

#pragma mark - 私有方法
#pragma mark 添加本地通知
-(void)addLocalNotification{
    
    //定義本地通知對象
    UILocalNotification *notification=[[UILocalNotification alloc]init];
    //設置調用時間
    notification.fireDate=[NSDate dateWithTimeIntervalSinceNow:10.0];//通知觸發的時間,10s之後
    notification.repeatInterval=2;//通知重複次數
    //notification.repeatCalendar=[NSCalendar currentCalendar];//當前日曆,使用前最好設置時區等信息以便可以自動同步時間
    
    //設置通知屬性
    notification.alertBody=@"最近添加了諸多有趣的特性,是否當即體驗?"; //通知主體
    notification.applicationIconBadgeNumber=1;//應用程序圖標右上角顯示的消息數
    notification.alertAction=@"打開應用"; //待機界面的滑動動做提示
    notification.alertLaunchImage=@"Default";//經過點擊通知打開應用時的啓動圖片,這裏使用程序啓動圖片
    //notification.soundName=UILocalNotificationDefaultSoundName;//收到通知時播放的聲音,默認消息聲音
    notification.soundName=@"msg.caf";//通知聲音(須要真機才能聽到聲音)
    
    //設置用戶信息
    notification.userInfo=@{@"id":@1,@"user":@"Kenshin Cui"};//綁定到通知上的其餘附加信息
    
    //調用通知
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
}

#pragma mark 移除本地通知,在不須要此通知時記得移除
-(void)removeNotification{
    [[UIApplication sharedApplication] cancelAllLocalNotifications];
}
@end

請求得到用戶容許通知的效果:sql

 LocalNotification_Request

應用退出到後彈出通知的效果:數據庫

 LocalNotification_Notification

鎖屏狀態下的通知效果(從這個界面能夠看到alertAction配置爲「打開應用」):服務器

LocalNotification_Lock

注意:網絡

  • 在使用通知以前必須註冊通知類型,若是用戶不容許應用程序發送通知,則之後就沒法發送通知,除非用戶手動到iOS設置中打開通知。 
  • 本地通知是有操做系通通一調度的,只有在應用退出到後臺或者關閉才能收到通知。(注意:這一點對於後面的推送通知也是徹底適用的。 ) 
  • 通知的聲音是由iOS系統播放的,格式必須是Linear PCM、MA4(IMA/ADPCM)、µLaw、aLaw中的一種,而且播放時間必須在30s內,不然將被系統聲音替換,同時自定義聲音文件必須放到main boundle中。 
  • 本地通知的數量是有限制的,最近的本地通知最多隻能有64個,超過這個數量將被系統忽略。 
  • 若是想要移除本地通知能夠調用UIApplication的cancelLocalNotification:cancelAllLocalNotifications移除指定通知或全部通知。

從上面的程序能夠看到userInfo這個屬性咱們設置了參數,那麼這個參數如何接收呢?session

在iOS中若是點擊一個彈出通知(或者鎖屏界面滑動查看通知),默認會自動打開當前應用。因爲通知由系統調度那麼此時進入應用有兩種狀況:若是應用程序已經徹底退出那麼此時會調用- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法;若是此時應用程序還在運行(不管是在前臺仍是在後臺)則會調用-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification方法接收消息參數。固然若是是後者天然沒必要多說,由於參數中已經能夠拿到notification對象,只要讀取userInfo屬性便可。若是是前者的話則能夠訪問launchOptions中鍵爲UIApplicationLaunchOptionsLocalNotificationKey的對象,這個對象就是發送的通知,由此對象再去訪問userInfo。爲了演示這個過程在下面的程序中將userInfo的內容寫入文件以便模擬關閉程序後再經過點擊通知打開應用獲取userInfo的過程。app

AppDelegate.m

//
//  AppDelegate.m
//  LocalNotification
//
//  Created by Kenshin Cui on 14/03/28.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "AppDelegate.h"
#import "KCMainViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate

#pragma mark - 應用代理方法
- (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];
    
    //設置全局導航條風格和顏色
    [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1]];
    [[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];
    
    KCMainViewController *mainController=[[KCMainViewController alloc]init];
    _window.rootViewController=mainController;
    
    [_window makeKeyAndVisible];

    //添加通知
    [self addLocalNotification];

    //接收通知參數
    UILocalNotification *notification=[launchOptions valueForKey:UIApplicationLaunchOptionsLocalNotificationKey];
    NSDictionary *userInfo= notification.userInfo;
    
    [userInfo writeToFile:@"/Users/kenshincui/Desktop/didFinishLaunchingWithOptions.txt" atomically:YES];
    NSLog(@"didFinishLaunchingWithOptions:The userInfo is %@.",userInfo);
    
    return YES;
}

#pragma mark 接收本地通知時觸發
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
    NSDictionary *userInfo=notification.userInfo;
    [userInfo writeToFile:@"/Users/kenshincui/Desktop/didReceiveLocalNotification.txt" atomically:YES];
    NSLog(@"didReceiveLocalNotification:The userInfo is %@",userInfo);
}

#pragma mark 調用過用戶註冊通知方法以後執行(也就是調用完registerUserNotificationSettings:方法以後執行)
-(void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings{
    if (notificationSettings.types!=UIUserNotificationTypeNone) {
        [self addLocalNotification];
    }
}

#pragma mark 進入前臺後設置消息信息
-(void)applicationWillEnterForeground:(UIApplication *)application{
    [[UIApplication sharedApplication]setApplicationIconBadgeNumber:0];//進入前臺取消應用消息圖標
}

#pragma mark - 私有方法
#pragma mark 添加本地通知
-(void)addLocalNotification{
    
    //定義本地通知對象
    UILocalNotification *notification=[[UILocalNotification alloc]init];
    //設置調用時間
    notification.fireDate=[NSDate dateWithTimeIntervalSinceNow:10.0];//通知觸發的時間,10s之後
    notification.repeatInterval=2;//通知重複次數
    //notification.repeatCalendar=[NSCalendar currentCalendar];//當前日曆,使用前最好設置時區等信息以便可以自動同步時間
    
    //設置通知屬性
    notification.alertBody=@"最近添加了諸多有趣的特性,是否當即體驗?"; //通知主體
    notification.applicationIconBadgeNumber=1;//應用程序圖標右上角顯示的消息數
    notification.alertAction=@"打開應用"; //待機界面的滑動動做提示
    notification.alertLaunchImage=@"Default";//經過點擊通知打開應用時的啓動圖片
    //notification.soundName=UILocalNotificationDefaultSoundName;//收到通知時播放的聲音,默認消息聲音
    notification.soundName=@"msg.caf";//通知聲音(須要真機)
    
    //設置用戶信息
    notification.userInfo=@{@"id":@1,@"user":@"Kenshin Cui"};//綁定到通知上的其餘額外信息
    
    //調用通知
    [[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
@end

上面的程序能夠分爲兩種狀況去運行:一種是啓動程序關閉程序,等到接收到通知以後點擊通知從新進入程序;另外一種是啓動程序後,進入後臺(其實在前臺也能夠,可是爲了明顯的體驗這個過程建議進入後臺),接收到通知後點擊通知進入應用。另種狀況會分別按照前面說的狀況調用不一樣的方法接收到userInfo寫入本地文件系統。有了userInfo通常來講就能夠根據這個信息進行一些處理,例如能夠根據不一樣的參數信息導航到不一樣的界面,假設是更新的通知則能夠導航到更新內容界面等。

推送通知

和本地通知不一樣,推送通知是由應用服務提供商發起的,經過蘋果的APNs(Apple Push Notification Server)發送到應用客戶端。下面是蘋果官方關於推送通知的過程示意圖:

PushNotification_FlowChart

推送通知的過程能夠分爲如下幾步:

  1. 應用服務提供商從服務器端把要發送的消息和設備令牌(device token)發送給蘋果的消息推送服務器APNs。 
  2. APNs根據設備令牌在已註冊的設備(iPhone、iPad、iTouch、mac等)查找對應的設備,將消息發送給相應的設備。 
  3. 客戶端設備接將接收到的消息傳遞給相應的應用程序,應用程序根據用戶設置彈出通知消息。

固然,這只是一個簡單的流程,有了這個流程咱們還無從下手編寫程序,將上面的流程細化能夠獲得以下流程圖(圖片來自互聯網),在這個過程當中會也會提到如何在程序中完成這些步驟:

PushNotification_FlowChartDetail

1.應用程序註冊APNs推送消息。

說明:

a.只有註冊過的應用纔有可能接收到消息,程序中一般經過UIApplication的registerUserNotificationSettings:方法註冊,iOS8中通知註冊的方法發生了改變,若是是iOS7及以前版本的iOS請參考其餘代碼。

b.註冊以前有兩個前提條件必須準備好:開發配置文件(provisioning profile,也就是.mobileprovision後綴的文件)的App ID不能使用通配ID必須使用指定APP ID而且生成配置文件中選擇Push Notifications服務,通常的開發配置文件沒法完成註冊;應用程序的Bundle Identifier必須和生成配置文件使用的APP ID徹底一致。

2.iOS從APNs接收device token,在應用程序獲取device token。

說明:

a.在UIApplication的-(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken代理方法中獲取令牌,此方法發生在註冊以後。

b.若是沒法正確得到device token能夠在UIApplication的-(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error代理方法中查看詳細錯誤信息,此方法發生在獲取device token失敗以後。

c.必須真機調試,模擬器沒法獲取device token。

3.iOS應用將device token發送給應用程序提供商,告訴服務器端當前設備容許接收消息。

說明:

a.device token的生成算法只有Apple掌握,爲了確保算法發生變化後仍然可以正常接收服務器端發送的通知,每次應用程序啓動都從新得到device token(注意:device token的獲取不會形成性能問題,蘋果官方已經作過優化)。

b.一般能夠建立一個網絡鏈接發送給應用程序提供商的服務器端, 在這個過程當中最好將上一次得到的device token存儲起來,避免重複發送,一旦發現device token發生了變化最好將原有的device token一塊發送給服務器端,服務器端刪除原有令牌存儲新令牌避免服務器端發送無效消息。

4.應用程序提供商在服務器端根據前面發送過來的device token組織信息發送給APNs。

說明:

a.發送時指定device token和消息內容,而且徹底按照蘋果官方的消息格式組織消息內容,一般狀況下能夠藉助其餘第三方消息推送框架來完成。

5.APNs根據消息中的device token查找已註冊的設備推送消息。

說明:

a.正常狀況下能夠根據device token將消息成功推送到客戶端設備中,可是也不排除用戶卸載程序的狀況,此時推送消息失敗,APNs會將這個錯誤消息通知服務器端以免資源浪費(服務器端此時能夠根據錯誤刪除已經存儲的device token,下次再也不發送)。

下面將簡單演示一下推送通知的簡單流程:

首先,看一下iOS客戶端代碼:

//
//  AppDelegate.m
//  pushnotification
//
//  Created by Kenshin Cui on 14/03/27.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "AppDelegate.h"
#import "KCMainViewController.h"

@interface AppDelegate ()

@end

@implementation AppDelegate

#pragma mark - 應用程序代理方法
#pragma mark 應用程序啓動以後
- (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];
    
    //設置全局導航條風格和顏色
    [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1]];
    [[UINavigationBar appearance] setBarStyle:UIBarStyleBlack];
    
    KCMainViewController *mainController=[[KCMainViewController alloc]init];
    _window.rootViewController=mainController;
    
    [_window makeKeyAndVisible];
    
    //註冊推送通知(注意iOS8註冊方法發生了變化)
    [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil]];
    [application registerForRemoteNotifications];
    
    return YES;
}
#pragma mark 註冊推送通知以後
//在此接收設備令牌
-(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 - 私有方法
/**
 *  添加設備令牌到服務器端
 *
 *  @param deviceToken 設備令牌
 */
-(void)addDeviceToken:(NSData *)deviceToken{
    NSString *key=@"DeviceToken";
    NSData *oldToken= [[NSUserDefaults standardUserDefaults]objectForKey:key];
    //若是偏好設置中的已存儲設備令牌和新獲取的令牌不一樣則存儲新令牌而且發送給服務器端
    if (![oldToken isEqualToData:deviceToken]) {
        [[NSUserDefaults standardUserDefaults] setObject:deviceToken forKey:key];
        [self sendDeviceTokenWidthOldDeviceToken:oldToken newDeviceToken:deviceToken];
    }
}

-(void)sendDeviceTokenWidthOldDeviceToken:(NSData *)oldToken newDeviceToken:(NSData *)newToken{
    //注意必定確保真機能夠正常訪問下面的地址
    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存儲到偏好設置中,而且若是新獲取的device token不一樣於偏好設置中存儲的數據則發送給服務器端,更新服務器端device token列表。

其次,因爲device token須要發送給服務器端,這裏使用一個Web應用做爲服務器端接收device token,這裏使用了ASP.NET程序來處理令牌接收註冊工做,固然你使用其餘技術一樣沒有問題。下面是對應的後臺代碼:

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 = "";
                //若是傳遞舊的設備令牌則刪除舊令牌添加新令牌
                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("註冊失敗,錯誤詳情:"+ex.ToString());
            }
        }
    }
}

這個過程主要就是保存device token到數據庫中,固然若是同時傳遞舊的設備令牌還須要先刪除就的設備令牌,這裏簡單的在數據庫中建立了一張Device表來保存設備令牌,其中記錄了應用程序Id和設備令牌。

第三步就是服務器端發送消息,若是要給APNs發送消息就必須按照Apple的標準消息格式組織消息內容。可是好在目前已經有不少開源的第三方類庫供咱們使用,具體消息如何包裝徹底不用本身組織,這裏使用一個開源的類庫Push Sharp來給APNs發送消息 ,除了能夠給Apple設備推送消息,Push Sharp還支持Android、Windows Phone等多種設備,更多詳細內容你們能夠參照官方說明。前面說過若是要開發消息推送應用不能使用通常的開發配置文件,這裏還須要注意:若是服務器端要給APNs發送消息其祕鑰也必須是經過APNs Development iOS類型的證書來導出的,通常的iOS Development 類型的證書導出的祕鑰沒法用做服務器端發送祕鑰。下面經過在一個簡單的WinForm程序中調用Push Sharp給APNs發送消息,這裏讀取以前Device表中的全部設備令牌循環發送消息:

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 發送消息
        /// <summary>
        /// 取得全部設備令牌
        /// </summary>
        /// <returns>設備令牌</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>
        /// 發送消息
        /// </summary>
        /// <param name="deviceToken">設備令牌</param>
        /// <param name="message">消息內容</param>
        private void SendMessage(List<string> deviceToken, string message)
        {
            //建立推送對象
            var pusher = new PushBroker();
            pusher.OnNotificationSent += pusher_OnNotificationSent;//發送成功事件
            pusher.OnNotificationFailed += pusher_OnNotificationFailed;//發送失敗事件
            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;
            //註冊推送服務
            byte[] certificateData = File.ReadAllBytes(@"E:\KenshinCui_Push.p12");
            pusher.RegisterAppleService(new ApplePushChannelSettings(certificateData, "123"));
            foreach (string token in deviceToken)
            {
                //給指定設備發送消息
                pusher.QueueNotification(new AppleNotification()
                    .ForDeviceToken(token)
                    .WithAlert(message) 
                    .WithBadge(1)
                    .WithSound("default"));
            }
        }

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

        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("消息發送失敗,錯誤詳情:" + error.ToString());
            PersonalizeMessageBox.Show(this, "消息發送失敗,錯誤詳情:" + error.ToString(), "系統提示");
        }

        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("消息發送失敗,錯誤詳情:" + error.ToString());
            PersonalizeMessageBox.Show(this, "消息發送失敗,錯誤詳情:"+error.ToString(), "系統提示");
        }

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

服務器端消息發送應用運行效果:

PushNotification_ServerClient

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

PushNotification_iOSApp

到目前爲止經過服務器端應用能夠順利發送消息給APNs而且iOS應用已經成功接收推送消息。

補充--iOS開發證書、祕鑰

iOS開發過程當中若是須要進行真機調試、發佈須要註冊申請不少證書,對於初學者每每疑惑不解,再加上今天的文章中會牽扯到一些特殊配置,這裏就簡單的對iOS開發的經常使用證書和祕鑰等作一說明。

證書

iOS經常使用的證書包括開發證書和發佈證書,不管是真機調試仍是最終發佈應用到App Store這兩個證書都是必須的,它是iOS開發的基本證書。

a.開發證書:開發證書又分爲普通開發證書和推送證書,若是僅僅是通常的應用則前者便可知足,可是若是開發推送應用則必須使用推送證書。

b.發佈證書:發佈證書又能夠分爲普通發佈證書、推送證書、Pass Type ID證書、站點發布證書、VoIP服務證書、蘋果支付證書。一樣的,對於須要使用特殊服務的應用則必須選擇對應的證書。

應用標識

App ID,應用程序的惟一標識,對應iOS應用的Bundle Identifier,App ID在蘋果開發者中心中分爲通配應用ID和明確的應用ID,前者通常用於普通應用開發,一個ID能夠適用於多個不一樣標識的應用;可是對於使用消息推送、Passbook、站點發布、iCloud等服務的應用必須配置明確的應用ID。

設備標識

UDID,用於標識每一臺硬件設備的標示符。注意它不是device token,device token是根據UDID使用一個只有Apple本身才知道的算法生成的一組標示符。

配置簡介

Provisioning Profiles,平時又稱爲PP文件。將UDID、App ID、開發證書打包在一塊兒的配置文件,一樣分爲開發和發佈兩類配置文件。

祕鑰

在申請開發證書時必需要首先提交一個祕鑰請求文件,對於生成祕鑰請求文件的mac,若是要作開發則只須要下載證書和配置簡介便可開發。可是若是要想在其餘機器上作開發則必須將證書中的祕鑰導出(導出以後是一個.p12文件),而後導入其餘機器。同時對於相似於推送服務器端應用若是要給APNs發送消息,一樣須要使用.p12祕鑰文件,而且這個祕鑰文件須要是推送證書導出的對應祕鑰。

補充--通知中心

對於不少初學者每每會把iOS中的本地通知、推送通知和iOS通知中心的概念弄混。其實兩者之間並無任何關係,事實上它們都不屬於一個框架,前者屬於UIKit框架,後者屬於Foundation框架。 

通知中心其實是iOS程序內部之間的一種消息廣播機制,主要爲了解決應用程序內部不一樣對象之間解耦而設計。它是基於觀察者模式設計的,不能跨應用程序進程通訊,當通知中心接收到消息以後會根據內部的消息轉發表,將消息發送給訂閱者。下面是一個簡單的流程示意圖: 

image

瞭解通知中心須要熟悉NSNotificationCenter和NSNotification兩個類: 

NSNotificationCenter:是通知系統的中心,用於註冊和發送通知,下表列出經常使用的方法。

方法 說明
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject 添加監聽,參數:
observer:監聽者
selector:監聽方法(監聽者監聽到通知後執行的方法)
  name:監聽的通知名稱
object:通知的發送者(若是指定nil則監放任何對象發送的通知)
- (id <NSObject>)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block  添加監聽,參數:
name:監聽的通知名稱
object:通知的發送者(若是指定nil則監放任何對象發送的通知)
queue:操做隊列,若是制定非主隊線程隊列則能夠異步執行block
block:監聽到通知後執行的操做
- (void)postNotification:(NSNotification *)notification 發送通知,參數:
notification:通知對象
- (void)postNotificationName:(NSString *)aName object:(id)anObject 發送通知,參數:
aName:通知名稱
anObject:通知發送者
- (void)postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo 發送通知,參數:
aName:通知名稱
anObject:通知發送者
aUserInfo:通知參數
- (void)removeObserver:(id)observer 移除監聽,參數:
observer:監聽對象
- (void)removeObserver:(id)observer name:(NSString *)aName object:(id)anObject 移除監聽,參數:
observer:監聽對象
aName:通知名稱
anObject:通知發送者

NSNotification:表明通知內容的載體,主要有三個屬性:name表明通知名稱,object表明通知的發送者,userInfo表明通知的附加信息。

雖然前面的文章中從未提到過通知中心,可是其實通知中心咱們並不陌生,前面文章中不少內容都是經過通知中心來進行應用中各個組件通訊的,只是沒有單獨拿出來講而已。例如前面的文章中討論的應用程序生命週期問題,當應用程序啓動後、進入後臺、進入前臺、得到焦點、失去焦點,窗口大小改變、隱藏等都會發送通知。這個通知能夠經過前面NSNotificationCenter進行訂閱便可接收對應的消息,下面的示例演示瞭如何添加監聽得到UIApplication的進入後臺和得到焦點的通知:

//
//  KCMainViewController.m
//  NotificationCenter
//
//  Created by Kenshin Cui on 14/03/27.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "KCMainViewController.h"

@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self addObserverToNotificationCenter];
    
}

#pragma mark 添加監聽
-(void)addObserverToNotificationCenter{
    /*添加應用程序進入後臺監聽
     * observer:監聽者
     * selector:監聽方法(監聽者監聽到通知後執行的方法)
     * name:監聽的通知名稱(下面的UIApplicationDidEnterBackgroundNotification是一個常量)
     * object:通知的發送者(若是指定nil則監放任何對象發送的通知)
     */
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]];
    
    /* 添加應用程序得到焦點的通知監聽
     * name:監聽的通知名稱
     * object:通知的發送者(若是指定nil則監放任何對象發送的通知)
     * queue:操做隊列,若是制定非主隊線程隊列則能夠異步執行block
     * block:監聽到通知後執行的操做
     */
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidBecomeActiveNotification object:[UIApplication sharedApplication] queue:operationQueue usingBlock:^(NSNotification *note) {
        NSLog(@"Application become active.");
    }];
}

#pragma mark 應用程序啓動監聽方法
-(void)applicationEnterBackground{
    NSLog(@"Application enter background.");
}
@end

固然不少時候使用通知中心是爲了添加自定義通知,並得到自定義通知消息。在前面的文章「iOS開發系列--視圖切換」中提到過如何進行多視圖之間參數傳遞,其實利用自定義通知也能夠進行參數傳遞。一般一個應用登陸後會顯示用戶信息,而登陸信息能夠經過登陸界面獲取。下面就以這樣一種場景爲例,在主界面中添加監聽,在登陸界面發送通知,一旦登陸成功將向通知中心發送成功登陸的通知,此時主界面中因爲已經添加通知監聽因此會收到通知並更新UI界面。

主界面KCMainViewController.m:

//
//  KCMainViewController.m
//  NotificationCenter
//
//  Created by Kenshin Cui on 14/03/27
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "KCMainViewController.h"
#import "KCLoginViewController.h"
#define UPDATE_LGOGIN_INFO_NOTIFICATION @"updateLoginInfo"

@interface KCMainViewController (){
    UILabel *_lbLoginInfo;
    UIButton *_btnLogin;
}

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setupUI];
}

-(void)setupUI{
    UILabel *label =[[UILabel alloc]initWithFrame:CGRectMake(0, 100,320 ,30)];
    label.textAlignment=NSTextAlignmentCenter;
    label.textColor=[UIColor colorWithRed:23/255.0 green:180/255.0 blue:237/255.0 alpha:1];
    _lbLoginInfo=label;
    [self.view addSubview:label];
    
    UIButton *button=[UIButton buttonWithType:UIButtonTypeSystem];
    button.frame=CGRectMake(60, 200, 200, 25);
    [button setTitle:@"登陸" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(loginOut) forControlEvents:UIControlEventTouchUpInside];
    _btnLogin=button;
    
    [self.view addSubview:button];
}

-(void)loginOut{
    //添加監聽
    [self addObserverToNotification];
    
    KCLoginViewController *loginController=[[KCLoginViewController alloc]init];
    
    [self presentViewController:loginController animated:YES completion:nil];
}

/**
 *  添加監聽
 */
-(void)addObserverToNotification{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateLoginInfo:) name:UPDATE_LGOGIN_INFO_NOTIFICATION object:nil];
}

/**
 *  更新登陸信息,注意在這裏能夠得到通知對象而且讀取附加信息
 */
-(void)updateLoginInfo:(NSNotification *)notification{
    NSDictionary *userInfo=notification.userInfo;
    _lbLoginInfo.text=userInfo[@"loginInfo"];
    _btnLogin.titleLabel.text=@"註銷";
}

-(void)dealloc{
    //移除監聽
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
@end

登陸界面KCLoginViewController.m:

//
//  KCLoginViewController.m
//  NotificationCenter
//
//  Created by Kenshin Cui on 14/03/27.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "KCLoginViewController.h"
#define UPDATE_LGOGIN_INFO_NOTIFICATION @"updateLoginInfo"

@interface KCLoginViewController (){
    UITextField *_txtUserName;
    UITextField *_txtPassword;
}

@end

@implementation KCLoginViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setupUI];
}

/**
 *  UI佈局
 */
-(void)setupUI{
    //用戶名
    UILabel *lbUserName=[[UILabel alloc]initWithFrame:CGRectMake(50, 150, 100, 30)];
    lbUserName.text=@"用戶名:";
    [self.view addSubview:lbUserName];
    
    _txtUserName=[[UITextField alloc]initWithFrame:CGRectMake(120, 150, 150, 30)];
    _txtUserName.borderStyle=UITextBorderStyleRoundedRect;
    [self.view addSubview:_txtUserName];
    
    //密碼
    UILabel *lbPassword=[[UILabel alloc]initWithFrame:CGRectMake(50, 200, 100, 30)];
    lbPassword.text=@"密碼:";
    [self.view addSubview:lbPassword];
    
    _txtPassword=[[UITextField alloc]initWithFrame:CGRectMake(120, 200, 150, 30)];
    _txtPassword.secureTextEntry=YES;
    _txtPassword.borderStyle=UITextBorderStyleRoundedRect;
    [self.view addSubview:_txtPassword];
    
    //登陸按鈕
    UIButton *btnLogin=[UIButton buttonWithType:UIButtonTypeSystem];
    btnLogin.frame=CGRectMake(70, 270, 80, 30);
    [btnLogin setTitle:@"登陸" forState:UIControlStateNormal];
    [self.view addSubview:btnLogin];
    [btnLogin addTarget:self action:@selector(login) forControlEvents:UIControlEventTouchUpInside];
    
    //取消登陸按鈕
    UIButton *btnCancel=[UIButton buttonWithType:UIButtonTypeSystem];
    btnCancel.frame=CGRectMake(170, 270, 80, 30);
    [btnCancel setTitle:@"取消" forState:UIControlStateNormal];
    [self.view addSubview:btnCancel];
    [btnCancel addTarget:self action:@selector(cancel) forControlEvents:UIControlEventTouchUpInside];
}

#pragma mark 登陸操做
-(void)login{
    if ([_txtUserName.text isEqualToString:@"kenshincui"] && [_txtPassword.text isEqualToString:@"123"] ) {
        //發送通知
        [self postNotification];
        [self dismissViewControllerAnimated:YES completion:nil];
    }else{
        //登陸失敗彈出提示信息
        UIAlertView *alertView=[[UIAlertView alloc]initWithTitle:@"系統信息" message:@"用戶名或密碼錯誤,請從新輸入!" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:nil];
        [alertView show];
    }
    
}

#pragma mark 點擊取消
-(void)cancel{
    [self dismissViewControllerAnimated:YES completion:nil];
}

/**
 *  添加通知,注意這裏設置了附加信息
 */
-(void)postNotification{
    NSDictionary *userInfo=@{@"loginInfo":[NSString stringWithFormat:@"Hello,%@!",_txtUserName.text]};
    NSLog(@"%@",userInfo);
    NSNotification *notification=[NSNotification notificationWithName:UPDATE_LGOGIN_INFO_NOTIFICATION object:self userInfo:userInfo];
    [[NSNotificationCenter defaultCenter] postNotification:notification];
//也可直接採用下面的方法
//    [[NSNotificationCenter defaultCenter] postNotificationName:UPDATE_LGOGIN_INFO_NOTIFICATION object:self userInfo:userInfo];

}
@end

運行效果:

NotificationCenter_CustomNotification

相關文章
相關標籤/搜索