com.farsunset.cim.sdk.android.CIMConnectorManager.java Source code

Java tutorial

Introduction

Here is the source code for com.farsunset.cim.sdk.android.CIMConnectorManager.java

Source

/**
 * Copyright 2013-2023 Xia Jun(3979434@qq.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***************************************************************************************
 *                                                                                     *
 *                        Website : http://www.farsunset.com                           *
 *                                                                                     *
 ***************************************************************************************
 */
package com.farsunset.cim.sdk.android;

import java.net.InetSocketAddress;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.channel.ChannelHandler.Sharable;

import com.farsunset.cim.sdk.android.constant.CIMConstant;
import com.farsunset.cim.sdk.android.filter.CIMLoggingHandler;
import com.farsunset.cim.sdk.android.filter.ClientMessageDecoder;
import com.farsunset.cim.sdk.android.filter.ClientMessageEncoder;
import com.farsunset.cim.sdk.android.exception.NetworkDisabledException;
import com.farsunset.cim.sdk.android.exception.SessionClosedException;
import com.farsunset.cim.sdk.android.model.HeartbeatRequest;
import com.farsunset.cim.sdk.android.model.HeartbeatResponse;
import com.farsunset.cim.sdk.android.model.Message;
import com.farsunset.cim.sdk.android.model.ReplyBody;
import com.farsunset.cim.sdk.android.model.SentBody;

@Sharable
class CIMConnectorManager extends SimpleChannelInboundHandler<Object> {

    private final int CONNECT_TIMEOUT = 10 * 1000;// 
    private final int WRITE_TIMEOUT = 10 * 1000;// 

    private final int READ_IDLE_TIME = 120;// 
    private final int HEARBEAT_TIME_OUT = (READ_IDLE_TIME + 20) * 1000;// ? 
    private final String KEY_LAST_HEART_TIME = "KEY_LAST_HEART_TIME";

    private Bootstrap bootstrap;
    private EventLoopGroup loopGroup;
    private Channel channel;;
    private ExecutorService executor = Executors.newFixedThreadPool(1);
    private Semaphore semaphore = new Semaphore(1, true);

    private Context context;

    private static CIMConnectorManager manager;

    private CIMConnectorManager(Context ctx) {
        context = ctx;

        bootstrap = new Bootstrap();
        loopGroup = new NioEventLoopGroup(1);
        bootstrap.group(loopGroup);
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.option(ChannelOption.TCP_NODELAY, true);
        bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
        bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT);
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new ClientMessageDecoder());
                ch.pipeline().addLast(new ClientMessageEncoder());
                ch.pipeline().addLast(new IdleStateHandler(READ_IDLE_TIME, 0, 0));
                ch.pipeline().addLast(CIMLoggingHandler.getLogger());
                ch.pipeline().addLast(CIMConnectorManager.this);
            }
        });

    }

    public synchronized static CIMConnectorManager getManager(Context context) {
        if (manager == null) {
            manager = new CIMConnectorManager(context);
        }
        return manager;

    }

    private void handleConnectFailure(Throwable error, InetSocketAddress remoteAddress) {

        long interval = CIMConstant.RECONN_INTERVAL_TIME - (5 * 1000 - new Random().nextInt(15 * 1000));

        CIMLoggingHandler.getLogger().connectFailure(remoteAddress, interval);

        Intent intent = new Intent();
        intent.setAction(CIMConstant.IntentAction.ACTION_CONNECTION_FAILED);
        intent.putExtra(Exception.class.getName(), error.getClass().getSimpleName());
        intent.putExtra("interval", interval);
        context.sendBroadcast(intent);

    }

    public void connect(final String host, final int port) {

        if (!isNetworkConnected(context)) {

            Intent intent = new Intent();
            intent.setAction(CIMConstant.IntentAction.ACTION_CONNECTION_FAILED);
            intent.putExtra(Exception.class.getName(), NetworkDisabledException.class.getSimpleName());
            context.sendBroadcast(intent);

            return;
        }

        if (isConnected() || !semaphore.tryAcquire()) {
            return;
        }

        executor.execute(new Runnable() {
            @Override
            public void run() {

                final InetSocketAddress remoteAddress = new InetSocketAddress(host, port);
                bootstrap.connect(remoteAddress).addListener(new GenericFutureListener<ChannelFuture>() {

                    @Override
                    public void operationComplete(ChannelFuture future) {
                        semaphore.release();
                        future.removeListener(this);
                        if (!future.isSuccess() && future.cause() != null) {
                            handleConnectFailure(future.cause(), remoteAddress);
                        }

                        if (future.isSuccess()) {
                            channel = future.channel();
                        }

                    }
                });
            }
        });

    }

    public void send(SentBody body) {

        boolean isSuccessed = false;

        String exceptionName = SessionClosedException.class.getSimpleName();

        if (isConnected()) {
            ChannelFuture future = channel.writeAndFlush(body);
            isSuccessed = future.awaitUninterruptibly(WRITE_TIMEOUT);
            if (!isSuccessed && future.cause() != null) {
                exceptionName = future.cause().getClass().getSimpleName();
            }

        }

        if (!isSuccessed) {
            Intent intent = new Intent();
            intent.setAction(CIMConstant.IntentAction.ACTION_SENT_FAILED);
            intent.putExtra(Exception.class.getName(), exceptionName);
            intent.putExtra(SentBody.class.getName(), body);
            context.sendBroadcast(intent);
        } else {
            Intent intent = new Intent();
            intent.setAction(CIMConstant.IntentAction.ACTION_SENT_SUCCESSED);
            intent.putExtra(SentBody.class.getName(), (SentBody) body);
            context.sendBroadcast(intent);
        }

    }

    public void destroy() {
        if (channel != null) {
            channel.close();
        }

        if (loopGroup != null) {
            loopGroup.shutdownGracefully();
        }

        manager = null;
    }

    public boolean isConnected() {
        return channel != null && channel.isActive();
    }

    public void closeSession() {
        if (channel != null) {
            channel.close();
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        setLastHeartbeatTime(ctx.channel());

        Intent intent = new Intent();
        intent.setAction(CIMConstant.IntentAction.ACTION_CONNECTION_SUCCESSED);
        context.sendBroadcast(intent);

    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {

        Intent intent = new Intent();
        intent.setAction(CIMConstant.IntentAction.ACTION_CONNECTION_CLOSED);
        context.sendBroadcast(intent);

    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {

        /**
         * wifi??? ???
         * 
         */
        if (evt instanceof IdleStateEvent && ((IdleStateEvent) evt).state().equals(IdleState.READER_IDLE)) {
            Long lastTime = getLastHeartbeatTime(ctx.channel());
            if (lastTime != null && System.currentTimeMillis() - lastTime > HEARBEAT_TIME_OUT) {
                channel.close();
            }
        }

    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof Message) {

            Intent intent = new Intent();
            intent.setAction(CIMConstant.IntentAction.ACTION_MESSAGE_RECEIVED);
            intent.putExtra(Message.class.getName(), (Message) msg);
            context.sendBroadcast(intent);

        }
        if (msg instanceof ReplyBody) {

            Intent intent = new Intent();
            intent.setAction(CIMConstant.IntentAction.ACTION_REPLY_RECEIVED);
            intent.putExtra(ReplyBody.class.getName(), (ReplyBody) msg);
            context.sendBroadcast(intent);
        }

        // ????
        if (msg instanceof HeartbeatRequest) {
            ctx.writeAndFlush(HeartbeatResponse.getInstance());
            setLastHeartbeatTime(ctx.channel());
        }

    }

    private void setLastHeartbeatTime(Channel channel) {
        channel.attr(AttributeKey.valueOf(KEY_LAST_HEART_TIME)).set(System.currentTimeMillis());
    }

    private Long getLastHeartbeatTime(Channel channel) {
        return (Long) channel.attr(AttributeKey.valueOf(KEY_LAST_HEART_TIME)).get();
    }

    public static boolean isNetworkConnected(Context context) {
        try {
            ConnectivityManager nw = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = nw.getActiveNetworkInfo();
            return networkInfo != null;
        } catch (Exception e) {
        }

        return false;
    }

}