endian11
2019-12-12 dce8fc9c184fb7fec64aaefa11a3be627a54093b
测试升级学员表(学员表增加一列);增加im_lib(tcp连接)
5个文件已修改
23个文件已添加
1328 ■■■■■ 已修改文件
app/build.gradle 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/safeluck/drive/evaluation/DB/Student.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/safeluck/drive/evaluation/DB/WorkRoomDataBase.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/safeluck/drive/evaluation/fragment/TrainFragment.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/build.gradle 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/libs/netty-all-4.1.43.jar 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/proguard-rules.pro 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/androidTest/java/com/anyun/im_lib/ExampleInstrumentedTest.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/AndroidManifest.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/ExecutorServiceFactory.java 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/HeartbeatHandler.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/HeartbeatRespHandler.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/IMSClientFactory.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/IMSConfig.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/LoginAuthRespHandler.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/MsgDispatcher.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/MsgTimeOutTimerManager.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/MsgTimeoutTimer.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/im/IMSClientBootstrap.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/interf/IMSClientInteface.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/listener/IMSConnectStatusCallback.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/listener/OnEventListener.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/netty/NettyTcpClient.java 497 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/netty/TCPChannelInitializerHandler.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/java/com/anyun/im_lib/netty/TCPReadHandler.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/main/res/values/strings.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
im_lib/src/test/java/com/anyun/im_lib/ExampleUnitTest.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
settings.gradle 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
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.jar
Binary 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'