Android中的异步消息处理机制Handler、message、Looper三剑客

来源:互联网 发布:软件论坛 编辑:程序博客网 时间:2024/04/30 02:09

我们异步处理可以通过:

  • 使用Handler + Looper + MessageQueue (本文"使用"前半部分)
  • 使用AsyncTask异步更新UI界面 (下一篇博文)
  • 使用Thread + Handler实现非UI线程更新UI界面 (本文"使用"后半部分)
今天我们来看第一个知识点:使用Handler + Looper + MessageQueue也就是消息机制。
当应用程序启动时,会开启一个主线程(也就是UI线程),由她来管理UI,监听用户点击,来响应用户并分发事件等。所以一般在主线程中不要执行比较耗时的操作,如联网下载数据等,否则出现ANR错误。所以就将这些操作放在子线程中,但是由于AndroidUI线程是不安全的,所以只能在主线程中更新UI。

1.名词解释

Message  消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。MessageQueue里存放的对象,可以调用removeMessages()时,将Message从Message Queue中删除和通过Handler对象的obtainMessage()获取一个Message实例,可以在线程中使用Handler对象的sendEmptyMessage()或者sendMessage()来传递Bundle对象到Handler,对Handler类提供handlerMessage(Message msg)判断,通过msg.what来区分每一条信息

MessageQueue 消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。

Looper 消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper负责管理线程的MessageQueue.除了主线程外,创建的线程默认是没有Looper和MessageQueue,创建一个Looper会同时创建一个MessageQueue,可以使用Looper.prepare()创建MessageQueue,Looper.loop()进入消息循环,Looper.release()释放资源.如需要接受,自己定义一个Looper对象(通过prepare函数),这样该线程就有了自己的Looper对象和MessageQueue数据结构了。 
Looper从MessageQueue中取出Message然后,交由Handler的handleMessage进行处理。处理完成后,调用Message.recycle()回收。

Handler  处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。一定要在主线程中使用handler。否则会出现: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()。原因很简单就是Looper的名词解释,在主线程中才会存在Looper。

Thread  线程,负责调度整个消息循环,即消息循环的执行场所。


他们之间的关系是:

handler负责将需要传递的信息封装成Message,通过调用handler对象的obtainMessage()来实现; 将消息传递给Looper,这是通过handler对象的sendMessage()来实现的。继而由Looper将Message放入MessageQueue中。 当Looper对象看到MessageQueue中含有Message,就将其广播出去。该handler对象收到该消息后,调用相应的handler对象的handleMessage()方法 对其进行处理。

      图片来自

2.API

① Message:

public final class Message implements Parcelable{...}
主要方法及构造:



what:用于标记message的id,便于handler针对处理

arg1,arg2:可以在消息中传递一些简单的int数据

obj:message传递对象数据,比如json数据

replyTo: 消息到达返回message

我们可以通过new Message()的方式创建Message实例,但是并不推荐采用这种方式,强烈推荐使用Message.obtain(...)的方式创建Message实例,因为后者在MessagePool消息缓冲池中会首先看看有没有现成的的Message,如果没有才会创建,提高了效率和降低了消耗。通过message.sendToTarget()代替handle.sendMessage()发送消息,其实实际还是调用的这个方法。peekData()方法同getData方法,前者不会延迟创建Bundle,当Bundle没创建时返回null。

② MessageQueue


很明显MessageQueue用来管理Message列表的,IdleHandler是队列id的一个接口。

③ Looper


前面说过如果我们要创建一个Looper要通过prepare()加入到队列才有效,通过myQueue()方法可以获取消息队列,循环调用loop();方法。

④ Handler


Handler要在主线程中创建,不管是继承还是创建对象都要复写

mHandler = new Handler() {public void handleMessage(android.os.Message msg) {// process incoming messages here}};

来抓取message,这是一个回调。

发送消息用sendMessage相关方法,延迟操作我们经常使用post方法,其实是把参数中的runnable作为回调传给Message的CallBack.即Post的各种方法是把一个Runnable发送给消息队列,它将在到达时进行处理。

相关参考:http://www.cnblogs.com/livesoft/archive/2011/04/19/2021001.html

3.使用

相关参考:http://www.cnblogs.com/keyindex/articles/1822463.html

这是一个倒计时程序。

xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent">    <TextView         android:id="@+id/tv"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_centerHorizontal="true"        android:layout_marginTop="20dp"        android:textSize="18sp"        android:text="20"/>    <LinearLayout         android:id="@+id/ll"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_below="@id/tv"        android:padding="10dp"        android:orientation="horizontal">        <Button             android:id="@+id/start"            android:layout_width="0dp"            android:layout_weight="1"            android:layout_height="wrap_content"            android:padding="10dp"            android:text="start"/>        <Button             android:id="@+id/stop"            android:layout_width="0dp"            android:layout_weight="1"            android:layout_height="wrap_content"            android:padding="10dp"            android:text="stop"/>    </LinearLayout></RelativeLayout>

MainActivity

package com.lzy.exploremessagedemo;import java.util.Timer;import java.util.TimerTask;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.util.Log;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;public class MainActivity extends Activity implements OnClickListener, Handler.Callback, ITaskCallBack{private TextView textView;private Button startButton, stopButton;private Handler mHandler;private Timer timer;private int total = 20;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mHandler = new Handler(this);//放在主线程中,因为这里采用的是实现Handler.Callback接口,所以要重写handleMessage方法,如果采用继承的话,要也要复写此方法        initView();    }private void initView() {textView = (TextView) findViewById(R.id.tv);startButton = (Button) findViewById(R.id.start);stopButton = (Button) findViewById(R.id.stop);startButton.setOnClickListener(this);stopButton.setOnClickListener(this);Log.d("ThreadId", "onCreate:"+ String.valueOf(Thread.currentThread().getId()));}@Overridepublic void onClick(View v) {int id = v.getId();if (id == R.id.start) {startTime();} else if (id == R.id.stop) {stopTime();}}private void stopTime() {timer.cancel();}private synchronized void startTime() {timer = new Timer();Task mTask = new Task(this);timer.schedule(mTask, 1000, 1000);}@Overridepublic boolean handleMessage(Message msg) {// 这是在主线程UI线程中更新界面switch (msg.what) {case 0x01:startButton.setClickable(false);textView.setText(String.valueOf(msg.arg1));//如果是复杂数据自然是采用msg.getData()/msg.obj方式获取了Log.d("ThreadId", "HandlerMessage:"+ String.valueOf(Thread.currentThread().getId()));break;case 0x02:startButton.setClickable(true);stopTime();textView.setText("计时结束");total = 20;break;default:break;}return false;}private class Task extends TimerTask{private ITaskCallBack callBack;public Task(ITaskCallBack callBack) {this.callBack = callBack;}@Overridepublic void run() {callBack.taskRun();}}@Overridepublic void taskRun() {updateTimerValues();}// 执行耗时的倒计时任务。private void updateTimerValues() {total --;Log.d("ThreadId","send:" + String.valueOf(Thread.currentThread().getId()));/** * 方式一 * */Message message = new Message();/** * 如果是复杂的数据采用obj 或者 Bundle * 少量数据而且是整形的推荐使用arg1 arg2 * *//*Bundle date = new Bundle();// 存放数据date.putInt("time", total);msg.setData(date);*/if (total > 0) {message.arg1 = total;message.what = 0x01;}else {message.what = 0x02;}mHandler.sendMessage(message);/** * 方式二 * *//*Message msg = Message.obtain();msg.arg1 = total;msg.what = 0x01;msg.sendToTarget();//推荐采用这个方法 */}@Overrideprotected void onDestroy() {timer.cancel();mHandler.removeMessages(0x01);super.onDestroy();}}interface ITaskCallBack{void taskRun();}
这里面其实有个MainLooper在不断地循环从MessageQueue中取出Message,由handler处理message从输出来看,主线程创建和接收铺的线程ID都是同一个,但是发送message的线程不一样,见效果图:



总结一下message和 handler的使用

①.在主线程中创建Handler,并复写handlerMessage方法处理返回message.what更新UI。

②.在后台或者是需要耗时的操作中创建Message对象,有两种方式一个使用构造,另外一个使用静态的Message.obtaion()创建。

③.在message中传入message的唯一ID根据需要传入要发送的数据。(根据数据的复杂性和需求灵活使用Message中的字段)

④.借助主线程的mHandler将该消息发送出去。(也是有两种方式,使用案例见上demo)

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

《以下来自》http://blog.csdn.net/jiangwei0910410003/article/details/17021809

Handler发送消息

有了handler之后,我们就可以使用 post(Runnable)postAtTime(Runnable, long)postDelayed(Runnable, long)sendEmptyMessage(int)sendMessage(Message)sendMessageAtTime(Message, long)和 sendMessageDelayed(Message, long)这些方法向MQ上发送消息了。光看这些API你可能会觉得handler能发两种消息,一种是Runnable对象,一种是message对象,这是直观的理解,但其实post发出的Runnable对象最后都被封装成message对象了,总之通过handler发出的message有如下特点:

1.message.target为该handler对象,这确保了looper执行到该message时能找到处理它的handler,即loop()方法中的关键代码

msg.target.dispatchMessage(msg);

2.post发出的message,其callback为Runnable对象

Handler处理消息

说完了消息的发送,再来看下handler如何处理消息。消息的处理是通过核心方法dispatchMessage(Message msg)与钩子方法handleMessage(Message msg)完成的

handler源码

<span style="font-size:12px;">public class handler { final MessageQueue mQueue; // 关联的MQ final Looper mLooper; // 关联的looper final Callback mCallback; //回调函数,他的执行是在handlerMessage方法之前执行的,具体可以查看dispatchMessage(msg)方法// 其他属性 public Handler() { if (FIND_POTENTIAL_LEAKS) { final Class<?extends Handler> 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()); } } // 默认将关联当前线程的looper mLooper = Looper.myLooper(); // looper不能为空,即该默认的构造方法只能在looper线程中使用 if (mLooper ==null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } // 重要!!!直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上 mQueue = mLooper.mQueue; mCallback =null; } // 其他方法 } </span>

dispatchMessage方法源码:

// 处理消息,该方法由looper调用 publicvoid dispatchMessage(Message msg) { if (msg.callback !=null) { // 如果message设置了callback,即runnable消息,处理callback! handleCallback(msg); } else { // 如果handler本身设置了callback,则执行callback if (mCallback !=null) { /* 这种方法允许让activity等来实现Handler.Callback接口,避免了自己编写handler重写handleMessage方法。见http://alex-yang-xiansoftware-com.iteye.com/blog/850865 */ if (mCallback.handleMessage(msg)) { return; } } // 如果message没有callback,则调用handler的钩子方法handleMessage handleMessage(msg); } } // 处理runnable消息 privatefinalvoid handleCallback(Message message) { message.callback.run(); //直接调用run方法! } // 由子类实现的钩子方法 publicvoid handleMessage(Message msg) { } 
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下来我们来看看Looper的使用

前面说过,自己的mLooper要通过Looper的两个方法处理才会有效,比如

public class LooperThread extends Thread { @Override publicvoid run() { // 将当前线程初始化为Looper线程 Looper.prepare(); // ...其他处理,如实例化handler // 开始循环处理消息队列 Looper.loop(); } } 

这样,我们自己的LooperThread就升级为Looper进程了。

我们在上面的基础上加以实现吧:

在xml添加了一个发送消息的按钮:

<Button        android:id="@+id/send"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_below="@id/ll"        android:padding="10dp"        android:text="send message" />


MainActivity改变如下/******************************looper**************************************/      作了标记

package com.lzy.exploremessagedemo;import java.util.Timer;import java.util.TimerTask;import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.util.Log;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.TextView;import android.widget.Toast;public class MainActivity extends Activity implements OnClickListener, Handler.Callback, ITaskCallBack{private TextView textView;private Button startButton, stopButton, sendButton;private Handler mHandler, mHandler2;private Timer timer;private int total = 20;/******************************looper**************************************/private LooperThread mLooperThread;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mHandler = new Handler(this);//放在主线程中,因为这里采用的是实现Handler.Callback接口,所以要重写handleMessage方法,如果采用继承的话,要也要复写此方法/******************************looper**************************************/                mLooperThread = new LooperThread();//开启线程mLooperThread.start();/******************************looper**************************************/        initView();    }private void initView() {textView = (TextView) findViewById(R.id.tv);startButton = (Button) findViewById(R.id.start);stopButton = (Button) findViewById(R.id.stop);/******************************looper**************************************/sendButton = (Button) findViewById(R.id.send);startButton.setOnClickListener(this);stopButton.setOnClickListener(this);/******************************looper**************************************/sendButton.setOnClickListener(this);Log.d("ThreadId", "onCreate:"+ String.valueOf(Thread.currentThread().getId()));}@Overridepublic void onClick(View v) {int id = v.getId();if (id == R.id.start) {startTime();} else if (id == R.id.stop) {stopTime();/******************************looper**************************************/} else {sendMessage();}/******************************looper**************************************/}/******************************looper**************************************/private void sendMessage() {Message message =new Message();message.obj = "hello";message.what = 0x3;mHandler2.sendMessageDelayed(message, 3000);}/******************************looper**************************************/private void stopTime() {timer.cancel();}private synchronized void startTime() {timer = new Timer();Task mTask = new Task(this);timer.schedule(mTask, 1000, 1000);}@Overridepublic boolean handleMessage(Message msg) {// 这是在主线程UI线程中更新界面switch (msg.what) {case 0x01:startButton.setClickable(false);textView.setText(String.valueOf(msg.arg1));//如果是复杂数据自然是采用msg.getData()/msg.obj方式获取了Log.d("ThreadId", "HandlerMessage:"+ String.valueOf(Thread.currentThread().getId()));break;case 0x02:startButton.setClickable(true);stopTime();textView.setText("计时结束");total = 20;break;default:break;}return false;}private class Task extends TimerTask{private ITaskCallBack callBack;public Task(ITaskCallBack callBack) {this.callBack = callBack;}@Overridepublic void run() {callBack.taskRun();}}@Overridepublic void taskRun() {updateTimerValues();}// 执行耗时的倒计时任务。private void updateTimerValues() {total --;Log.d("ThreadId","send:" + String.valueOf(Thread.currentThread().getId()));/** * 方式一 * */Message message = new Message();/** * 如果是复杂的数据采用obj 或者 Bundle * 少量数据而且是整形的推荐使用arg1 arg2 * *//*Bundle date = new Bundle();// 存放数据date.putInt("time", total);msg.setData(date);*/if (total > 0) {message.arg1 = total;message.what = 0x01;}else {message.what = 0x02;}mHandler.sendMessage(message);/** * 方式二 * *//*Message msg = Message.obtain();msg.arg1 = total;msg.what = 0x01;msg.sendToTarget();//推荐采用这个方法 */}@Overrideprotected void onDestroy() {timer.cancel();mHandler.removeMessages(0x01);/******************************looper**************************************/mHandler2.removeMessages(0x03);mLooperThread.destroy();/******************************looper**************************************/super.onDestroy();}/******************************looper**************************************/class LooperThread extends Thread{@Overridepublic void run() {// 进行进程升级Looper.prepare();//线程中有一个Looper对象,它的内部维护了一个消息队列MQ。注意,一个Thread只能有一个Looper对象,可以看到,一个线程可以有多个Handler,但是只能有一个Looper!mHandler = new Handler();mHandler2 = new Handler(){@Overridepublic void handleMessage(Message msg) {if (msg.what == 0x3) {Toast.makeText(getApplicationContext(), "这是三秒后发过来的 "+ msg.obj + "消息", Toast.LENGTH_LONG).show();}}};Looper.loop();}}/******************************looper**************************************/}interface ITaskCallBack{void taskRun();}

OK,基本就是这样,遇到问题欢迎回复指正。

—— lovey  hy

0 0
原创粉丝点击