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

我们异步处理可以通过:

  • 使用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发送给消息队列,它将在到达时进行处理。

相关参考:Android 中Message,MessageQueue,Looper,Handler详解+实例<转> - livesoft - 博客园

3.使用

相关参考:android的Handler - DotDot - 博客园

这是一个倒计时程序。

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()));
	}

	@Override
	public 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);
	}

	@Override
	public 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;
		}
		
		@Override
		public void run() {
			callBack.taskRun();
		}
		
	}

	@Override
	public 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();//推荐采用这个方法
		 */
	}
	
	@Override
	protected 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()));
	}

	@Override
	public 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);
	}

	@Override
	public 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;
		}
		
		@Override
		public void run() {
			callBack.taskRun();
		}
		
	}

	@Override
	public 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();//推荐采用这个方法
		 */
	}
	
	@Override
	protected void onDestroy() {
		timer.cancel();
		mHandler.removeMessages(0x01);
/******************************looper**************************************/
		mHandler2.removeMessages(0x03);
		mLooperThread.destroy();
/******************************looper**************************************/
		super.onDestroy();
	}
/******************************looper**************************************/
	class LooperThread extends Thread{

		@Override
		public void run() {
			// 进行进程升级
			Looper.prepare();
			//线程中有一个Looper对象,它的内部维护了一个消息队列MQ。注意,一个Thread只能有一个Looper对象,可以看到,一个线程可以有多个Handler,但是只能有一个Looper!
			mHandler = new Handler();
			mHandler2 = new Handler(){

				@Override
				public 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,基本就是这样,遇到问题欢迎回复指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值