im_lib/.gitignore
New file @@ -0,0 +1 @@ /build im_lib/build.gradle
New file @@ -0,0 +1,34 @@ apply plugin: 'com.android.library' android { compileSdkVersion 29 defaultConfig { minSdkVersion 21 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles 'consumer-rules.pro' } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.0.2' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: []) } im_lib/consumer-rules.pro
im_lib/libs/netty-all-4.1.43.jarBinary files differ
im_lib/proguard-rules.pro
New file @@ -0,0 +1,21 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile im_lib/src/androidTest/java/com/anyun/im_lib/ExampleInstrumentedTest.java
New file @@ -0,0 +1,27 @@ package com.anyun.im_lib; import android.content.Context; import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; /** * Instrumented test, which will execute on an Android device. * * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() { // Context of the app under test. Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); assertEquals("com.anyun.im_lib.test", appContext.getPackageName()); } } im_lib/src/main/AndroidManifest.xml
New file @@ -0,0 +1,2 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.anyun.im_lib" /> im_lib/src/main/java/com/anyun/im_lib/ExecutorServiceFactory.java
New file @@ -0,0 +1,100 @@ package com.anyun.im_lib; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * MyApplication2 * Created by lzw on 2019/12/2. 14:15:59 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class ExecutorServiceFactory { /*** 管理线程组,负责重连***/ private ExecutorService bossPool; /*** 工作线程组,负责心跳***/ private ExecutorService workPool; /** * 初始化Boss线程池 */ public synchronized void initBossLoopGroup(){ initBossLoopGroup(1); } /** * 初始化work线程池 */ public synchronized void initWorkLoopGroup(){ initWorkLoopGroup(1); } /** * 执行Boss任务 * @param runnable */ public void execBossTask(Runnable runnable){ if (bossPool == null){ initBossLoopGroup(); } bossPool.execute(runnable); } /** * 执行work任务 * @param runnable */ public void execWorkTask(Runnable runnable){ if (workPool == null){ initWorkLoopGroup(); } workPool.execute(runnable); } private void initWorkLoopGroup(int size) { destroyWorkLoopGroup(); workPool = Executors.newFixedThreadPool(size); } private void initBossLoopGroup(int size) { destroyBossLoopGroup(); bossPool = Executors.newFixedThreadPool(size); } /** * 释放boss线程池 */ private void destroyBossLoopGroup() { if (bossPool != null){ try { bossPool.shutdownNow(); } catch (Exception e) { e.printStackTrace(); } finally { bossPool = null; } } } /** * 释放work线程池 */ public void destroyWorkLoopGroup() { if (workPool != null){ try { workPool.shutdownNow(); } catch (Exception e) { e.printStackTrace(); } finally { workPool = null; } } } /** * 释放所有线程 */ public void destroy() { destroyBossLoopGroup(); destroyWorkLoopGroup(); } } im_lib/src/main/java/com/anyun/im_lib/HeartbeatHandler.java
New file @@ -0,0 +1,12 @@ package com.anyun.im_lib; import io.netty.channel.ChannelInboundHandlerAdapter; /** * MyApplication2 * Created by lzw on 2019/12/4. 11:31:25 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class HeartbeatHandler extends ChannelInboundHandlerAdapter { } im_lib/src/main/java/com/anyun/im_lib/HeartbeatRespHandler.java
New file @@ -0,0 +1,22 @@ package com.anyun.im_lib; import com.anyun.im_lib.interf.IMSClientInteface; import io.netty.channel.ChannelInboundHandlerAdapter; /** * MyApplication2 * Created by lzw on 2019/12/4. 11:50:05 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class HeartbeatRespHandler extends ChannelInboundHandlerAdapter { private static final String TAG = HeartbeatRespHandler.class.getSimpleName(); private IMSClientInteface imsClient; public HeartbeatRespHandler(IMSClientInteface imsClient) { this.imsClient = imsClient; } } im_lib/src/main/java/com/anyun/im_lib/IMSClientFactory.java
New file @@ -0,0 +1,16 @@ package com.anyun.im_lib; import com.anyun.im_lib.interf.IMSClientInteface; import com.anyun.im_lib.netty.NettyTcpClient; /** * MyApplication2 * Created by lzw on 2019/12/2. 13:13:44 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class IMSClientFactory { public static IMSClientInteface getIMSClient() { return NettyTcpClient.getInstance(); } } im_lib/src/main/java/com/anyun/im_lib/IMSConfig.java
New file @@ -0,0 +1,28 @@ package com.anyun.im_lib; /** * MyApplication2 * Created by lzw on 2019/12/2. 14:16:22 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class IMSConfig { public static final int CONNECT_STATE_FAILURE = -1; public static final int DEFAULT_RECONNECT_BASE_DELAY_TIME = 3 * 1000; public static final int DEFAULT_CONNECT_TIMEOUT = 10 * 1000; public static final int DEFAULT_HEARTBEAT_INTERVAL_FOREGROUND = 3 * 1000; public static final int DEFAULT_HEARTBEAT_INTERVAL_BACKGROUND = 30 * 1000; /*** 应用在前台标识***/ public static final int APP_STATUS_FOREGROUND = 0; /*** 应用在后台标识***/ public static final int APP_STATUS_BACKGROUND = -1; /*** 默认消息发送失败重发次数 ***/ public static final int DEFAULT_RESEND_COUNT = 3; public static final int DEFAULT_RESEND_INTERVAL = 8 * 1000; /******默认重连一个周期失败间隔时长******/ public static final int DEFAULT_RECONNECT_INTERVAL = 3 * 1000; public static final int CONNECT_STATE_CONNECTING = 0; public static final int CONNECT_STATE_SUCCESSFUL = 1; } im_lib/src/main/java/com/anyun/im_lib/LoginAuthRespHandler.java
New file @@ -0,0 +1,42 @@ package com.anyun.im_lib; import android.util.Log; import com.anyun.im_lib.interf.IMSClientInteface; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; /** * MyApplication2 * 设备鉴权 * Created by lzw on 2019/12/4. 11:25:33 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class LoginAuthRespHandler extends ChannelInboundHandlerAdapter { private static final String TAG = LoginAuthRespHandler.class.getSimpleName(); private IMSClientInteface imsClient; /** * 构造函数 * @param imsClient */ public LoginAuthRespHandler(IMSClientInteface imsClient) { this.imsClient = imsClient; } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { super.channelReadComplete(ctx); Log.i(TAG, "channelReadComplete"); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { super.channelRead(ctx, msg); Log.i(TAG, "channelRead"); // TODO: 2019/12/4 } } im_lib/src/main/java/com/anyun/im_lib/MsgDispatcher.java
New file @@ -0,0 +1,15 @@ package com.anyun.im_lib; import com.anyun.im_lib.listener.OnEventListener; /** * MyApplication2 * Created by lzw on 2019/12/2. 14:02:14 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class MsgDispatcher { public void setOnEventListener(OnEventListener listener) { } } im_lib/src/main/java/com/anyun/im_lib/MsgTimeOutTimerManager.java
New file @@ -0,0 +1,33 @@ package com.anyun.im_lib; import com.anyun.im_lib.interf.IMSClientInteface; import com.anyun.im_lib.netty.NettyTcpClient; import java.util.HashMap; import java.util.Map; /** * MyApplication2 * Created by lzw on 2019/12/2. 14:03:15 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class MsgTimeOutTimerManager { private IMSClientInteface imsClient; private Map<String,MsgTimeoutTimer> mMsgTimeoutMap = new HashMap<>(); public MsgTimeOutTimerManager(NettyTcpClient nettyTcpClient) { this.imsClient = nettyTcpClient; } /** * 添加消息到发送超时管理器 * @param message */ public void add(String message){ if (message == null ){ } } } im_lib/src/main/java/com/anyun/im_lib/MsgTimeoutTimer.java
New file @@ -0,0 +1,36 @@ package com.anyun.im_lib; import android.util.Log; import com.anyun.im_lib.interf.IMSClientInteface; import java.util.Timer; import java.util.TimerTask; /** * MyApplication2 * Created by lzw on 2019/12/2. 15:09:39 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class MsgTimeoutTimer extends Timer { private static final String TAG = "MsgTimeoutTimer"; private IMSClientInteface imsClient; private Object message;//发送的消息 private int currentResendCount;//当前重发次数 private MsgTimeoutTask task;//消息发送超时任务 public MsgTimeoutTimer(IMSClientInteface imsClient) { Log.i(TAG, "MsgTimeoutTimer: "); this.imsClient = imsClient; this.schedule(task,imsClient.getResendInterval(),imsClient.getResendInterval()); } private class MsgTimeoutTask extends TimerTask{ @Override public void run() { } } } im_lib/src/main/java/com/anyun/im_lib/im/IMSClientBootstrap.java
New file @@ -0,0 +1,61 @@ package com.anyun.im_lib.im; import android.util.Log; import com.anyun.im_lib.IMSClientFactory; import com.anyun.im_lib.interf.IMSClientInteface; import java.util.Vector; import static android.content.ContentValues.TAG; /** * MyApplication2 * Created by lzw on 2019/12/2. 11:56:55 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class IMSClientBootstrap { private static final IMSClientBootstrap INSTANCE= new IMSClientBootstrap(); private IMSClientInteface imsClient; /**标记IMSClientBootstrap是否已经初始化**/ private boolean isAlive; /** * * @param userId * @param token * @param hosts * @param appStatus */ public synchronized void init(String userId,String token,String hosts,int appStatus){ if (!isAlive){ Vector<String> serverUrlList = convertHosts(hosts); if (serverUrlList == null || serverUrlList.size() ==0){ Log.i(TAG, "init IMLibClientBootstrap error,ims hosts is null"); return; } isAlive = true; Log.i(TAG, "init IMLibClientBootstrap ,server="+hosts); } if (null != imsClient){ imsClient.close(); } //初始化IMSClientInteface imsClient = IMSClientFactory.getIMSClient(); updateAppStatus(appStatus); // imsClient.init(serverUrlList,new OnEventListener(userId,token),new IMSConnectStatusCallback()); } private void updateAppStatus(int appStatus) { } private Vector<String> convertHosts(String hosts) { //TODO return null; } } im_lib/src/main/java/com/anyun/im_lib/interf/IMSClientInteface.java
New file @@ -0,0 +1,114 @@ package com.anyun.im_lib.interf; import com.anyun.im_lib.MsgDispatcher; import com.anyun.im_lib.MsgTimeOutTimerManager; import com.anyun.im_lib.listener.IMSConnectStatusCallback; import com.anyun.im_lib.listener.OnEventListener; import java.util.Vector; /** * MyApplication2 * Created by lzw on 2019/12/2. 11:58:51 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public interface IMSClientInteface { /** * 初始化 * @param serverUrlList * @param listener * @param callback */ void init(Vector<String> serverUrlList, OnEventListener listener, IMSConnectStatusCallback callback); /** * 重置连接,也就是重连 * 首次连接也可认为是重连 */ void resetConnect(); /** * 重置连接,也就是重连 * 首次连接也可认为是重连 * 重载 * @param isFirst 是否首次连接 */ void resetConnect(boolean isFirst); /** * 关闭连接,同时释放资源 */ void close(); /** * 表示ims是否已经关闭 * @return */ boolean isClosed(); /** * 发送消息 * @param msg */ void sendMsg(String msg); /** * 发送消息 * 重载 * @param msg * @param isJoinTimeoutManager 是否加入超时管理器 */ void sendMsg(String msg, boolean isJoinTimeoutManager); /** * 获取重连间隔时长 * @return */ int getReconnectInterval(); /** * 获取连间隔时长 * @return */ int getConnectTimeout(); /** * 获取应用在前台时心跳间隔 * @return */ int getForegroundHeartbeatInterval(); /** * 设置app前后台状态 * @param appStatus */ void setAppStatus(int appStatus); /** * 获取应用在后台时心跳间隔 * @return */ int getBackgroundHeartbeatInterval(); /** * 获取应用层消息发送超时重发次数 * @return */ int getResendCount(); /** * 获取应用层消息发送超时间隔 * @return */ int getResendInterval(); /** * 获取消息转发器 * @return */ MsgDispatcher getMsgDispatcher(); MsgTimeOutTimerManager getMsgTimeOutTimerManager(); } im_lib/src/main/java/com/anyun/im_lib/listener/IMSConnectStatusCallback.java
New file @@ -0,0 +1,20 @@ package com.anyun.im_lib.listener; /** * MyApplication2 * Created by lzw on 2019/12/2. 13:18:05 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public interface IMSConnectStatusCallback { /***ims连接中*/ void connecting(); /***ims连接成功**/ void onConnected(); /***ims连接失败**/ void onConnectFailed(); } im_lib/src/main/java/com/anyun/im_lib/listener/OnEventListener.java
New file @@ -0,0 +1,51 @@ package com.anyun.im_lib.listener; /** * MyApplication2 * Created by lzw on 2019/12/2. 13:17:49 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public interface OnEventListener { /** * 分发消息到应用层 * @param message */ void dispatchMsg(Object message); /** * 从应用层获取网络是否可用 * @return */ boolean isNetWorkAvailable(); /** * 获取重连间隔时长 * @return */ int getConnectTimeout(); /** * 获取应用在前台时心跳间隔 * @return */ int getForegroundHeartbeatInterval(); /** * 获取应用在后台时心跳间隔 * @return */ int getBackgroundHeartbeatInterval(); /** * 获取应用层消息发送超时重发次数 * @return */ int getResendCount(); /** * 获取应用层消息发送超时间隔 * @return */ int getResendInterval(); } im_lib/src/main/java/com/anyun/im_lib/netty/NettyTcpClient.java
New file @@ -0,0 +1,378 @@ package com.anyun.im_lib.netty; import android.text.TextUtils; import android.util.Log; import com.anyun.im_lib.ExecutorServiceFactory; import com.anyun.im_lib.HeartbeatHandler; import com.anyun.im_lib.IMSConfig; import com.anyun.im_lib.MsgDispatcher; import com.anyun.im_lib.MsgTimeOutTimerManager; import com.anyun.im_lib.interf.IMSClientInteface; import com.anyun.im_lib.listener.IMSConnectStatusCallback; import com.anyun.im_lib.listener.OnEventListener; import java.util.Vector; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.timeout.IdleStateHandler; /** * MyApplication2 * Created by lzw on 2019/12/2. 13:14:52 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class NettyTcpClient implements IMSClientInteface { private static final String TAG = NettyTcpClient.class.getSimpleName(); private static volatile NettyTcpClient instance; private Bootstrap bootstrap; private Channel channel; /****标识IMS是否已关闭***/ private boolean isClosed = false; /****ims服务器地址组***/ private Vector<String> serverUrlList; /****与应用层交互的listener***/ private OnEventListener mOnEventListener; /*** ims连接状态回调监听器**/ private IMSConnectStatusCallback imsConnectStatusCallback; /****消息转发器***/ private MsgDispatcher msgDispatcher; /***线程池工厂**/ private ExecutorServiceFactory loopGroup; /****是否正在进行重连***/ private boolean isReconnecting = false; /*** ims连接状态,初始化为连接失败 ***/ private int connectStatus = IMSConfig.CONNECT_STATE_FAILURE; /*** 重连间隔时长 ***/ private int reconnectInterval = IMSConfig.DEFAULT_RECONNECT_BASE_DELAY_TIME; /*** 连接超时时长 ***/ private int connectTimeOut = IMSConfig.DEFAULT_CONNECT_TIMEOUT; private int heartBeatInterval = IMSConfig.DEFAULT_HEARTBEAT_INTERVAL_FOREGROUND; private int foregroundHeartBeatInterval = IMSConfig.DEFAULT_HEARTBEAT_INTERVAL_FOREGROUND; private int backgroundHeartBeatInterval = IMSConfig.DEFAULT_HEARTBEAT_INTERVAL_BACKGROUND; private int appStatus = IMSConfig.APP_STATUS_FOREGROUND; /*** 消息发送超时重发次数***/ private int resendCount = IMSConfig.DEFAULT_RESEND_COUNT; /*** 消息发送失败重发间隔时长***/ private int resendInterval = IMSConfig.DEFAULT_RESEND_INTERVAL; /*** 当前连接host***/ private String currentHost = null; /*** 当前连接port***/ private int currentPort = -1; /*** 消息发送超时定时管理器***/ private MsgTimeOutTimerManager msgTimeOutTimerManager; private NettyTcpClient(){ } public static IMSClientInteface getInstance() { if (null == instance){ synchronized (NettyTcpClient.class){ if (null==instance){ instance = new NettyTcpClient(); } } } return instance; } /** * 初始化 * @param serverUrlList 服务器地址列表 * @param listener 与应用层交互的listener * @param callback ims连接状态回调 */ @Override public void init(Vector<String> serverUrlList, OnEventListener listener, IMSConnectStatusCallback callback) { close(); isClosed = false; this.serverUrlList = serverUrlList; this.mOnEventListener = listener; this.imsConnectStatusCallback = callback; msgDispatcher = new MsgDispatcher(); msgDispatcher.setOnEventListener(listener); loopGroup = new ExecutorServiceFactory(); loopGroup.initBossLoopGroup(); msgTimeOutTimerManager = new MsgTimeOutTimerManager(this); /*** 进行第一次连接***/ resetConnect(true); } @Override public void resetConnect() { this.resetConnect(false); } @Override public void resetConnect(boolean isFirst) { if (!isFirst){ try { Thread.sleep(IMSConfig.DEFAULT_RECONNECT_INTERVAL); } catch (InterruptedException e) { e.printStackTrace(); } } //只有第一个调用者才能赋值并调用重连 if (!isClosed && !isReconnecting){ synchronized (this){ if (!isClosed && !isReconnecting){ //标识重连任务进行中... isReconnecting = true; onConnectStatusCallback(IMSConfig.CONNECT_STATE_CONNECTING); closeChannel(); loopGroup.execBossTask(new ResetConnectRunnable(isFirst)); } } } } private class ResetConnectRunnable implements Runnable{ private boolean isFirst; public ResetConnectRunnable(boolean isFirst) { this.isFirst = isFirst; } @Override public void run() { if (!isFirst){ onConnectStatusCallback(IMSConfig.CONNECT_STATE_FAILURE); } try { //重连时,释放工作组线程池,也就是停止心跳 loopGroup.destroyWorkLoopGroup(); while (!isClosed){ if (!isNetworkAvaliable()){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } continue; } //网络可用才进行连接 int status; if ((status=reConnect()) == IMSConfig.CONNECT_STATE_SUCCESSFUL){ onConnectStatusCallback(status); //连接成功,调出循环 break; } if (status == IMSConfig.CONNECT_STATE_FAILURE){ onConnectStatusCallback(status); try { Thread.sleep(IMSConfig.DEFAULT_RECONNECT_INTERVAL); } catch (InterruptedException e) { e.printStackTrace(); } } } } finally { //标识重连任务停止 isReconnecting = false; } } } /** * 重连,首次连接也认为是第一次重连 * @return */ private int reConnect() { if (!isClosed){ try { //先释放EventLoop线程组 if (bootstrap != null){ bootstrap.group().shutdownGracefully(); } } finally { bootstrap = null; } initBootstrap(); return connectServer(); } return IMSConfig.CONNECT_STATE_FAILURE; } /*** * 初始化Bootstrap */ private void initBootstrap() { EventLoopGroup loopGroup = new NioEventLoopGroup(4); bootstrap = new Bootstrap(); bootstrap.group(loopGroup).channel(NioSocketChannel.class); // 设置该项以后,如果在两小时内没有数据通信时,TCP会自动发送一个活动探测数据报文 bootstrap.option(ChannelOption.SO_KEEPALIVE,true); bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,getConnectTimeout()); bootstrap.handler(new TCPChannelInitializerHandler(this)); } private int connectServer(){ if (serverUrlList == null || serverUrlList.size() == 0){ return IMSConfig.CONNECT_STATE_FAILURE; } for (int i = 0; i < serverUrlList.size(); i++) { String serverUrl = serverUrlList.get(i); if (TextUtils.isEmpty(serverUrl)){ return IMSConfig.CONNECT_STATE_FAILURE; } } } private boolean isNetworkAvaliable() { if (mOnEventListener != null){ return mOnEventListener.isNetWorkAvailable(); } return false; } private void onConnectStatusCallback(int connectStateConnecting) { } @Override public void close() { if (isClosed){ return; } isClosed = true; /*** 关闭channel***/ try { closeChannel(); } catch (Exception e) { e.printStackTrace(); } /*** 关闭bootstrap ***/ try { if (bootstrap != null){ bootstrap.group().shutdownGracefully(); } } catch (Exception e) { e.printStackTrace(); } /*** 释放线程池 ***/ try { if (loopGroup != null){ loopGroup.destroy(); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (serverUrlList != null){ serverUrlList.clear(); } } catch (Exception e) { e.printStackTrace(); } isReconnecting = false; channel = null; bootstrap = null; } } private void closeChannel() { if (channel != null){ removeHandler(HeartbeatHandler.class.getSimpleName()); removeHandler(TCPReadHandler.class.getSimpleName()); removeHandler(IdleStateHandler.class.getSimpleName()); } } /** * 移除指定handler * @param handlerName */ private void removeHandler(String handlerName) { try { if (channel.pipeline().get(handlerName) != null){ channel.pipeline().remove(handlerName); } } catch (Exception e) { e.printStackTrace(); Log.i(TAG, "removeHandler fail,handlerName="+handlerName); } } @Override public boolean isClosed() { return false; } @Override public void sendMsg(String msg) { } @Override public void sendMsg(String msg, boolean isJoinTimeoutManager) { } @Override public int getReconnectInterval() { return 0; } @Override public int getConnectTimeout() { return 0; } @Override public int getForegroundHeartbeatInterval() { return 0; } @Override public void setAppStatus(int appStatus) { } @Override public int getBackgroundHeartbeatInterval() { return 0; } @Override public int getResendCount() { return 0; } @Override public int getResendInterval() { return 0; } @Override public MsgDispatcher getMsgDispatcher() { return null; } @Override public MsgTimeOutTimerManager getMsgTimeOutTimerManager() { return null; } } im_lib/src/main/java/com/anyun/im_lib/netty/TCPChannelInitializerHandler.java
New file @@ -0,0 +1,44 @@ package com.anyun.im_lib.netty; import com.anyun.im_lib.HeartbeatRespHandler; import com.anyun.im_lib.LoginAuthRespHandler; import com.anyun.im_lib.interf.IMSClientInteface; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; /** * MyApplication2 * channel初始化 * Created by lzw on 2019/12/2. 15:56:39 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class TCPChannelInitializerHandler extends ChannelInitializer<Channel> { private IMSClientInteface imsClient; public TCPChannelInitializerHandler(NettyTcpClient nettyTcpClient) { this.imsClient = nettyTcpClient; } @Override protected void initChannel(Channel channel) throws Exception { ChannelPipeline pipeline = channel.pipeline(); //netty提供的自定义长度解码器,解决TP拆包/粘包问题 // TODO: 2019/12/4 //握手认证消息相应处理handler pipeline.addLast(LoginAuthRespHandler.class.getSimpleName(), new LoginAuthRespHandler(imsClient)); //心跳消息响应处理handler pipeline.addLast(HeartbeatRespHandler.class.getSimpleName(), new HeartbeatRespHandler(imsClient)); //接收消息处理handler pipeline.addLast(TCPReadHandler.class.getSimpleName(),new TCPReadHandler(imsClient)); } } im_lib/src/main/java/com/anyun/im_lib/netty/TCPReadHandler.java
New file @@ -0,0 +1,65 @@ package com.anyun.im_lib.netty; import android.util.Log; import com.anyun.im_lib.interf.IMSClientInteface; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; /** * MyApplication2 * 消息接收处理handler * Created by lzw on 2019/12/4. 10:58:43 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class TCPReadHandler extends ChannelInboundHandlerAdapter { private static final String TAG = TCPReadHandler.class.getSimpleName(); private IMSClientInteface imsClient; public TCPReadHandler(IMSClientInteface imsClient) { this.imsClient = imsClient; } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); Log.i(TAG, "channelInactive"); Channel channel = ctx.channel(); if (channel != null){ channel.close(); ctx.close(); } //触发重连 imsClient.resetConnect(false); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); Log.i(TAG, "exceptionCaught: "+cause.getMessage()); Channel channel = ctx.channel(); if (channel != null){ channel.close(); } //触发重连 imsClient.resetConnect(false); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { super.channelRead(ctx, msg); // TODO: 2019/12/4 } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { super.channelReadComplete(ctx); Log.i(TAG, "channelReadComplete"); } } im_lib/src/main/res/values/strings.xml
New file @@ -0,0 +1,3 @@ <resources> <string name="app_name">im_lib</string> </resources> im_lib/src/test/java/com/anyun/im_lib/ExampleUnitTest.java
New file @@ -0,0 +1,17 @@ package com.anyun.im_lib; import org.junit.Test; import static org.junit.Assert.*; /** * Example local unit test, which will execute on the development machine (host). * * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> */ public class ExampleUnitTest { @Test public void addition_isCorrect() { assertEquals(4, 2 + 2); } }