Handler机制和Can't create handler inside thread that has not called Looper.prepare()异常

时间:2023-02-14 20:42:09

Android开发的人都知道Handler处理机制,handler的出现就是为了保证UI线程安全,对UI的修改只有UI线程可以操作,不允许其他线程操作,下面对Handler异步消息处理机制再做一下简单的介绍: 
1、成员介绍 
Message:主要功能是进行消息的封装,同时可以指定消息的操作形式; 
Looper:消息循环泵,用来为一个线程跑一个消息循环。每一个线程最多只可以拥有一个。 
MessageQueue:就是一个消息队列,存放消息的地方。每一个线程最多只可以拥有一个。 
Handler:消息的处理者,handler 负责将需要传递的信息封装成Message,发送给Looper,继而由Looper将Message放入MessageQueue中。当Looper对象看到MessageQueue中含有Message,就将其广播出去。该handler 对象收到该消息后,调用相应的handler 对象的handleMessage()方法对其进行处理。 
2、同线程各成员的关系及数量 
①一个线程中只能有一个Looper,只能有一个MessageQueue,可以有多个Handler,多个Messge; 
②一个Looper只能维护唯一一个MessageQueue,可以接受多个Handler发来的消息; 
③一个Message只能属于唯一一个Handler; 
④同一个Handler只能处理自己发送给Looper的那些Message;

理清各成员之间的关系就好办了,网上好多都是在其他线程中Handler讲解,下面我来简单说一下在同一线程中,多个handler的用法,有的人可能会说,同一个线程为啥还要多个Handler,其实就是为了相互独立,互不干扰,各自处理各自的消息,比如说Handler1收到一个点击消息,它想将消息队列里面的所有点击消息,那它可以通过removeMessages(int what) 方法清除,这时如果handler2如果也有点击消息,就不会受到影响,因为谁发的Message,谁处理。 

如果在一个Handler的方法新机类,类中再次新建了内部的handler应该怎么办?

这时我们发现系统会报Can't create handler inside thread that has not called Looper.prepare()异常

这时两个handler公用一个looper,系统就不知道该将消息发给谁了,我们可以在新建Handler的时候给他制定一个Handler就可以解决了,

private Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {

}
}
当然Can't create handler inside thread that has not called Looper.prepare()并不是只有这种情况会报,网上其他的例子,比如在
线程间调用的时候也是经常出现:

这是因为Handler对象与其调用者在同一线程中,如果在Handler中设置了延时操作,则调用线程也会堵塞。每个Handler对象都会绑定一个Looper对象,每个Looper对象对应一个消息队列(MessageQueue)。如果在创建Handler时不指定与其绑定的Looper对象,系统默认会将当前线程的Looper绑定到该Handler上。
在主线程中,可以直接使用new Handler()创建Handler对象,其将自动与主线程的Looper对象绑定;在非主线程中直接这样创建Handler则会报错,因为Android系统默认情况下非主线程中没有开启Looper,而Handler对象必须绑定Looper对象。这种情况下,则有两种方法可以解决此问题:

方法1:需先在该线程中手动开启Looper(Looper.prepare()-->Looper.loop()),然后将其绑定到Handler对象上;

final Runnable runnable = new Runnable() {
  @Override
  public void run() {
    //执行耗时操作
    try {

      Log.e("bm", "runnable线程: " + Thread.currentThread().getId()+ " name:" + Thread.currentThread().getName());

      Thread.sleep(2000);
      Log.e("bm", "执行完耗时操作了~");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
  }
};
new Thread() {
  public void run() {
    Looper.prepare();
    new Handler().post(runnable);//在子线程中直接去new 一个handler
    Looper.loop();    //这种情况下,Runnable对象是运行在子线程中的,可以进行联网操作,但是不能更新UI

  }
}.start();

 

方法2:通过Looper.getMainLooper(),获得主线程的Looper,将其绑定到此Handler对象上。

final Runnable runnable = new Runnable() {
  @Override
  public void run() {
    //执行耗时操作
    try {

      Log.e("bm", "runnable线程: " + Thread.currentThread().getId()+ " name:" + Thread.currentThread().getName());
      Thread.sleep(2000);
      Log.e("bm", "执行完耗时操作了~");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
  }
};
new Thread() {
  public void run() {
    new Handler(Looper.getMainLooper()).post(runnable);//在子线程中直接去new 一个handler

    //这种情况下,Runnable对象是运行在主线程中的,不可以进行联网操作,但是可以更新UI
  }
}.start();


在调用handler的方法前执行Looper.prepare()。Looper用于封装了android线程中的消息循环,默认情况下一个线程是不存在消息循环(message loop)的,需要调用Looper.prepare()来给线程创建一个消息循环,调用Looper.loop()来使消息循环起作用。

因为activity的主线程是自己创建消息队列的,所以在Activity中新建Handler时,不需要先调用Looper.prepare()