Android Handler之消息循环的深入解析

时间:2022-05-04 15:18:33

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来实现循环时序。
Android Handler之消息循环的深入解析Android 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();
 }
    }
}