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/bean/DriveExamProtocol.java
New file @@ -0,0 +1,132 @@ package safeluck.drive.evaluation.bean; import android.util.Log; import com.anyun.im_lib.util.ByteUtil; /** * MyApplication2 * Created by lzw on 2019/12/17. 15:39:23 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public abstract class DriveExamProtocol { private static final String TAG = "DriveExamProtocol"; /***======================== 消息结构表 标识位 消息头 消息体 校验码 标识位 0x7E 0x7E ======================**/ // 标识位(字节流开始) private byte MESSAGE_HEAD = 0x7e; //标识位(字节流结束) private byte MESSAGE_TAIL = 0x7e; //校验码指从消息头开始,同后一字节异或,直到校验码前一个字节,占用一个字节 //校验码 先暂时写死 todo private byte checkCode = 0x78; /***===========以下是消息头=============***/ //协议版本号 BYTE 235,固定 private short protocol_version = 235; //消息ID private short msg_id; /** 消息体属性格式结构表 15 14 13 | 12 11 10 | 9 8 7 6 5 4 3 2 1 0 保留 数据加密方式 消息体长度 **/ //消息体属性 private short msg_property =2; /** * 终端手机号 字符串长度必须为16 **/ private String phoneOnTerminal = "0008618513021245"; /** * * 13 消息流水号 WORD 按发送顺序从0开始循环累加 * */ public static short msg_serial_num=0; //15 预留 BYTE 预留 private byte reserve = 0x00; /***===========消息头结束=============***/ /** * 消息体 需要子类实现 */ protected abstract byte[] createMessageBody(); /** * 构造函数 * @param msg_id 消息ID */ public DriveExamProtocol(short msg_id) { this.msg_id = msg_id; } /** * 消息转为byte数组 7E......7E * @return */ public byte[] toBytes(){ byte[] desBytes = new byte[1+16+2+1+1]; int pos = 0; //标识位 desBytes[pos] = MESSAGE_HEAD; pos++; //协议版本号 byte[] protoVersion = ByteUtil.shortGetByte(protocol_version); System.arraycopy(protoVersion,0,desBytes,pos,protoVersion.length); pos +=protoVersion.length; //消息ID byte[] msgIdBytes = ByteUtil.shortGetBytes(msg_id); System.arraycopy(msgIdBytes,0,desBytes,pos,msgIdBytes.length); pos+=msgIdBytes.length; //消息体属性 byte[] msg_pro_bytes = ByteUtil.shortGetBytes(msg_property); System.arraycopy(msg_pro_bytes,0,desBytes,pos,msg_pro_bytes.length); pos+=msg_pro_bytes.length; //终端手机号 byte[] phoneBytes = ByteUtil.str2Bcd(phoneOnTerminal); System.arraycopy(phoneBytes,0,desBytes,pos,phoneBytes.length); pos+=phoneBytes.length; //消息流水号 byte[] msg_serialNum = ByteUtil.shortGetBytes(msg_serial_num++); System.arraycopy(msg_serialNum,0,desBytes,pos,msg_serialNum.length); pos+=msg_serialNum.length; //保留byte desBytes[pos] = reserve; pos++; //消息体 byte[] messageBodyBytes = createMessageBody(); System.arraycopy(messageBodyBytes,0,desBytes,pos,messageBodyBytes.length); pos+=messageBodyBytes.length; //校验码 // TODO: 2019/12/18 校验码需要计算 还有转义需要处理 desBytes[pos] = checkCode; pos++; //末尾结束标识位 desBytes[pos] = MESSAGE_TAIL; Log.i(TAG, "包长度="+(pos+1)); Log.i(TAG, "包内容: "+ByteUtil.byte2HexStr(desBytes)); return desBytes; } } app/src/main/java/safeluck/drive/evaluation/bean/GainStuMessage.java
New file @@ -0,0 +1,26 @@ package safeluck.drive.evaluation.bean; /** * 获取学员信息消息(发送身份证ID上去给平台) * MyApplication2 * Created by lzw on 2019/12/19. 18:26:35 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class GainStuMessage extends DriveExamProtocol { // TODO: 2019/12/19 /** * 构造函数 * * @param msg_id 消息ID */ public GainStuMessage(short msg_id) { super(msg_id); } @Override protected byte[] createMessageBody() { return new byte[0]; } } app/src/main/java/safeluck/drive/evaluation/bean/KeepaliveMessage.java
New file @@ -0,0 +1,22 @@ package safeluck.drive.evaluation.bean; /** * 心跳消息 * MyApplication2 * Created by lzw on 2019/12/19. 18:24:47 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class KeepaliveMessage extends DriveExamProtocol { // TODO: 2019/12/19 public KeepaliveMessage(short msg_id) { super(msg_id); } @Override protected byte[] createMessageBody() { return new byte[0]; } } app/src/main/java/safeluck/drive/evaluation/bean/RegisterMessage.java
New file @@ -0,0 +1,27 @@ package safeluck.drive.evaluation.bean; /** * 注册消息 * MyApplication2 * Created by lzw on 2019/12/17. 17:51:44 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class RegisterMessage extends DriveExamProtocol { /** * 构造函数 * * @param msg_id 消息ID */ public RegisterMessage(short msg_id) { super(msg_id); } @Override protected byte[] createMessageBody() { byte[] messageBody = new byte[2]; messageBody[0] = 0x65; messageBody[1] = 0x66; return messageBody; } } app/src/main/java/safeluck/drive/evaluation/bean/StartExamMessage.java
New file @@ -0,0 +1,25 @@ package safeluck.drive.evaluation.bean; /** * 给平台发送开始考试消息 * MyApplication2 * Created by lzw on 2019/12/19. 18:28:47 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class StartExamMessage extends DriveExamProtocol { // TODO: 2019/12/19 /** * 构造函数 * * @param msg_id 消息ID */ public StartExamMessage(short msg_id) { super(msg_id); } @Override protected byte[] createMessageBody() { return new byte[0]; } } app/src/main/java/safeluck/drive/evaluation/cEventCenter/CEvent.java
New file @@ -0,0 +1,70 @@ package safeluck.drive.evaluation.cEventCenter; /** * 事件模型,需要传递的消息事件对象 * MyApplication2 * Created by lzw on 2019/12/30. 17:00:54 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class CEvent implements PooledObject { //主题 private String topic; private int msgCode;//消息类型 private int resultCode;//预留参数 private Object obj;//回调返回数据 public CEvent() { } public CEvent(String topic, int msgCode, int resultCode, Object obj) { this.topic = topic; this.msgCode = msgCode; this.resultCode = resultCode; this.obj = obj; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public int getMsgCode() { return msgCode; } public void setMsgCode(int msgCode) { this.msgCode = msgCode; } public int getResultCode() { return resultCode; } public void setResultCode(int resultCode) { this.resultCode = resultCode; } public Object getObj() { return obj; } public void setObj(Object obj) { this.obj = obj; } @Override public void reset() { this.obj = null; this.msgCode = 0; this.resultCode = 0; this.topic = null; } } app/src/main/java/safeluck/drive/evaluation/cEventCenter/ObjectPool.java
New file @@ -0,0 +1,55 @@ package safeluck.drive.evaluation.cEventCenter; /** * 自定义的对象池 * MyApplication2 * Created by lzw on 2019/12/30. 17:06:49 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public abstract class ObjectPool<T extends PooledObject> { private T[] mContainer;//对象容器 private final Object LOCK = new Object();//对象锁 private int length;//每次返回对象都放到数据末端,length表示前面可用对象数 public ObjectPool(int capacity) { mContainer = createObjPool(capacity); } /** * 创建对象池 * @param capacity 最大限度容量 * @return */ protected abstract T[] createObjPool(int capacity) ; /** * 创建一个新的对象 * @return */ protected abstract T createNewObj(); public final T get(){ //先从池中找到空闲的对象,如果没有,则重新创建一个对象 T obj = findFreeObject(); if (null == obj){ obj = createNewObj(); }else{ obj.reset(); } return obj; } /** * 从池中找到空闲的对象 * @return */ private T findFreeObject() { return null; } } app/src/main/java/safeluck/drive/evaluation/cEventCenter/PooledObject.java
New file @@ -0,0 +1,15 @@ package safeluck.drive.evaluation.cEventCenter; /** * 对象池 * MyApplication2 * Created by lzw on 2019/12/30. 16:59:40 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public interface PooledObject { /** * 恢复到默认状态 */ void reset(); } app/src/main/java/safeluck/drive/evaluation/fragment/TcpFragment.java
@@ -10,6 +10,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.widget.AppCompatEditText; @@ -31,6 +32,8 @@ import safeluck.drive.evaluation.DB.failitems.FailedProj; import safeluck.drive.evaluation.DB.failitems.FailedProj_select; import safeluck.drive.evaluation.R; import safeluck.drive.evaluation.im.IMSClientBootstrap; import safeluck.drive.evaluation.im.MessageProcessor; import safeluck.drive.evaluation.tcp.ConnectThread; /** @@ -45,9 +48,9 @@ private static final String TAG = TcpFragment.class.getSimpleName(); private TextInputEditText ip; private TextInputEditText port; private TextView tv_content; private Button btn_connect; private Button btn_send; private ConnectThread connectThread; private AppCompatEditText sendEditText; private CriteriaIViewModel workViewModel; private int item_id=0; @@ -61,7 +64,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.layout_tcpclient, container, false); initView(view); workViewModel =ViewModelProviders.of(this).get(CriteriaIViewModel.class); // workViewModel =ViewModelProviders.of(this).get(CriteriaIViewModel.class); return view; @@ -71,6 +74,7 @@ ip = view.findViewById(R.id.input_ip); port = view.findViewById(R.id.input_port); sendEditText = view.findViewById(R.id.sendtxt); tv_content = view.findViewById(R.id.content); btn_connect = view.findViewById(R.id.btn_connect); btn_send = view.findViewById(R.id.btn_send); @@ -83,22 +87,37 @@ public void onClick(View v) { switch (v.getId()){ case R.id.btn_connect: // String serverIp = ip.getText().toString().trim(); // String serverPort = port.getText().toStrirng().trim(); // connectThread = new ConnectThread(serverIp,Integer.parseInt(serverPort)); // connectThread.start(); Random random = new Random(); r = random.nextInt(30)+1; Log.i(TAG, "onClick: 随机数="+r); WorkRoomDataBase.dataBaseWriteExecutor.execute(new Runnable() { @Override public void run() { WorkRoomDataBase.getWorkRoomDataBase(getContext().getApplicationContext()).getFailProjDao().insert(new FailedProj(flag?1:2, r,flag?1001:1000)); flag = !flag; } }); /**======================以下用于测试数据库==================*/ // Random random = new Random(); // r = random.nextInt(30)+1; // Log.i(TAG, "onClick: 随机数="+r); // WorkRoomDataBase.dataBaseWriteExecutor.execute(new Runnable() { // @Override // public void run() { // // WorkRoomDataBase.getWorkRoomDataBase(getContext().getApplicationContext()).getFailProjDao().insert(new FailedProj(flag?1:2, r,flag?1001:1000)); // flag = !flag; // } // }); /**======================测试数据库结束==================*/ String userId = "100002"; String token = "token_" + userId; String hosts = "[{\"host\":\"192.168.10.234\", \"port\":8855}]"; IMSClientBootstrap.getInstance().init(userId,token,hosts,1); break; case R.id.btn_send: // if (connectThread != null){ @@ -106,6 +125,11 @@ //// sendEditText.getText().clear(); //// } MessageProcessor.getInstance().sendMessage(sendEditText.getText().toString().trim()); sendEditText.getText().clear(); break; } } 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; app/src/main/java/safeluck/drive/evaluation/im/IMSClientBootstrap.java
New file @@ -0,0 +1,109 @@ package safeluck.drive.evaluation.im; import android.util.Log; import com.anyun.im_lib.interf.IMSClientInteface; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; import java.util.Vector; /** * MyApplication2 * Created by lzw on 2019/12/12. 16:05:30 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class IMSClientBootstrap { private static final String TAG = "IMSClientBootstrap"; private static final IMSClientBootstrap INSTANCE= new IMSClientBootstrap(); private IMSClientInteface imsClient; /**标记IMSClientBootstrap是否已经初始化**/ private boolean isActive; private IMSClientBootstrap(){ } public static IMSClientBootstrap getInstance(){ return INSTANCE; } /** * * @param userId * @param token * @param hosts * @param appStatus */ public synchronized void init(String userId,String token,String hosts,int appStatus){ if (!isActive){ Vector<String> serverUrlList = convertHosts(hosts); if (serverUrlList == null || serverUrlList.size() ==0){ Log.i(TAG, "init IMLibClientBootstrap error,ims hosts is null"); return; } isActive = true; Log.i(TAG, "init IMLibClientBootstrap ,server="+hosts); if (null != imsClient){ imsClient.close(); } //初始化IMSClientInteface imsClient = IMSClientFactory.getIMSClient(); updateAppStatus(appStatus); imsClient.init(serverUrlList,new IMSEventListener(userId,token),new IMSConnectStatusListener()); } } public boolean isActive(){ return isActive; } public void updateAppStatus(int appStatus) { if (imsClient == null){ return; } imsClient.setAppStatus(appStatus); } private Vector<String> convertHosts(String hosts) { Log.i(TAG, "convertHosts: "+hosts); if (hosts != null && hosts.length() > 0) { Vector<String> serverUrlList = new Vector<>(); JsonArray jsonArray =JsonParser.parseString(hosts).getAsJsonArray(); for (int i = 0; i < jsonArray.size(); i++) { JsonObject host = jsonArray.get(i).getAsJsonObject(); String hostName = host.get("host").getAsString(); int port = host.get("port").getAsInt(); Log.i(TAG, "convertHosts: hostname="+hostName+" port="+port); serverUrlList.add(hostName+" "+port); } return serverUrlList; } return null; } public void sendMessage(String message){ if (isActive){ imsClient.sendMsg(message); } } } app/src/main/java/safeluck/drive/evaluation/im/IMSClientFactory.java
New file @@ -0,0 +1,16 @@ package safeluck.drive.evaluation.im; 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(); } } app/src/main/java/safeluck/drive/evaluation/im/IMSConnectStatusListener.java
New file @@ -0,0 +1,31 @@ package safeluck.drive.evaluation.im; import android.util.Log; import com.anyun.im_lib.listener.IMSConnectStatusCallback; /** * MyApplication2 * Created by lzw on 2019/12/12. 16:30:33 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class IMSConnectStatusListener implements IMSConnectStatusCallback { private static final String TAG = IMSConnectStatusListener.class.getSimpleName(); @Override public void onConnecting() { Log.i(TAG, "onConnecting: "); } @Override public void onConnected() { Log.i(TAG, "onConnected: "); } @Override public void onConnectFailed() { Log.i(TAG, "onConnectFailed: "); } } app/src/main/java/safeluck/drive/evaluation/im/IMSEventListener.java
New file @@ -0,0 +1,76 @@ package safeluck.drive.evaluation.im; import com.anyun.im_lib.listener.OnEventListener; import safeluck.drive.evaluation.bean.RegisterMessage; /** * MyApplication2 * Created by lzw on 2019/12/12. 16:12:40 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class IMSEventListener implements OnEventListener { private String userId; private String token; public IMSEventListener(String userId, String token) { this.userId = userId; this.token = token; } @Override public void dispatchMsg(Object message) { MessageProcessor.getInstance().receiveMsg((String)message); } @Override public boolean isNetWorkAvailable() { return true; } /** * 连接超时时长 * @return ms */ @Override public int getConnectTimeout() { return 3000; } @Override public int getForegroundHeartbeatInterval() { return 0; } @Override public int getBackgroundHeartbeatInterval() { return 0; } @Override public int getServerSentReportMsgType() { return 0; } @Override public int getResendCount() { return 0; } @Override public int getResendInterval() { return 0; } @Override public int getReConnectInterval() { return 0; } @Override public byte[] getRegisterMessage() { return new RegisterMessage((short) 0x802).toBytes(); } } app/src/main/java/safeluck/drive/evaluation/im/IMessageProcessor.java
New file @@ -0,0 +1,12 @@ package safeluck.drive.evaluation.im; /** * MyApplication2 * Created by lzw on 2019/12/12. 16:14:57 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public interface IMessageProcessor { void receiveMsg(String message); void sendMessage(String msg); } app/src/main/java/safeluck/drive/evaluation/im/MessageProcessor.java
New file @@ -0,0 +1,48 @@ package safeluck.drive.evaluation.im; import android.util.Log; import safeluck.drive.evaluation.util.CThreadPoolExecutor; /** * MyApplication2 * Created by lzw on 2019/12/12. 16:14:33 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public class MessageProcessor implements IMessageProcessor { private static final String TAG = MessageProcessor.class.getSimpleName(); private MessageProcessor(){ } private static class MessageProcessorInstance{ private static final IMessageProcessor INSTANCE = new MessageProcessor(); } public static IMessageProcessor getInstance(){ return MessageProcessorInstance.INSTANCE; } @Override public void receiveMsg(String message) { Log.i(TAG, "receiveMsg: "+message); } @Override public void sendMessage(final String msg) { CThreadPoolExecutor.runInBackground(new Runnable() { @Override public void run() { if (IMSClientBootstrap.getInstance().isActive()){ IMSClientBootstrap.getInstance().sendMessage(msg); }else{ Log.e(TAG, "run: 发送消息失败,未初始化连接NettyTcp"); } } }); } } app/src/main/java/safeluck/drive/evaluation/im/handler/AbstractMessageHandler.java
New file @@ -0,0 +1,15 @@ package safeluck.drive.evaluation.im.handler; /** * MyApplication2 * Created by lzw on 2019/12/12. 16:09:02 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public abstract class AbstractMessageHandler implements IMessageHandler { @Override public void execute(String msg) { action(msg); } protected abstract void action(String msg); } app/src/main/java/safeluck/drive/evaluation/im/handler/IMessageHandler.java
New file @@ -0,0 +1,11 @@ package safeluck.drive.evaluation.im.handler; /** * MyApplication2 * Created by lzw on 2019/12/12. 16:04:29 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD */ public interface IMessageHandler { void execute(String msg); } app/src/main/java/safeluck/drive/evaluation/util/CThreadPoolExecutor.java
New file @@ -0,0 +1,332 @@ package safeluck.drive.evaluation.util; import android.os.Handler; import android.os.Looper; import android.util.Log; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * MyApplication2 * Created by lzw on 2019/12/12. 16:22:49 * 邮箱:632393724@qq.com * All Rights Saved! Chongqing AnYun Tech co. LTD * * * * * <p>@ClassName: CThreadPoolExecutor.java</p> * * <b> * * <p>@Description: 自定义固定大小的线程池 * * 每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。 * * 线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。 * * <p> * * 合理利用线程池能够带来三个好处: * * 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 * * 第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 * * 第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 * * 我们可以通过ThreadPoolExecutor来创建一个线程池: * * new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler); * * <p> * * corePoolSize(线程池的基本大小): * * 当提交一个任务到线程池时,线程池会创建一个线程来执行任务, * * 即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。 * * 如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。 * * <p> * * runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。 * * ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。 * * <p> * * LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。 * * 静态工厂方法Executors.newFixedThreadPool()使用了这个队列。 * * <p> * * SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态, * * 吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。 * * <p> * * PriorityBlockingQueue:一个具有优先级的无限阻塞队列。 * * <p> * * maximumPoolSize(线程池最大大小): * * 线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。 * * <p> * * ThreadFactory: * * 用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。 * * <p> * * RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。 * * 这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。 * * AbortPolicy:直接抛出异常。 * * CallerRunsPolicy:只用调用者所在线程来运行任务。 * * DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。 * * DiscardPolicy:不处理,丢弃掉。 * * 当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。 * * <p> * * keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。 * * 所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。 * * <p> * * TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES), * * 毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。</p> * * </b> * * <p>@author: FreddyChen</p> * * <p>@date: 2019/2/3 15:35</p> * * <p>@email: chenshichao@outlook.com</p> * * * * @see http://www.infoq.com/cn/articles/java-threadPool * */ public class CThreadPoolExecutor { private static final String TAG = CThreadPoolExecutor.class.getSimpleName(); private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();// CPU个数 // private static final int CORE_POOL_SIZE = CPU_COUNT + 1;// 线程池中核心线程的数量 // private static final int MAXIMUM_POOL_SIZE = 2 * CPU_COUNT + 1;// 线程池中最大线程数量 private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));// 线程池中核心线程的数量 private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;// 线程池中最大线程数量 private static final long KEEP_ALIVE_TIME = 30L;// 非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则该参数也表示核心线程的超时时长 private static final int WAIT_COUNT = 128; // 最多排队个数,这里控制线程创建的频率 private static ThreadPoolExecutor pool = createThreadPoolExecutor(); private static ThreadPoolExecutor createThreadPoolExecutor() { if (pool == null) { pool = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(WAIT_COUNT), new CThreadFactory("CThreadPool", Thread.NORM_PRIORITY - 2), new CHandlerException()); } return pool; } public static class CThreadFactory implements ThreadFactory { private AtomicInteger counter = new AtomicInteger(1); private String prefix = ""; private int priority = Thread.NORM_PRIORITY; public CThreadFactory(String prefix, int priority) { this.prefix = prefix; this.priority = priority; } public CThreadFactory(String prefix) { this.prefix = prefix; } public Thread newThread(Runnable r) { Thread executor = new Thread(r, prefix + " #" + counter.getAndIncrement()); executor.setDaemon(true); executor.setPriority(priority); return executor; } } /** * 抛弃当前的任务 */ private static class CHandlerException extends ThreadPoolExecutor.AbortPolicy { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { Log.d(TAG, "rejectedExecution:" + r); Log.e(TAG, logAllThreadStackTrace().toString()); // Tips.showForce("任务被拒绝", 5000); if (!pool.isShutdown()) { pool.shutdown(); pool = null; } pool = createThreadPoolExecutor(); } } private static ExecutorService jobsForUI = Executors.newFixedThreadPool( CORE_POOL_SIZE, new CThreadFactory("CJobsForUI", Thread.NORM_PRIORITY - 1)); /** * 启动一个消耗线程,常驻后台 * * @param r */ public static void startConsumer(final Runnable r, final String name) { runInBackground(new Runnable() { public void run() { new CThreadFactory(name, Thread.NORM_PRIORITY - 3).newThread(r).start(); } }); } /** * 提交到其他线程去跑,需要取数据的时候会等待任务完成再继续 * * @param task * @return */ public static <T> Future<T> submitTask(Callable<T> task) { return jobsForUI.submit(task); } /** * 强制清理任务 * * @param task * @return */ public static <T> void cancelTask(Future<T> task) { if (task != null) { task.cancel(true); } } /** * 从 Future 中获取值,如果发生异常,打日志 * * @param future * @param tag * @param name * @return */ public static <T> T getFromTask(Future<T> future, String tag, String name) { try { return future.get(); } catch (Exception e) { Log.e(tag, (name != null ? name + ": " : "") + e.toString()); } return null; } public static void runInBackground(Runnable runnable) { if (pool == null) { createThreadPoolExecutor(); } pool.execute(runnable); // Future future = pool.submit(runnable); // try { // future.get(); // } catch (InterruptedException e) { // e.printStackTrace(); // } catch (ExecutionException e) { // e.printStackTrace(); // } } private static Thread mainThread; private static Handler mainHandler; static { Looper mainLooper = Looper.getMainLooper(); mainThread = mainLooper.getThread(); mainHandler = new Handler(mainLooper); } public static boolean isOnMainThread() { return mainThread == Thread.currentThread(); } public static void runOnMainThread(Runnable r) { if (isOnMainThread()) { r.run(); } else { mainHandler.post(r); } } public static void runOnMainThread(Runnable r, long delayMillis) { if (delayMillis <= 0) { runOnMainThread(r); } else { mainHandler.postDelayed(r, delayMillis); } } // 用于记录后台等待的Runnable,第一个参数外面的Runnable,第二个参数是等待中的Runnable private static HashMap<Runnable, Runnable> mapToMainHandler = new HashMap<Runnable, Runnable>(); public static void runInBackground(final Runnable runnable, long delayMillis) { if (delayMillis <= 0) { runInBackground(runnable); } else { Runnable mainRunnable = new Runnable() { @Override public void run() { mapToMainHandler.remove(runnable); pool.execute(runnable); } }; mapToMainHandler.put(runnable, mainRunnable); mainHandler.postDelayed(mainRunnable, delayMillis); } } /** * 对runOnMainThread的,移除Runnable * * @param r */ public static void removeCallbackOnMainThread(Runnable r) { mainHandler.removeCallbacks(r); } public static void removeCallbackInBackground(Runnable runnable) { Runnable mainRunnable = mapToMainHandler.get(runnable); if (mainRunnable != null) { mainHandler.removeCallbacks(mainRunnable); } } public static void logStatus() { StringBuilder sb = new StringBuilder(); sb.append("getActiveCount"); sb.append(pool.getActiveCount()); sb.append("\ngetTaskCount"); sb.append(pool.getTaskCount()); sb.append("\ngetCompletedTaskCount"); sb.append(pool.getCompletedTaskCount()); Log.d(TAG, sb.toString()); } public static StringBuilder logAllThreadStackTrace() { StringBuilder builder = new StringBuilder(); Map<Thread, StackTraceElement[]> liveThreads = Thread.getAllStackTraces(); for (Iterator<Thread> i = liveThreads.keySet().iterator(); i.hasNext(); ) { Thread key = i.next(); builder.append("Thread ").append(key.getName()) .append("\n"); StackTraceElement[] trace = liveThreads.get(key); for (int j = 0; j < trace.length; j++) { builder.append("\tat ").append(trace[j]).append("\n"); } } return builder; } public static void main(String[] args) { for (int i = 0; i < 10000; i++) { final int index = i; System.out.println("index=" + index); CThreadPoolExecutor.runInBackground(new Runnable() { @Override public void run() { System.out.println("正在运行第[" + (index + 1) + "]个线程."); } }); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }