碰到一个bug,因为服务器在国内,国外用户在使用时可能因为网络原因即使请求发送了,但是服务器依旧没有收到上传的数据,需要确保客户端把请求成功发送。
因此我的思路是把失败的请求收集起来,添加到请求失败队列中,在一段周期性时间后对队列中所有失败请求重新进行一次请求,如果成功就从队列中删除,失败则等待进行下一次的请求。
开始想到的是Timer,但是查到timer存在bug。
如果TimerTask抛出未检查的异常,Timer将会产生无法预料的行为。Timer线程并不捕获异常,所以TimerTask抛出的未检查的异常会终止timer线程。这种情况下,Timer也不会再重新恢复线程的执行了;它错误的认为整个Timer都被取消了。此时,已经被安排但尚未执行的TimerTask永远不会再执行了,新的任务也不能被调度了。
然后想到Java提供的四种线程池中的newScheduledThreadPool
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:
//线程池能按时间计划来执行任务,允许用户设定计划执行任务的时间,int类型的参数是设定
//线程池中线程的最小数目。当任务较多时,线程池可能会自动创建更多的工作线程来执行任务
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
System.out.println("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
//表示延迟3秒执行
定期执行示例代码如下:
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
//表示延迟1秒后每3秒执行一次
ScheduledExecutorService比Timer更安全,功能更强大,满心欢喜的在Java项目下测试了半天,觉得可以使用,但是一到Android项目中就出问题。
当在一次请求的失败回调中执行周期task,task竟然失效并且捕捉到了 Can’t create handler inside thread that has not called Looper.prepare() 异常
@Override
public void fail(Object result) {
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
UpdateBusStatusTime updateBusStatusTime = new UpdateBusStatusTime();
//网络请求
GetHttpResultTool tool = new GetHttpResultTool(updateBusStatusTime);
tool.setCallback(new GetHttpResultTool.CallBack() {
@Override
public void success(Object result) {
throw new NullPointerException();
}
@Override
public void fail(Object result) {}
});
tool.setPost(true);
tool.execute();
}
},5,5,TimeUnit.SECONDS);
}
去掉okhttp的访问请求则一切正常,应该是okhttp的锅,没办法接着换,最终找到了HandlerThread
HandlerThread
HandlerThread继承于Thread,所以它本质就是个Thread。与普通Thread的差别就在于,它不仅建立了一个线程,并且创立了消息队列,有自己的looper,可以让我们在自己的线程中分发和处理消息,并对外提供自己这个Looper对象的get方法。
HandlerThread自带Looper使他可以通过消息队列,来重复使用当前线程。当有耗时任务投放到该循环线程中时,线程执行耗时任务,执行完之后循环线程处于等待状态,直到下一个新的耗时任务被投放进来。这样一来就避免了多次创建Thread线程导致的性能问题了。
这是它的优点也是缺点,每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
HandlerThread的常规用法
1.在onCreate方法中进行初始化
protected void onCreate(Bundle savedInstanceState) {
//创建一个HandlerThread的实例对象
mCheckMsgThread= new HandlerThread("handler_thread");
//启动一个HandlerThread线程
mCheckMsgThread.start();
//构建子线程中的Handler
mCheckMsgHandler = new Handler(mCheckMsgThread.getLooper(), mSubCallback);
//延迟1分钟进行循环检测是否有失败请求
mCheckMsgHandler.sendEmptyMessageDelayed(1, 60 * 1000);
}
2.构建循环消息处理机制
private Handler.Callback mSubCallback = new Handler.Callback() {
//该接口的实现就是处理异步耗时任务的,因此该方法执行在子线程中
@Override
public boolean handleMessage(Message msg) {
checkForUpdate();
//每5分钟进行一次重试
mCheckMsgHandler.sendEmptyMessageDelayed(1, 5 * 60 * 1000);
return false;
}
};
/**
* 重复执行请求
*/
private void checkForUpdate() {
if (errorQueueMap != null && errorQueueMap.size() > 0) {
for (final Map.Entry<UpdateBusStatusTime, Boolean> entry : errorQueueMap.entrySet()) {
if (!entry.getValue()) {
//失败后重新执行网络请求
final GetHttpResultTool tool = new GetHttpResultTool(entry.getKey());
tool.setCallback(new GetHttpResultTool.CallBack() {
@Override
public void success(final Object result) {
Log.i("", "网络成功,循环结束 " + result);
entry.setValue(true);
//我做完了,现在将消息通知回主线程
mUIHandler .sendEmptyMessage(2) ;
}
@Override
public void fail(Object result) {
Log.i("", "网络请求失败,继续执行循环 " + result);
}
});
}
}
}
}
3.构建UI线程Handler处理消息
private Handler mUIHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//当子线程的handler处理完事件后,通知在UI线程中做的事情,比如弹个吐司提醒用户啊
}
};
4.退出循环
Looper是通过调用loop方法驱动着消息循环的进行: 从MessageQueue中阻塞式地取出一个消息,然后让Handler处理该消息,周而复始,loop方法是个死循环方法。因此在onDestroy的时候需要退出释放资源。
mCheckMsgThread.quit() ;
原生线程间通信
略提一下,抛开Handler在原生Java中其实也有办法做线程间通信。只是方法要么不够优雅,要么会造成cpu运算负荷超高或死锁等情况。而且经常因为技术不到位,导致翻车的情况。所以android中的Hanlder通信机制非常巧妙的避开这样的问题,提供一下原生方法参考学习。
- 共享内存变量 使用同步锁(容易死锁)
- 共享内存变量 判断变量状态 (极其消耗内存)
- 管道通信
总结
- HandelrThread是一个自带Looper的线程,因此只能作为子线程使用,因此也实现了能够循环执行网络请求操作
- HandlerThread处理任务是串行执行,按消息发送顺序进行处理,里面的网络请求是异步的与此并不冲突。
- 通过mCheckMsgHandler = new Handler(mCheckMsgThread.getLooper(), mSubCallback) 这句代码mCheckMsgHandler 获得了HandlerThread线程的Looper,这样就把他们联系到一起。而这句代码之所以在 mCheckMsgThread.start()后执行,是因为start后run()方法才能跑起来,Looper才能够创建,mCheckMsgThread.getLooper()才能够正确得到。
- 通过HandlerThread联系子线程的Handler和UI线程的Handler实现了线程间通信。
参考
http://www.cnblogs.com/wufeng0927/p/5374191.html
http://www.jianshu.com/p/69c826c8a87d
http://blog.csdn.net/m0_37837382/article/details/70143224