Android消息机制(一) Handler looper messengerQueue

参考
Handler和他的小伙伴们(上)
Android:异步处理之Handler+Thread的应用(一)
Android:异步处理之Handler、Looper、MessageQueue之间的恩怨(三)
android的消息处理机制(图+源码分析)——Looper,Handler,Message
Android中为什么主线程不会因为Looper.loop()里的死循环卡死

主线程是指进程所拥有的线程,默认情况下一个进程只有一个线程就是主线程,主线程主要处理界面交互,又叫UI线程;除了主线程之外都是子线程,又叫工作线程。

一、为什么不能直接更新UI

不要在UI主线程中进行耗时操作,你可能会疑问什么是UI主线程,UI主线程主要运行的就是Activity、Service等里面的生命周期方法,所以不要在生命周期方法如onCreate()中进行下载这些大事件。对于耗时操作,我们应该新建一个子线程并交给他处理。但是还需要注意一点,不要在子线程中更新UI界面。
为什么不允许子线程访问UI呢?这是因为UI控件是线程不安全的,如果多线程并发访问会导致UI控件处于不可预期状态。那为什么不加锁机制来解决呢?缺点有两个:

  • 锁机制会让UI访问逻辑变复杂
  • 锁机制会阻塞某些线程的执行,降低UI访问效率
    所以最简单高效方式就是单线程处理UI操作。
二、Handler

现在我们需要进行耗时操作(例如下载文件)时不能在主线程执行,我们又需要在UI界面通知用户我们活干完了不能在子线程中执行。这似乎是一个棘手的热山芋呀,幸好谷歌给我们提供了一个救我们于危难之中的Handler,一个能让主线程监听子线程发送来消息的东东。


private Button btn;
private TextView text;

private Handler handler = new Handler(){
  private int process = 0;
  @Override
  public void handleMessage(Message msg) {
    switch(msg.what){
    case 0://更细下载进度
      process += 1;
      text.setText("下载" + process + "%");//在主线程中更新UI界面
      break;
    case 1://提示下载完成
      text.setText("下载完成");//在主线程中更新UI界面
      break;
    default:
      break;
    }
  }
};
//onCreate之类的生命周期的方法就是允许在UI主线程中
@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

btn = (Button) findViewById(R.id.btn);
  text = (TextView) findViewById(R.id.text);

btn.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    new Thread(){
      @Override
      public void run() {
        //为了不阻塞主线程,将下载任务通过子线程来执行
        for(int i = 0; i < 100; i++){
          try {
            Thread.sleep(200);//休眠0.2秒,模拟耗时操作
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          handler.sendEmptyMessage(0);//发送消息到handler,通知下载进度
        }
        handler.sendEmptyMessage(1);//发送消失到handler,通知主线程下载完成
        }
      }.start();
    }
  });
}


但是如果你觉得每次都要重写handlerMessage()比较麻烦,我们完全可以用更加简略的方法来解决我们的需求,就是用handler中的post方法。代码如下

new Thread(){
  @Override
  public void run() {
    //在子线程中进行下载操作
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    handler.post(new Runnable() {
      @Override
      public void run() {
        text.setText("下载完成");
      }
    });//发送消失到handler,通知主线程下载完成
  }
}.start();

这样处理的话我们就可以不用重写handlerMessage()方法了,适合子线程与主线程进行较为单一的交流。但在这里我们要强调的一点的是,post里面的Runnable还是在UI主线程中运行的,而不会另外开启线程运行,千万不要在Runnable的run()里面进行耗时任务
如果你有时候连handler都不想搞,还可以这样写代码滴。我们只需要把handler换成View组件进行post,更新任务自然会加载到UI主线程中进行处理。

text.post(new Runnable() {
  @Override
  public void run() {
    text.setText("下载完成");
  }
});//发送消失到handler,通知主线程下载完成

关于sendMessage和post详细区别,可参考承香墨影Android--多线程之Handler

三、Handler looper messengerQueue

这里给大家写了一个向子线程发送消息并显示输出的例子,强调一下下哦,是向子线程哟。


public class MainActivity extends ActionBarActivity {

private Handler handler;
private Button btn;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    btn = (Button) findViewById(R.id.sendmsg);
    
    new HandlerThread().start();//启动子线程
    
    btn.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            handler.sendEmptyMessage(0);//向子线程发送消息
        }
    });
}

class HandlerThread extends Thread{
    @Override
    public void run() {
        //开始建立消息循环
        Looper.prepare();//初始化Looper
        handler = new Handler(){//默认绑定本线程的Looper
            @Override
            public void handleMessage(Message msg) {
                switch(msg.what){
                case 0:
                    Toast.makeText(MainActivity.this, "子线程收到消息", Toast.LENGTH_SHORT).show();
                }
            }
        };
        Looper.loop();//启动消息循环
    }
}

}

说一下消息发送的过程:

1、启动一个子线程,并在子线程初始化一个Looper。


public static void prepare() {
  prepare(true);
}
private static void prepare(boolean quitAllowed) {
  if (sThreadLocal.get() != null) {
    throw new RuntimeException("Only one Looper may be created per thread");
  }
  sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
  mQueue = new MessageQueue(quitAllowed);
  mThread = Thread.currentThread();
}

在Looper()中,实例化了一个消息队列(MessageQueue)!并且如我们所愿的绑定到了mQueue这个局部变量上,在这里我们可以得出这么一个结论:调用Looper. prepare()的线程就建立起一个消息循环的对象。

2、在HandlerThread中实例化Handler,Handler自动绑定上当前线程的Looper。


public Handler() {
  this(null, false);
}
public Handler(Callback callback, boolean async) {
  if (FIND_POTENTIAL_LEAKS) {
    final Class klass = getClass();
  if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
      (klass.getModifiers() & Modifier.STATIC) == 0) {
    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
      klass.getCanonicalName());
    }
  }
  mLooper = Looper.myLooper();
  if (mLooper == null) {
    throw new RuntimeException(
      "Can't create handler inside thread that has not called Looper.prepare()");
  }
  mQueue = mLooper.mQueue;
  mCallback = callback;
  mAsynchronous = async;
}

在代码中我们通过handler = new Handler() 调用到了Handler(Callback callback, boolean async)这个方法;我们发现mLooper = Looper.myLooper()把线程中的Looper绑定到了Handler上,通过mQueue = mLooper.mQueue获取了线程的消息队列(单链表,方便插入删除),我当然也可以换句话说:Handler已经绑定到了创建此Handler对象的线程的消息队列上了,所以咱们可以开始干坏事了。。。。

3、重写Handler里面的消息处理方法。


@Override
public void handleMessage(Message msg) {
switch(msg.what){
case 0:
Toast.makeText(MainActivity.this, "子线程收到消息", Toast.LENGTH_SHORT).show();
}
}

4、执行Looper.loop()启动消息循环,子线程进入等待消息状态。


public static void loop() {
  final Looper me = myLooper();
  if (me == null) {
    throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  }
  final MessageQueue queue = me.mQueue;

Binder.clearCallingIdentity();
  final long ident = Binder.clearCallingIdentity();

for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
      return;
    }

Printer logging = me.mLogging;
    if (logging != null) {
      logging.println(">>>>> Dispatching to " + msg.target + " " +
        msg.callback + ": " + msg.what);
    }

msg.target.dispatchMessage(msg);

if (logging != null) {
      logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
    }

final long newIdent = Binder.clearCallingIdentity();
      if (ident != newIdent) {
        Log.wtf(TAG, "Thread identity changed from 0x"
              + Long.toHexString(ident) + " to 0x"
              + Long.toHexString(newIdent) + " while dispatching to "
              + msg.target.getClass().getName() + " "
              + msg.callback + " what=" + msg.what);
      }

msg.recycle();
  }
}


在loop()的这个静态方法中,我们可以注意到for (;;)这个方法,这是死胡同死循环,所以我们将其称作为“消息循环”,说起来挺形象滴。在消息循环中会调用queue.next()来获取消息队列中排队等待处理的消息,并将其赋值到msg这个变量上;接下来就判断如果msg != null 就开始分发消息,也就是执行msg.target.dispatchMessage(msg)。在分发消息结束后,将会回收掉这个消息,体现在msg.recycle()这个函数上。
msg.target是一个handler对象,表示需要处理这个消息的handler对象,所以我们回到Handler看看dispatchMessage()这个方法了:

public void dispatchMessage(Message msg) {
  if (msg.callback != null) {
    handleCallback(msg);//post方法传递的Runnable参数
  } else {
    if (mCallback != null) {
      if (mCallback.handleMessage(msg)) {
        return;
      }
    }
    handleMessage(msg);
  }
}

不知道大家有没有一眼发现handleMessage()这个方法,这可不是我们在第三步重写Handler中的方法么。真相大白,当 msg.callback != null 并且 mCallback != null 时将会调用 handleMessage(msg) 来处理其他线程发送来的消息,我们通过覆盖这个方法来实现我们具体的消息处理过程;这也就是Handler消息处理机制的全部内容。
通读全文,我们可以知道消息循环机制的核心就是Looper,因为Looper持有了MessageQueue的对象,并且可以被一个线程设为该线程的一个局部变量,我们可以这么认为这个线程通过Looper拥有了一个消息队列。而Handler的用处就是封装了消息发送和消息处理的方法,在线程通信中,线程可以通过Handler发送消息给创建Handler的线程,通过Looper将消息放入进入消息接收线程的消息队列,等待Looper取出消息并在最后交给Handler处理具体消息。
我们会发现在Activity中实例化一个Handler并不需要Looper.prepare()来初始化一个Looper和Looper.loop()来启动消息循环,因为Activity在构造过程中已经对Looper进行了初始化并且建立了消息循环,参见ActivityThread.java中的代码:

public final class ActivityThread {
public static final void main(String[] args) {
......
Looper.prepareMainLooper();
......
ActivityThread thread = new ActivityThread();
thread.attach(false);
......
Looper.loop();
......
thread.detach();
......
}
}

Android应用程序进程在启动的时候,会在进程中加载ActivityThread类,并且执行这个类的main函数,应用程序的消息循环过程就是在这个main函数里面实现的。

总结:一个线程只有一个Looper, 而一个Looper持有一个MessageQueue, 当调用Looper.prepare()时,Looper就与当前线程关联起来了(在Activity里没有显示调用Looper.prepare()是因为系统自动在主线程里帮我们调用了),而Handler是与Looper的线程是绑定的,查看Handler类的源码可以发现它几个构造函数,其中有接收一个Looper参数的,也有不接收Looper参数的,从上面的代码上看,我们没有为Handler指定Looper,那么Handler就默认更当前线程(即主线程)的Looper关联起来了,之所以啰嗦那么多就是因为这决定了Handler.handlerMessage(msg)方法体里的代码到底在哪个线程里执行,我们再梳理一下,Looper.prepare调用决定了Looper与哪个线程关联,间接决定了与这个Looper相关联的Handler.handlerMessage(msg)方法体里的代码执行的线程。(太啰嗦了)
现在回到上面的代码,我们的Handler是在主线程里的定义的,所以也默认跟主线程的Looper相关联,即handlerMessage方法的代码会在UI线程执行。

你可能感兴趣的:(Android消息机制(一) Handler looper messengerQueue)