• iOS原生实现推送 一、iOS推送机制 Provider是给你手机应用发出推送消息的服务器,而APNS(Apple Push Notification Service)则是苹果消息推送服务器。你本地的服务器当需要给应用推送一条消息的时候,先要将...

    iOS原生实现推送

    一、iOS推送机制



    Provider是给你手机应用发出推送消息的服务器,而APNS(Apple Push Notification Service)则是苹果消息推送服务器。你本地的服务器当需要给应用推送一条消息的时候,先要将消息发出到苹果推送服务器,然后再由苹果推送服务器将消息发到安装了该应用的手机。

    接下来再看一张解释图:


    根据上图的逻辑:

    1. iOS应用需要去注册APNS消息推送功能;

    2.当苹果APNS推送服收到来自你应用的注册消息就会给客户端返回一串device token(很重要);

    3.将应用收到的device Token传给本地的Push服务器;

    4.当你需要为应用推送消息的时候,你本地的推送服务器会将消息;以及Device Token打包发送到苹果的APNS服;

    5.APNS再将消息推送给目的iphone。


    二、申请推送证书

    证书分为开发推送证书和生产推送证书,当在开发真机测试时使用开发推送证书.p12,在AdHoc(打包测试)及上线时使用生产推送证书.p12

    三、后台集成推送环境(集成以开发环境为实例)

     

    接下来我们打开终端将他们生成.pem文件:

    1.  把aps_development .cer文件生成.pcm文件,cd到push文件夹下

    yangliang:~ apple$ openssl x509 -in aps_development.cer-inform der -out PushCert.pem

     

    2.  将开发证书p.12生产.pem文件:

    yangliang:Jpush apple$ openssl pkcs12 -nocerts -outPushCertKey.pem -in 生产证书.p12

    进行该步骤是要验证证书的秘钥(需要验证两次密码ABC123)

    Enter Import Password:

    MAC verified OK

    Enter PEM pass phrase:

    Verifying - Enter PEM pass phrase:

    yangliang:Jpush apple$

     

    上边输入的密码则是你导出证书所设的密码,即ABC123 接着还会让你输入.pem文件的密码,还是使用ABC123好了,防止混淆。

     

    这样我们在push文件夹中就又得到了两个文件,。

    3.  把PushCert.pem和PushCertKey.pem合并为一个pem文件:

    yangliang:Jpush apple$ cat PushCert.pem pushCertKey.pem> ck.pem

    在push文件夹中又多了一个ck.pem文件,以上我们把需要使用的文件都准备好了

     

    四、测试环境

    为了测试证书工作的状况,我们可以使用“telnet gateway.sandbox.push.apple.com2195”来检测一下,如果显示:

    yangliang:Jpush apple$ telnetgateway.sandbox.push.apple.com 2195

    Trying 17.188.137.58...

    Connected to gateway.sandbox.push-apple.com.akadns.net.

    Escape character is '^]'.

    则表示成功。

     

    最后使用我们生成的证书和私钥来设置一个安全的链接去链接苹果服务器:

    yangliang:Jpush apple$ openssl s_client -connectgateway.sandbox.push.apple.com:2195 -cert PushChatCert.pem -key PushKey.pem

     

    需要输入密码(ABC123我们刚才所设置的)

     

    当返回下面数据时则表示连接成功:

    CONNECTED(00000003)

    depth=1 /C=US/O=Entrust, Inc./OU=Seewww.entrust.net/legal-terms/OU=(c) 2012 Entrust, Inc. - for authorized useonly/CN=Entrust Certification Authority - L1K

    verify error:num=20:unable to get localissuer certificate

    verify return:0

    ---

    Certificate chain

     0s:/C=US/ST=California/L=Cupertino/O=AppleInc./CN=gateway.sandbox.push.apple.com

      i:/C=US/O=Entrust, Inc./OU=See www.entrust.net/legal-terms/OU=(c) 2012Entrust, Inc. - for authorized use only/CN=Entrust Certification Authority -L1K

     1s:/C=US/O=Entrust, Inc./OU=See www.entrust.net/legal-terms/OU=(c) 2012 Entrust,Inc. - for authorized use only/CN=Entrust Certification Authority - L1K

      i:/O=Entrust.net/OU=www.entrust.net/CPS_2048 incorp. by ref. (limitsliab.)/OU=(c) 1999 Entrust.net Limited/CN=Entrust.net Certification Authority(2048)

    ---

    Server certificate

    -----BEGIN CERTIFICATE-----

    MIIFUTCCBDmgAwIBAgIRAP/KN+WwyNu6AAAAAFDYCGYwDQYJKoZIhvcNAQELBQAw

    gboxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL

    Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg

    MjAxMiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxLjAs

    BgNVBAMTJUVudHJ1c3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBMMUswHhcN

    MTYwNTA1MTcyMjM1WhcNMTgwNTAyMTc1MjM0WjB0MQswCQYDVQQGEwJVUzETMBEG

    A1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJQ3VwZXJ0aW5vMRMwEQYDVQQKEwpB

    cHBsZSBJbmMuMScwJQYDVQQDEx5nYXRld2F5LnNhbmRib3gucHVzaC5hcHBsZS5j

    b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDu8g0rJ6zbAczB/gpg

    QSDkK9ftwW38Z8bAgsV1uZXvYfjmq8h69bIy0v1TMie03mEMSm/WVHWisCFSJSIG

    11QX3Z6mgw+Qff4M3VccSTZaPiEofCp/feUXQqzImBhe4/RVyem9n6ZURAYyWYN9

    4xTedpSgvQQb5aTnEpmTGMgxL7pKBRRErtKB6PlqixJ176AisR4Q2KxVYwGakM9e

    a34qj+qbVu+v6B8XXV1CUA4OmS59dpQ5YXBYdHFEaqaDs+IhUrElB2hAg7Gcc649

    O/EeQqjs8VwsmGM+FYIftvfkyCfeJz1Lfk9fdNMcAm4jWh/n2LzqPT1fZaDS9L1E

    z1vjAgMBAAGjggGVMIIBkTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB

    BQUHAwEGCCsGAQUFBwMCMDMGA1UdHwQsMCowKKAmoCSGImh0dHA6Ly9jcmwuZW50

    cnVzdC5uZXQvbGV2ZWwxay5jcmwwSwYDVR0gBEQwQjA2BgpghkgBhvpsCgEFMCgw

    JgYIKwYBBQUHAgEWGmh0dHA6Ly93d3cuZW50cnVzdC5uZXQvcnBhMAgGBmeBDAEC

    AjBoBggrBgEFBQcBAQRcMFowIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmVudHJ1

    c3QubmV0MDMGCCsGAQUFBzAChidodHRwOi8vYWlhLmVudHJ1c3QubmV0L2wxay1j

    aGFpbjI1Ni5jZXIwKQYDVR0RBCIwIIIeZ2F0ZXdheS5zYW5kYm94LnB1c2guYXBw

    bGUuY29tMB8GA1UdIwQYMBaAFIKicHTdvFM/z3vU981/p2DGCky/MB0GA1UdDgQW

    BBQilGEKLJhxmPA0qq6OtdmvSADjszAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUA

    A4IBAQB9KaD/0IUHBykbu1detIDPgDCkvEkcTg+Aoge6h1yj1wkSv3d94mJif+jy

    /p9Yz0Uf69d7tMAK8nrefin4cBFSdJpj9HSk0AUaQoDFAz+BLuGMN8J05iwWKYf+

    IkS7sr+z3RgGkFyH9XCMAu+MrvCz/r+SIuM6AO2/F/iwmBU1UrerbCjBfPxM+sNB

    BU0lUv0w0Xm3/ZyDh+Em0u15vM8D4NtkK6v0K+3GdgBU6xGgpX68eqszSIbkRzMu

    Hke/uLEPEGV/r6N1NSgQqbajUVgqUCrG3GPHsmuVHCFWSP1YYc+2FwKYOLEUgbma

    yYBeaZ+LYzqYxyZzBvj+jTaRKi56

    -----END CERTIFICATE-----

    subject=/C=US/ST=California/L=Cupertino/O=AppleInc./CN=gateway.sandbox.push.apple.com

    issuer=/C=US/O=Entrust, Inc./OU=Seewww.entrust.net/legal-terms/OU=(c) 2012 Entrust, Inc. - for authorized use only/CN=EntrustCertification Authority - L1K

    ---

    Acceptable client certificate CA names

    /C=US/O=Apple Inc./OU=Apple CertificationAuthority/CN=Apple Root CA

    /C=US/O=Apple Inc./OU=Apple CertificationAuthority/CN=Apple Application Integration Certification Authority

    /C=US/O=Apple Inc./OU=Apple WorldwideDeveloper Relations/CN=Apple Worldwide Developer Relations CertificationAuthority

    /C=US/ST=CA/L=Cupertino/O=AppleInc./OU=Internet Software and Services/CN=iCloudTest/emailAddress=APNS-Dev@group.apple.com

    /CN=Apple Application Integration 2Certification Authority/OU=Apple Certification Authority/O=Apple Inc./C=US

    ---

    SSL handshake has read 3522 bytes and written2155 bytes

    ---

    New, TLSv1/SSLv3, Cipher is AES256-SHA

    Server public key is 2048 bit

    Secure Renegotiation IS supported

    Compression: NONE

    Expansion: NONE

    SSL-Session:

       Protocol  : TLSv1

       Cipher    : AES256-SHA

       Session-ID:

       Session-ID-ctx:

       Master-Key:7BD6F71EAF67BDB0ED6186E7471DDC701E255FE10CD6647DBC82A05D83BE682BC704E293EE85AC6A5B094DB70542E2A9

       Key-Arg   : None

       Start Time: 1480319802

       Timeout   : 300 (sec)

       Verify return code: 0 (ok)

    ---

    五.服务器端推送脚本(PHP):

    将iOSJpush.php这个推送脚本也放在push文件夹中(iOSJpush.php见附件):


    deviceToken填写你接收到的token,passPhrase则填写你的ck.pem设置的密码(即ristone2016)。

    使用终端进入到push文件夹,在终端输入 

    $ php iOSJpush.php

    Connected to APNS

    Message successfully delivered

    若显示以上提示则表示推送成功了。

     

    附上推送图片:



    iOSJpush.php


    <?php

        

        // ??????????deviceToken???????????????

        //ios10

        //$deviceToken = '715458212b09cf55c3385cc665f76a187f5abcd9db3f1888a9a69a60ee14413d';

        

        //ios9

        $deviceToken = 'e0cba87a9c99769c5fe1051fa3ac5b8fade2aaa6e732742c39a8dad3d46d4111';

        // Put your private key's passphrase here:

        $passphrase = 'ABC123';

        

        // Put your alert message here:

        $message = '[晨读]马上到吃饭时间了';

        

        

        

        ////////////////////////////////////////////////////////////////////////////////

        

        $ctx = stream_context_create();

        stream_context_set_option($ctx, 'ssl','local_cert', 'ck.pem');

        stream_context_set_option($ctx, 'ssl','passphrase', $passphrase);

        

        // Open a connection to the APNS server

        //??????????

        //$fp = stream_socket_client(?ssl://gateway.push.apple.com:2195?, $err, $errstr, 60, //STREAM_CLIENT_CONNECT, $ctx);

        //?????????????appstore??????

        $fp = stream_socket_client(

                                   'ssl://gateway.sandbox.push.apple.com:2195', $err,

                                   $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);

        

        if (!$fp)

        exit("Failed to connect: $err $errstr". PHP_EOL);

        

        echo'Connected to APNS' . PHP_EOL;

        

        // Create the payload body

        $body['aps'] =array(

                             'alert' => $message,

                             'sound' =>'default',

                             'badge' =>+1,

                             'type' =>1

                             );

        

        // Encode the payload as JSON

        $payload = json_encode($body);

        

        // Build the binary notification

        $msg = chr(0). pack('n',32) . pack('H*', $deviceToken). pack('n', strlen($payload)). $payload;

        

        // Send it to the server  

        $result = fwrite($fp, $msg, strlen($msg));  

        

        if (!$result)  

        echo'Message not delivered' . PHP_EOL;  

        else  

        echo'Message successfully delivered'. PHP_EOL;  

        

        // Close the connection to the server  

        fclose($fp);  

        ?>




    展开全文
  • 参考:...1.前期准备工作 (1)证书的创建 进入苹果Apple Developer -&gt; Member Center -&gt; Certificates, Identifiers &amp; Profiles – &...Identifiers - &...如上图所示,勾...

    参考:http://www.jianshu.com/p/9eae61bcc42e
    1.前期准备工作
    (1)证书的创建
    进入苹果Apple Developer -> Member Center -> Certificates, Identifiers & Profiles – >Identifiers - >App IDs–>Edit

     

    如上图所示,勾选Push Notifications,然后点击下面的Create Certificate,分别创建测试环境与生产环境的SSL推送证书。
    (2)用证书文件与私钥合成.pem文件给后台的同学
    完成第(1)步后,然后在进入苹果Apple Developer -> Member Center -> Certificates, Identifiers & Profiles – >Certificates,找到刚才的推送证书然后下载->双击安装。在钥匙串中找到这两个证书(production&developerment)。分别导出.p12文件(证书的p12与密钥的p12),如下图。

     

    得到证书的p12与密钥的p12后,打开命令行把p12文件转化为.pem文件
    假设我的证书的p12与密钥的p12分别命名为:apns-dev-cert.p12;apns-dev-key.p12
    首先cd到这两个文件的目录下,使用下面的命令分别得到apns-dev-cert.pem; apns-dev-key.pem;

    openssl pkcs12 -clcerts -nokeys -out apns-dev-cert.pem -in apns-dev-cert.p12
    
    openssl pkcs12 -nocerts -out apns-dev-key.pem -in apns-dev-key.p12
    

    然后再用apns-dev-cert.pem; apns-dev-key.pem;合成最终后台可以用来推送的apns-dev.pem文件。你把apns-dev.pem文件交给后台的小伙伴就可以了。

    cat apns-dev-cert.pem apns-dev-key.pem > apns-dev.pem
    

    production环境的.pem与developerment一样。
    这篇文章 详细的介绍了.pem如何制作的。
    2.实现推送
    打开你的项目->capabilication打开push notifications与background Modes,勾选最后一个remote notifications。


    如果是iOS10以上版本还需要引入UserNotifications.framework、UserNotificationsUI.framework这两个framework
    然后再application引入头文件#import <UserNotifications/UserNotifications.h>
    (1)注册推送

     

    在application里面的- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {}
    中添加代码。

    if (IOS_VERSION >= 10.0) {
    UNUserNotificationCenter * center = [UNUserNotificationCenter currentNotificationCenter];
    [center setDelegate:self];
    UNAuthorizationOptions type = UNAuthorizationOptionBadge|UNAuthorizationOptionSound|UNAuthorizationOptionAlert;
    [center requestAuthorizationWithOptions:type completionHandler:^(BOOL granted, NSError * _Nullable error) {
    if (granted) {
    DBLog(@"注册成功");
    }else{
    DBLog(@"注册失败");
    }
    }];
    }else if (IOS_VERSION >= 8.0){
    UIUserNotificationType notificationTypes = UIUserNotificationTypeBadge |
    UIUserNotificationTypeSound |
    UIUserNotificationTypeAlert;
    UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:notificationTypes categories:nil];
    [application registerUserNotificationSettings:settings];
    }else{//ios8一下
    UIRemoteNotificationType notificationTypes = UIRemoteNotificationTypeBadge |
    UIRemoteNotificationTypeSound |
    UIRemoteNotificationTypeAlert;
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:notificationTypes];
    }
    
    // 注册获得device Token
    
    [application registerForRemoteNotifications];
    

    (2)获取Token

    // 将得到的deviceToken传给SDK
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
    NSString *deviceTokenStr = [[[[deviceToken description]
    stringByReplacingOccurrencesOfString:@"<" withString:@""]
    stringByReplacingOccurrencesOfString:@">" withString:@""]
    stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSLog(@"deviceTokenStr:\n%@",deviceTokenStr);
    }
    
    // 注册deviceToken失败
    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
    NSLog(@"error -- %@",error);
    }
    

    在application里面的添加上面两个方法。然后再获取设备token成功的方法里面,我们需要把获取到的设备的token发送给后台,然后后台拿token去推送。
    (3)处理推送过来的消息
    1.iOS10以上版本的处理

    //在前台
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{
    // 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型可以设置
    completionHandler(UNNotificationPresentationOptionBadge|
    UNNotificationPresentationOptionSound|
    UNNotificationPresentationOptionAlert);
    }
    

    上面的这个方法,加上completionHandler(UNNotificationPresentationOptionBadge|
    UNNotificationPresentationOptionSound|
    UNNotificationPresentationOptionAlert);
    用户即使在前台,收到推送时通知栏也会出现,有声音和角标。如果去掉应用在前台有推送时并不会收到。

    - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler{
    //处理推送过来的数据
    [self handlePushMessage:response.notification.request.content.userInfo];
    completionHandler();        
    }
    

    这个方法是在用户点击了消息栏的通知,进入app后会来到这里。我们可以业务逻辑。比如跳转到相应的页面等。
    2.iOS10以下的处理

    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary * _Nonnull)userInfo fetchCompletionHandler:(void (^ _Nonnull)(UIBackgroundFetchResult))completionHandler{
    NSLog(@"didReceiveRemoteNotification:%@",userInfo);
    /*
    UIApplicationStateActive 应用程序处于前台
    UIApplicationStateBackground 应用程序在后台,用户从通知中心点击消息将程序从后台调至前台
    UIApplicationStateInactive 用用程序处于关闭状态(不在前台也不在后台),用户通过点击通知中心的消息将客户端从关闭状态调至前台
    */
    //应用程序在前台给一个提示特别消息
    if (application.applicationState == UIApplicationStateActive) {
    //应用程序在前台
    [self createAlertViewControllerWithPushDict:userInfo];
    }else{
    //其他两种情况,一种在后台程序没有被杀死,另一种是在程序已经杀死。用户点击推送的消息进入app的情况处理。
    [self handlePushMessage:userInfo];
    }
    completionHandler(UIBackgroundFetchResultNewData);
    }
    

    在application里面的添加上面的方法,iOS10以下,应用在前台的时候,有推送来,会直接来到这个方法。但是通知栏不会有提示,角标也不会有。应用如果在后台或者在关闭状态,点击推送来的消息也会来到这个方法。我们可以在这里处理业务逻辑。
    3.测试推送是否成功
    到这里我们完成了基本的推送功能,但是是否能够成功还不知道?我们可以测试一下。这里是一个测试推送的软件 大家从github下载下来,直接运行。
    1.把你的证书路径、token以及要推送的消息放到指定的地方。
    2.点击连接服务器,这个时候会有访问钥匙串的请求,允许访问钥匙串。
    3.点击发送。
    4.iOS10推送的进阶使用
    iOS10还可以实现推送页面UI的自定义,以及添加事件,下一篇文章会介绍。
    Xcode打开项目,File-->New-->Target;


    然后分别选UNNotificationServiceExtension、UNNotificationContent创建Target;

     

    然后在UNNotificationServiceExtension的- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {}方法中添加下面的代码;

    NSMutableArray *actionMutableArr = [[NSMutableArray alloc] initWithCapacity:1];
    UNNotificationAction * actionA  =[UNNotificationAction actionWithIdentifier:@"ActionA" title:@"不感兴趣" options:UNNotificationActionOptionAuthenticationRequired];
    UNNotificationAction * actionB = [UNNotificationAction actionWithIdentifier:@"ActionB" title:@"不感兴趣" options:UNNotificationActionOptionDestructive];
    UNNotificationAction * actionC = [UNNotificationAction actionWithIdentifier:@"ActionC" title:@"进去瞅瞅" options:UNNotificationActionOptionForeground];
    UNTextInputNotificationAction * actionD = [UNTextInputNotificationAction actionWithIdentifier:@"ActionD" title:@"作出评论" options:UNNotificationActionOptionDestructive textInputButtonTitle:@"send" textInputPlaceholder:@"say some thing"];
    [actionMutableArr addObjectsFromArray:@[actionA,actionB,actionC,actionD]];
    if (actionMutableArr.count) {
    UNNotificationCategory * notficationCategory = [UNNotificationCategory categoryWithIdentifier:@"categoryNoOperationAction" actions:actionMutableArr intentIdentifiers:@[@"ActionA",@"ActionB",@"ActionC",@"ActionD"] options:UNNotificationCategoryOptionCustomDismissAction];
    [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithObject:notficationCategory]];
    }
    

    上面的方法是添加推送消息下面的事件(进入应用查看,取消查看,快捷回复)的,如果你的应用不需要可以忽略;

    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    self.bestAttemptContent.categoryIdentifier = @"categoryNoOperationAction";
    // Modify the notification content here...
    //    self.bestAttemptContent.title = [NSString stringWithFormat:@"点击查看更多内容"];
    NSDictionary *dict =  self.bestAttemptContent.userInfo;
    //    NSDictionary *notiDict = dict[@"aps"];
    NSString *mediaUrl = [NSString stringWithFormat:@"%@",dict[@"media"][@"url"]];
    NSLog(@"%@",mediaUrl);
    if (!mediaUrl.length) {
    self.contentHandler(self.bestAttemptContent);
    }
    [self loadAttachmentForUrlString:mediaUrl withType:dict[@"media"][@"type"] completionHandle:^(UNNotificationAttachment *attach) {
    if (attach) {
    self.bestAttemptContent.attachments = [NSArray arrayWithObject:attach];
    }
    self.contentHandler(self.bestAttemptContent);
    }];
    
    //处理视频,图片的等多媒体
    - (void)loadAttachmentForUrlString:(NSString *)urlStr
    withType:(NSString *)type
    completionHandle:(void(^)(UNNotificationAttachment *attach))completionHandler{
    __block UNNotificationAttachment *attachment = nil;
    NSURL *attachmentURL = [NSURL URLWithString:urlStr];
    NSString *fileExt = [self fileExtensionForMediaType:type];
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    [[session downloadTaskWithURL:attachmentURL
    completionHandler:^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
    if (error != nil) {
    NSLog(@"%@", error.localizedDescription);
    } else {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *localURL = [NSURL fileURLWithPath:[temporaryFileLocation.path stringByAppendingString:fileExt]];
    [fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error];
    NSError *attachmentError = nil;
    attachment = [UNNotificationAttachment attachmentWithIdentifier:@"" URL:localURL options:nil error:&attachmentError];
    if (attachmentError) {
    NSLog(@"%@", attachmentError.localizedDescription);
    }
    }
    completionHandler(attachment);
    }] resume];
    }
    - (NSString *)fileExtensionForMediaType:(NSString *)type {
    NSString *ext = type;
    if ([type isEqualToString:@"image"]) {
    ext = @"jpg";
    }
    if ([type isEqualToString:@"video"]) {
    ext = @"mp4";
    }
    if ([type isEqualToString:@"audio"]) {
    ext = @"mp3";
    }
    return [@"." stringByAppendingString:ext];
    }
    

    上面的这两段代码是当推送消息来了后,我们将mdeia下的url内的文件下载到本地,然后将路径交给系统,进而实现推送多媒体文件的目的;
    这里说一下必须注意的两个坑、个坑、坑:
    1.将UNNotificationServiceExtension中的pilst文件中添加
    (1)在Info.plist中添加NSAppTransportSecurity类型Dictionary。
    (2)在NSAppTransportSecurity下添加NSAllowsArbitraryLoads类型Boolean,值设为YES
    这是因为从iOS9开始苹果不允许直接http访问,加上这两个字段就可以访问http,如果你不添加,只有你推送https的media文件才能被下载,http则不能被下载;

     

    2.选中工程---> UNNotificationServiceExtension所对应的Target-->Deploy Target设置为iOS10,因为是从iOS10才支持推送多媒体文件,它默认是从当前Xocde支持的最高版本,比如小编的手机版本iOS10.0.2,它默认是iOS 10.2.0.刚开始小编没有修改,推送死活出不来图片,只有文字;后台检查才发现这里是从iOS 10.2.0支持的,心中一万个草泥马崩腾而过;

     

     

    忙活了这么多还是看一下效果吧,我只做了图片与视频,声音应该差别不大。

     

    自定义推送的UI
    1.在UNNotificationServiceExtension的- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler方法内为self.bestAttemptContent添加categoryIdentifier

    self.bestAttemptContent.categoryIdentifier = @"myNotificationCategory";
    

    然后将这个categoryIdentifier粘贴在UNNotificationContent的infoplist内NSExtension-->NSExtensionAttributes-->UNNotificationExtensionCategory的字段内;然后在下面在添加一个字段UNNotificationExtensionDefaultContentHidden设置bool值为YES,这里是隐藏系统的UI;

     

    1. 在上面的下载多媒体文件的- (void)loadAttachmentForUrlString:(NSString *)urlStr withType:(NSString *)type completionHandle:(void(^(UNNotificationAttachment *attach))completionHandler;方法内添加
    NSMutableDictionary * dict = [self.bestAttemptContent.userInfo mutableCopy];
    [dict setObject:[NSData dataWithContentsOfURL:localURL] forKey:@"image"];
    self.bestAttemptContent.userInfo = dict;
    

    我这里是将图片的Data数据放到推送的userInfo里面,然后在自定义UI的UNNotificationContent内获取这个Date,然后加载UI;

    1. 在UNNotificationContent的maininterface.storyboard内部将UI做好,然后在UNNotificationContent的- (void)didReceiveNotification:(UNNotification *)notification ;方法内进行UI加载。

       

       

      来看一下自定义UI的效果。

       

     

    展开全文
  • IOS原生推送两个必要依赖
  • IOS原生代码实现推送

    2016-05-30 16:06:35
    好久没有写过博客啦,今天就由本菜鸟给大家做一个简单的IOSApp消息推送教程吧!一切从0开始,包括XCode6, IOS8, 以及苹果开发者中心最新如何注册应用,申请证书以及下载配置概要文件,相信很多刚开始接触ios的人会很...

    好久没有写过博客啦,今天就由本菜鸟给大家做一个简单的IOSApp消息推送教程吧!一切从0开始,包括XCode6, IOS8, 以及苹果开发者中心最新如何注册应用,申请证书以及下载配置概要文件,相信很多刚开始接触ios的人会很想了解一下。(ps:网上看了一下虽然有很多讲述推送的好教程,我也是看着一步步学会的,但是这些教程的时间都是去年或者更早时期的,对引导新手来说不是很合适)

    第一部分

    首先第一步当然是介绍一下苹果的推送机制(APNS)咯(ps:其实每一篇教程都有),先来看一张苹果官方对其推送做出解释的概要图。

    Provider是给你手机应用发出推送消息的服务器,而APNS(Apple Push Notification Service)则是苹果消息推送服务器。你本地的服务器当需要给应用推送一条消息的时候,先要将消息发出到苹果推送服务器,然后再由苹果推送服务器将消息发到安装了该应用的手机。

    接下来再看一张解释图:


    根据上图的逻辑我来给大家解释一下:

    1.你的IOS应用需要去注册APNS消息推送功能。

    2.当苹果APNS推送服收到来自你应用的注册消息就会返回一串device token给你(很重要)

    3.将应用收到的device Token传给你本地的Push服务器。

    4.当你需要为应用推送消息的时候,你本地的推送服务器会将消息,以及Device Token打包发送到苹果的APNS服

    5.APNS再将消息推送给目的iphone


    第二部分

    1.从证书颁发机构颁发证书

    打开你mac的钥匙串访问: 然后点击钥匙串访问

    随后它会弹出一个窗口 用户电子邮件信息

    就填写你苹果开发者账号的名称即可(应该是一个邮件名称),点击保存到磁盘的选项,点击继续,显示如下



    点击存储,文件名为:CertificateSigningRequest.certSigningRequest 随后将他放在一个文件夹中我们取名push吧!


    第三部分


    访问苹果开发者网址:https://developer.apple.com/

    选中MemberCenter选项,进入登陆页面,用你的苹果开发者账号登陆,过一会网页就会自动跳转到下图。

    点击红色所选部分

    内容进行下一步的操作。


    选择Certificates选项,设置证书,如图所示先解释一下

     

    Development选项的作用顾名思义就是用来作为开发使用的证书,Production选项则

    是用来发布产品使用的,名称很陌生是不是,之前的开发者网页是没有这一选项的,可能是苹果把他修改了,用这个名称更加能让人

    理解吧(字面上解释就是产品么)。两个选项生成证书的步骤是一样的,现在我们使用开发者的选项进行证书的制作,步骤如下:

    选择Development选项


    点击上面的加号选项,


    选择APNS选项(开发么当然是在沙盒环境下了,模拟真实情况),然后Continue


    这个AppID我们在下一部分讲如何生成,现在我用的是已经生成好的一个应用ID,继续Continue


    这边就要选择在钥匙串访问环节下载下来的CertificateSigningRequest.certSigningRequest文件了,选择并生成


    点击下载,得到aps_development .cer,保存到push文件中去。


    第四部分

    新建一个AppID,选择网页上的AppIDs,然后点击右上角的 “加号”


    App的取名只要按照苹果要求的就可以了


    然后BundleID是比较重要的,在提交审核以及测试(苹果的TestFlight)和付费环节都需要用到,也只需按照苹果要求来写就好了。


    接下来就是对你的应用需要使用苹果的哪些服务进行选择就行了,例如广告,游戏中心,推送,付费等等情况。


    最后选择“Submit”选项,在下一个界面中选择“done”选项,这样我们设置AppID的步骤我们就完成了。


    第五部分:生成Provisioning Profiles

    这个配置概要文件分为两种,一种是为开发使用的,还有一种则是为发布到appStore上面。


    创建发布版的ProvisioningProfile与开发版的流程相同,点击Development然后点击右上角的加号

    会进入选择何种配置概要文件的界面


    我们现在时测试,所以选择“IOS App Development”的选项,在下面的Distribution发布选项中有两个选择,“App Store”以及“Ad hoc”,你可以根据下面的描述

    选择你发布所需的选项。点击Continue进入下一步。


    选择你上一步创建的AppID,点击Continue 进行下一步


    选择你的开发者账号,Continue进行下一步


    在这一步上选择你的设备(你只有在这一步上勾选了你的设备,你才能在设备上用这个签名进行调试)。关于如何将你的设备号添加进去也是非常

    简单的,选择左侧的"Devices",然后点击右上角的加号,在随后出来的页面上添加你设备的UUID(在XCode中可以查看到)以及name( 可以随便取,自己看的懂就行)

    然后Register一下,照着流程走到最后一步就完成了。

    好咋们继续回到上面的Provisioning Profile配置环节,当你选好了你的设备后点击“Continue”进入下一页,


    输入一个文件名(最好是起的能看懂是干嘛的,当然也可以随便起),点击“Generate”进入下一个页面,在这个页面中就会有一个下载按钮让你下载这个文件,

    我们把它下载下来放在Push文件夹中。


    第六部分

    好了,前期的准备工作都已经做完了,现在让我们开始推送吧!(吼吼)

    首先双击我们生成的 “aps_development .cer” 文件,进入钥匙串访问,找到我们的专用秘钥(根据在第二部分中从证书机构颁发证书操作中填写的常用名)


    我在第二部分填写的是“silicon”,由于换了一台mac之前安装的没有了,之前没有截图,所以随便找了个图给大家看一下,凭大家的聪明才智应该不难理解吧。

    然后右击导出,会弹出如下所示的图。



    将他存储到push文件夹中,命名为“push.p12”,在这一步中导出会让你输入密码并验证,你可以自定义一个密码,例如abc123


    现在push文件夹中应该有几个文件“aps_development .cer” ,"push.p12",“CertificateSigningRequest.certSigningRequest”以及刚才下下来的配置概要文件。


    接下来我们打开终端将他们生成.pem文件

    1.把aps_development .cer文件生成.pcm文件,cd到push文件夹下


    2.把push.p12文件生成为.pem文件


    上边输入的密码则是你导出证书所设的密码,即abc123.接着还会让你输入.pem文件的密码,还是使用abc123好了,防止混淆。

    这样我们在push文件夹中就又得到了两个文件,PushChatCert.pem和PushChatKey.pem。


    3.把PushChatCert.pem和PushChatKey.pem合并为一个pem文件,


    在push文件夹中又多了一个ck.pem文件,以上我们把需要使用的文件都准备好了


    接下来就要测试一下啦,是不是很激动~

    为了测试证书工作的状况,我们可以使用“telnet gateway.sandbox.push.apple.com 2195”来检测一下,如果显示


    则表示成功了。


    然后,我们使用我们生成的证书和私钥来设置一个安全的链接去链接苹果服务器

    在终端输入如下命令:openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert PushChatCert.pem -key PushChatKey.pem

    需要输入密码(abc123 我们刚才所设置的)。

    然后他会返回一系列的数据,这里我就粘贴一部分啦:

    CONNECTED(00000003)

    depth=1 /C=US/O=Entrust, Inc./OU=www.entrust.net/rpa is incorporated by reference/OU=(c) 2009 Entrust, Inc./CN=Entrust Certification Authority - L1C

    verify error:num=20:unable to get local issuer certificate

    verify return:0

    ---

    Certificate chain

    。。。。。(省略)

    。。。。。(省略)

    。。。。。(省略)

        Start Time: 1416389389

        Timeout   : 300 (sec)

        Verify return code: 0 (ok)

    ---

    测试就到这里啦。。。


    第七部分

    1.建立推送项目

    [objc] view plain copy
     在CODE上查看代码片派生到我的代码片
    1. //  
    2. //  AppDelegate.m  
    3. //  TestPushNotifiy  
    4. //  
    5. //  Created by silicon on 14-10-30.  
    6. //  Copyright (c) 2014年 silicon. All rights reserved.  
    7. //  
    8.   
    9. #import "AppDelegate.h"  
    10.   
    11. @implementation AppDelegate  
    12. @synthesize mainView = _mainView;  
    13.   
    14. - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions  
    15. {  
    16.     if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)])  
    17.     {  
    18.         //IOS8  
    19.         //创建UIUserNotificationSettings,并设置消息的显示类类型  
    20.         UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIRemoteNotificationTypeSound) categories:nil];  
    21.           
    22.         [application registerUserNotificationSettings:notiSettings];  
    23.           
    24.     } else// ios7  
    25.         [application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge                                       |UIRemoteNotificationTypeSound                                      |UIRemoteNotificationTypeAlert)];  
    26.     }  
    27.       
    28.     self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];  
    29.     // Override point for customization after application launch.  
    30.     self.window.backgroundColor = [UIColor whiteColor];  
    31.     [self.window makeKeyAndVisible];  
    32.       
    33.     self.mainView = [[MainViewController alloc] initWithNibName:@"MainViewController" bundle:nil];  
    34.     self.window.rootViewController = self.mainView;  
    35.     return YES;  
    36. }  
    37.   
    38. - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken{  
    39.     NSLog(@"---Token--%@", pToken);  
    40. }  
    41.   
    42. - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{  
    43.       
    44.     NSLog(@"userInfo == %@",userInfo);  
    45.     NSString *message = [[userInfo objectForKey:@"aps"]objectForKey:@"alert"];  
    46.       
    47.     UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:message delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil nil];  
    48.       
    49.     [alert show];  
    50. }  
    51.   
    52. - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{  
    53.   
    54.     NSLog(@"Regist fail%@",error);  
    55. }  
    56.   
    57. - (void)applicationWillResignActive:(UIApplication *)application  
    58. {  
    59.     // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.  
    60.     // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.  
    61. }  
    62.   
    63. - (void)applicationDidEnterBackground:(UIApplication *)application  
    64. {  
    65.     // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.   
    66.     // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.  
    67. }  
    68.   
    69. - (void)applicationWillEnterForeground:(UIApplication *)application  
    70. {  
    71.     // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.  
    72. }  
    73.   
    74. - (void)applicationDidBecomeActive:(UIApplication *)application  
    75. {  
    76.     // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.  
    77. }  
    78.   
    79. - (void)applicationWillTerminate:(UIApplication *)application  
    80. {  
    81.     // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.  
    82. }  
    83.   
    84. @end  

    在appdelegate.m中加入以上代码,

    [objc] view plain copy
     在CODE上查看代码片派生到我的代码片
    1. if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)])  
    2.     {  
    3.         //IOS8  
    4.         //创建UIUserNotificationSettings,并设置消息的显示类类型  
    5.         UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIRemoteNotificationTypeSound) categories:nil];  
    6.           
    7.         [application registerUserNotificationSettings:notiSettings];  
    8.           
    9.     } else// ios7  
    10.         [application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge                                       |UIRemoteNotificationTypeSound                                      |UIRemoteNotificationTypeAlert)];  
    11.     }  
    由于ios8的推送跟ios7及以下的不一样,所以需要加判断来注册消息推送。

    函数:

    [objc] view plain copy
     在CODE上查看代码片派生到我的代码片
    1. - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken{  
    2.     NSLog(@"---Token--%@", pToken);  
    3. }  
    会接收来自苹果服务器给你返回的deviceToken,然后你需要将它添加到你本地的推送服务器上。(很重要,决定你的设备能不能接收到推送消息)。

    [objc] view plain copy
     在CODE上查看代码片派生到我的代码片
    1. - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{  
    2.       
    3.     NSLog(@"userInfo == %@",userInfo);  
    4.     NSString *message = [[userInfo objectForKey:@"aps"]objectForKey:@"alert"];  
    5.       
    6.     UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:message delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil nil];  
    7.       
    8.     [alert show];  
    9. }  

    这个函数则是当设备接收到来自苹果推送服务器的消息时触发的,用来显示推送消息。

    [objc] view plain copy
     在CODE上查看代码片派生到我的代码片
    1. - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{  
    2.   
    3.     NSLog(@"Regist fail%@",error);  
    4. }  
    当注册失败时,触发此函数。


    2.PHP服务端

    将simplepush.php这个推送脚本也放在push文件夹中

    [php] view plain copy
     在CODE上查看代码片派生到我的代码片
    1. <?php  
    2.   
    3. // ??????????deviceToken???????????????  
    4. $deviceToken = 'c95f661371b085e2517b4c12cc76293522775e5fd9bb1dea17dd80fe85583b41';  
    5.   
    6. // Put your private key's passphrase here:  
    7. $passphrase = 'abc123';  
    8.   
    9. // Put your alert message here:  
    10. $message = 'My first push test!';  
    11.   
    12. ////////////////////////////////////////////////////////////////////////////////  
    13.   
    14. $ctx = stream_context_create();  
    15. stream_context_set_option($ctx'ssl''local_cert''ck.pem');  
    16. stream_context_set_option($ctx'ssl''passphrase'$passphrase);  
    17.   
    18. // Open a connection to the APNS server  
    19. //??????????  
    20.  //$fp = stream_socket_client(?ssl://gateway.push.apple.com:2195?, $err, $errstr, 60, //STREAM_CLIENT_CONNECT, $ctx);  
    21. //?????????????appstore??????  
    22. $fp = stream_socket_client(  
    23. 'ssl://gateway.sandbox.push.apple.com:2195'$err,  
    24. $errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);  
    25.   
    26. if (!$fp)  
    27. exit("Failed to connect: $err $errstr" . PHP_EOL);  
    28.   
    29. echo 'Connected to APNS' . PHP_EOL;  
    30.   
    31. // Create the payload body  
    32. $body['aps'] = array(  
    33. 'alert' => $message,  
    34. 'sound' => 'default'  
    35. );  
    36.   
    37. // Encode the payload as JSON  
    38. $payload = json_encode($body);  
    39.   
    40. // Build the binary notification  
    41. $msg = chr(0) . pack('n', 32) . pack('H*'$deviceToken) . pack('n'strlen($payload)) . $payload;  
    42.   
    43. // Send it to the server  
    44. $result = fwrite($fp$msgstrlen($msg));  
    45.   
    46. if (!$result)  
    47. echo 'Message not delivered' . PHP_EOL;  
    48. else  
    49. echo 'Message successfully delivered' . PHP_EOL;  
    50.   
    51. // Close the connection to the server  
    52. fclose($fp);  
    53. ?>  
    deviceToken填写你接收到的token,passPhrase则填写你的ck.pem设置的密码。

    此刻就是见证奇迹的时候了

    使用终端进入到push文件夹,在终端输入 php simplepush.php


    若显示以上提示则表示推送成功了。

    附上一张成功图。



    推送就到这边吧!

    展开全文
  • ios 原生推送配置

    2016-04-15 11:03:28
    点击打开链接 点击打开链接 ... 1、将aps_developer_identity.cer转换成aps_developer_identity.pem格式   openssl x509 -in aps_developer_identity.cer -inform DER -out aps_

    点击打开链接

    点击打开链接

    http://eric-gao.iteye.com/blog/1567777


    1、将aps_developer_identity.cer转换成aps_developer_identity.pem格式


    openssl x509 -in aps_developer_identity.cer -inform DER -outaps_developer_identity.pem -outform PEM  
     

     2、将p12格式的私钥转换成pem


    openssl pkcs12 -nocerts -out Push_Noenc.pem -inPush.p12 


    4、创建p12文件


    openssl pkcs12 -export -in aps_developer_identity.pem -inkeyPush_Noenc.pem -certfile Push.certSigningRequest -name "aps_developer_identity" -outaps_developer_identity.p12  
     
    这样我们就得到了在.net或java等后台应用程序中使用的证书文件:aps_developer_identity.p12

    接下来就要测试一下啦,是不是很激动~

    为了测试证书工作的状况,我们可以使用“telnet gateway.sandbox.push.apple.com 2195”来检测一下,如果显示



    则表示成功了。


     

    然后,我们使用我们生成的证书和私钥来设置一个安全的链接去链接苹果服务器

    在终端输入如下命令:openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert PushChatCert.pem -key PushChatKey.pem

    需要输入密码(abc123 我们刚才所设置的)。

    然后他会返回一系列的数据,这里我就粘贴一部分啦:


    CONNECTED(00000003)

    depth=1 /C=US/O=Entrust, Inc./OU=www.entrust.net/rpa is incorporated by reference/OU=(c) 2009 Entrust, Inc./CN=Entrust Certification Authority - L1C

    verify error:num=20:unable to get local issuer certificate

    verify return:0

    ---

    Certificate chain

    。。。。。(省略)

    。。。。。(省略)

    。。。。。(省略)


        Start Time: 1416389389

        Timeout   : 300 (sec)

        Verify return code: 0 (ok)

    ---

    测试就到这里啦。。。



    ------代码----

    #pragma mark - 接受deviceToken



    -(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{

        

        __block NSString *token1 = [NSString stringWithFormat:@"%@", deviceToken];



        NSLog(@"My token is:%@", token1);

        

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

            

            token1 = [token1 stringByReplacingOccurrencesOfString:@"<" withString:@""];

            token1 = [token1 stringByReplacingOccurrencesOfString:@">" withString:@""];

            

            [SANDBOXHELPER saveCoreInfoToSandBox:token1 withKey:kDeviceTokenInSandBox];

            

            NSMutableDictionary * dic = [NSMutableDictionary dictionaryWithObject:token1 forKey:@"deviceToken"];

            

            if(getUserId && ![[getUserId stringValue] isEqualToString:@""]){//如果userID不为空

                

                [dic setObject:getUserId forKey:@"memberId"];

            }

            

            [[JSFAPI shareInstance] do_request:upload_device_token withData:dic withSuccessHandler:^(NSDictionary *json) {

                

                PYLog(@"%@",json);

                

            } fail:^(NSError *error) {

                

                PYLog(@"%@",error);

                

            }];

            

        });

        

        

    }



    - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings{



        NSLog(@"My token is:");

        

    }





    - (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error

    {

        NSLog(@"Failed to get token, error: %@" , error);

    }



    #pragma mark - 接受到push



    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{

        

        PYLog(@"%@",userInfo);

        

        [UIApplication sharedApplication].applicationIconBadgeNumber++;

        

        UIAlertView * alert = [[UIAlertView alloc] initWithTitle:nil message:userInfo[@"aps"][@"alert"] delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"去看看", nil];

        

        [alert show];

        

        objc_setAssociatedObject(alert, &alertViewAndUserInfo, userInfo, OBJC_ASSOCIATION_RETAIN);



    }

    展开全文
  • iOS 推送(苹果原生)

    2020-02-27 18:52:53
    推送对App的重要性不言而喻,是每一个iOS开发者必修的技能。网上的资料对于初学者并不友好(至少对于我来说),其中有许多坑。并且由于要配置证书,只能真机调试等,学起来更是难上加难。这篇文章是从刚开始接触推送...

    来自:https://www.jianshu.com/p/3fc46a8764ed

    前言

    推送对App的重要性不言而喻,是每一个iOS开发者必修的技能。网上的资料对于初学者并不友好(至少对于我来说),其中有许多坑。并且由于要配置证书,只能真机调试等,学起来更是难上加难。这篇文章是从刚开始接触推送的起点写起。最近有点忙,有些地方没有细究,只是暂时知道了能实现什么,并且贴出了一些文章的链接,方便进一步研究,如果有错误的地方请指出来。

    image

    这篇先探究苹果原生态推送,下篇再写极光推送和IMiOS —— 极光推送和极光IM

    目录

    1. 苹果原生态推送

    2. 推送知识点及疑惑的地方

    3. 推送消息调用方法的时机,以及系统能做到的更多骚扩展。

    苹果原生态推送

    • iOS推送分为本地推送和远程推送。

      它们都需要用户授权。本地不需要联网和证书,远程需要联网和证书。

      本地推送比如闹钟,可以设置推送时间,是否重复推送,通知内容等。

      远程推送,就多坑了。本文主要探讨远程推送。

    远程推送整体流程

    image


    实现步骤

    1. 在项目 target 中,打开Capabilitie -> Push Notifications,并会自动在项目中生成 .entitlement 文件。打开Capablitie -> Background Modes -> Remote notifications。

    2. iOS8.0以上在AppDelegate.m中

    #ifdef NSFoundationVersionNumber_iOS_9_x_Max
    #import <UserNotifications/UserNotifications.h>
    #endif
    

    并且遵循协议UNUserNotificationCenterDelegate(该协议是iOS10.0+用,后面再提到)。

    1. 向APNs服务器注册deviceToken
    • 作用:苹果APNs服务器找到对应的设备和App。

    • 原理:通过设备UDID和App的Bundle Identifier生成deviceToken。这样APNs服务器接受到信息就知道转发给哪个设备的哪个App。

    • 做法:

    在程序首次调用以下方法时,会请求推送权限。

    首先用户要允许App发送通知。然后App发起注册,最终注册成功回调方法获得deviceToken。这个过程通常是在程序刚启动的时候。

    注意:发起注册的方法在iOS8.0后更新了一次,10.0后又更新了一次。

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        [self replyPushNotificationAuthorization:application];
        return YES;
    }
    
    
    - (void)replyPushNotificationAuthorization:(UIApplication *)application{
        if (IOS10_OR_LATER) {
            //iOS 10 later
            UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
            //必须写代理,不然无法监听通知的接收与点击事件
            center.delegate = self;
            [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
                if (!error && granted) {
                    //用户点击允许
                    NSLog(@"注册成功");
                }else{
                    //用户点击不允许
                    NSLog(@"注册失败");
                }
            }];
     
            // 可以通过 getNotificationSettingsWithCompletionHandler 获取权限设置
            //之前注册推送服务,用户点击了同意还是不同意,以及用户之后又做了怎样的更改我们都无从得知,现在 apple 开放了这个 API,我们可以直接获取到用户的设定信息了。注意UNNotificationSettings是只读对象哦,不能直接修改!
            [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
                NSLog(@"========%@",settings);
    //打印结果 ========<UNNotificationSettings: 0x1740887f0; authorizationStatus: Authorized, notificationCenterSetting: Enabled, soundSetting: Enabled, badgeSetting: Enabled, lockScreenSetting: Enabled, alertSetting: NotSupported, carPlaySetting: Enabled, alertStyle: Banner>        
            }];
        }else if (IOS8_OR_LATER){
            //iOS 8 - iOS 10系统
            UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
            [application registerUserNotificationSettings:settings];
        }else{
            //iOS 8.0系统以下
            [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound];
        }
     
        //注册远端消息通知获取device token
        [application registerForRemoteNotifications];
    
    }
        
    

    对以上注册方法说几句

    • iOS8.0和iOS10.0方法都有个categories,这里为了方便写为nil。其作用是针对推送做拓展功能。这里给出图,让读者暂时知道有什么用,还能自定义这个界面,文章后面会再提到。

    iOS8的方法,能直接输入。

     

    image

    iOS10的方法,能增加按钮事件。

     

    image

     

    iOS10的方法,自定义界面。

     

    image

    • iOS8.0以上的设备不能用registerForRemoteNotificationTypes:,否则无论如何也不能注册成功。
    • 参数枚举,表示收到推送后要如何显示。
    None:不显示任何东西。
    Alert:弹出提示框。
    Badge:App小红点。
    Sound:发出声音或震动。
    

    实测极光推送,Alert< Badge < Sound。即只设置了Badge还是会默认带上Alert,设置了Sound会默认带上Badge和Sound。不过看接口是按位或操作,还是乖乖写JPAuthorizationOptionAlert|JPAuthorizationOptionBadge|JPAuthorizationOptionSound

    image


    注册结果回调

    #pragma  mark - 获取device Token
    //获取DeviceToken成功
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
     
        //解析NSData获取字符串
        //我看网上这部分直接使用下面方法转换为string,你会得到一个nil(别怪我不告诉你哦)
        //错误写法
        //NSString *string = [[NSString alloc] initWithData:deviceToken encoding:NSUTF8StringEncoding];
     
     
        //正确写法
        NSString *deviceString = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]];
        deviceString = [deviceString stringByReplacingOccurrencesOfString:@" " withString:@""];
     
        NSLog(@"deviceToken===========%@",deviceString);
    }
     
    //获取DeviceToken失败
    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
        NSLog(@"[DeviceToken Error]:%@\n",error.description);
    }
    
    

    获取deviceToken存在本地,等用户登录后,和账号id一起发送到服务器绑定起来。如整体流程图那样。

    1. 收到推送后回调方法

    iOS10.0之前

    • 处理本地推送
    // 处理本地推送
    - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
        
    }
    
    • 处理远程消息

    调用时机:App处于前台收到推送;在iOS7后,开启了 Remote Notification,App处于后台收到推送。

    // 处理远程消息
    // 方法二是在iOS 7之后新增的方法,可以说是 方法一 的升级版本,如果app最低支持iOS 7的话可以不用添加 方法一了。
    //- (void)application:(UIApplication *)application //didReceiveRemoteNotification:(NSDictionary *)userInfo
    //{
    //    NSLog(@"userinfo:%@",userInfo);
    //    NSLog(@"收到推送消息:%@",[[userInfo objectForKey:@"aps"] objectForKey:@"alert"]);
    //}
    
    // 其中completionHandler这个block可以填写的参数UIBackgroundFetchResult是一个枚举值。主要是用来在后台状态下进行一些操作的,比如请求数据,操作完成之后,必须通知系统获取完成,可供选择的结果有
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
        NSLog(@"userinfo:%@",userInfo);
        NSLog(@"收到推送消息:%@",[[userInfo objectForKey:@"aps"] objectForKey:@"alert"]);
        
        completionHandler(UIBackgroundFetchResultNewData);
    //    typedef NS_ENUM(NSUInteger, UIBackgroundFetchResult) {
    //        UIBackgroundFetchResultNewData,
    //        UIBackgroundFetchResultNoData,
    //        UIBackgroundFetchResultFailed
    //    }
    
    }
    
    

    这里解释一下userInfo,详细点进链接看。

    {
      "aps" : {
        "alert" : {
          "title" : "iOS远程消息,我是主标题!-title",
          "subtitle" : "iOS远程消息,我是主标题!-Subtitle",
          "body" : "Dely,why am i so handsome -body"
        },
        "badge" : "2"
      }
    }
    
    作者:Dely
    链接:https://www.jianshu.com/p/c58f8322a278
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
    

    image


    这里补充一点东西。

    远程推送通知,分为 普通推送/后台推送/静默推送 3 种类型。

    推送通知-后台通知/静默通知文章里,有深入介绍。

    简单的说,userInfo中的aps中可以设置一个键值对"content-available" : 1,其代表后台推送。在后台推送基础上不设置badge、sound、aleert的静默推送可以静悄悄让App更新数据。

    这三种类型对应调用的方法会有所不同,文末会说。


    iOS10.0之后,新增两个方法。原来的方法还是需要实现的,各自的调用时机不一样。

    • 本地推送和远程推送合在同一个方法里。
    // App处于前台接收到通知
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
        if([notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
            NSLog(@"iOS10 收到远程通知");
        }else {
            // 判断为本地通知
            NSLog(@"iOS10 收到本地通知");
        }
        
        // 在前台默认不显示推送,如果要显示,就要设置以下内容
        // 微信设置里-新消息通知-微信打开时-声音or振动  就是该原理
        // 需要执行这个方法,选择是否提醒用户,有Badge、Sound、Alert三种类型可以设置
        completionHandler(UNNotificationPresentationOptionBadge|
                          UNNotificationPresentationOptionSound|
                          UNNotificationPresentationOptionAlert);
    
    }
    

    个人觉得这里iOS 10的方法didRecieve有歧义,实际是点击推送才调用。

    // 点击通知后会调用
    - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
        completionHandler(UIBackgroundFetchResultNewData);
        NSDictionary *userInfo = response.notification.request.content.userInfo;
        //程序关闭状态点击推送消息打开 可以在App启动方法的launchOptions获知
        if (self.isLaunchedByNotification) {
            //TODO
        }
        else{
            //前台运行
            if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) {
                //TODO
            }
            //后台挂起时
            else{
                //TODO
            }
            //收到推送消息手机震动,播放音效
            AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
            AudioServicesPlaySystemSound(1007);
        }
        //设置应用程序角标数为1
        [UIApplication sharedApplication].applicationIconBadgeNumber = 1;
        
        // 此处必须要执行下行代码,不然会报错
        completionHandler();
    }
    

    这些方法还有坑,笔者放到本文最后的调用时机里再说。


    到这里已经把整个流程大致描绘出来了。

    这里补充一些知识点,和提出一些疑惑的地方。

    补充的知识点

    补充点小知识点,应该能更好地理解原理。

    • APNs与设备保持长连接。(APNs服务器真猛,信息量这么大)

    • 基于上一点,不难理解,推送本身是 iOS 系统的行为。所以在 App 没有运行(没有在前台也没有在后台)的时候:

    1⃣️仍然能够推送及接收(通知中心通知、顶部横幅、刷新 App 右上角的小圆点即 badge [以下简称角标] 等都会由系统来控制和展示)。

    2⃣️(收到推送时,是无法在 App 的代码中获取到通知内容的。因为沙盒机制,此时 App 的任何代码都不可能被执行。)这个观点是在另一篇文章看到的,但笔者实测发现并不是这么回事。处于后台收到推送是会执行代码的。

    所以就算App关掉后台应用刷新,照样能接收到推送。

    以微信为例,如果你打开了后台应用刷新,那么当你收到新消息提醒之后,你打开微信,未读信息已经在那了(如果网速没问题的话);而当你收到新消息提示,打开微信后,消息才刚刚开始收取。

    • iOS的推送,内容的大小最大是4KB。
      A发送很长的文字给B,是发到服务器,由服务器暂存数据,通知B来取。B收到推送后去服务器下载。(图片、语音等类似)

    • iOS10以后,收到推送能在后台下载附件,限制:图片是10M Video是50M。
      有兴趣的可以去研究下,https://www.jianshu.com/p/f5337e8f336d

    • APNs服务器会向App服务器返回一个发送结果。(虽然这是后台的事,还是稍微了解下)

    疑惑的地方

    • 怎么针对特定的人群发出推送。例如群聊。

      是把群id发到服务器,然后服务器查出成员各自的id对应的deviceToken发出推送吗?极光有个根据Tag推送,以后了解到会更新文章。

    • 如果该账号并不在线,那这条消息要怎么处理?

      把消息暂时存在后台服务器,等待有设备登录这个账号后绑定userId和deviceToken后,发起推送。


    点击推送默认只是打开App。但也能增加一些操作,例如跳转到某个界面、微信直接回复等。

    所以最后深入了解推送还能怎么玩,以及解释App在前台、挂起、关闭状态下对应的方法。

    推送消息的方法调用时机以及特殊处理

    • App在各种状态下的调用方法

    1.如果App被kill掉的情况下,点击了推送,那么首先要启动App,会调用- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    参数launchOptions能分辨出是通过点击App icon还是点击推送打开App。

    1.  

    在iOS7.0之前,处于前台收到推送,调用(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo

    在iOS7.0~10.0-,处于前台收到推送,调用- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler

    坑了笔者的是,在iOS7.0+(包括10.0后),收到后台推送和静默推送,调用- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler

    3.iOS10.0+就功能就丰(皮)富了。

    在前台时收到推送,调用- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler

    所以,App处于前台时收到后台推送或静默推送的话,在iOS10+会调用两个方法,注意不要重复处理。- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler。如果你还点击了推送,那么还会调用下面的方法。。。

    点击推送,调用- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler

    在iOS10后,针对这三个方法,为了不重复处理同一条推送的内容。笔者建议尽量同一模块的推送,在这各个方法中不要相同逻辑处理。

    例如QQ,如果采用后台或静默推送,就只在静默中处理;

    不是静默推送。在前台收到,就红点显示,通知栏不要显示。如果不在前台,通知栏显示,点击后可跳转到对应聊天框。

    • 之前上面提过的category对推送做拓展。因为项目暂时没有这个需求,而求最近有点忙,就先不钻研这部分了。找到了一篇好文章,介绍了iOS10的推送按钮操作。

    • iOS10.0后能对推送进行查找和删除
      利用场景,例如微信撤回后,原本的推送将被改为"用户"撤回了一条信息。

    // Notification requests that are waiting for their trigger to fire
    //获取未送达的所有消息列表
    - (void)getPendingNotificationRequestsWithCompletionHandler:(void(^)(NSArray<UNNotificationRequest *> *requests))completionHandler;
    //删除所有未送达的特定id的消息
    - (void)removePendingNotificationRequestsWithIdentifiers:(NSArray<NSString *> *)identifiers;
    //删除所有未送达的消息
    - (void)removeAllPendingNotificationRequests;
    
    // Notifications that have been delivered and remain in Notification Center. Notifiations triggered by location cannot be retrieved, but can be removed.
    //获取已送达的所有消息列表
    - (void)getDeliveredNotificationsWithCompletionHandler:(void(^)(NSArray<UNNotification *> *notifications))completionHandler __TVOS_PROHIBITED;
    //删除所有已送达的特定id的消息
    - (void)removeDeliveredNotificationsWithIdentifiers:(NSArray<NSString *> *)identifiers __TVOS_PROHIBITED;
    //删除所有已送达的消息
    - (void)removeAllDeliveredNotifications __TVOS_PROHIBITED;
    
    作者:Dely
    链接:https://www.jianshu.com/p/c58f8322a278
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
    

    参考

    展开全文
  • ios原生推送

    2016-11-08 13:08:06
    准备: (1) .cer的SSH证书 (2) 私钥.p12文件 ... 1. 将证书转化成.pem文件 ...openssl x509 -in aps_dev.ceer -inform der -out cert.pem ...openssl pkcs12 -nocerts -out key.p12 -in key.pem ...Enter I
  • 以前曾经写过iOS的远程推送那时候是用OC语言实现的,现在简单的介绍一下iOS的本地推送 相比远程推送本地推送省略了麻烦的证书问题所以很简单。 一样的我们在程序的开始都需要注册推送: func application...
  • 原文链接:http://www.jianshu.com/p/032bfc949917 前言:现在第三方推送也很多 ,比如极光,融云,信鸽,其原理也是相同利用APNS推送机制 ,前段公司让做自己的推送,1.避...
  • PHP实现IOS消息推送

    2017-09-08 20:08:34
    IOS推送消息是许多IOS应用都具备的功能,最近也在研究这个功能,参考了很多资料终于搞定了,下面就把步骤拿出来分享下:       iOS消息推送的工作机制可以简单的用下图来概括:   Provider是指某个...
  • 推送是手机中非常常见的功能了。...每个时候只要保持一个与APNs的常链接,服务器将要推送的消息发送给APNs,APNs再将消息转发到响应的手机APP,就能很好的实现推送功能。由于手机APP所有的推送功能有统
  • 首先我们看一下苹果官方给出的对ios推送机制的解释。如下图   Provider就是我们自己程序的后台服务器,APNS是Apple Push Notification Service的缩写,也就是苹果的推送服务器。 上图可以分为三个阶段: 第...
  • 苹果原生推送服务

    2015-05-14 13:26:05
    苹果原生推送服务   一、APNS的推送机制 二、APNS推送步骤 三、APNS推送的详细工作流程 四、APNS推送开发准备 五、研发与测试
  • 前言 我们首先要在AppDelegate里面进行iOS的...iOS原生推送(APNS)的实现, 如果已经适配过了请忽略。 程序实现 Xcode打开项目,File–&gt;New–&gt;Target; 然后分别选UNNotificationServi...
  • IOS消息推送机制 ios消息推送主要主流有两种方式,一种是基于javaapns.jar和javaPNS.jar的开源的消息推送,javaPNS.jar支持多线程。
  • ios 推送丢失问题

    2014-09-24 10:35:29
    以下要讨论的推送丢失问题,前提是ios已经成功推送,但到达率不高的现象。 推送的整体实现环境:APP推送 架设 | ios 推送 PHP具体实现 在采用app推送后,一度为了追求最大的推送消息量,而将ios推送的一次...
  • 本文主要介绍Java服务器端如何借助第三方推送平台(百度云推送推送给移动端消息。 使用案例介绍: 根据客户的需求,需要做一个类似淘宝消息推送的功能,客户下订单、订单付款、订单商品已发货,以及客户完成评论,...
  • ios原生php端推送信息

    2016-10-14 14:01:12
    准备: (1) .cer的SSH证书 (2) 私钥.p12文件 ... 1. 将证书转化成.pem文件 ...openssl x509 -in aps_dev.ceer -inform der -out cert.pem ...openssl pkcs12 -nocerts -out key.p12 -in key.pem ...Enter I
  • IOS推送消息(java实现)

    2016-10-09 18:01:35
    1 首先在mac系统下制作用于推送的证书,然后导入钥匙串。证书制作过程这里不在叙述。将证书和专有密钥一起导出,输入密码。然后导出,命名aaa.p12文件,将文件拷贝出来,放到windows下。   2 打开eclipse,...
1 2 3 4 5 ... 20
收藏数 8,390
精华内容 3,356
关键字:

11原生推送实现 ios