精华内容
下载资源
问答
  • iOS 开发之实现 App 消息推送(最新)

    万次阅读 多人点赞 2014-11-19 21:13:10
    今天就由本菜鸟给大家做一个简单的IOSApp消息推送教程吧!一切从0开始,包括XCode6, IOS8, 以及苹果开发者中心最新如何注册应用,申请证书以及下载配置概要文件,相信很多刚开始接触ios的人会很想了解一下。(ps:...

    好久没有写过博客啦,今天就由本菜鸟给大家做一个简单的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.建立推送项目

     

    //
    //  AppDelegate.m
    //  TestPushNotifiy
    //
    //  Created by silicon on 14-10-30.
    //  Copyright (c) 2014年 silicon. All rights reserved.
    //
    
    #import "AppDelegate.h"
    
    @implementation AppDelegate
    @synthesize mainView = _mainView;
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)])
        {
            //IOS8
            //创建UIUserNotificationSettings,并设置消息的显示类类型
            UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIRemoteNotificationTypeSound) categories:nil];
            
            [application registerUserNotificationSettings:notiSettings];
            
        } else{ // ios7
            [application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge										 |UIRemoteNotificationTypeSound										 |UIRemoteNotificationTypeAlert)];
        }
        
        self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        // Override point for customization after application launch.
        self.window.backgroundColor = [UIColor whiteColor];
        [self.window makeKeyAndVisible];
        
        self.mainView = [[MainViewController alloc] initWithNibName:@"MainViewController" bundle:nil];
        self.window.rootViewController = self.mainView;
        return YES;
    }
    
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken{
        NSLog(@"---Token--%@", pToken);
    }
    
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
        
        NSLog(@"userInfo == %@",userInfo);
        NSString *message = [[userInfo objectForKey:@"aps"]objectForKey:@"alert"];
        
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:message delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
        
        [alert show];
    }
    
    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
    
        NSLog(@"Regist fail%@",error);
    }
    
    - (void)applicationWillResignActive:(UIApplication *)application
    {
        // 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.
        // 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.
    }
    
    - (void)applicationDidEnterBackground:(UIApplication *)application
    {
        // 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. 
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }
    
    - (void)applicationWillEnterForeground:(UIApplication *)application
    {
        // 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.
    }
    
    - (void)applicationDidBecomeActive:(UIApplication *)application
    {
        // 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.
    }
    
    - (void)applicationWillTerminate:(UIApplication *)application
    {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }
    
    @end
    

     

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

     

     

     

    if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)])
        {
            //IOS8
            //创建UIUserNotificationSettings,并设置消息的显示类类型
            UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIRemoteNotificationTypeSound) categories:nil];
            
            [application registerUserNotificationSettings:notiSettings];
            
        } else{ // ios7
            [application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge										 |UIRemoteNotificationTypeSound										 |UIRemoteNotificationTypeAlert)];
        }

    由于ios8的推送跟ios7及以下的不一样,所以需要加判断来注册消息推送。

    函数:

     

    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken{
        NSLog(@"---Token--%@", pToken);
    }

     

    会接收来自苹果服务器给你返回的deviceToken,然后你需要将它添加到你本地的推送服务器上。(很重要,决定你的设备能不能接收到推送消息)。

     

    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{
        
        NSLog(@"userInfo == %@",userInfo);
        NSString *message = [[userInfo objectForKey:@"aps"]objectForKey:@"alert"];
        
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"提示" message:message delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
        
        [alert show];
    }

     

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

     

     

    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
    
        NSLog(@"Regist fail%@",error);
    }

    当注册失败时,触发此函数。

     

     

    2.PHP服务端

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

     

    <?php
    
    // ??????????deviceToken???????????????
    $deviceToken = 'c95f661371b085e2517b4c12cc76293522775e5fd9bb1dea17dd80fe85583b41';
    
    // Put your private key's passphrase here:
    $passphrase = 'abc123';
    
    // Put your alert message here:
    $message = 'My first push test!';
    
    
    
    $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'
    );
    
    // 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);
    ?>

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

    此刻就是见证奇迹的时候了,使用终端进入到push文件夹,在终端输入 php simplepush.php

     

    若显示以上提示则表示推送成功了。附上一张成功图。

     

    推送就到这边吧!

    感谢这篇博客的指导:http://blog.csdn.net/showhilllee/article/details/8631734

     

    simplepush.php以及XCode工程我会上传到我的资源里去,大家可以去那边下载。有什么不明白的地方大家可以留言或者私信我,我会第一之间回复的~

     

     

    大家可以关注我的微信公众号与我互动,相关问题也可以直接用公众号联系我:

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 每次看到iOS的远程消息推送,总是感觉很头大,即便后来项目都做完了,还是觉得摸不着远程推送的脉门,网上介绍的资料虽然多如牛毛,但不是写的太简单了,就是写的太详细了,不能一下抓住要点,今天终于能够抽出点...

        

        每次看到iOS的远程消息推送,总是感觉很头大,即便后来项目都做完了,还是觉得摸不着远程推送的脉门,网上介绍的资料虽多,但不是写的太简单了,就是写的太详细了,不能一下抓住要点,今天终于能够抽出点时间,来扒一扒这其中究竟有怎样的奥秘。

        根据苹果掌控一切的习惯,消息推送也当然不能例外,不论你在哪里推送,也不论你用什么方式推送,都必须首先把消息发给苹果的消息推送服务器APNs(Apple Push Notification Service),然后再由APNs发给指定的设备,也就是说消息推送的控制权完全掌握在苹果手中。

    有张很经典的图来描述整个消息推送的过程,我们也借鉴一下



        由此图可以看出,要想进行消息推送,首先必须获取deviceToken,由要接收消息的iOS设备向APNs发送请求,APNs根据设备的请求信息来验证其是否合法,验证通过之后,会给设备发来一个deviceToken,这个东东就能够唯一标识这台设备(貌似deviceToken是设备的identifier经过Apple私有算法而生成的,总之,知道deviceToken可以唯一标识一个设备就可以了),然后再使用deviceToken和其他资料就可以进行消息推送了。


    根据我经常提到的最小系统原则(用最少的代码实现需要的功能),远程消息推送实际上可以分为两步进行:

    1.获取deviceToken,只有获取到deviceToken,才拥有向这台设备推送消息的资格;

    2.使用deviceToken作为门票向设备推送消息(当然还有其他的东东)。


    所以我们可以把任务细分,第一步就是要获取设备的deviceToken,只要能成功获取到deviceToken,我们便成功了一半。


    第一步 :获取  deviceToken


    说起来很简单,但实际上还是需要花些功夫,这主要归功于苹果层层的身份验证。

        要想推送消息,必须首先申请推送证书,跟开发证书和发布证书一样,推送证书也分为开发版和发布版两种,当然,要获取推送证书,也必须像开发证书和发布证书那样向苹果进贡,即申请付费版的苹果开发者账号,关于苹果开发者账号的相关问题可以参看我前面写的文章,这里不做介绍。与开发证书和发布证书不同的是,推送证书与具体的App相互绑定,也就是说,开发版和发布版证书,都可以同时服务于多个App,但是推送证书只能服务于一个App,在创建的时候就必须指定App ID,且无法修改。当然,因为这个,推送证书并不像开发证书和发布证书那样最多都只能申请2个,推送证书并没有个数限制。

    下面正式开始我们的获取deviceToken之旅,我假定你已经拥有苹果开发者账号并能够熟练使用。


    1.申请App ID


    创建一个新的应用需要一个新的 App ID,申请App ID记得开启消息推送功能


    App ID申请成功后会有如下页面


        

        从这里也可以看出推送证书分为开发版推送证书和发布版推送证书,黄色的 Configurable 表示还没有创建该App ID 对应的推送证书


    2.创建开发版推送证书



    注意区分开发版推送证书和发布版推送证书



    因为推送证书是与具体App绑定的,所以要选择 App ID,此处选择我们刚刚创建的 App ID



    选择证书请求文件



    关于如何生成请求文件我会在后续文章中介绍



    开发版推送证书申请成功,点击 “Download” 按钮 下载安装



    安装成功后会自动跳转到钥匙串工具


        

        这就是我们刚刚申请的开发版推送证书,与开发证书和发布证书不同的是,推送证书本身就带有 App ID,创建的时候就与某一具体的App绑定,只能服务于一个App,前面的 “Apple Development” 表示这是一个开发版的推送证书。


    此时我们再回头去看App ID


        

        此时 Push Notifications 的开发版选项变成绿色的 Enabled,而发布版选项依然是黄色的 Configurable,这说明我们生成了开发版的推送证书,没有生成发布版的推送证书。


    3.创建Profile文件

        因为我们创建了一个新的 App ID,要想在真机上调试该应用,就必须创建相应的Profile文件,因为开发证书是通用的,所以此处可以不创建新的开发证书,但Profile文件也是与具体 App 绑定的,所以必须新建一个



    Profile文件也分为开发版和发布版两种



    选择我们刚刚创建的App ID


        

        选择开发证书(注意是开发证书,不是刚刚创建的推送证书),此处我使用以前创建的,如果没有则需要创建一个新的开发证书。



    选择移动设备



    给新建的Profile文件起个拉风名字,以便编译前设置选择



    Profile文件创建成功,下载安装



    证书的问题解决了,下面就开始创建App了


    4.创建 App

    打开Xcode,创建一个Single View工程



    工程创建成功之后,在AppDelegate.m的 didFinishLaunchingWithOptions 函数中输入注册远程通知的代码

    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) { //iOS8.0以后注册方法与之前有所不同,需要要分别对待
            
            UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes: (UIUserNotificationTypeAlert | UIUserNotificationTypeSound | UIUserNotificationTypeBadge) categories: nil];
        
            [application registerUserNotificationSettings: settings];
            
            [application registerForRemoteNotifications];
            
        }else{ //iOS8.0以前版本的注册方法
            
            [application registerForRemoteNotificationTypes: (UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeBadge)];
        }

    成功接收到deviceToken后会回调下面的方法

    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken{
    
        NSLog(@"DeviceToken: %@", deviceToken);
    }

    接收失败调用的方法

    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{
    
        NSLog(@"RegisterError: %@", error.localizedDescription);
    }


    5. 运行 App

    首先将 App 的 Bundle id 设置为我们刚刚生成的App ID



    其次需要设置刚刚生成的profile文件和使用的开发版证书




    然后便可以运行App了,真机上将会出现以下界面(一定要在真机上运行,模拟器无法申请到deviceToken)



    该通知只会出现一次(当然把程序删除,重新安装又会重新出现),点击“好”将会接收远程消息

    如果在Xcode输出栏看到下面信息


    那么恭喜你,成功获取到了deviceToken,尖括号内的内容就是该设备的deviceToken。

    获取deviceToken的工作完成了。


    第二步:消息推送


        消息推送,严格来说,这已经是服务器的工作了,现在市面上有很多的第三方消息推送平台可以用来帮助我们完成此项工作,比较有名的有 极光推送、个推、小米、友盟、华为、腾讯信鸽、百度云推送、Parse推送等等。

        但是为了搞清楚推送的原理,我们还是选择自己来实现。

        不借助这些第三方平台,我们自己也能完成简单的推送功能,苹果的APNs对大家来说都是一样的,第三方无非是多了一些其他的功能。


    1. 准备原材料

    首先下载刚刚生成的开发版推送证书 aps_development-15.cer

    其次将编译时使用的开发证书的秘钥导出来生成p12文件 APNsDevTest001.p12 ,千万不要搞反了。





    2.生成pem文件

        由于推送服务对于Java语言,需要要用到p12格式,而 PHP语言,则要用到 pem 格式,因为我们使用的语言是PHP,所以要转换成pem格式,使用openssl来完成转换工作


    首先将开发版推送证书文件 aps_development-15.cer 转换成pem文件 APNsDevTestCert.pem



    其次将开发证书导出的私钥p12文件 APNsDevTest001.p12 转换成pem文件


        

        转换过程需要输入3次密码,因为p12文件是有密码的,所以首先验证p12文件的密码,正确了才能往下进行,然后输入生成的pem文件的密码,这个密码要输两便,后面会用到。

        最后将生成的两个pem文件合并成一个pem文件,这个文件在后面服务器推送时会用到。

        好了,材料已经准备好了,开始下一步工作。


    3.连接苹果推送服务器APNs,验证证书是否可用


    苹果推送服务器也分为开发服务器和发布服务器两个,一定要分清楚

    我们现在用的是开发版消息推送服务器:gateway.sandbox.push.apple.com:2195


    首先使用telnet工具测试是否可以连接苹果APNs



    如果看到以上信息,说明你的电脑可以连接苹果APNs,按Ctrl + C 取消,否则就要去检查是不是你的防火墙未开放2195端口

    其次验证证书是否正确,此处用到合并之前的两个pem文件

    输入命令:

    openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert APNsDevTestCert.pem -key APNsDevTestKey.pem




    如果看到后面的信息,说明生成的证书是没有问题的,可以进行消息推送。


    4.消息推送

    一切具备,开始消息推送

    此处我们使用PHP语言作为推送语言



    代码不多,需要将以下几个信息替换掉即可

    a. 获取到的deviceToken,当然需要去掉尖括号,去掉中间的空格

    b. p12文件转换成pem文件时输入的密码(后面输入两次的密码)

    c. 合并之后生成的pem文件,必须放在php文件相同的目录下

    d. 苹果APNs地址,一定要区分清开发版APNs和发布版APNs的地址

    当然,你还可以修改显示消息的内容


    下面附上PHP文件源码

    simplepush.php

    <?php
    
    // Put your device token here (without spaces):
    $deviceToken = 'b3e6888636a6ae3e29492a293125c01d448e1bec35b0b76a3894a19ba351dcf6';
    
    // Put your private key's passphrase here:
    $passphrase = '12345';
    
    // Put your alert message here:
    $message = '我的推送消息!';
    
    
    
    $ctx = stream_context_create();
    stream_context_set_option($ctx, 'ssl', 'local_cert', 'APNsDevTestCK.pem');
    stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
    // Open a connection to the APNS server
    $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' => 99,
            );
    
    // 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);
    
    ?>

    确认信息无误之后执行PHP脚本


    看到上面的信息说明消息推送成功


    手机端显示(需要将应用切回后台,应用在前台默认是不会弹出通知消息的)



    有没有点小激动呢,伟大的工程终于完工了。

    开发版的消息推送掌握了,发布版的消息推送应该也能搞定了吧,这里就不做介绍了。


        通过自己完成整个推送过程至少可以搞明白一件事,如果你无法收到远程消息,那么首先就要去看是否能够获取到deviceToken,如果获取不到deviceToken,说明你的推送证书有问题或者是App ID 不匹配等等,如果能获取到deviceToken,那要么你没有把deviceToken上传给消息推送服务器,要么是传给消息推送服务器的推送证书有问题。


    下面来做个总结


    1.苹果消息推送服务器APNs地址

      开发版:gateway.sandbox.push.apple.com   2195

      发布版:gateway.push.apple.com   2195


    2.证书的时效

      开发版:有效期大概四个月左右

      发布版:有效期是一年


    3.deviceToken的唯一性

        在iOS7之前,苹果对于一个设备上的多个App,生成相同的deviceToken,iOS7之后(包括iOS7),苹果对于一个设备上的多个App,生成不同的deviceToken,经测试,同一个App,删除之后重新编译安装,获取到的deviceToken也不相同。也就是说一个deviceToken只能对于一个iOS设备,但一个iOS设备可以同时拥有多个deviceToken。

        这种新改变导致APNs上创建了一张新老token的映射表,如果你一直用老的token,那没问题,但是,一旦服务器使用新的deviceToken,映射表中的记录就会被删除,这意味着,老的deviceToken就不能用了,必然发送失败。


    4. 消息时效

        如果推送的时候deviceToken对应的机器在APNs服务器上是离线状态,苹果会保存推送信息“一段时间”,当机器恢复在线状态时,推送信息到该机器。如果机器长时间不在线,苹果会抛弃掉这条消息。这个“一段时间”没有明文说多久,而且不知道苹果在不同情况下对这个时间有没有动态调整,所以无法推测这个时间对于信息丢失情况的影响。
        对于连续推送的情况,针对离线设备,苹果永远只存储最新的一条,上一条信息会被抛弃。
        有多条推送任务时,苹果推荐使用单个连接持续发送,而不是重复的开关连接,否则会被苹果认为D-O-S攻击给拒绝掉。如果有多台服务器,可以并发连接到APNS,分摊推送任务,可以更高效的执行任务。
        发送多条推送任务时,如果其中有一条推送使用了错误的deviceToken,那么连接就会被断掉,导致后面的推送任务停止执行。苹果通过一个“The Feedback Service”的服务来定期告知provider无效的deviceToken列表,如何使用这个服务参见苹果官方文档中的详细说明。


    5.推送原理

        我们知道,iOS设备对于应用程序在后台运行有诸多限制,考虑到手机电池电量,系统不允许应用在后台进行过多的操作。因此,当用户切换到其他程序后,原先的程序将无法保持运行状态。对于那些需要保持持续连接状态的应用程序(比如社区网络应用),将不能收到实时的信息。为解决这一限制,苹果推出了APNs(苹果推送通知服务 Apple Push Notification services),APNs是解决轮询所造成的流量消耗和电量消耗的一个比较好的解决方案。

        APNs 允许iOS设备与苹果的推送通知服务器保持常连接状态,当你想发送一个推送通知给某个用户的iOS设备上的应用程序时,你可以使用 APNs 发送一个推送消息给目标设备上已安装的某个应用程序。

        苹果的推送服务APNs基本原理简单来说就是苹果利用自己专门的推送服务器(APNs)接收来自我们自己的应用服务器发过来的需要被推送的信息,然后推送到指定的iOS设备上,再由iOS设备通知到我们的应用程序,设备以通知或者声音的形式通知用户有新的消息。

        推送的前提是装有我们应用的设备需要向APNs服务器注册,注册成功后APNs服务器会将我们的设备加入到Push服务的设备列表中,同时返给我们一个 deviceToken,拿到这个 deviceToken 后我们将这个 deviceToken 发给我们自己的应用服务器,当有需要被推送的消息时,我们自己的应用服务器会将消息按指定的格式打包,然后结合设备的 deviceToken 一并发给APNs服务器,APNs会在自己维护的Push设备列表中查找,找到匹配的设备后,将消息发送到这些设备上,实际上,此时的 deviceToken 就相当于一个设备的地址。由于我们的应用和APNs维持一个基于SSL协议的TCP流通讯长连接,APNs能够将新消息推送到我们设备上,然后在屏幕上显示出新消息来。

        需要注意的是 App 需要每次启动的时候都去注册远程通知,但是这并不会带来额外的负担,因为iOS系统在第一次获得了有效的 deviceToken 之后,会本地缓存起来,以后 App 再调用 registerForRemoteNotifications: 的时候会立刻返回,并不会再进行网络请求。这个工作由iOS系统来做,而我们的 App 层面不应该对 deviceToken 进行缓存,因为 deviceToken 也有可能变化—— 比如重装系统或重装应用都会造成deviceToken的改变,又或者是,用户restore了原来的backup到新的设备上,那么原来的 deviceToken 也会失效。


    6.推送过程


      Provider:  为指定iOS设备应用程序提供消息推送的服务器,即我们自己的用来推送消息给APNs的服务器
      APNS:      苹果消息推送服务器Apple Push Notification Service
      iPhone:    用来接收APNs下发下来的消息的iOS设备;
      Client App:iOS设备上的应用程序,用来接收APNs下发消息的客户端 App(消息的最终响应者)


    推送过程可以归纳为以下几步:

    1.用户iOS设备上的某个App使用设备id向APNs注册远程消息推送服务

    2.APNs验证请求的合法性,验证成功后生成该设备的deviceToken,

      在自己维护的Push设备列表中加入该设备,并将deviceToken返回给请求者

    3.用户App成功获取到deviceToken之后,将deviceToken提交给自己的消息推送服务器Provider

    4.有消息需要推送时,Provider会将要推送的消息和目标设备的唯一标识按指定格式打包,发给APNs

    5.APNs在自己维护的Push设备列表中查找相应标识的iOS设备,找到后将消息发送给该设备

    6.iOS设备接收到APNs下发的消息后,根据标识传递给对应的App,并按照设定格式弹出Push通知。


    注意:

        Provider只是把消息发送给了APNs,由APNs完成真正的推送工作,我们自己的应用服务器只是将需要推送的消息告诉苹果服务器,至于如何维护消息队列或如何保证消息能被推送到指定的设备上,这些工作都由APNs帮助我们完成。

        deviceToken用来唯一标识一个设备,App ID用来唯一标识一个用于,二者结合就能找到指定设备上的指定应用。


    7.APNs推送接口


    Apple 为应用开发者提供了一个 APNs 推送接口,称为 binary interface

    最初版本的 binary interface 协议如下图,这里我们称之为 v1。

    Binary Interface v1



        v1协议包括五个部分:第一个部分是命令标示符,第二个部分是我们的deviceToken的长度,第三部分是deviceToken 的内容,第四部分是推送消息体(payload)的长度,最后一部分也就是真正的消息内容了,里面包含了推送消息的基本信息,比如消息内容,应用icon右上角显示多少数字以及推送消息到达时所播放的声音等。


    v1 协议有几个问题:


    消息是否发送成功没有明确的反馈;
        如果一个消息发送失败,比如因为 deviceToken 不合法,APNs 会在大约 500ms 后断掉链接,在断链前发送的消息也会发送失败;
        经我们验证,feedback service 只会报告应用被卸载后,造成 deviceToken 失效的错误。而不会报告 deviceToken 不合法这种类型的推送错误。也就是说如果我们给一批用户发消息,只要有一个 deviceToken 不合法,将会有可能造成若干个用户收不到消息。并且没办法确认哪些 deviceToken 不合法,哪些 deviceToken 需要被重发。这应该是 APNs 丢消息的一个重要的原因。


        经过开发者不断的向 Apple 反馈这个问题,Apple 终于推出了一个新版本的 binary interface,称为 enhanced binary interface,我们称这为 v2。


    Binary Interface V2




    我们发现,在 v1 的基础上增加了两个字段:


    Identifier:一个任意的值,用于一条消息的识别。如果发送出现问题,错误应答里会把 Identifier 带回来。
    Expiry:    离线消息超时的时间,如果为0或者小于0,APNs 不会保存这条消息。

    和 v1 一样,如果消息发送没有问题,APNs 不会有任何返回。和 v1 不同,并且很重要的改进是,如果发送出现错误,v2 会在断链之前返回一个错误应答,带上发消息时的 Identifier 和一个错误码。

    error-response packet



    根据这个错误应答,我们有机会找到是哪条消息发送出错,并确定哪些消息需要被重发。


        有一种情况,当我们将应用从设备卸载后,推送的消息改如何处理呢?我们知道,当我们将应用从设备卸载后,我们是收不到Provider给我们推送的消息的,但是,如何让APNs和Provider都知道不去向这台卸载了应用的设备推送消息呢?

        针对这个问题,苹果也已经帮我们解决了,那就是Feedback service。他是APNs的一部分。APNs会持续的更新Feedback service的列表,当我们的Provider将信息发给APNs推送到我们的设备时,如果这时设备无法将消息推送到指定的应用,就会向APNs服务器报告一个反馈信息,而这个信息就记录在Feedback service中。按照这种方式,Provider应该定时的去检测Feedback service的列表,然后删除在自己数据库中记录的存在于反馈列表中的 deviceToken,从而不再向这些设备发送推送信息。

        连接Feedback service的过程同样使用Socket的方式,连接上后,直接接收由APNs传输给我们的反馈列表,传输完成后断开连接,然后我们根据这个最新的反馈列表再更新我们自己的数据库,删除那些不再需要推送信息的设备的deviceToken。

        从Feedback service读取的数据结构如下:




    结构中包含三个部分:

        第一部分是一个时间戳,记录设备失效后的时间信息;

        第二个部分是deviceToken的长度

        第三部分就是失效的deviceToken

    我们所要获取的就是第三部分,跟我们的数据库进行对比后,删除对应的deviceToken,下次不再向这些设备发送推送信息。


    8.消息大批量发送问题


    由于APNs本身机制的原因,目前easy APNs的消息发送机制为:

        对每一条发送的消息,为所有需要推送的设备都在数据库中创建一条消息,然后通过轮训数据库表来一条一条向苹果消息推送服务器ANPs发送消息,也就是,如果有10W台设备,就需要发送10W次。这种方式必然会导致在需要推送的设备较多的情况下,由于存在大量的网络链接,从而产生较长时间的延迟。

        也就是说,APNs消息推送不支持群发,只能一个一个发.如果你的App有100万个用户,就只能老老实实发100万次。


    9.消息提醒表现形式

    4种:徽章、提示框、声音和横幅



    10.消息推送机制的优点


        财大气粗的苹果提供了一堆服务器来保障每个iOS设备和这些服务器保持了一个长连接,iOS版本更新提示,手机时钟校准什么的都是通过这个连接。
        苹果把这个长连接开放出来给大家推送消息用,很积德,因为这是个全球服务,几十亿台iOS设备,服务器少说也需要上万台,还没有钱可以赚。Android爸爸就不做这个,于是各个App为了发消息,只能直接拼命赖在后台维持一个长连接,电就是这样被耗光的。
        苹果提供的APNs,只是长连接机器的一部分,你要向你的用户发消息,必须通过APNs中转,由Provider发给APNs,APNs转发给你的手机,你的推送程序和用户手机没有直接联系。

        我们知道,iOS系统不允许应用在后台活动,这样做的好处我想用过iOS设备的人都知道,省电是一方面,最明显的就是无论你安装了多少应用,无论你打开了多少App,只要存储空间还够,就不用去删除应用,不会因为程序安装或打开太多而卡顿。

        但是有了这个限制,对于终端设备,有些应用又是有必要“通知”到户的,需要随时与用户主动沟通起来的,比较典型的如QQ、微信等即时通信软件。于是,APNs应运而生。

        APNs 的做法是:iOS系统自己做了个长连接,依托一个或几个系统常驻内存的进程运作,保持与APNs之间的通讯,接管所有应用的消息推送,独立于应用之外,而且这种长连接一直保持,即使在手机休眠的时候。

        对于大部分人来说,最不理解的就是,休眠时候都保持在那里的 TCP 长连接,不会耗电很厉害么?
        答案是:不会。这是手机的设计来做到的。TCP长连接有个心跳的时间,在国外可以很长,比如30分钟,在国内则因为网络环境复杂一般10分钟。客户端发起的心跳,会短暂地消耗手机电能,但在这个心跳间隔期间,则消耗电能是很少的。当在心跳期间服务器端有推送信息过来时,客户端可以收到并做处理。


    APNs存在的好处是:

    1.安全:只有身份验证成功者才可以通过APNs推送消息
    2.快速,稳定,可靠:这一点由APNs和iOS系统来保障
    3.更省电:有了APNs就可以实现App无需后台运行
    4.让整个系统的体验更统一和简单:不用大量 App和App的服务为了推送挂后台,

                                    也不会出现 App 被杀就收不到推送这种脑残事
    5.开发容易:当然,开发者还是要做些事情,比如维护个服务器什么的,但是复杂度无疑降低了很多


    当然苹果还是要为此付出一些代价的:苹果需要维护一个代价不小的服务器集群,而且要为服务器的宕机负责。


    选择低风险的技术方案 Bug 更少,减轻了用户的痛苦,这是构架师的功劳。
    苹果承担责任,尽可能的减少了不可控的意外,保证了用户体验。这,只能说是公司决策者的功劳。


    其实,现在手机主流平台都有自家提供Push的功能,让应用开发者能够很方便地把Push能力集成到应用中。

    Android:       GCM (Google Cloud Messaging)
    iOS:           APNs(Apple Push Notification service)

    Windows Phone: MPNs(Microsoft Push Notification service)。


    由于Windows Phone的市场占比不高,所以一般也就没有人会专门做WP系统的推送。

    至于GCM在国内基本上是不可用的。原因主要有以下两点: 

      1.国内大部分Android手机都不带Google服务,也就用不了GCM,这是主要的问题。 
      2.在国内Google的服务一般都不太稳定,原因你懂的。
    所以现在正常在用也只有iOS平台的APNs服务。


        除了官方推送服务之外,还有很多第三方的推送服务可用,如极光推送、百度云推送等等,但他们使用的基本技术都是长连接和心跳包。


    啰啰嗦嗦唠叨了这么多,最后借用一张很有意思的图片来结束这片文章吧。






    参考文章:

    http://www.cocoachina.com/industry/20140528/8582.html
    http://www.360doc.com/content/15/0118/17/1073512_441822850.shtml
    http://www.360doc.com/content/15/0414/19/1073512_463201381.shtml
    http://blog.sina.com.cn/s/blog_6f9a9718010128hi.html
    http://www.cocoachina.com/industry/20130321/5862.html
    http://www.cnblogs.com/iphone520/archive/2013/04/16/3023687.html
    http://my.oschina.net/w11h22j33/blog/208744
    http://www.zhihu.com/question/20667886



    展开全文
  • 在Android开发中,消息推送功能的使用非常常见。为了降低开发成本,使用第三方推送是现今较为流行的解决方案。 今天,我将手把手教大家如何在你的应用里集成小米推送 该文档基于小米推送官方Demo,并给出简易推送...

    前言

    • 在Android开发中,消息推送功能的使用非常常见。

    推送消息截图

    • 为了降低开发成本,使用第三方推送是现今较为流行的解决方案。

    • 今天,我将手把手教大家如何在你的应用里集成小米推送

    1. 该文档基于小米推送官方Demo,并给出简易推送Demo
    2. 看该文档前,请先阅读我写的另外两篇文章:
      史上最全解析Android消息推送解决方案
      Android推送:第三方消息推送平台详细解析

    目录

    目录


    1. 官方Demo解析

    首先,我们先对小米官方的推送Demo进行解析。

    请先到官网下载官方DemoSDK说明文档

    1.1 Demo概况

    Demo目录

    目录说明:

    • DemoApplication类
      继承自Application类,其作用主要是:设置App的ID & Key、注册推送服务

    • DemoMessageReceiver类
      继承自BroadcastReceiver,用于接收推送消息并对这些消息进行处理

    • MainActivity
      实现界面按钮处理 & 设置本地推送方案

    • TimeIntervalDialog
      设置推送的时间间段

    接下来,我将对每个类进行详细分析

    1.2 详细分析

    1.2.1 DemoApplication类

    继承自Application类,其作用主要是:

    • 设置App的ID & Key
    • 注册推送服务

    接下来我们通过代码来看下这两个功能如何实现:

    DemoApplication.java

    package com.xiaomi.mipushdemo;
    
    import android.app.ActivityManager;
    import android.app.ActivityManager.RunningAppProcessInfo;
    import android.app.Application;
    import android.content.Context;
    import android.os.Handler;
    import android.os.Message;
    import android.os.Process;
    import android.text.TextUtils;
    import android.util.Log;
    import android.widget.Toast;
    
    import com.xiaomi.channel.commonutils.logger.LoggerInterface;
    import com.xiaomi.mipush.sdk.Logger;
    import com.xiaomi.mipush.sdk.MiPushClient;
    
    import java.util.List;
    
    
    public class DemoApplication extends Application {
    
        // 使用自己APP的ID(官网注册的)
        private static final String APP_ID = "1000270";
        // 使用自己APP的KEY(官网注册的)
        private static final String APP_KEY = "670100056270";
    
        // 此TAG在adb logcat中检索自己所需要的信息, 只需在命令行终端输入 adb logcat | grep
        // com.xiaomi.mipushdemo
        public static final String TAG = "com.xiaomi.mipushdemo";
    
        private static DemoHandler sHandler = null;
        private static MainActivity sMainActivity = null;
    
        //为了提高推送服务的注册率,官方Demo建议在Application的onCreate中初始化推送服务
        //你也可以根据需要,在其他地方初始化推送服务
        
        @Override
        public void onCreate() {
    
            super.onCreate();
            
            //判断用户是否已经打开App,详细见下面方法定义
            if (shouldInit()) {
            //注册推送服务
            //注册成功后会向DemoMessageReceiver发送广播
            // 可以从DemoMessageReceiver的onCommandResult方法中MiPushCommandMessage对象参数中获取注册信息
                MiPushClient.registerPush(this, APP_ID, APP_KEY);
             //参数说明
            //context:Android平台上app的上下文,建议传入当前app的application context
            //appID:在开发者网站上注册时生成的,MiPush推送服务颁发给app的唯一认证标识
           //appKey:在开发者网站上注册时生成的,与appID相对应,用于验证appID是否合法
            }
    
    
    		//下面是与测试相关的日志设置
            LoggerInterface newLogger = new LoggerInterface() {
    
                @Override
                public void setTag(String tag) {
                    // ignore
                }
    
                @Override
                public void log(String content, Throwable t) {
                    Log.d(TAG, content, t);
                }
    
                @Override
                public void log(String content) {
                    Log.d(TAG, content);
                }
            };
            Logger.setLogger(this, newLogger);
            if (sHandler == null) {
                sHandler = new DemoHandler(getApplicationContext());
            }
        }
    
    
    //通过判断手机里的所有进程是否有这个App的进程
    //从而判断该App是否有打开
        private boolean shouldInit() {
    //通过ActivityManager我们可以获得系统里正在运行的activities
    //包括进程(Process)等、应用程序/包、服务(Service)、任务(Task)信息。
            ActivityManager am = ((ActivityManager) getSystemService(Context.ACTIVITY_SERVICE));
            List<RunningAppProcessInfo> processInfos = am.getRunningAppProcesses();
            String mainProcessName = getPackageName();
            
           //获取本App的唯一标识
            int myPid = Process.myPid();
            //利用一个增强for循环取出手机里的所有进程
            for (RunningAppProcessInfo info : processInfos) {
                //通过比较进程的唯一标识和包名判断进程里是否存在该App
                if (info.pid == myPid && mainProcessName.equals(info.processName)) {
                    return true;
                }
            }
            return false;
        }
    
        public static DemoHandler getHandler() {
            return sHandler;
        }
    
        public static void setMainActivity(MainActivity activity) {
            sMainActivity = activity;
        }
    
    
    //通过设置Handler来设置提示文案
        public static class DemoHandler extends Handler {
    
            private Context context;
    
            public DemoHandler(Context context) {
                this.context = context;
            }
    
            @Override
            public void handleMessage(Message msg) {
                String s = (String) msg.obj;
                if (sMainActivity != null) {
                    sMainActivity.refreshLogInfo();
                }
                if (!TextUtils.isEmpty(s)) {
                    Toast.makeText(context, s, Toast.LENGTH_LONG).show();
                }
            }
        }
    }
    

    总结:

    • 步骤1:先判断应用App是否已开启 - 通过判断系统里的进程
    • 通过静态方法
    public static void registerPush(Context context, String appID, String appKey)
    

    进行推送服务注册,详细参数如下:

    • 为了提高注册率,最好在Application的onCreate中初始化推送服务

    你也可以根据需要,在其他地方初始化推送服务

    1.2.2 DemoMessageReceiver类

    继承自PushMessageReceiver(抽象类,继承自BroadcastReceiver),其作用主要是:

    • 接收推送消息
    • 对推送消息进行处理

    DemoMessageReceiver.java

    package com.xiaomi.mipushdemo;
    
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.os.Message;
    import android.text.TextUtils;
    import android.util.Log;
    
    import com.xiaomi.mipush.sdk.ErrorCode;
    import com.xiaomi.mipush.sdk.MiPushClient;
    import com.xiaomi.mipush.sdk.MiPushCommandMessage;
    import com.xiaomi.mipush.sdk.MiPushMessage;
    import com.xiaomi.mipush.sdk.PushMessageReceiver;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.List;
    
    /**
     * 1、PushMessageReceiver 是个抽象类,该类继承了 BroadcastReceiver。
     * 2、需要将自定义的 DemoMessageReceiver 注册在 AndroidManifest.xml
    
    
    
    public class DemoMessageReceiver extends PushMessageReceiver {
    
        private String mRegId;
        private String mTopic;
        private String mAlias;
        private String mAccount;
        private String mStartTime;
        private String mEndTime;
    
    
        //透传消息到达客户端时调用
        //作用:可通过参数message从而获得透传消息,具体请看官方SDK文档
        @Override
        public void onReceivePassThroughMessage(Context context, MiPushMessage message) {
            Log.v(DemoApplication.TAG,
                    "onReceivePassThroughMessage is called. " + message.toString());
            String log = context.getString(R.string.recv_passthrough_message, message.getContent());
            MainActivity.logList.add(0, getSimpleDate() + " " + log);
    
            if (!TextUtils.isEmpty(message.getTopic())) {
                mTopic = message.getTopic();
            } else if (!TextUtils.isEmpty(message.getAlias())) {
                mAlias = message.getAlias();
            }
    
            Message msg = Message.obtain();
            msg.obj = log;
            DemoApplication.getHandler().sendMessage(msg);
        }
    
    
    //通知消息到达客户端时调用
     	 //注:应用在前台时不弹出通知的通知消息到达客户端时也会回调函数
        //作用:通过参数message从而获得通知消息,具体请看官方SDK文档
       
        @Override
        public void onNotificationMessageArrived(Context context, MiPushMessage message) {
            Log.v(DemoApplication.TAG,
                    "onNotificationMessageArrived is called. " + message.toString());
            String log = context.getString(R.string.arrive_notification_message, message.getContent());
            MainActivity.logList.add(0, getSimpleDate() + " " + log);
    
            if (!TextUtils.isEmpty(message.getTopic())) {
                mTopic = message.getTopic();
            } else if (!TextUtils.isEmpty(message.getAlias())) {
                mAlias = message.getAlias();
            }
    
            Message msg = Message.obtain();
            msg.obj = log;
            DemoApplication.getHandler().sendMessage(msg);
        }
        
     	//用户手动点击通知栏消息时调用
     	 //注:应用在前台时不弹出通知的通知消息到达客户端时也会回调函数
        //作用:1. 通过参数message从而获得通知消息,具体请看官方SDK文档
        //2. 设置用户点击消息后打开应用 or 网页 or 其他页面
    
        @Override
        public void onNotificationMessageClicked(Context context, MiPushMessage message) {
            Log.v(DemoApplication.TAG,
                    "onNotificationMessageClicked is called. " + message.toString());
            String log = context.getString(R.string.click_notification_message, message.getContent());
            MainActivity.logList.add(0, getSimpleDate() + " " + log);
    
            if (!TextUtils.isEmpty(message.getTopic())) {
                mTopic = message.getTopic();
            } else if (!TextUtils.isEmpty(message.getAlias())) {
                mAlias = message.getAlias();
            }
    
            Message msg = Message.obtain();
            if (message.isNotified()) {
                msg.obj = log;
            }
            DemoApplication.getHandler().sendMessage(msg);
        }
    
    
     	
    	//用来接收客户端向服务器发送命令后的响应结果。
        @Override
        public void onCommandResult(Context context, MiPushCommandMessage message) {
            Log.v(DemoApplication.TAG,
                    "onCommandResult is called. " + message.toString());
            String command = message.getCommand();
            List<String> arguments = message.getCommandArguments();
            String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null);
            String cmdArg2 = ((arguments != null && arguments.size() > 1) ? arguments.get(1) : null);
            String log;
            if (MiPushClient.COMMAND_REGISTER.equals(command)) {
                if (message.getResultCode() == ErrorCode.SUCCESS) {
                    mRegId = cmdArg1;
                    log = context.getString(R.string.register_success);
    
                } else {
                    log = context.getString(R.string.register_fail);
                }
            } else if (MiPushClient.COMMAND_SET_ALIAS.equals(command)) {
                if (message.getResultCode() == ErrorCode.SUCCESS) {
                    mAlias = cmdArg1;
                    log = context.getString(R.string.set_alias_success, mAlias);
                } else {
                    log = context.getString(R.string.set_alias_fail, message.getReason());
                }
            } else if (MiPushClient.COMMAND_UNSET_ALIAS.equals(command)) {
                if (message.getResultCode() == ErrorCode.SUCCESS) {
                    mAlias = cmdArg1;
                    log = context.getString(R.string.unset_alias_success, mAlias);
                } else {
                    log = context.getString(R.string.unset_alias_fail, message.getReason());
                }
            } else if (MiPushClient.COMMAND_SET_ACCOUNT.equals(command)) {
                if (message.getResultCode() == ErrorCode.SUCCESS) {
                    mAccount = cmdArg1;
                    log = context.getString(R.string.set_account_success, mAccount);
                } else {
                    log = context.getString(R.string.set_account_fail, message.getReason());
                }
            } else if (MiPushClient.COMMAND_UNSET_ACCOUNT.equals(command)) {
                if (message.getResultCode() == ErrorCode.SUCCESS) {
                    mAccount = cmdArg1;
                    log = context.getString(R.string.unset_account_success, mAccount);
                } else {
                    log = context.getString(R.string.unset_account_fail, message.getReason());
                }
            } else if (MiPushClient.COMMAND_SUBSCRIBE_TOPIC.equals(command)) {
                if (message.getResultCode() == ErrorCode.SUCCESS) {
                    mTopic = cmdArg1;
                    log = context.getString(R.string.subscribe_topic_success, mTopic);
                } else {
                    log = context.getString(R.string.subscribe_topic_fail, message.getReason());
                }
            } else if (MiPushClient.COMMAND_UNSUBSCRIBE_TOPIC.equals(command)) {
                if (message.getResultCode() == ErrorCode.SUCCESS) {
                    mTopic = cmdArg1;
                    log = context.getString(R.string.unsubscribe_topic_success, mTopic);
                } else {
                    log = context.getString(R.string.unsubscribe_topic_fail, message.getReason());
                }
            } else if (MiPushClient.COMMAND_SET_ACCEPT_TIME.equals(command)) {
                if (message.getResultCode() == ErrorCode.SUCCESS) {
                    mStartTime = cmdArg1;
                    mEndTime = cmdArg2;
                    log = context.getString(R.string.set_accept_time_success, mStartTime, mEndTime);
                } else {
                    log = context.getString(R.string.set_accept_time_fail, message.getReason());
                }
            } else {
                log = message.getReason();
            }
            MainActivity.logList.add(0, getSimpleDate() + "    " + log);
    
            Message msg = Message.obtain();
            msg.obj = log;
            DemoApplication.getHandler().sendMessage(msg);
        }
    
    
     	//用于接收客户端向服务器发送注册命令后的响应结果。
        @Override
        public void onReceiveRegisterResult(Context context, MiPushCommandMessage message) {
            Log.v(DemoApplication.TAG,
                    "onReceiveRegisterResult is called. " + message.toString());
            String command = message.getCommand();
            List<String> arguments = message.getCommandArguments();
            String cmdArg1 = ((arguments != null && arguments.size() > 0) ? arguments.get(0) : null);
            String log;
            if (MiPushClient.COMMAND_REGISTER.equals(command)) {
                if (message.getResultCode() == ErrorCode.SUCCESS) {
                    mRegId = cmdArg1;
                    //打印日志:注册成功
                    log = context.getString(R.string.register_success);
                } else {
                		  //打印日志:注册失败
                    log = context.getString(R.string.register_fail);
                }
            } else {
                log = message.getReason();
            }
    
            Message msg = Message.obtain();
            msg.obj = log;
            DemoApplication.getHandler().sendMessage(msg);
        }
    
        @SuppressLint("SimpleDateFormat")
        private static String getSimpleDate() {
            return new SimpleDateFormat("MM-dd hh:mm:ss").format(new Date());
        }
    
    }
    

    总结

    • 根据需要复写PushMessageReceiver里对消息的相关处理方法,以下是相关方法的详情:
      相关方法详情

    • 关于onCommandResult(Context context,MiPushCommandMessage message)
      a. 作用:当客户端向服务器发送注册push、设置alias、取消注册alias、订阅topic、取消订阅topic等等命令后,从服务器返回结果。
      b. 参数说明
      参数说明

    1.2.3 MainActivity

    用于给用户设置标识,如别名、标签、账号等等

    MainActivity.java

    public class MainActivity extends Activity {
    
        public static List<String> logList = new CopyOnWriteArrayList<String>();
    
        private TextView mLogView = null;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            DemoApplication.setMainActivity(this);
    
            mLogView = (TextView) findViewById(R.id.log);
            
            // 设置别名
            findViewById(R.id.set_alias).setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    final EditText editText = new EditText(MainActivity.this);
                    new AlertDialog.Builder(MainActivity.this)
                            .setTitle(R.string.set_alias)
                            .setView(editText)
                            .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
    
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    String alias = editText.getText().toString();
    //调用静态方法进行设置                                MiPushClient.setAlias(MainActivity.this, alias, null);
                                }
    
                            })
                            .setNegativeButton(R.string.cancel, null)
                            .show();
                }
            });
            // 撤销别名
            findViewById(R.id.unset_alias).setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    final EditText editText = new EditText(MainActivity.this);
                    new AlertDialog.Builder(MainActivity.this)
                            .setTitle(R.string.unset_alias)
                            .setView(editText)
                            .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
    
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    String alias = editText.getText().toString();
    //调用静态方法进行设置                                  MiPushClient.unsetAlias(MainActivity.this, alias, null);
                                }
    
                            })
                            .setNegativeButton(R.string.cancel, null)
                            .show();
    
                }
            });
            // 设置帐号
            findViewById(R.id.set_account).setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    final EditText editText = new EditText(MainActivity.this);
                    new AlertDialog.Builder(MainActivity.this)
                            .setTitle(R.string.set_account)
                            .setView(editText)
                            .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
    
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    String account = editText.getText().toString();
    //调用静态方法进行设置                                  MiPushClient.setUserAccount(MainActivity.this, account, null);
                                }
    
                            })
                            .setNegativeButton(R.string.cancel, null)
                            .show();
    
                }
            });
            // 撤销帐号
            findViewById(R.id.unset_account).setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    final EditText editText = new EditText(MainActivity.this);
                    new AlertDialog.Builder(MainActivity.this)
                            .setTitle(R.string.unset_account)
                            .setView(editText)
                            .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
    
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    String account = editText.getText().toString();
    //调用静态方法进行设置                                  MiPushClient.unsetUserAccount(MainActivity.this, account, null);
                                }
    
                            })
                            .setNegativeButton(R.string.cancel, null)
                            .show();
                }
            });
            // 设置标签
            findViewById(R.id.subscribe_topic).setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    final EditText editText = new EditText(MainActivity.this);
                    new AlertDialog.Builder(MainActivity.this)
                            .setTitle(R.string.subscribe_topic)
                            .setView(editText)
                            .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
    
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    String topic = editText.getText().toString();
    //调用静态方法进行设置                                  MiPushClient.subscribe(MainActivity.this, topic, null);
                                }
    
                            })
                            .setNegativeButton(R.string.cancel, null)
                            .show();
                }
            });
            // 撤销标签
            findViewById(R.id.unsubscribe_topic).setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    final EditText editText = new EditText(MainActivity.this);
                    new AlertDialog.Builder(MainActivity.this)
                            .setTitle(R.string.unsubscribe_topic)
                            .setView(editText)
                            .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
    
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    String topic = editText.getText().toString();
    //调用静态方法进行设置                                  MiPushClient.unsubscribe(MainActivity.this, topic, null);
                                }
    
                            })
                            .setNegativeButton(R.string.cancel, null)
                            .show();
                }
            });
            // 设置接收消息时间
            findViewById(R.id.set_accept_time).setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    new TimeIntervalDialog(MainActivity.this, new TimeIntervalInterface() {
    
                        @Override
                        public void apply(int startHour, int startMin, int endHour,
                                          int endMin) {
                            //调用静态方法进行设置  
                            MiPushClient.setAcceptTime(MainActivity.this, startHour, startMin, endHour, endMin, null);
                        }
    
                        @Override
                        public void cancel() {
                            //ignore
                        }
    
                    })
                            .show();
                }
            });
            // 暂停推送
            findViewById(R.id.pause_push).setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                    MiPushClient.pausePush(MainActivity.this, null);
                }
            });
    
            findViewById(R.id.resume_push).setOnClickListener(new OnClickListener() {
    
                @Override
                public void onClick(View v) {
                //调用静态方法进行设置  
                    MiPushClient.resumePush(MainActivity.this, null);
                }
            });
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            refreshLogInfo();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            DemoApplication.setMainActivity(null);
        }
    
        public void refreshLogInfo() {
            String AllLog = "";
            for (String log : logList) {
                AllLog = AllLog + log + "\n\n";
            }
            mLogView.setText(AllLog);
        }
    }
    

    总结

    根据需求对不同用户设置不同的推送标识,如别名、标签等等。

    a. 别名(Alias)

    • 开发者可以为指定用户设置别名,然后给这个别名推送消息,

    效果等同于给RegId推送消息,Alias是除Regid(自动生成的)和UserAccount之外的第三个用户标识

    • 开发者可以取消指定用户的某个别名,服务器就不会给这个别名推送消息了。
    //设置别名
    MiPushClient.setAlias(Context context, String alias, String category);
    
    //撤销别名
    MiPushClient.unsetAlias(Context context, String alias, String category);
    //参数说明
    //context:Android平台上app的上下文,建议传入当前app的application context
    //alias:为指定用户设置别名 / 为指定用户取消别名
    //category:扩展参数,暂时没有用途,直接填null
    
    //获取该客户端所有的别名
    public static List<String> getAllAlias(final Context context)
    

    b. 用户账号(UserAccoun)

    • 开发者可以为指定用户设置userAccount
    • 开发者可以取消指定用户的某个userAccount,服务器就不会给这个userAccount推送消息了
    //设置
    MiPushClient.setUserAccount(final Context context, final String userAccount, String
    category)
    
    //撤销
    MiPushClient.unsetUserAccount(final Context context, final String userAccount, String
    category)
    //参数说明
    //context:Android平台上app的上下文,建议传入当前app的application context
    //userAccount:为指定用户设置userAccount / 为指定用户取消userAccount
    //category:扩展参数,暂时没有用途,直接填null
    
    //获取该客户端所有设置的账号
    public static List<String> getAllUserAccount(final Context context)
    

    c. 标签(Topic)

    • 开发者可以结合自己的业务特征,给用户打上不同的标签。
    • 消息推送时,开发者可以结合每条消息的内容和目标用户,为每条消息选择对应的标签,为开发者可以根据订阅的主题实现分组群发,从而进行消息的精准推送
    //设置标签
    MiPushClient.subscribe(Context context, String topic, String category);
    //撤销标签
    MiPushClient.unsubscribe(Context context, String topic, String category);
    //参数说明
    //context:Android平台上app的上下文,建议传入当前app的application context
    //topic:为指定用户设置设置订阅的主题 / 为指定用户取消订阅的主题
    //category:扩展参数,暂时没有用途,直接填null
    
    //获取该客户端所有的标签
    public static List<String> getAllTopic(final Context context);
    
    

    TimeIntervalDialog

    作用:用于设置推送的时间-开始时间+暂停时间

    package com.xiaomi.mipushdemo;
    
    import android.app.Dialog;
    import android.content.Context;
    import android.os.Bundle;
    import android.view.View;
    import android.widget.Button;
    import android.widget.TimePicker;
    import android.widget.TimePicker.OnTimeChangedListener;
    
    //继承OnTimeChangedListener接口
    public class TimeIntervalDialog extends Dialog implements OnTimeChangedListener {
    
    	
        private TimeIntervalInterface mTimeIntervalInterface;
        private Context mContext;
        private TimePicker mStartTimePicker, mEndTimePicker;
        private int mStartHour, mStartMinute, mEndHour, mEndMinute;
    
        private Button.OnClickListener clickListener = new Button.OnClickListener() {
    
            @Override
            public void onClick(View v) {
                switch (v.getId()) {
                    case R.id.apply:
                        dismiss();
                        //设置时间参数
                        mTimeIntervalInterface.apply(mStartHour, mStartMinute, mEndHour, mEndMinute);
                        break;
                    case R.id.cancel:
                        dismiss();
                        mTimeIntervalInterface.cancel();
                        break;
                    default:
                        break;
                }
            }
        };
    
        public TimeIntervalDialog(Context context, TimeIntervalInterface timeIntervalInterface,
                                  int startHour, int startMinute, int endHour, int endMinute) {
            super(context);
            mContext = context;
            this.mTimeIntervalInterface = timeIntervalInterface;
            this.mStartHour = startHour;
            this.mStartMinute = startMinute;
            this.mEndHour = endHour;
            this.mEndMinute = endMinute;
        }
    
        public TimeIntervalDialog(Context context, TimeIntervalInterface timeIntervalInterface) {
            this(context, timeIntervalInterface, 0, 0, 23, 59);
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.set_time_dialog);
            setCancelable(true);
            setTitle(mContext.getString(R.string.set_accept_time));
            mStartTimePicker = (TimePicker) findViewById(R.id.startTimePicker);
            mStartTimePicker.setIs24HourView(true);
            mStartTimePicker.setCurrentHour(mStartHour);
            mStartTimePicker.setCurrentMinute(mStartMinute);
            mStartTimePicker.setOnTimeChangedListener(this);
            mEndTimePicker = (TimePicker) findViewById(R.id.endTimePicker);
            mEndTimePicker.setIs24HourView(true);
            mEndTimePicker.setCurrentHour(mEndHour);
            mEndTimePicker.setCurrentMinute(mEndMinute);
            mEndTimePicker.setOnTimeChangedListener(this);
            Button applyBtn = (Button) findViewById(R.id.apply);
            applyBtn.setOnClickListener(clickListener);
            Button cancelBtn = (Button) findViewById(R.id.cancel);
            cancelBtn.setOnClickListener(clickListener);
        }
    
        @Override
        public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
            if (view == mStartTimePicker) {
                mStartHour = hourOfDay;
                mStartMinute = minute;
            } else if (view == mEndTimePicker) {
                mEndHour = hourOfDay;
                mEndMinute = minute;
            }
        }
    
        interface TimeIntervalInterface {
            void apply(int startHour, int startMin, int endHour, int endMin);
    
            void cancel();
        }
    }
    
    

    总结

    • 使用一个继承了Dialog类的TimeIntervalDialog类进行推送时间的配置
    • 可进行的配置:设置推送时间(开始 & 结束)、暂停推送时间、恢复推送时间
    //设置推送时间(开始 & 结束)
    MiPushClient.setAcceptTime(Context context, int startHour, int startMin, int endHour,
    int endMin, String category)
    //设置暂停推送时间、恢复推送时间
    pausePush(Context context, String category)`和`resumePush(Context context, String category)
    //参数说明
    //context:Android平台上app的上下文,建议传入当前app的application context
    //startHour:接收时段开始时间的小时
    //startMin	:接收时段开始时间的分钟
    //endHour:接收时段结束时间的小时
    //endMin:接收时段结束时间的分钟
    //category:扩展参数,暂时没有用途,直接填null
    
    

    AndroidManifest文件的配置

    //小米推送支持最低的Android版本是2.2
    <uses-sdk  android:minSdkVersion="8"/>
    
    //设置一系列权限
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
        <uses-permission android:name="android.permission.READ_PHONE_STATE" />
        <uses-permission android:name="android.permission.GET_TASKS" />
        <uses-permission android:name="android.permission.VIBRATE" />
    
    //这里com.xiaomi.mipushdemo改成自身app的包名
    	<permission android:name="com.xiaomi.mipushdemo.permission.MIPUSH_RECEIVE" android:protectionLevel="signature" />
    
    //这里com.xiaomi.mipushdemo改成自身app的包名
    	<uses-permission android:name="com.xiaomi.mipushdemo.permission.MIPUSH_RECEIVE" />
    
    
    //注册广播BroadcastReceiver & Service
    //都是静态注册,因为要长期处在后台运行
    //注:共是3个广播接收器和4个服务,其中包括继承了PushMessageReceiver的DemoMessageReceiver
                    
            //4个后台服务
    		<service
    		  android:enabled="true"
    		  android:process=":pushservice"
    		  android:name="com.xiaomi.push.service.XMPushService"/>
    
            //此service必须在3.0.1版本以后(包括3.0.1版本)加入
    		<service
    		  android:name="com.xiaomi.push.service.XMJobService"
    		  android:enabled="true"
    		  android:exported="false"
    		  android:permission="android.permission.BIND_JOB_SERVICE"
    		  android:process=":pushservice" />
    		
            //此service必须在2.2.5版本以后(包括2.2.5版本)加入
    		<service
    		  android:enabled="true"
    		  android:exported="true"
    		  android:name="com.xiaomi.mipush.sdk.PushMessageHandler" /> 
    
    		<service android:enabled="true"
    		  android:name="com.xiaomi.mipush.sdk.MessageHandleService" /> 
    		
    
            //3个广播
    		<receiver
    		  android:exported="true"
    		  android:name="com.xiaomi.push.service.receivers.NetworkStatusReceiver" >
    		  <intent-filter>
    		    <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    		    <category android:name="android.intent.category.DEFAULT" />
    		  </intent-filter>
    		</receiver>
    
    		<receiver
    		  android:exported="false"
    		  android:process=":pushservice"
    		  android:name="com.xiaomi.push.service.receivers.PingReceiver" >
    		  <intent-filter>
    		    <action android:name="com.xiaomi.push.PING_TIMER" />
    		  </intent-filter>
    		</receiver>
    
    //继承了PushMessageReceiver的DemoMessageReceiver的广播注册
    		<receiver
                android:name="com.xiaomi.mipushdemo.DemoMessageReceiver"
                android:exported="true">
                <intent-filter>
                    <action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE" />
                </intent-filter>
                <intent-filter>
                    <action android:name="com.xiaomi.mipush.MESSAGE_ARRIVED" />
                </intent-filter>
                <intent-filter>
                    <action android:name="com.xiaomi.mipush.ERROR" />
                </intent-filter>
            </receiver>
    

    2. 集成小米推送步骤汇总

    • 步骤1:在小米推送平台进行相关注册开发者账号,并进行应用的注册:应用包名,AppID和AppKey
    • 步骤2:将小米推送的SDK包加入库
    • 步骤3:在应用内初始化小米推送服务
    • 步骤4:继承PushMessageReceiver,并复写相关推送消息的方法
    • 步骤5:在AndroidManifest文件里面配置好权限、注册Service和BroadcastReceiver

    在Android6.0里面的权限需要动态获取

    • 步骤6:根据需要设置一系列的推送设置,如用户别名、标签等等

    接下来,我们来按照上面的步骤,一步步来实现一个简易的小米推送Demo

    3. 实例解析

    步骤1:在小米推送平台进行相关注册开发者账号,并进行应用的注册:应用包名,AppID和AppKey

    注意,填入的包名要跟你的应用App的包名是一致的
    创建应用

    AppID和Key

    步骤2:将小米推送的SDK包加入到你应用的库里

    点击此处进行下载

    小米推送SDK

    步骤3:在应用内初始化小米推送服务

    为了提高推送服务的注册率,我选择在Application的onCreate中初始化推送服务

    BaseActivity.java

    package scut.carson_ho.demo_mipush;
    
    import android.app.ActivityManager;
    import android.app.Application;
    import android.content.Context;
    import android.os.Process;
    
    import com.xiaomi.mipush.sdk.MiPushClient;
    
    import java.util.List;
    
    /**
     * Created by Carson_Ho on 16/10/26.
     */
    
        //主要要继承Application
    public class BaseActivity extends Application {
        // 使用自己APP的ID(官网注册的)
        private static final String APP_ID = "2882303761517520369";
        // 使用自己APP的Key(官网注册的)
        private static final String APP_KEY = "5401752085369";
    
    
        //为了提高推送服务的注册率,我建议在Application的onCreate中初始化推送服务
        //你也可以根据需要,在其他地方初始化推送服务
        @Override
        public void onCreate() {
            super.onCreate();
    
    
            if (shouldInit()) {
                //注册推送服务
                //注册成功后会向DemoMessageReceiver发送广播
                // 可以从DemoMessageReceiver的onCommandResult方法中MiPushCommandMessage对象参数中获取注册信息
                MiPushClient.registerPush(this, APP_ID, APP_KEY);
            }
        }
    
        //通过判断手机里的所有进程是否有这个App的进程
        //从而判断该App是否有打开
        private boolean shouldInit() {
    
        //通过ActivityManager我们可以获得系统里正在运行的activities
        //包括进程(Process)等、应用程序/包、服务(Service)、任务(Task)信息。
            ActivityManager am = ((ActivityManager) getSystemService(Context.ACTIVITY_SERVICE));
            List<ActivityManager.RunningAppProcessInfo> processInfos = am.getRunningAppProcesses();
            String mainProcessName = getPackageName();
    
            //获取本App的唯一标识
            int myPid = Process.myPid();
            //利用一个增强for循环取出手机里的所有进程
            for (ActivityManager.RunningAppProcessInfo info : processInfos) {
                //通过比较进程的唯一标识和包名判断进程里是否存在该App
                if (info.pid == myPid && mainProcessName.equals(info.processName)) {
                    return true;
                }
            }
            return false;
        }
    }
    

    注意要在Android.manifest.xml里的application里加入

    android:name=".BaseActivity"
    

    这样在应用初始化时是第一个加载BaseActivity.java类文件的
    如下图:
    示意图

    步骤4:设置子类继承PushMessageReceiver,并复写相关推送消息的方法

    Mipush_Broadcast.java

    package scut.carson_ho.demo_mipush;
    
    import android.content.Context;
    
    import com.xiaomi.mipush.sdk.ErrorCode;
    import com.xiaomi.mipush.sdk.MiPushClient;
    import com.xiaomi.mipush.sdk.MiPushCommandMessage;
    import com.xiaomi.mipush.sdk.MiPushMessage;
    import com.xiaomi.mipush.sdk.PushMessageReceiver;
    
    /**
     * Created by Carson_Ho on 16/10/26.
     */
    
    public class Mipush_Broadcast extends PushMessageReceiver {
    
        //透传消息到达客户端时调用
        //作用:可通过参数message从而获得透传消息,具体请看官方SDK文档
        @Override
        public void onReceivePassThroughMessage(Context context, MiPushMessage message) {
    
            //打印消息方便测试
            System.out.println("透传消息到达了");
            System.out.println("透传消息是"+message.toString());
    
        }
    
    
    //通知消息到达客户端时调用
        //注:应用在前台时不弹出通知的通知消息到达客户端时也会回调函数
        //作用:通过参数message从而获得通知消息,具体请看官方SDK文档
    
        @Override
        public void onNotificationMessageArrived(Context context, MiPushMessage message) {
            //打印消息方便测试
            System.out.println("通知消息到达了");
            System.out.println("通知消息是"+message.toString());
        }
    
        //用户手动点击通知栏消息时调用
        //注:应用在前台时不弹出通知的通知消息到达客户端时也会回调函数
        //作用:1. 通过参数message从而获得通知消息,具体请看官方SDK文档
        //2. 设置用户点击消息后打开应用 or 网页 or 其他页面
    
        @Override
        public void onNotificationMessageClicked(Context context, MiPushMessage message) {
    
            //打印消息方便测试
            System.out.println("用户点击了通知消息");
            System.out.println("通知消息是" + message.toString());
            System.out.println("点击后,会进入应用" );
    
        }
    
        //用来接收客户端向服务器发送命令后的响应结果。
        @Override
        public void onCommandResult(Context context, MiPushCommandMessage message) {
    
            String command = message.getCommand();
            System.out.println(command );
            
    
            if (MiPushClient.COMMAND_REGISTER.equals(command)) {
                if (message.getResultCode() == ErrorCode.SUCCESS) {
                    
                    //打印信息便于测试注册成功与否
                    System.out.println("注册成功");
    
                } else {
                    System.out.println("注册失败");
                }
            }
        }
    
        //用于接收客户端向服务器发送注册命令后的响应结果。
        @Override
        public void onReceiveRegisterResult(Context context, MiPushCommandMessage message) {
    
            String command = message.getCommand();
            System.out.println(command );
        
            if (MiPushClient.COMMAND_REGISTER.equals(command)) {
                if (message.getResultCode() == ErrorCode.SUCCESS) {
                    
                    //打印日志:注册成功
                    System.out.println("注册成功");
                } else {
                    //打印日志:注册失败
                    System.out.println("注册失败");
                }
            } else {
                System.out.println("其他情况"+message.getReason());
            }
        }
    
    }
    

    具体设置请看官方SDK文档,这里只给出最简单Demo,不作过多描述

    步骤5:在AndroidManifest文件里面配置好权限、注册Service和BroadcastReceiver

    AndroidManifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="scut.carson_ho.demo_mipush">
    
        //相关权限
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
        <uses-permission android:name="android.permission.READ_PHONE_STATE" />
        <uses-permission android:name="android.permission.GET_TASKS" />
        <uses-permission android:name="android.permission.VIBRATE" />
    
    
        //注意这里.permission.MIPUSH_RECEIVE是自身app的包名
        <permission android:name="scut.carson_ho.demo_mipush.permission.MIPUSH_RECEIVE" android:protectionLevel="signature" />
    
        //注意这里.permission.MIPUSH_RECEIVE是自身app的包名
        <uses-permission android:name="scut.carson_ho.demo_mipush.permission.MIPUSH_RECEIVE" />
    
    //注意要初始化BaseActivity.java类
        <application
            android:name=".BaseActivity"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
    
    
    
        //注册广播BroadcastReceiver和Service
        //都是静态注册,因为要长期处在后台运行
        //注:共是3个广播接收器和4个服务,其中包括继承了PushMessageReceiver的DemoMessageReceiver
    
        //4个后台服务
        <service
            android:enabled="true"
            android:process=":pushservice"
            android:name="com.xiaomi.push.service.XMPushService"/>
    
        //此service必须在3.0.1版本以后(包括3.0.1版本)加入
        <service
            android:name="com.xiaomi.push.service.XMJobService"
            android:enabled="true"
            android:exported="false"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:process=":pushservice" />
    
        //此service必须在2.2.5版本以后(包括2.2.5版本)加入
        <service
            android:enabled="true"
            android:exported="true"
            android:name="com.xiaomi.mipush.sdk.PushMessageHandler" />
    
        <service android:enabled="true"
            android:name="com.xiaomi.mipush.sdk.MessageHandleService" />
    
    
        //3个广播
        <receiver
            android:exported="true"
            android:name="com.xiaomi.push.service.receivers.NetworkStatusReceiver" >
            <intent-filter>
                <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </receiver>
    
        <receiver
            android:exported="false"
            android:process=":pushservice"
            android:name="com.xiaomi.push.service.receivers.PingReceiver" >
            <intent-filter>
                <action android:name="com.xiaomi.push.PING_TIMER" />
            </intent-filter>
        </receiver>
    
        //继承了PushMessageReceiver的DemoMessageReceiver的广播注册
        <receiver
            android:name=".Mipush_Broadcast"
            android:exported="true">
            <intent-filter>
                <action android:name="com.xiaomi.mipush.RECEIVE_MESSAGE" />
            </intent-filter>
            <intent-filter>
                <action android:name="com.xiaomi.mipush.MESSAGE_ARRIVED" />
            </intent-filter>
            <intent-filter>
                <action android:name="com.xiaomi.mipush.ERROR" />
            </intent-filter>
        </receiver>
    
    
        </application>
    </manifest>
    
    

    步骤6:根据需要设置一系列的推送设置,如用户别名、标签等等

    • 此处是简单Demo,所以不作过多的设置
    • 更多设置请回看上方官方Demo解析

    运行结果

    测试成功结果

    好了,客户端的代码写好后,可以去小米官网测试一下消息推送了

    步骤1:在小米官网的消息推送里选择你创建的应用,然后点击“推送工具”

    点击推送工具

    步骤2:设置推送消息的相关信息

    可进行的配置非常全面,基本上能满足推送的需求

    设置推送消息
    设置推送消息

    推送的结果

    消息到达客户端

    测试结果
    测试结果:收到的信息

    点击通知栏消息后

    4. Demo下载地址

    Carson的Github:Demo_MiPush

    5. 关于对小米推送的思考(问题)

    上述说的小米推送看似简单:初始化推送服务 + 相关推送设置。但是,好的代码不仅能在正常情况下工作,还应该充分考虑失败情况。那么,有什么样的失败情况需要我们考虑呢?

    • 背景:在这个初始化推送服务的过程中,是需要联系小米推送的服务器来申请reg id(即推送token)。
    • 冲突:初始化过程可能失败:网络问题(没网or网络信号弱)、服务器问题导致初始化失败。那么,当失败以后,该什么时候再次进行初始化呢?

    小米推送的Demo里并没有相关措施解决这个问题

    • 解决方案:在初始化失败的情况下提供重试机制,直到初始化成功(可以通过检测是否已经拿到推送token来确定),问题解决的逻辑如下:

    解决逻辑

    • 具体代码在这里就不作过多描述,如果你希望获得含注册重试机制的小米推送源代码,请在评论留下你的邮箱,我将亲自发送到你的邮箱
    1. 知识点涵盖:网络数据的检测 & 广播接收器
    2. 具体请看我写的另外两篇文章:
      Android:BroadcastReceiver广播接收器最全面解析
      Android:检测网络状态&监听网络变化

    总结

    全面考虑到所有异常问题并恰当地进行处理才能真正体现程序猿的功力,希望大家做撸代码的时候不要只做代码的搬运工,纯粹写代码并不会让你成长,关键在于思考

    6. 总结


    请帮顶!因为你的鼓励是我写作的最大动力!

    展开全文
  • 我们的手机,每天只要数据开了,一些应用都会有每天的及时推送,告诉我们最新的消息,提醒我们版本的更新,那么这个技术点就是使用了通知机制的通知栏框架,它使用于交互事件的通知,它是位于顶层可以展开的通知列表...

    我们的手机,每天只要数据开了,一些应用都会有每天的及时推送,告诉我们最新的消息,提醒我们版本的更新,那么这个技术点就是使用了通知机制的通知栏框架,它使用于交互事件的通知,它是位于顶层可以展开的通知列表

    Notification有哪些功能作用呢

    1》显示接收的短信,消息(QQ,微信,新浪,爱奇艺等)

    2》显示客户端的推送消息

    3》显示正在进行的事务(正在播放的音乐,下载进度条)

    通知状态栏主要涉及到两个类,Notification、NotificationManager

    Notification是通知消息类,它里面对应的通知栏的各种属性

    NotificationManager是状态栏通知的管理类,负责发送,清除消息通知等

    Notification使用步骤

    <1>.得到通知管理者,因为NotificationManager是一个系统Service,所以需要通过getService得到

    NotificationManager notificationManager= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

    <2>.创建Notification

    NotificationCompat.Builder builder=new NotificationCompat.Builder(this);

    <3>.为Notification设置各种属性

    <4>.通过通知管理者NotificationManager发送通知

    notificationManager.notify(0x101,notification);

    <5>.删除通知

    notificationManager.cancel(NOTIFICATION_ID);


    然后再看看完整的代码

    布局

    <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="sendNotification"
                android:text="发送通知"
                />

    Activity

    public void sendNotification(View view){
            //实例化通知管理器
            NotificationManager notificationManager= (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            //实例化通知
            NotificationCompat.Builder builder=new NotificationCompat.Builder(this);
            builder.setContentTitle("大事件");//设置通知标题
            builder.setContentText("不要放孔明灯,容易起火");//设置通知内容
            builder.setDefaults(NotificationCompat.DEFAULT_ALL);//设置通知的方式,震动、LED灯、音乐等
            builder.setAutoCancel(true);//点击通知后,状态栏自动删除通知
            builder.setSmallIcon(android.R.drawable.ic_media_play);//设置小图标
            builder.setContentIntent(PendingIntent.getActivity(this,0x102,new Intent(this,RaingRecived.class),0));//设置点击通知后将要启动的程序组件对应的PendingIntent
            Notification notification=builder.build();
    
            //发送通知
            notificationManager.notify(0x101,notification);
    
        }


    如果使用手机的震动,闪光灯的话就需要添加权限

    <1>.震动权限:<uses-permission android name="android.permission.VIBRATE">

    <2>.闪光灯权限:<uses-permission android name="android.permission.FLASHLIGHT">


    如果是自定义不带按钮的通知栏

    实现方法同上,只是再实例化通知后加上两句代码:

    	RemoteViews remoteViews=new RemoteViews("com.aa.familyapp",R.layout.activity_mynotification);
            builder.setCustomContentView(remoteViews);

    相当于解析了一个布局文件
    
    




    展开全文
  • 这里利用极光推送来实现Android消息推送
  • 一.开发前准备 微信模板消息发送API (建议多看) ...1. ConfirmTemplate(推送接口) package com.zero.jimu.utils.pushMessage; import com.google.gson.Gson; import org.apache.ht...
  • 极光推送之自定义消息推送

    千次阅读 2019-05-09 16:27:24
    一、引入Maven jar 包 官网最新版jar地址:https://docs.jiguang.cn/jpush/server/sdk/java_sdk/ <!-- 极光推送 --> <groupId>cn.jpush.api</groupId> <artifactId>jpush-cl...
  • 网站消息推送

    千次阅读 2016-04-07 23:11:57
    第一次写自己的博客,还是挺紧张的。话不多说,直接进入正文! 本文介绍使用jq库写的简单消息推送,源代码来自于www.helloweba.com">www.helloweba.com,...接下来才是本文的重点,即如何调用jq从而实现消息推送
  • iOS极光推送和腾讯IM消息推送冲突解决 最近app收不到后台推送来的极光推送了,但是在极光官网测试消息推送可以正常推来。因为本身我们的app里有腾讯IM的即时通讯功能块,后台的逻辑是生成群聊消息时同时推送一条极光...
  • 从APP端集成极光推送插件,启动时调用方法获取极光推送返回的registrationId,登录时传入后台保存在数据库里,后台服务集成极光的java版SDK封装自己常用的推送服务方法的服务类,即可实现消息推送。 一、APP后台...
  • 在好几年前,就已经注意到DDPush这款推送中间件,不过看近来发展也还是停留在V1.0...DDPush 任意门 消息推送 DDPush是什么 DDPush可以做什么 移动互联网消息推送 IM实时消息系统核心组件 物联网设备控制与交互 ...
  • 微信公众号开发消息推送以及图文推送

    万次阅读 热门讨论 2018-10-30 17:11:44
    今天给大家分享的关注公众号自动推送图文消息,以及做一个超牛逼的机器人。 先看看效果。 发错图了。。。这是我昨天开发的一款机器人chu了会骂人啥都不会了。 我今天将它词库进行了更新和升级,接入了...
  • 一个轻量级、可插拔的 Android 消息推送框架。一键集成推送(极光推送、友盟推送、华为、小米推送等),提供有效的保活机制,支持推送的拓展,充分解耦推送和业务逻辑,解放你的双手! 更多:作者提 Bug官网 标签...
  • iOS开发——远程消息推送的实现

    千次阅读 2015-12-18 09:55:55
    首先消息推送分为本地消息推送和远程消息推送,而其中又以远程消息最为常用。但是在推送远程消息之前,有两个前提条件,你需要购买苹果的开发者账号,也就是每年99刀;并且有一台iOS真机(模拟器不能测试推送)。...
  • 使用EMQ实现消息推送

    万次阅读 2019-05-06 10:44:52
    笔者希望能为一些选择了EMQ作为消息推送服务的同学启发,并将使用EMQ过程中笔者遇到的问题暴露出来,当然也希望其他使用EMQ的同学能够给笔者更好的建议。本文的食用人群为对EMQ做过调研或者有相关实践经验的同学,...
  • PHP实现IOS消息推送

    千次阅读 2017-09-08 20:07:56
    IOS推送消息是许多IOS应用都具备的功能,最近也在研究这个功能,参考了很多资料终于搞定了,下面就把步骤拿出来...iOS消息推送的工作机制可以简单的用下图来概括:   Provider是指某个iPhone软件的Push服务
  • 一个轻量级、可插拔的Android消息推送框架。一键集成推送(极光推送、友盟推送、华为、小米推送等),提供有效的保活机制,支持推送的拓展,充分解耦推送和业务逻辑,解放你的双手! 在提issue前,请先阅读【提问的...
  • iOS消息推送之本地推送

    千次阅读 2016-07-20 20:40:14
    //进入前台取消应用消息图标 } 当不再需要这个通知时,清除它 [[UIApplication sharedApplication] cancelAllLocalNotifications]; 四、获取通知中的用户参数字典 在...
  • 极光推送在众多的消息推送里,口碑算是很好的,项目中我负责的是这一块,就整理了这篇博客帮助记忆; 极光推送官方SDK文档:https://docs.jiguang.cn/jpush/server/sdk/java_sdk/ 错误码信息:...
  • java后台实现微信公众号和支付宝生活号消息推送,微信和支付宝的消息推送换汤不换药,实现方法类似,都需要先申请消息模板,官网有API文档,写的也很详细,具体代码如下: 微信消息推送: /** * 挂号成功消息推送 ...
  • 近日有关微信将取消消息推送的新闻引起了多方关注。自公众账号推出至今,围绕公号的价值,已经成为多方关注的焦点。公众账号的消息推送也成就了微信上的一些自媒体人,比如:逻辑思维、潘越飞、小道消息等。但随着...
  • 微信小程序订阅消息推送

    千次阅读 2020-07-20 15:12:41
    这几天在使用小程序的模板消息推送接口的时候,出现了个报错信息 “the formId is no longer available in develop or trial version”,去文档查看了一下才发现,模板消息功能在今年1月份已经下架了,现在统一都是...
  • 最近公司项目用到实时推送消息,所以把方法附上,各位有不同见解的留言。现在代码附上: index.html页面内引入js <script type="text/javascript" src="/static/sockjs.min.js" charset="utf-8"></...
  • 使用MQTT协议完成消息推送功能
  • MQTT实现消息推送

    万次阅读 热门讨论 2012-07-24 14:33:21
    MQTT实现消息推送   MQTT实现消息接收(接收消息需实现MqttSimpleCallback接口并实现它的publishArrived方法)必须注册接收消息方法 mqttClient.registerSimpleHandler(simpleCallbackHandler);// 注册接收消息...
  • IOS 推送消息 php做推送服务端

    千次阅读 2014-01-11 16:53:13
    IOS推送消息是许多IOS应用都具备...iOS消息推送的工作机制可以简单的用下图来概括:   Provider是指某个iPhone软件的Push服务器,APNS是Apple Push Notification Service的缩写,是苹果的服务器。  
  • MQTT C Client实现消息推送(入门指南)

    万次阅读 多人点赞 2017-05-01 14:26:25
    MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,通过MQTT协议,...随着移动互联网的发展,MQTT由于开放源代码,耗电量小等特点,将会在移动消息推送领域会有更多的贡献。
  • IOS-推送消息(本地推送

    千次阅读 2014-03-27 11:46:56
    // 创建一个本地推送 UILocalNotification *notification = [[UILocalNotification alloc] init]; //设置10秒之后 //如果设置为立即推送可以不设置pushdate NSDate *pushDate = [NSDate dateWithTi
  • Android GCM消息推送

    千次阅读 2014-12-01 11:25:48
    //这个方法是可选的并且只有当你想显示信息给用户或想取消重试操作的时候才会被重写。 @Override protected boolean onRecoverableError(Context context, String errorId) { return super.onRecoverableError...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 24,120
精华内容 9,648
关键字:

如何取消消息推送