iOS开发Swift

IOS APNS消息推送框架介绍(pushy)以及详细使用方法

最近公司需要做IOS消息推送的功能,我负责后台推送,IOS端数据处理以及回调我不负责,本篇文章主要介绍基于java的apns消息推送,使用框架为pushy。
声明:我先前也没有接触过这个IOS推送,自己研究了两天,通过百度,对比各个框架的优缺点,最后选择了这个框架,有说的不对的地方,还希望指出来,当然现在很多公司都是采用第三方平台的方式来推送,这里不介绍。
目前比较流行的框架在github上可以直接看到,通过star的多少,可以判断其流行程度,原本准备使用notnoop框架,但是考虑到其基于http1框架,没有返回值,而且框架比较老,很久没有维护了,所以没用。


pushy框架介绍


Pushy是用于发送APN(iOS,MacOS和Safari)推送通知的Java库。它由Turo的工程师编写和维护。

Pushy使用Apple的基于HTTP / 2的APN协议发送推送通知,并支持TLS和基于令牌的身份验证。它与其他推送通知库区别开来,重点在于全面的文档记录,异步操作和用于工业规模操作的设计; 通过Pushy,维护到APN网关的多个并行连接可以轻松高效地向大量不同的应用程序(“主题”)发送大量通知。

我们相信Pushy已经是从Java应用程序发送APN推送通知的最佳工具,并且我们希望您能通过错误报告和拉取请求帮助我们更好地实现它。如果您对使用Pushy有任何疑问,请加入我们的Pushy邮件列表或查看wiki。谢谢!

上面摘抄于git文档
留一个网址,具体源码大家自己去看吧https://github.com/relayrides/pushy

代码详解


我用的maven项目,需要先加入jar

        <dependency>
            <groupId>com.turo</groupId>
            <artifactId>pushy</artifactId>
            <version>0.13.1</version>
        </dependency>

直接先上代码

package com.huali.excutemq.util;

import java.util.Date;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.turo.pushy.apns.ApnsClient;
import com.turo.pushy.apns.PushNotificationResponse;
import com.turo.pushy.apns.util.ApnsPayloadBuilder;
import com.turo.pushy.apns.util.SimpleApnsPushNotification;
import com.turo.pushy.apns.util.TokenUtil;

import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

public class IOSPush {

    private static final Logger logger = LoggerFactory.getLogger(IOSPush.class);

   // private static ApnsClient apnsClient = null;

    //private static final Semaphore semaphore = new Semaphore(10000);//Semaphore又称信号量,是操作系统中的一个概念,在Java并发编程中,信号量控制的是线程并发的数量。

    @SuppressWarnings("rawtypes")
    public void push(final String deviceToken, String alertTitle, String alertBody,boolean contentAvailable,Map<String, Object> customProperty,int badge) {

       // long startTime = System.currentTimeMillis();

        ApnsClient apnsClient = APNSConnect.getAPNSConnect(); 

       // long total = deviceTokens.size();

      //  final CountDownLatch latch = new CountDownLatch(deviceTokens.size());//每次完成一个任务(不一定需要线程走完),latch减1,直到所有的任务都完成,就可以执行下一阶段任务,可提高性能

        final AtomicLong successCnt = new AtomicLong(0);//线程安全的计数器

       // long startPushTime =  System.currentTimeMillis();

       // for (String deviceToken : deviceTokens) { 

             ApnsPayloadBuilder payloadBuilder = new ApnsPayloadBuilder();

             if(alertBody!=null&&alertTitle!=null) {
                 payloadBuilder.setAlertBody(alertBody);
                 payloadBuilder.setAlertTitle(alertTitle);
             }

             //如果badge小于0,则不推送这个右上角的角标,主要用于消息盒子新增或者已读时,更新此状态
             if(badge>0) {
                payloadBuilder.setBadgeNumber(badge); 
             }

             //将所有的附加参数全部放进去
             if(customProperty!=null) {
                 for(Map.Entry<String, Object> map:customProperty.entrySet()) {
                     payloadBuilder.addCustomProperty(map.getKey(),map.getValue()); 
                 }
             }



             payloadBuilder.setContentAvailable(contentAvailable); 

            String payload = payloadBuilder.buildWithDefaultMaximumLength();
            final String token = TokenUtil.sanitizeTokenString(deviceToken);
            SimpleApnsPushNotification pushNotification = new SimpleApnsPushNotification(token, "com.Huali-tec.HLSmartWay", payload);

//            try {
//                semaphore.acquire();//从信号量中获取一个允许机会
//            } catch (Exception e) {
//                logger.error("ios push get semaphore failed, deviceToken:{}", deviceToken);//线程太多了,没有多余的信号量可以获取了
//                e.printStackTrace();
//            }

            final Future<PushNotificationResponse<SimpleApnsPushNotification>> future = apnsClient.sendNotification(pushNotification);

            future.addListener(new GenericFutureListener<Future<PushNotificationResponse>>() { 
                @Override
                public void operationComplete(Future<PushNotificationResponse> pushNotificationResponseFuture) throws Exception {
                    if (future.isSuccess()) {
                        final PushNotificationResponse<SimpleApnsPushNotification> response = future.getNow();
                        if (response.isAccepted()) {
                            successCnt.incrementAndGet();
                        } else {
                            Date invalidTime = response.getTokenInvalidationTimestamp();
                            logger.error("Notification rejected by the APNs gateway: " + response.getRejectionReason());
                            if (invalidTime != null) {
                                logger.error("\t…and the token is invalid as of " + response.getTokenInvalidationTimestamp());
                            }
                        }
                    } else {
                        logger.error("send notification device token={} is failed {} ", token, future.cause().getMessage());
                    }
                  //  latch.countDown();
                   // semaphore.release();//释放允许,将占有的信号量归还
                }
            });

        //}

//        try {
//            latch.await(20, TimeUnit.SECONDS);
//        } catch (Exception e) { 
//            logger.error("ios push latch await failed!");
//            e.printStackTrace();
//        }

        //long endPushTime = System.currentTimeMillis();

      //  logger.info("test pushMessage success. [共推送" + total + "个][成功" + (successCnt.get()) + "个],totalcost= " + (endPushTime - startTime) + ", pushCost=" + (endPushTime - startPushTime));
    }
}

苹果消息推送大致流程:

  • 1、首先通过上面这段代码,将消息推送到苹果服务器(苹果公司买了几十万台服务器专门用于做这个事情),然后苹果服务器收到消息以后,根据devicetoken将对应的内容推送到对应的设备(ps:而安卓手机就是在系统后台有一个监听程序,一直在耗电,这也是为什么苹果手机比较省电的原因,而安卓手机比较费电,你下载的app越多,这个差异就越明显)
  • 2、先前在网上看资料说如果你推送的devicetoken有错误,链接就会中断或者暂停一段时间,这样就会造成延迟,目前我使用这个框架测试十万条数据,暂时没有发现这个情况。
  • 3、由于本案例使用的是消息队列加多个消费者推送消息,所以代码中每次只推送了一条消息,注释部分打开,devicetoken可以存放多个用户设备标志用于多次推送,这里用了多个消费者。
  • 4、此段代码封装了一个方法getAPNSConnect(),代码如下
package com.huali.excutemq.util;

import java.io.File;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.turo.pushy.apns.ApnsClient;
import com.turo.pushy.apns.ApnsClientBuilder;

import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;

public class APNSConnect {

    private static final Logger logger = LoggerFactory.getLogger(APNSConnect.class);

    private static ApnsClient apnsClient = null;

    public static ApnsClient getAPNSConnect() { 

        if (apnsClient == null) {  
            try {
                EventLoopGroup eventLoopGroup = new NioEventLoopGroup(4);
                apnsClient = new ApnsClientBuilder().setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
                        .setClientCredentials(new File("C:\\Users\\Administrator\\Desktop\\PushCertificate-HualiSmartWay.p12"), "#edcvfr4%t")
                        .setConcurrentConnections(4).setEventLoopGroup(eventLoopGroup).build();
            } catch (Exception e) {
                logger.error("ios get pushy apns client failed!"); 
                e.printStackTrace();
            } 
        }
        return apnsClient;

    }
}
  • 这个方法用于服务器与苹果服务器的链接,为什么要单独写出来呢?主要是总不能每消费一个devicetoken就重新链接一次苹果服务器吧,每次执行推送消息程序的时候,会去检查一下这个链接是否还在(与苹果服务器的连接长时间没有数据发过去的话,苹果服务器会将它主动断掉),如果还在的话,就直接用了。
  • 5、这里主要介绍一下setConcurrentConnections和setEventLoopGroup, setConcurrentConnections用于设置服务器与苹果服务器建立几个链接通道,这里是建立了四个,链接通道并不是越多越好的,具体速度自己百度setEventLoopGroup的作用是建立几个线程来处理,说白了就是多线程,我这里设置的都是4,相当于16个线程同时处理。
  • 6、关于推送速度,我没有测试这种4乘以4的速度,我测试了十万条数据,分别是2、10、50消费者的时候的消费速度,2个消费者速度大概600个推送每秒,10个消费者3000个推送每秒。50个消费者6000个推送每秒,大致上跟网上说的速度差不多。
  • 7、上面代码中涉及到一些多线程的知识,我在后面有简单的标注,具体的还请大家自己再网上学习。
  • 8、关于苹果服务器返回的消息(返回消息是否推送成功),第一个返回的消息是Future> future里面包含服务器发送消息到苹果服务器是否成功。这个成功的话还需要知道苹果服务器是否将消息成功推送到用户的手机上,这个是final PushNotificationResponse response = future.getNow();这里获取的response是是否将消息成功推送到用户手机上,这里是一个监听,即在服务器将消息发送以后,就设置了一个监听,用来监听苹果服务器是否将消息成功推送到用户手机上,当然,监听不会等到消息返回以后才继续往下执行代码,而是直接往下执行代码。所以这里有一个代码latch.await(20, TimeUnit.SECONDS);这句代码的意思就是等待当前线程执行完毕(即有返回消息以后),再继续往下执行,目的是为了统计消息发送成功的数量,当然我这里是一个个执行的,所以就没有必要去统计了,不然会有延迟的。
  • 9、顺便贴上一张图:


这个图片是消息消息推送的主要参数。这里说一下,苹果推送分为静默推送(contentAvailable值为0,app端没有回调函数)和非静态推送(contentAvailable值为1,app端有回调函数,比如 对数据进行一些处理什么的。),如果你设置title和body为null,则app不会推送(就是下拉没有数据,这个情况一般用来更新badge数量)
还有一点顺带提一下:payloadBuilder.addCustomProperty(map.getKey(),map.getValue());这句代码是后台服务器传给app的附加参数,这个需要根据具体需求,具体分析。

对了还有一个证书的问题:自己去苹果官网申请,只需要一个.p12格式的证书,以及证书的密码就可以了。具体申请步骤请自己百度