Android Handler之消息循环的深入解析
程序员文章站
2023-11-20 17:36:10
handler是用于操作线程内部的消息队列的类。这有点绕,没关系,我们慢慢的来讲。前面looper一篇讲到了looper是用于给线程创建消息队列用的,也就是说looper可...
handler是用于操作线程内部的消息队列的类。这有点绕,没关系,我们慢慢的来讲。前面looper一篇讲到了looper是用于给线程创建消息队列用的,也就是说looper可以让消息队列(messagequeue)附属在线程之内,并让消息队列循环起来,接收并处理消息。但,我们并不直接的操作消息队列,而是用handler来操作消息队列,给消息队列发送消息,和从消息队列中取出消息并处理。这就是handler的职责。
handler,looper和messagequeue是属于一个线程内部的数据,但是它提供给外部线程访问的接口,handler就是公开给外部线程,与线程通讯的接口。换句话说,这三个东西都是用来线程间通讯用的(itc--inter thread communication),与进行间通讯(ipc--inter process communication)的消息队列msgque的核心思想是一致的。messagequeue是相对较底层的,较少直接使用,looper和handler就是专门用来操作底层messagequeue的。
还有一个重要的数据结构是通讯的基本元素,就是消息对象(message),message从来不单独使用,它都是跟随handler来使用的。具体方法可以参考文档,但需要注意的是同一个消息对象不能发送二次,否则会有androidruntimeexception: { what=1000 when=-15ms obj=.. } this message is already in use."。每次发送消息前都要通过message.obtain()来获取新的对象,或者,对于不需要传送额外数据的直接发送空消息就好handler.sendemptymessage(int)。另外也需要注意消息对象是不能手动回收的,也就是说你不能调用message.recycle()来释放一个消息对象,因为当该对象被从队列中取出处理完毕后,messagequeue内部会自动的去做recycle()。这个理解起来也很容易,因为发送一个消息到消息队列后,消息什么时候会被处理,对于应用程序来讲是不知道的,只有messagequeue才会知道,所以只能由messagequeue来做回收释放的动作。
因为handler是用于操作一个线程内部的消息队列的,所以handler必须依附于一个线程,而且只能是一个线程。换句话说,你必须在一个线程内创建handler,同时指定handler的回调handlermessage(message msg)。
handler主要有二个用途,一个是用于线程内部消息循环; 另外一个就是用于线程间通讯。
handler的基本用法可以参考文档,说的还是比较清楚的。
用于线程内部消息循环
主要是用作在将来定时做某个动作,或者循环性,周期性的做某个动作。主要的接口就是
handler.sendemptymessagedelayed(int msgid, long after);
handler.sendmessagedelayed(message msg, long after);
handler.postdelayed(runnable task, long after);
handler.sendmessageattime(message msg, long timemillis);
handler.sendemptymessageattime(int id, long timemiilis);
handler.postattime(runnable task, long timemillis);
这些方法的目的都是设置一个定时器,在指定的时间后,或者在指定的时间向handler所在的messagequeue发送消息。这样就非常方便应用程序实现定时操作,或者循环时序操作(处理消息时再延时发送消息,以达成循环时序)。
这个使用起来并不难,但需要注意一点的是,线程内部消息循环并不是并发处理,也就是所有的消息都是在handler所属的线程内处理的,所以虽然你用post(runnable r),发给messagequeue一个runnable,但这并不会创建新的线程来执行,处理此消息时仅是调用r.run()。(想要另起线程执行,必须把runnable放到一个thread中)。
实例
这里用一个实例来展示主线程通过handler与后台线程进行通信,并且主线程用handler来实现循环时序。
播放一个视频,线程用于创建和初始化mediaplayer,初始化好后会通过主线程的handler告诉主线程,然后主线程可以播放视频,在播放过程中通过sendmessagedelayed()来实现播放进度的不断更新:
public class handlersimpledemo extends activity {
protected static final string tag = "handlersimpledemo";
private static final int media_player_ready = 0;
private static final int refresh_progress = 1;
private button mstart;
private button mstop;
private surfaceholder msurfaceholder;
private progressbar mprogressbar;
private surfaceview mdisplay;
private mediaplayer mmediaplayer;
private handler mmainhandler = new handler() {
@override
public void handlemessage(message msg) {
switch (msg.what) {
case media_player_ready:
mprogressbar.setmax(mmediaplayer.getduration());
mmediaplayer.setoncompletionlistener(new mediaplayer.oncompletionlistener() {
public void oncompletion(mediaplayer mp) {
mprogressbar.setprogress(mmediaplayer.getduration());
mmainhandler.removemessages(refresh_progress);
}
});
mstart.setenabled(true);
mstop.setenabled(true);
break;
case refresh_progress:
int cp = mmediaplayer.getcurrentposition();
mprogressbar.setprogress(cp);
int delay = 1000 - (cp % 1000);
mmainhandler.sendemptymessagedelayed(refresh_progress, delay);
break;
default:
break;
}
}
};
@suppresswarnings("deprecation")
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.handler_simple_demo);
mstart = (button) findviewbyid(r.id.handler_simple_start);
mstart.setonclicklistener(new view.onclicklistener() {
public void onclick(view v) {
mmediaplayer.start();
mmainhandler.sendemptymessage(refresh_progress);
}
});
mstart.setenabled(false);
mstop = (button) findviewbyid(r.id.handler_simple_stop);
mstop.setonclicklistener(new view.onclicklistener() {
public void onclick(view v) {
mmainhandler.removemessages(refresh_progress);
mmediaplayer.pause();
}
});
mstop.setenabled(false);
mprogressbar = (progressbar) findviewbyid(r.id.handler_simple_progress);
mdisplay = (surfaceview) findviewbyid(r.id.handler_simple_display);
msurfaceholder = mdisplay.getholder();
msurfaceholder.setfixedsize(mdisplay.getwidth(), mdisplay.getheight());
// do not believe the document, settype is necessary, otherwise, video won't play correctly
msurfaceholder.settype(surfaceholder.surface_type_push_buffers);
new thread(new runnable() {
public void run() {
try {
mmediaplayer = mediaplayer.create(getapplication(), r.raw.flug);
mmediaplayer.setdisplay(msurfaceholder);
mmainhandler.sendemptymessage(media_player_ready);
} catch (illegalargumentexception e) {
log.e(tag, "caught exception e", e);
} catch (securityexception e) {
log.e(tag, "caught exception e", e);
} catch (illegalstateexception e) {
log.e(tag, "caught exception e", e);
}
}
}).start();
}
@override
protected void ondestroy() {
super.ondestroy();
mmainhandler.removemessages(refresh_progress);
if (mmediaplayer != null) {
mmediaplayer.release();
}
}
}
handler,looper和messagequeue是属于一个线程内部的数据,但是它提供给外部线程访问的接口,handler就是公开给外部线程,与线程通讯的接口。换句话说,这三个东西都是用来线程间通讯用的(itc--inter thread communication),与进行间通讯(ipc--inter process communication)的消息队列msgque的核心思想是一致的。messagequeue是相对较底层的,较少直接使用,looper和handler就是专门用来操作底层messagequeue的。
还有一个重要的数据结构是通讯的基本元素,就是消息对象(message),message从来不单独使用,它都是跟随handler来使用的。具体方法可以参考文档,但需要注意的是同一个消息对象不能发送二次,否则会有androidruntimeexception: { what=1000 when=-15ms obj=.. } this message is already in use."。每次发送消息前都要通过message.obtain()来获取新的对象,或者,对于不需要传送额外数据的直接发送空消息就好handler.sendemptymessage(int)。另外也需要注意消息对象是不能手动回收的,也就是说你不能调用message.recycle()来释放一个消息对象,因为当该对象被从队列中取出处理完毕后,messagequeue内部会自动的去做recycle()。这个理解起来也很容易,因为发送一个消息到消息队列后,消息什么时候会被处理,对于应用程序来讲是不知道的,只有messagequeue才会知道,所以只能由messagequeue来做回收释放的动作。
因为handler是用于操作一个线程内部的消息队列的,所以handler必须依附于一个线程,而且只能是一个线程。换句话说,你必须在一个线程内创建handler,同时指定handler的回调handlermessage(message msg)。
handler主要有二个用途,一个是用于线程内部消息循环; 另外一个就是用于线程间通讯。
handler的基本用法可以参考文档,说的还是比较清楚的。
用于线程内部消息循环
主要是用作在将来定时做某个动作,或者循环性,周期性的做某个动作。主要的接口就是
handler.sendemptymessagedelayed(int msgid, long after);
handler.sendmessagedelayed(message msg, long after);
handler.postdelayed(runnable task, long after);
handler.sendmessageattime(message msg, long timemillis);
handler.sendemptymessageattime(int id, long timemiilis);
handler.postattime(runnable task, long timemillis);
这些方法的目的都是设置一个定时器,在指定的时间后,或者在指定的时间向handler所在的messagequeue发送消息。这样就非常方便应用程序实现定时操作,或者循环时序操作(处理消息时再延时发送消息,以达成循环时序)。
这个使用起来并不难,但需要注意一点的是,线程内部消息循环并不是并发处理,也就是所有的消息都是在handler所属的线程内处理的,所以虽然你用post(runnable r),发给messagequeue一个runnable,但这并不会创建新的线程来执行,处理此消息时仅是调用r.run()。(想要另起线程执行,必须把runnable放到一个thread中)。
实例
这里用一个实例来展示主线程通过handler与后台线程进行通信,并且主线程用handler来实现循环时序。
播放一个视频,线程用于创建和初始化mediaplayer,初始化好后会通过主线程的handler告诉主线程,然后主线程可以播放视频,在播放过程中通过sendmessagedelayed()来实现播放进度的不断更新:
复制代码 代码如下:
public class handlersimpledemo extends activity {
protected static final string tag = "handlersimpledemo";
private static final int media_player_ready = 0;
private static final int refresh_progress = 1;
private button mstart;
private button mstop;
private surfaceholder msurfaceholder;
private progressbar mprogressbar;
private surfaceview mdisplay;
private mediaplayer mmediaplayer;
private handler mmainhandler = new handler() {
@override
public void handlemessage(message msg) {
switch (msg.what) {
case media_player_ready:
mprogressbar.setmax(mmediaplayer.getduration());
mmediaplayer.setoncompletionlistener(new mediaplayer.oncompletionlistener() {
public void oncompletion(mediaplayer mp) {
mprogressbar.setprogress(mmediaplayer.getduration());
mmainhandler.removemessages(refresh_progress);
}
});
mstart.setenabled(true);
mstop.setenabled(true);
break;
case refresh_progress:
int cp = mmediaplayer.getcurrentposition();
mprogressbar.setprogress(cp);
int delay = 1000 - (cp % 1000);
mmainhandler.sendemptymessagedelayed(refresh_progress, delay);
break;
default:
break;
}
}
};
@suppresswarnings("deprecation")
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.handler_simple_demo);
mstart = (button) findviewbyid(r.id.handler_simple_start);
mstart.setonclicklistener(new view.onclicklistener() {
public void onclick(view v) {
mmediaplayer.start();
mmainhandler.sendemptymessage(refresh_progress);
}
});
mstart.setenabled(false);
mstop = (button) findviewbyid(r.id.handler_simple_stop);
mstop.setonclicklistener(new view.onclicklistener() {
public void onclick(view v) {
mmainhandler.removemessages(refresh_progress);
mmediaplayer.pause();
}
});
mstop.setenabled(false);
mprogressbar = (progressbar) findviewbyid(r.id.handler_simple_progress);
mdisplay = (surfaceview) findviewbyid(r.id.handler_simple_display);
msurfaceholder = mdisplay.getholder();
msurfaceholder.setfixedsize(mdisplay.getwidth(), mdisplay.getheight());
// do not believe the document, settype is necessary, otherwise, video won't play correctly
msurfaceholder.settype(surfaceholder.surface_type_push_buffers);
new thread(new runnable() {
public void run() {
try {
mmediaplayer = mediaplayer.create(getapplication(), r.raw.flug);
mmediaplayer.setdisplay(msurfaceholder);
mmainhandler.sendemptymessage(media_player_ready);
} catch (illegalargumentexception e) {
log.e(tag, "caught exception e", e);
} catch (securityexception e) {
log.e(tag, "caught exception e", e);
} catch (illegalstateexception e) {
log.e(tag, "caught exception e", e);
}
}
}).start();
}
@override
protected void ondestroy() {
super.ondestroy();
mmainhandler.removemessages(refresh_progress);
if (mmediaplayer != null) {
mmediaplayer.release();
}
}
}
推荐阅读
-
Android开发笔记之:消息循环与Looper的详解
-
Android Handler之消息循环的深入解析
-
Android消息通信机制Handler详解,Handler,Looper,MessageQueue,源码解析,讲解这几个类怎么配合工作的
-
Android异步消息处理机制 深入理解Looper、Handler、Message的关系
-
Android消息机制原理,仿写Handler Looper源码解析跨线程通信原理--之仿写模拟Handler(四)
-
Android异步消息机制-深入理解Handler、Looper和MessageQueue之间的关系
-
Android异步消息机制-深入理解Handler、Looper和MessageQueue之间的关系
-
【Android 异步操作】Handler 机制 ( Android 提供的 Handler 源码解析 | Handler 构造与消息分发 | MessageQueue 消息队列相关方法 )
-
Android之handler异步消息处理机制解析
-
轻松而深入理解Android的消息机制之Handler运行机制