APNs(英文全称:Apple Push Notification service),中文翻译为:苹果推送通知服务。 该技术由
苹果公司提供的APNs服务。
苹果推送通知服务的传输和路由的通知从一个给定的供应商给定的设备。通知是由两个主要部分组成的数据:设备令牌和有效载荷的短消息。设备令牌是类似于一个电话号码,它包含的信息,使的APN定位的设备上安装客户端应用程序。APN还用它来验证通知的
路由。
有效载荷是一个JSON定义的属性列表中指定的设备上的应用程序的用户将被提醒。
APNs在java的使用,这篇文章非常有效
APNs入门学习和使用
https://www.jianshu.com/p/bcdf0c609f7d
作者:Coselding
基本对着这个就能写出基本的APNs实例,本文主要讨论代码编写过程中出现的问题
1.java.lang.NoClassDefFoundError: sun/security/ssl/EllipticCurvesExtension假如你已经加了java虚拟机参数
里面的alpn-boot-8.1.12.v20180117.jar是用来替换虚拟机中的jar包的,这时候出了异常,异常信息可能如下:
[nioEventLoopGroup-2-1] WARN io.netty.handler.ssl.ApplicationProtocolNegotiationHandler - [id: 0x548cffb1, L://*你的ip*/:11364 - R:api.development.push.apple.com/17.188.166.29:443] Failed to select the application-level protocol:
java.lang.NoClassDefFoundError: sun/security/ssl/EllipticCurvesExtension
at sun.security.ssl.ClientHandshaker.getKickstartMessage(ClientHandshaker.java:1526)
at sun.security.ssl.Handshaker.kickstart(Handshaker.java:1061)
at sun.security.ssl.SSLEngineImpl.kickstartHandshake(SSLEngineImpl.java:734)
at sun.security.ssl.SSLEngineImpl.beginHandshake(SSLEngineImpl.java:756)
at io.netty.handler.ssl.JdkSslEngine.beginHandshake(JdkSslEngine.java:147)
at io.netty.handler.ssl.SslHandler.handshake(SslHandler.java:1360)
at io.netty.handler.ssl.SslHandler.channelActive(SslHandler.java:1399)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:223)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:209)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelActive(AbstractChannelHandlerContext.java:202)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelActive(DefaultChannelPipeline.java:1322)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:223)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:209)
at io.netty.channel.DefaultChannelPipeline.fireChannelActive(DefaultChannelPipeline.java:902)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.fulfillConnectPromise(AbstractNioChannel.java:311)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:341)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:627)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:551)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:465)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:437)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:873)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
at java.lang.Thread.run(Thread.java:748)
[nioEventLoopGroup-2-1] INFO com.relayrides.pushy.apns.ApnsClient - Failed to connect.
java.lang.IllegalStateException: Channel closed before HTTP/2 preface completed.
at com.relayrides.pushy.apns.ApnsClient$2.operationComplete(ApnsClient.java:412)
at com.relayrides.pushy.apns.ApnsClient$2.operationComplete(ApnsClient.java:404)
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:514)
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:488)
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:427)
at io.netty.util.concurrent.DefaultPromise.trySuccess(DefaultPromise.java:111)
at io.netty.channel.DefaultChannelPromise.trySuccess(DefaultChannelPromise.java:82)
at io.netty.channel.AbstractChannel$CloseFuture.setClosed(AbstractChannel.java:1058)
at io.netty.channel.AbstractChannel$AbstractUnsafe.doClose0(AbstractChannel.java:686)
at io.netty.channel.AbstractChannel$AbstractUnsafe.close(AbstractChannel.java:664)
at io.netty.channel.AbstractChannel$AbstractUnsafe.close(AbstractChannel.java:607)
at io.netty.channel.DefaultChannelPipeline$HeadContext.close(DefaultChannelPipeline.java:1276)
at io.netty.channel.AbstractChannelHandlerContext.invokeClose(AbstractChannelHandlerContext.java:634)
at io.netty.channel.AbstractChannelHandlerContext.close(AbstractChannelHandlerContext.java:618)
at io.netty.channel.ChannelOutboundHandlerAdapter.close(ChannelOutboundHandlerAdapter.java:71)
at io.netty.channel.AbstractChannelHandlerContext.invokeClose(AbstractChannelHandlerContext.java:634)
at io.netty.channel.AbstractChannelHandlerContext.close(AbstractChannelHandlerContext.java:618)
at io.netty.handler.ssl.SslHandler$8.operationComplete(SslHandler.java:1437)
at io.netty.handler.ssl.SslHandler$8.operationComplete(SslHandler.java:1428)
at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:514)
at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:488)
at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:427)
at io.netty.util.concurrent.DefaultPromise.addListener(DefaultPromise.java:170)
at io.netty.channel.DefaultChannelPromise.addListener(DefaultChannelPromise.java:93)
at io.netty.channel.DefaultChannelPromise.addListener(DefaultChannelPromise.java:28)
at io.netty.handler.ssl.SslHandler.safeClose(SslHandler.java:1428)
at io.netty.handler.ssl.SslHandler.closeOutboundAndChannel(SslHandler.java:1259)
at io.netty.handler.ssl.SslHandler.close(SslHandler.java:449)
at io.netty.channel.AbstractChannelHandlerContext.invokeClose(AbstractChannelHandlerContext.java:634)
at io.netty.channel.AbstractChannelHandlerContext.close(AbstractChannelHandlerContext.java:618)
at io.netty.channel.AbstractChannelHandlerContext.close(AbstractChannelHandlerContext.java:475)
at io.netty.handler.ssl.ApplicationProtocolNegotiationHandler.exceptionCaught(ApplicationProtocolNegotiationHandler.java:122)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:295)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:274)
at io.netty.channel.AbstractChannelHandlerContext.fireExceptionCaught(AbstractChannelHandlerContext.java:266)
at io.netty.handler.ssl.SslHandler.exceptionCaught(SslHandler.java:743)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:295)
at io.netty.channel.AbstractChannelHandlerContext.notifyHandlerException(AbstractChannelHandlerContext.java:862)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:225)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:209)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelActive(AbstractChannelHandlerContext.java:202)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelActive(DefaultChannelPipeline.java:1322)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:223)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:209)
at io.netty.channel.DefaultChannelPipeline.fireChannelActive(DefaultChannelPipeline.java:902)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.fulfillConnectPromise(AbstractNioChannel.java:311)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:341)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:627)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:551)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:465)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:437)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:873)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
at java.lang.Thread.run(Thread.java:748)
[nioEventLoopGroup-2-1] WARN io.netty.handler.ssl.ApplicationProtocolNegotiationHandler - [id: 0x548cffb1, L:/192.168.2.71:11364 ! R:api.development.push.apple.com/17.188.166.29:443] TLS handshake failed:
java.nio.channels.ClosedChannelException
at io.netty.handler.ssl.SslHandler.channelInactive(...)(Unknown Source)
每个alpn-boot包都对应一个单独的jdk版本,并且严格对照版本,因此需要找到对应jdk版本的包,下面是包与jdk的对照表:
具体参照https://www.eclipse.org/jetty/documentation/current/alpn-chapter.html
看网上说用
jetty-alpn-agent-2.0.6.jar
可以自动选择与jdk版本对应的包
2.slf4报错
pushy依赖于slf4j-api的包,但是并没有slf4j的具体实现,因此会出现如下错误:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
这时只需要添加slf4j-simple的依赖就可以了
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
3.消息发送阻塞
final Future<PushNotificationResponse<SimpleApnsPushNotification>> sendNotificationFuture = apnsClient.sendNotification(pushNotification);
final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse = future.get();
如果有多条消息发送,在下一条消息发送之前,只能等上一条消息发送之后得到响应才能发送下一条消息,这无疑是相当耗费时间的,如果想并行的发送消息,就是其他文章说的异步回调方式,可以用:
final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse = future.getNow();
这样的话不用等消息发送后得到响应才发送下一条消息了
贴一下项目代码
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLException;
import com.relayrides.pushy.apns.ApnsClient;
import com.relayrides.pushy.apns.ApnsClientBuilder;
import com.relayrides.pushy.apns.PushNotificationResponse;
import com.relayrides.pushy.apns.util.ApnsPayloadBuilder;
import com.relayrides.pushy.apns.util.SimpleApnsPushNotification;
import com.relayrides.pushy.apns.util.TokenUtil;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
public class MyApnsClient {
static ApnsClient apnsClient;
/*
* static URL url = MyApnsClient.class.getClassLoader().getResource("dev.p12");
* static File file = new File(url.getFile());
*/
public static void main(String[] args) {
try {
//并行线程数
EventLoopGroup loopGroup = new NioEventLoopGroup(4);
//建立apns的客户端,可添加自己写的监听类,实现ApnsClientMetricsListener接口
apnsClient = new ApnsClientBuilder().setEventLoopGroup(loopGroup)
.setMetricsListener(new MyApnsClientListener())
.setClientCredentials(new File("G:\\path\\test.p12"), "123456").build();
//设置apns服务器host
final Future<Void> connectFuture = apnsClient.connect(ApnsClient.DEVELOPMENT_APNS_HOST);
// 设置等待时间,可抛出中断异常
connectFuture.await();
//connectFuture.await(10, TimeUnit.MINUTES);
List<SimpleApnsPushNotification> pushNotifications=new ArrayList<SimpleApnsPushNotification>();
for(int i=0;i<10;i++) {
final SimpleApnsPushNotification pushNotification;
{
//消息的载体
final ApnsPayloadBuilder payloadBuilder = new ApnsPayloadBuilder();
payloadBuilder.setAlertBody("hello world");
payloadBuilder.setSoundFileName("system_sing.mp3");
payloadBuilder.setBadgeNumber(1);
payloadBuilder.setContentAvailable(true);
final String payload = payloadBuilder.buildWithDefaultMaximumLength();
//设备的唯一标识号devicetoken
final String token = TokenUtil
.sanitizeTokenString("your device token");
pushNotification = new SimpleApnsPushNotification(token, null, payload);
}
pushNotifications.add(pushNotification);
}
for(SimpleApnsPushNotification pushNotification:pushNotifications) {
//发送信息得到回应的实例,是异步的,会立马创建一个实例,但可能是发送未成功的状态
final Future<PushNotificationResponse<SimpleApnsPushNotification>> sendNotificationFuture = apnsClient
.sendNotification(pushNotification);
sendNotificationFuture.addListener(
new GenericFutureListener<Future<PushNotificationResponse<SimpleApnsPushNotification>>>() {
@Override
public void operationComplete(
Future<PushNotificationResponse<SimpleApnsPushNotification>> future) throws Exception {
final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse = future
.getNow();
if (pushNotificationResponse.isAccepted()) {
System.out.println("Push notification accepted by APNs gateway.");
} else {
System.out.println("Notification rejected by the APNs gateway: "
+ pushNotificationResponse.getRejectionReason());
if (pushNotificationResponse.getTokenInvalidationTimestamp() != null) {
System.out.println("\t…and the token is invalid as of "
+ pushNotificationResponse.getTokenInvalidationTimestamp());
}
}
}
});
}
final Future<Void> disconnectFuture = apnsClient.disconnect();
try {
disconnectFuture.await();
if (disconnectFuture.isSuccess()) {
System.out.println("连接关闭");
}
} catch (InterruptedException e) {
System.out.println("Failed to disconnect APNs , timeout");
e.printStackTrace();
}
} catch (SSLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
System.out.println("Failed to connect APNs , timeout");
e.printStackTrace();
} /*
* catch (ExecutionException e) {
* System.err.println("Failed to send push notification."); if (e.getCause()
* instanceof ClientNotConnectedException) {
* System.out.println("Waiting for client to reconnect…"); try {
* apnsClient.getReconnectionFuture().await(); } catch (InterruptedException e1)
* { System.out.println("Failed to connect APNs , timeout");
* e1.printStackTrace(); } System.out.println("Reconnected."); }
* e.printStackTrace(); }
*/
}
}