The experience of accessing native APNS in a job

scene

The company's products mainly interact with the intelligent hardware on the APP side, involving the push of hardware alarm and status, which must be delivered to the user in real time. At the beginning, we chose the integrated Aurora push free version, and the effect can also be achieved, but on the 4th-1st, the aurora server crashed in the afternoon, As a result, the service is not available, and the customers in the production environment are all fried pans, so we plan to build our own wheels and replace IOS with native APNS

Investigation and research

Based on the java integrated APNS native push, I studied it for two days. Through Baidu, I compared the advantages and disadvantages of each framework, and finally chose pushy as the framework. Here are some frameworks I researched, which may not be the best and most complete, for reference only

  1. apns-http2
  2. pushy
  3. java-apns

Integrate

Because the previous maintenance of Aurora push was performed by APP colleagues. After choosing pushy as the native push, it was found that. p12 certificate and certificate password were needed. Baidu issued. p12 certificate, which was generated by IOS development colleagues and sent to me. There is a hole. Because Android still uses Aurora push in our project, so the dependency package of Aurora push is not removed, which leads to an error when importing push. Check the error log and find out that the reason is that the version of netty that Aurora push depends on is 4.1.6.Final, which is too low, which leads to an error when running Io.netty.resolver.dns.roundrobin dnsaddressresolvergroup and other parameters are wrong. Just remove the netty that Aurora push depends on

 <dependencies>

        <dependency>
            <groupId>cn.jpush.api</groupId>
            <artifactId>jpush-client</artifactId>
            <version>3.3.4</version>
            <exclusions>
                <exclusion>
                    <groupId>io.netty</groupId>
                    <artifactId>netty-all</artifactId>
                </exclusion>
            </exclusions>
        </dependency>


        <dependency>
            <groupId>com.eatthepath</groupId>
            <artifactId>pushy</artifactId>
            <version>0.13.11</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-tcnative-boringssl-static</artifactId>
            <version>2.0.26.Final</version>
            <scope>runtime</scope>
        </dependency>

    </dependencies>



Push core code

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 {
                //certificate
                final String p12Password = "123456789a";
                InputStream certificate = APNSConnect.class.getResourceAsStream("/iosPush.p12");
                EventLoopGroup eventLoopGroup = new NioEventLoopGroup(4);
                apnsClient = new ApnsClientBuilder().setApnsServer(ApnsClientBuilder.DEVELOPMENT_APNS_HOST)
                        .setClientCredentials(certificate, p12Password)
                        .setConcurrentConnections(4).setEventLoopGroup(eventLoopGroup).build();

            } catch (Exception e) {
                logger.error("ios get pushy apns client failed!");
                e.printStackTrace();
            }
        }
        return apnsClient;

    }
}



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

    /**
     * Semaphore Semaphore, also known as semaphore, is a concept in the operating system. In Java Concurrent Programming, semaphore controls the number of concurrent threads.
     */
    private static final Semaphore semaphore = new Semaphore(10000);
    private static final String topic = "com.elzj.camera";

    /**
     * ios Push of
     *
     * @param deviceTokens     Unique ID of the push
     * @param alertTitle       Title of the push
     * @param alertBody        Push content
     * @param contentAvailable true: Indicates product release push service false: indicates product test push service
     * @param customProperty   Additional parameters
     * @param badge            If the badge is less than 0, the upper right corner corner marker will not be pushed. It is mainly used to update the status when the message box is added or read
     */
    @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();
        //Each time a task is completed (not necessarily completed by a thread), latch minus 1, until all tasks are completed, the next phase of tasks can be executed, which can improve performance
        final CountDownLatch latch = new CountDownLatch(deviceTokens.size());
        //Thread safe counters
        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);
            }
            //If the badge is less than 0, the upper right corner corner marker will not be pushed. It is mainly used to update the status when the message box is added or read
            if (badge > 0) {
                payloadBuilder.setBadgeNumber(badge);
            }

            //Put all the additional parameters in
            if (customProperty != null) {
                for (Map.Entry<String, Object> map : customProperty.entrySet()) {
                    payloadBuilder.addCustomProperty(map.getKey(), map.getValue());
                }
            }
            // true: indicates product release push service false: indicates product test push service
            payloadBuilder.setContentAvailable(contentAvailable);
            String payload = payloadBuilder.buildWithDefaultMaximumLength();
            final String token = TokenUtil.sanitizeTokenString(deviceToken);
            SimpleApnsPushNotification pushNotification = new SimpleApnsPushNotification(token, topic, payload);
            try {
                //Get a permissive opportunity from semaphores
                semaphore.acquire();
            } catch (Exception e) {
                //Too many threads, no extra semaphores to get
                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();//Release permission, return the occupied semaphore
                    }
                });
                //------------------------------------------------------------------

                if (pushNotificationResponse.isAccepted()) {
                } else {
                    logger.error("Notification rejected by the APNs gateway: " + pushNotificationResponse.getRejectionReason());

                    if (pushNotificationResponse.getTokenInvalidationTimestamp() != null) {
                        logger.error("\t...and the token is invalid as of " + pushNotificationResponse.getTokenInvalidationTimestamp());
                    }
                }
            } catch (final ExecutionException e) {
                logger.error(e.getMessage(), e);
            } catch (InterruptedException e) {
                logger.error(e.getMessage(), e);
            }
            //---------------------------------------------------------------------------------------------------

        }

        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. [Co push" + total + "individual][Success" + (successCnt.get()) + "individual],totalcost= " + (endPushTime - startTime) + ", pushCost=" + (endPushTime - startPushTime));
    }

Epilogue

ios native push access is over. At present, it runs stably in the production environment for one month, with good results and no problems. Through this incident, we have learned a lesson that the service provided by any third party is not guaranteed to be reliable. Sometimes we still need to make wheel insurance by ourselves

Tags: Programming iOS Netty Java less

Posted on Thu, 07 May 2020 23:00:42 -0400 by wwfc_barmy_army