您的位置:首页 > 产品设计 > UI/UE

关于Handler消息机制一点愚见与总结

2016-08-20 16:18 393 查看
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();
}
});

}

}


感谢任玉刚的开发探索与艺术,以及鸿样大神的启发。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android ui