Handler消息机制在android刷新UI中是经常使用到的一个工具,但是Handler并不是专门用于刷新UI,是为了让任务能够轻易的切换到Handler中操作。
出现的缘由,主要是因为android中的UI组件为了追求效率,而放弃了对UI组件在并发情况下加锁情况的解决。而是规定了子线程无法访问UI控件。
这个时候,修改主线程的UI该怎么解决呢?这时就需要Handler的消息机制,来完成对UI操作切换到主线程。
Handler消息机制包含四个部分:Message,MessageQueue,Looper,Handler。
Message :顾名思义就是线程中传递消息。(IPC中Messenger中也用到Message)。
MessageQueue:是指消息队列,每个消息进入之后,就进入到该队列进行排队,等待Handler的处理。
Handler:处理发送过来的消息。,一般需要重写handleMessage()来达到目的。
Looper:可以说是核心的模块,作为每个线程MessageQueue的管理者。
上面个模块相信开发者都比较熟悉,这一篇文章主要是讲自己对Looper的理解和总结。
Looper从名字可以知道这是一个循环,具体是做什么的呢?
当我们在子线程中声明Handler的时候会发现下面这个错误:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
这个错误就是没有调用Looper.prepare()这个函数。
看看Android下面是怎么写的:
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(true));
}
从上面可以知道,Looper.prepare()是获取ThreadLocal中的数据(ThreadLocal是用于在线程中存储数据)。由于子线程声明Handler像主线程里面自己已经有MainLooper。所以我们必须在子线程中添加Looper.prepare(),获取存储在线程中数据。
别忘了Handler就是为了将任务切换到Handler线程中去。
Looper主要的工作除了获取线程上的数据,更重要的工作是在loop()方法中有一个死循环,不断的循环检测MessageQueue中是否为空:
源码:
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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
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);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
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();
}
}
可以知道在这个循环机制中,调用Message msg = queue.next();来不断的获取队列中下一个message,知道获取到的信息为null。接着通过看一下next()函数,真正的消息阻塞是来自于MessageQueue方法中next()。
MessageQueue的next()就是一个循环体,每当消息传来的时候,next()会返回消息,并且从MessageQueue这个链表中移除。
最后通过msg.target.dispatchMessage(msg);将消息传到Handler中处理。
而行代码就是切换线程的关键:
源码:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在这里就进行了对消息的处理,那么切换线程有从何而来呢?
当然是指这个msg.target这个对象了,它来源于下面:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这里的msg.target指派了this,是指插入队列函数enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)这个函数的上下文,哪里又调用它呢,就是Handler的sendMessage(Message msg)嵌套调用了。
这就完成了Handler的分析了,总结为一个图:
下面是一个错误的尝试:
public class MyHandler extends Handler{
private Looper myLooper;
public MyHandler(Looper myLooper){
this.myLooper = myLooper;
}
@Override
public void handleMessage(Message msg){
switch (msg.what) {
case 2:
Log.e("threadmsg",""+msg.what);
// Message message = handler.obtainMessage();
// message.what = 2;
// handler.sendMessage(message);
text.setText("hello world");
myLooper.quitSafely();
break;
default:
super.handleMessage(msg);
break;
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView)findViewById(R.id.text);
button = (Button)findViewById(R.id.change);
Tbutton = (Button)findViewById(R.id.change2);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
});
Tbutton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Looper.prepare();
MyHandler myHandler = new MyHandler(Looper.myLooper());
Message msg = new Message();
msg.what = 2;
myHandler.sendMessage(msg);
Looper.loop();
}
}).start();
}
});
}
就会弹出下面这个错误警告,看到了吧,在线程中创建Handler,最后还是返回到handler的线程,违反了子线程不能访问 UI线程的原则。如果硬要这么要修改就必须中间多一个来自主线程创建的Handler才能够修改上面的textView,不推荐这么做。
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
正确的调用时第一个Button来调用调用创建在主线程的Handler:
public class MainActivity extends Activity {
private TextView text;
private Button button;
private Button Tbutton;
private Handler handler = new Handler(){
public void handleMessage(Message msg){
switch (msg.what) {
case 1:
text.setText("nice to meet you");
break;
case 2:
text.setText("hello world");
default:
break;
}
}
};
public class MyHandler extends Handler{
private Looper myLooper;
public MyHandler(Looper myLooper){
this.myLooper = myLooper;
}
@Override
public void handleMessage(Message msg){
switch (msg.what) {
case 2:
Log.e("threadmsg",""+msg.what);
Message message = handler.obtainMessage();
message.what = 2;
message.sendToTarget();
myLooper.quitSafely();
break;
default:
super.handleMessage(msg);
break;
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text = (TextView)findViewById(R.id.text);
button = (Button)findViewById(R.id.change);
Tbutton = (Button)findViewById(R.id.change2);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
});
Tbutton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Looper.prepare();
MyHandler myHandler = new MyHandler(Looper.myLooper());
Message msg = new Message();
msg.what = 2;
myHandler.sendMessage(msg);
Looper.loop();
}
}).start();
}
});
}
}
感谢任玉刚的开发探索与艺术,以及鸿样大神的启发。