app/build.gradle
@@ -55,4 +55,5 @@ implementation project(path: ':lib') implementation 'com.google.code.gson:gson:2.8.6' implementation 'com.facebook.stetho:stetho:1.5.0' implementation project(path: ':im_lib') } app/src/main/java/safeluck/drive/evaluation/DB/Student.java
@@ -30,6 +30,16 @@ @ColumnInfo(name = "sex") private int sex; public long getBegin_time() { return begin_time; } public void setBegin_time(long begin_time) { this.begin_time = begin_time; } private long begin_time; public Student(long stu_id, @NonNull String name, String ID, int sex) { this.stu_id = stu_id; this.name = name; app/src/main/java/safeluck/drive/evaluation/DB/WorkRoomDataBase.java
@@ -7,6 +7,7 @@ import androidx.room.Database; import androidx.room.Room; import androidx.room.RoomDatabase; import androidx.room.migration.Migration; import androidx.sqlite.db.SupportSQLiteDatabase; import androidx.work.OneTimeWorkRequest; import androidx.work.WorkManager; @@ -31,7 +32,7 @@ * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ @Database(entities = {Student.class, CriteriaForI.class, FailedProj.class, CriteriaForII.class},version = 1,exportSchema = false) @Database(entities = {Student.class, CriteriaForI.class, FailedProj.class, CriteriaForII.class},version = 2,exportSchema = false) public abstract class WorkRoomDataBase extends RoomDatabase { private static final String TAG = "WorkRoomDataBase"; public abstract StudentDao getstudentDao(); @@ -46,6 +47,14 @@ private static final int NUMBER_OF_THREADS = 4; public static final ExecutorService dataBaseWriteExecutor = Executors.newFixedThreadPool(NUMBER_OF_THREADS); static Migration migration = new Migration(1,2) { @Override public void migrate(@NonNull SupportSQLiteDatabase database) { database.execSQL("alter table student_table ADD COLUMN begin_time INTEGER NOT NULL DEFAULT 0"); } }; private static final Migration[] ALL_MIGRATIONS = new Migration[]{migration}; public static WorkRoomDataBase getWorkRoomDataBase(final Context mContext){ Log.i(TAG, "getWorkRoomDataBase"); @@ -54,6 +63,7 @@ if (workRoomDataBase == null){ Log.i(TAG, "getWorkRoomDataBase==null "); workRoomDataBase = Room.databaseBuilder(mContext.getApplicationContext(),WorkRoomDataBase.class,"work_database") .addMigrations(ALL_MIGRATIONS) .addCallback(new Callback() { @Override public void onCreate(@NonNull SupportSQLiteDatabase db) { app/src/main/java/safeluck/drive/evaluation/fragment/TrainFragment.java
@@ -67,7 +67,7 @@ FailedProjViewModel failedProjViewModel =ViewModelProviders.of(this).get(FailedProjViewModel.class); failedProjViewModel.getFailedProjectsForI(1000).observe(this, new Observer<List<FailedProj_select>>() { failedProjViewModel.getFailedProjectsForI(1001).observe(this, new Observer<List<FailedProj_select>>() { @Override public void onChanged(List<FailedProj_select> failedProj_selects) { item_id = 0; 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/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,29 @@ 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; public static final int DEFAULT_RECONNECT_COUNT = 3; } 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"+(String)msg); // TODO: 2019/12/4 } } im_lib/src/main/java/com/anyun/im_lib/MsgDispatcher.java
New file @@ -0,0 +1,25 @@ 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 { private OnEventListener onEventListener; public void setOnEventListener(OnEventListener listener) { this.onEventListener = listener; } public void receivedMsg(String msg){ // TODO: 2019/12/12 if (onEventListener != null){ onEventListener.dispatchMsg(msg); } } } 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 onConnecting(); /***ims连接成功**/ void onConnected(); /***ims连接失败**/ void onConnectFailed(); } im_lib/src/main/java/com/anyun/im_lib/listener/OnEventListener.java
New file @@ -0,0 +1,60 @@ 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 getServerSentReportMsgType(); /** * 获取应用层消息发送超时重发次数 * @return */ int getResendCount(); /** * 获取应用层消息发送超时间隔 * @return */ int getResendInterval(); int getReConnectInterval(); } im_lib/src/main/java/com/anyun/im_lib/netty/NettyTcpClient.java
New file @@ -0,0 +1,497 @@ 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 java.util.function.ToDoubleBiFunction; 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)); } } } } /*** * 初始化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); //设置禁用nagle算法 bootstrap.option(ChannelOption.TCP_NODELAY,true); bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,getConnectTimeout()); bootstrap.handler(new TCPChannelInitializerHandler(this)); } /** * 真正连接服务器的地方 */ private void toServer() { try { channel = bootstrap.connect(currentHost,currentPort).sync().channel(); } catch (InterruptedException e) { try { Thread.sleep(500); } catch (InterruptedException ex) { ex.printStackTrace(); } Log.i(TAG, String.format("连接Server(ip[%s],port[%d]失败)",currentHost,currentPort)); channel = null; } } private boolean isNetworkAvaliable() { if (mOnEventListener != null){ return mOnEventListener.isNetWorkAvailable(); } return false; } /** * 回调ims连接状态 * @param connectStateConnecting */ private void onConnectStatusCallback(int connectStateConnecting) { this.connectStatus = connectStateConnecting; switch (connectStatus){ case IMSConfig.CONNECT_STATE_CONNECTING: Log.i(TAG, "onConnectStatusCallback: ims连接中..."); if (imsConnectStatusCallback != null){ imsConnectStatusCallback.onConnecting(); } break; case IMSConfig.CONNECT_STATE_SUCCESSFUL: Log.i(TAG, String.format("onConnectStatusCallback: ims连接成功,host[%s],port[%s]",currentHost,currentPort)); if (imsConnectStatusCallback != null){ imsConnectStatusCallback.onConnected(); } // TODO: 2019/12/12 连接成功 ,发送握手消息(需要的话——) break; case IMSConfig.CONNECT_STATE_FAILURE: default: Log.i(TAG, "onConnectStatusCallback: ims连接失败"); if (imsConnectStatusCallback != null){ imsConnectStatusCallback.onConnectFailed(); } break; } } /** * 关闭连接,同时释放资源 */ @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) { this.sendMsg(msg,true); } /** * * @param msg * @param isJoinTimeoutManager 是否加入超时管理器 */ @Override public void sendMsg(String msg, boolean isJoinTimeoutManager) { if (msg==null ){ return; } // TODO: 2019/12/12 根据MSG_ID 来加入 超时, 暂不写 if (channel == null){ Log.i(TAG, "sendMsg fail,channel为空"+msg); } try { channel.writeAndFlush(msg); } catch (Exception e) { Log.i(TAG, "发送消息失败,reason="+e.getMessage()+"\t"+msg); } } /** * 获取客户设置的重连时长 * @return */ @Override public int getReconnectInterval() { if (mOnEventListener != null && mOnEventListener.getReConnectInterval()>0){ return reconnectInterval = mOnEventListener.getReConnectInterval(); } return reconnectInterval; } @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 msgDispatcher; } @Override public MsgTimeOutTimerManager getMsgTimeOutTimerManager() { return msgTimeOutTimerManager; } /** * 重连任务 */ 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; } 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; } String[] address = serverUrl.split(" "); for (int j = 0; j < IMSConfig.DEFAULT_RECONNECT_COUNT; j++) { //如果ims已经关闭,或网络不可用,直接回调连接状态,不再进行连接 if (isClosed || !isNetworkAvaliable()){ return IMSConfig.CONNECT_STATE_FAILURE; } //回调连接状态 if(connectStatus != IMSConfig.CONNECT_STATE_CONNECTING){ onConnectStatusCallback(IMSConfig.CONNECT_STATE_CONNECTING); } Log.i(TAG, String.format("正在进行connectServer【%s】的第[%d]连接,当前重连延时时长为[%dms]: ",serverUrl,j,j*getReconnectInterval())); try { currentPort = Integer.parseInt(address[1]); currentHost = address[0]; //连接服务器 toServer(); //channel不为空,则认为连接已经成功 if(channel != null){ return IMSConfig.CONNECT_STATE_SUCCESSFUL; }else{ Thread.sleep(j*getReconnectInterval()); } } catch (InterruptedException e) { e.printStackTrace(); close(); break; } } } return IMSConfig.CONNECT_STATE_FAILURE; } } } im_lib/src/main/java/com/anyun/im_lib/netty/TCPChannelInitializerHandler.java
New file @@ -0,0 +1,64 @@ 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; import io.netty.handler.codec.FixedLengthFrameDecoder; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; /** * 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 // (1) maxFrameLength - 发送的数据包最大长度; // //(2) lengthFieldOffset - 长度域偏移量,指的是长度域位于整个数据包字节数组中的下标; // //(3) lengthFieldLength - 长度域的自己的字节数长度。 // //(4) lengthAdjustment – 长度域的偏移量矫正。 如果长度域的值,除了包含有效数据域的长度外,还包含了其他域(如长度域自身)长度,那么,就需要进行矫正。矫正的值为:包长 - 长度域的值 – 长度域偏移 – 长度域长。 // //(5) initialBytesToStrip – 丢弃的起始字节数。丢弃处于有效数据前面的字节数量。比如前面有4个节点的长度域,则它的值为4。 // // 在上面的例子中,自定义长度解码器的构造参数值如下: // // LengthFieldBasedFrameDecoder spliter=new LengthFieldBasedFrameDecoder(1024,0,4,0,4); // 第一个参数为1024,表示数据包的最大长度为1024;第二个参数0,表示长度域的偏移量为0,也就是长度域放在了最前面,处于包的起始位置;第三个参数为4,表示长度域占用4个字节;第四个参数为0,表示长度域保存的值,仅仅为有效数据长度,不包含其他域(如长度域)的长度;第五个参数为4,表示最终的取到的目标数据包,抛弃最前面的4个字节数据,长度域的值被抛弃。 // // 为了更加清楚的说明一下上面的规则,调整一下例子中的代码。在写入通道前,在数据 // pipeline.addLast(new LengthFieldBasedFrameDecoder(1024,3 ,4,0,13)); pipeline.addLast(new FixedLengthFrameDecoder(31)); // 测试用 固定长度消息 //握手认证消息相应处理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,66 @@ 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 Log.i(TAG, "channelRead: "); imsClient.getMsgDispatcher().receivedMsg((String)msg); } @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); } } settings.gradle
@@ -1 +1 @@ include ':app', ':lib' include ':app', ':lib', ':im_lib'