Handler消息传递机制详解
1、出于线程考虑,Android的UI不是线程安全的,者意味着如果有多个线程并发操作UI组件,可能导致线程安全问题。为了解决这一问题,Android指定了一个简单的规则:只允许UI线程修改Android里的UI组件(UI线程也叫主线程)
那问题来了?因为我们知道在Activity里不能处理耗时任务,会引发ANR,新开启个线程处理,但是新线程还不能修改UI组件。因此Android提供了一个handler消息传递机制来解决这个问题,(后面还有一个异步任务也能解决,其实这个异步任务的内部实现就是handler和Thread组成)
2、Handler主要作用:
①在新线程中发送消息。
②在主线程中获取,处理消息。
上述似乎就两步完成了,但是我们要考虑两个问题,何时发送消息?何时接收消息呢?
为了让主线程中“适时”地处理消息,显然只能用回调的方式来实现,----开发者通过只要重写Handler类中处理信息的方法,当新启动的线程发送消息是,消息会发送到与之关联的MessageQueue,而Handler会不断地从MessageQueue中获取并处理消息-----这将导致Handler类中处理消息的方法被回调
3、Handler类包含的包含的方法
①void handlerMessage(Message msg):处理消息的方法
②final boolean hasMessages(int what):检查消息队列中是否包含what属性为指定值得消息
③final boolean has Messages(int what,Object object):检查消息中是否包含what属性为指定值的消息,且object属性为指定对象的消息。(常用)
④sendEmptyMessage(int what):发送空消息
final boolean sendEmptyMessageDelayed(int what,long delayMillis):指定多少毫秒之后发送空消息。
⑤final boolean sendMessage(Message msg):立即发送消息(常用)(这是发送的是消息对象)
⑥final boolean sendMessageDelayed(Message msg,long delayMillis):指定多少毫秒之后发送消息。
下面看一个简单的例子,
public class MainActivity extends Activity implements View.OnClickListener {
private static final int COMPLETED = 0;
private TextView stateText;
private Button btn;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == COMPLETED) {
stateText.setText("completed");
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
stateText = (TextView) findViewById(R.id.tv);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
new WorkThread().start();
}
//工作线程
private class WorkThread extends Thread {
@Override
public void run() {
//......处理比较耗时的操作,
//处理完成后给handler发送消息
Message msg = new Message();
msg.what = COMPLETED;
handler.sendMessage(msg);
}
}
}
4、下面,我们就来分析一下Android中的消息机制。
熟悉Windows编程的朋友知道Windows程序是消息驱动的,并且有全局的消息循环系统。Google参考了Windows的消息循环机制,也在Android系统中实现了消息循环机制。Android通过Looper、Handler来实现消息循环机制。Android的消息循环是针对线程的,每个线程都可以有自己的消息队列和消息循环。
Android系统中的Looper负责管理线程的消息队列和消息循环。通过Looper.myLooper()得到当前线程的Looper对象,通过Looper.getMainLooper()得到当前进程的主线程的Looper对象。
前面提到,Android的消息队列和消息循环都是针对具体线程的,一个线程可以存在一个消息队列和消息循环,特定线程的消息只能分发给本线程,不能跨线程和跨进程通讯。但是创建的工作线程默认是没有消息队列和消息循环的,如果想让工作线程具有消息队列和消息循环,就需要在线程中先调用Looper.prepare()来创建消息队列,然后调用Looper.loop()进入消息循环。下面是我们创建的工作线程:
class WorkThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// 处理收到的消息
}
};
Looper.loop();
}
}
这样一来,我们创建的工作线程就具有了消息处理机制了。
那么,为什么前边的示例中,我们怎么没有看到Looper.prepare()和Looper.loop()的调用呢?原因在于,我们的Activity是一个UI线程,运行在主线程中,Android系统会在Activity启动时为其创建一个消息队列和消息循环。
前面提到最多的是消息队列(MessageQueue)和消息循环(Looper),但是我们看到每个消息处理的地方都有Handler的存在,它是做什么的呢?Handler的作用是把消息加入特定的Looper所管理的消息队列中,并分发和处理该消息队列中的消息。构造Handler的时候可以指定一个Looper对象,如果不指定则利用当前线程的Looper对象创建。下面是Handler的两个构造方法:
/**
* Default constructor associates this handler with the queue for the
* current thread.
*
* If there isn't one, this handler won't be able to receive messages.
*/
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());
}
}
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 = null;
}
/**
* Use the provided queue instead of the default one.
*/
public Handler(Looper looper) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = null;
}
消息机制中几个重要成员
有 handler, Looper, MessageQueue
工作流程:首先在主线程中创建handler 对象,在handler构造函数中会获取当前线程Looper对象,(而主线程中默认有一个Looper对象,这里就不用去创建它了。否则其他子线程,就要先创建一个Looper对象,通过Looper.prepare()创建,Looper.loop()开启looper)当工作线程中通过handler调用sendMessage(msg)时,就会将消息加入到主线程的Looper里,然后会脚本handler的handlerMessage()方法处理。所以这里的关键就是handler对象创建时是以主线程对象创建的。才能保证是否能和主线程通信
希望好好理解上面的话
线面看一个子线程处理消息的例子:
package org.crazyit.handler;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
public class CalPrime extends Activity
{
static final String UPPER_NUM = "upper";
EditText etNum;
CalThread calThread;
// 定义一个线程类
class CalThread extends Thread
{
public Handler mHandler;
public void run()
{
Looper.prepare();
mHandler = new Handler()
{
// 定义处理消息的方法
@Override
public void handleMessage(Message msg)
{
if(msg.what == 0x123)
{
int upper = msg.getData().getInt(UPPER_NUM);
List<Integer> nums = new ArrayList<Integer>();
// 计算从2开始、到upper的所有质数
outer:
for (int i = 2 ; i <= upper ; i++)
{
// 用i处于从2开始、到i的平方根的所有数
for (int j = 2 ; j <= Math.sqrt(i) ; j++)
{
// 如果可以整除,表明这个数不是质数
if(i != 2 && i % j == 0)
{
continue outer;
}
}
nums.add(i);
}
// 使用Toast显示统计出来的所有质数
Toast.makeText(CalPrime.this , nums.toString()
, Toast.LENGTH_LONG).show();
}
}
};
Looper.loop();
}
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
etNum = (EditText)findViewById(R.id.etNum);
calThread = new CalThread();
// 启动新线程
calThread.start();
}
// 为按钮的点击事件提供事件处理函数
public void cal(View source)
{
// 创建消息
Message msg = new Message();
msg.what = 0x123;
Bundle bundle = new Bundle();
bundle.putInt(UPPER_NUM ,
Integer.parseInt(etNum.getText().toString()));
msg.setData(bundle);
// 向新线程中的Handler发送消息
calThread.mHandler.sendMessage(msg);
}
}
1、扩展:
一个Activity中可以创建出多个工作线程,如果这些线程把他们消息放入Activity主线程的消息队列中,那么消息就会在主线程中处理了。因为主线程一般负责视图组件的更新操作,对于不是线程安全的视图组件来说,这种方式能够很好的实现视图的更新。
2、可能存在的疑惑:
对于子线程访问主线程的Handler对象,你可能会问,多个子线程都访问主线程的Handler对象,发送消息和处理消息的过程中会不会出现数据的不一致呢?答案是Handler对象不会出现问题,因为Handler对象管理的Looper对象是线程安全的,不管是添加消息到消息队列还是从消息队列中读取消息都是同步保护的,所以不会出现数据不一致现象。