因爲被這個困擾了好久,所以記錄一下…
網上大部分都是關於.p12的代碼,沒有看到有.p8的。
先上github:https://github.com/relayrides/pushy(如果有不懂,可以再來這裏看)
在Java端使用Pushy進行APNs消息推送
1 首先先加入這兩個包:
<!-- ios推送 pushy框架-->
<dependency>
<groupId>com.turo</groupId>
<artifactId>pushy</artifactId>
<version>0.13.7</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<version>2.0.20.Final</version>
<scope>runtime</scope>
</dependency>
2 身份認證:
蘋果APNs提供了兩種認證的方式:基於JWT的身份信息token認證和基於證書的身份認證。Pushy也同樣支持這兩種認證方式。(待會會有區別)
如何獲取蘋果APNs身份認證證書可以查考官方文檔。
3 Pushy使用:
這是.p8的:
package com.imagedt.message.util;
import java.io.File;
import com.turo.pushy.apns.auth.ApnsSigningKey;
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)
.setSigningKey(ApnsSigningKey.loadFromPkcs8File(new File("path/你的放置.p8證書的路徑"),
"你的teamid", "你的keyid"))
.setConcurrentConnections(4).setEventLoopGroup(eventLoopGroup).build();
} catch (Exception e) {
logger.error("ios get pushy apns client failed!");
e.printStackTrace();
}
}
return apnsClient;
}
}
這是.p12證書的:
package com.imagedt.message.util;
import java.io.File;
import com.turo.pushy.apns.auth.ApnsSigningKey;
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("/path/to/certificate.p12"), "p12-file-password")
.setConcurrentConnections(4).setEventLoopGroup(eventLoopGroup).build();
} catch (Exception e) {
logger.error("ios get pushy apns client failed!");
e.printStackTrace();
}
}
return apnsClient;
}
}
區別就是
.p8的:
.setSigningKey(ApnsSigningKey.loadFromPkcs8File(new File(".p8路徑"),
"teamid", "keyid"))
.p12的:
.setClientCredentials(new File("/path/to/certificate.p12"), "p12-file-password")
7、Pushy的最佳實踐
參考Pushy的官方最佳實踐,我們加入瞭如下操作:
通過Semaphore來進行流控,防止緩存過大,內存不足;
通過CountDownLatch來標記消息是否發送完成;
使用AtomicLong完成匿名內部類operationComplete方法中的計數;
使用Netty的Future對象進行消息推送結果的判斷。
具體用法參考如下代碼:
package com.imagedt.message.util;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import com.turo.pushy.apns.util.concurrent.PushNotificationFuture;
import com.turo.pushy.apns.util.concurrent.PushNotificationResponseListener;
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;
/**
* ios消息推送
*/
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併發編程中,信號量控制的是線程併發的數量。
/**
*
* ios的推送
* @param deviceTokens
* @param alertTitle
* @param alertBody
* @param contentAvailable true:表示的是產品發佈推送服務 false:表示的是產品測試推送服務
* @param customProperty 附加參數
* @param badge 如果badge小於0,則不推送這個右上角的角標,主要用於消息盒子新增或者已讀時,更新此狀態
*/
@SuppressWarnings("rawtypes")
public static void push(final List<String> deviceTokens, 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());
}
}
// true:表示的是產品發佈推送服務 false:表示的是產品測試推送服務
payloadBuilder.setContentAvailable(contentAvailable);
String payload = payloadBuilder.buildWithDefaultMaximumLength();
final String token = TokenUtil.sanitizeTokenString(deviceToken);
SimpleApnsPushNotification pushNotification = new SimpleApnsPushNotification(token, "com.imagedt.basho.dev", payload);
try {
semaphore.acquire();//從信號量中獲取一個允許機會
} catch (Exception e) {
logger.error("ios push get semaphore failed, deviceToken:{}", deviceToken);//線程太多了,沒有多餘的信號量可以獲取了
e.printStackTrace();
}
final PushNotificationFuture<SimpleApnsPushNotification, PushNotificationResponse<SimpleApnsPushNotification>>
sendNotificationFuture = apnsClient.sendNotification(pushNotification);
//---------------------------------------------------------------------------------------------------
try {
final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse = sendNotificationFuture.get();
// System.out.println(sendNotificationFuture.isSuccess());
// System.out.println(pushNotificationResponse.isAccepted());
sendNotificationFuture.addListener(new PushNotificationResponseListener<SimpleApnsPushNotification>() {
@Override
public void operationComplete(final PushNotificationFuture<SimpleApnsPushNotification, PushNotificationResponse<SimpleApnsPushNotification>> future) throws Exception {
// When using a listener, callers should check for a failure to send a
// notification by checking whether the future itself was successful
// since an exception will not be thrown.
if (future.isSuccess()) {
final PushNotificationResponse<SimpleApnsPushNotification> pushNotificationResponse =
sendNotificationFuture.getNow();
if (pushNotificationResponse.isAccepted()) {
successCnt.incrementAndGet();
} else {
Date invalidTime = pushNotificationResponse.getTokenInvalidationTimestamp();
logger.error("Notification rejected by the APNs gateway: " + pushNotificationResponse.getRejectionReason());
if (invalidTime != null) {
logger.error("\t…and the token is invalid as of " + pushNotificationResponse.getTokenInvalidationTimestamp());
}
}
// Handle the push notification response as before from here.
} else {
// Something went wrong when trying to send the notification to the
// APNs gateway. We can find the exception that caused the failure
// by getting future.cause().
future.cause().printStackTrace();
}
latch.countDown();
semaphore.release();//釋放允許,將佔有的信號量歸還
}
});
//------------------------------------------------------------------
/*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());
}
}*/
} catch (final ExecutionException e) {
System.err.println("Failed to send push notification.");
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
//---------------------------------------------------------------------------------------------------
}
// 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));
}
}
關於多線程調用client:
Pushy ApnsClient是線程安全的,可以使用多線程來調用。