在项目中遇到了登录取消的问题,由于登录用的是使用HandlerThrad新开一条线程的做法,网上的教程都是设置标志位或者使用HandlerThread的interrupt()方法。
个人认为使用handler新开一条线程更新UI的方法,只要handler使用.post()方法把线程放入了线程队列中,而且队列中无其他线程对象,该线程会立即执行,所以无法取消,只能通过handler的消息处理方法,将返回的Message不做处理而已。如果该线程对象在队列中未执行,可以使用Handler的removeCallbacks (Runnable r)方法将线程对象从队列中移除。
网上说得取消一个线程中在线程开始处加标志位做判断的方法:
在线程中做个标志boolean cancel = false;线程每一步操作之前都检测if(cancel),用户取消的时候cancel = true;
boolean cancel = false;
//线程每一步操作之前都检测while(cancel),用户取消的时候
cancel = true;
Runnable updateThread =new Runnable() {
@Override
public void run() {
while(cancel){
handler.post(updateThread); //类似递归,也可使用handler.postDelayed(updateThread, 3000);
}
}
}
handler.post(updateThread);
这种仅适合于处理很耗时的操作,比如下载一个大文件,为了保持线程的运行 一般都是用循环来保持线程处于执行状态, 线程每一步操作之前都检测cancle这个标志位,这种方法的弊端就是不够实时,可能会有一定的延时。如果是采用新开一个线程处理发送一个请求并或得服务器响应的情况,使用标志位的做法就毫无意义,因为这种情况下线程不是出于循环状态。
正确的处理登录取消的关键代码如下:
private boolean iscancel = false; //用户手动取消登录的标志位
handlerThread = new HandlerThread("myHandlerThread");
handlerThread.start();
handler = new MyHandler(handlerThread.getLooper());
// 将要执行的线程对象添加到线程队列中
handler.post(new Runnable() {
@Override
public void run() {
Message message = handler.obtainMessage();
UserBean user = Bbs.getInstance().Login(username, password);//耗时任务
Bundle b = new Bundle();
b.putSerializable("user", user);
message.setData(b);
message.sendToTarget(); //或使用 handler.sendMessage(message);
}
});
class MyHandler extends Handler {
public MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
if(iscancel == false){
// 操作UI线程的代码
Bundle b = msg.getData();
UserBean user = (UserBean)b.get("user");
......
}
}
}
当用户点击登录按钮登录的时候,弹出等待的ProgressDialog,这个ProgressDialog要添加取消的监听:
private ProgressDialog processDialog;
processDialog.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
iscancel = true;
}
}
AsyncTask在这个问题上处理任务取消的方法和HandlerThread类似,只是线程取消是再handleMessage方法中处理,而AsyncTask对应HandlerMessage的方法是onPostExecute。AsyncTask比使用HandlerThread更加轻量,推荐使用。