Android消息机制之Handler

来源:互联网 发布:云计算和大数据年收入 编辑:程序博客网 时间:2024/05/17 05:56

Android API中对handler的介绍是这样的:

A Handler allows you to send and process Message and Runnable objects associated with a thread’s MessageQueue. Each Handler instance is associated with a single thread and that thread’s message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it – from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

翻译:一个handler允许发送、处理Message以及和线程的MessageQueue相关联的Runable对象。每一个handler的对象都会关联单个线程以及这个线程的消息队列。当创建一个handler的时候,它会被绑定到这个线程或者绑定创建它的线程的消息队列。从那个时候起,它(handler)将发送消息和runables到消息队列,并且处理这个消息队列里的message和runables。

Android为什么要提供Handler

Android建议我们不要在UI线程中执行耗时操作,因为这很容易导致ANR异常(在Android源码中我们可以看到,UI如果对用户的操作超过5秒无响应,就会报ANR异常)。因此,一些耗时操作都会在子线程中完成。当我们在子线程中获取了数据,要将其显示到UI中,如果没有Handler,这将很难完成。因此,Android之所以提供Handler,就是为了解决子线程访问UI的问题。
为什么Android不允许在子线程中访问UI呢?显然这样做不安全,多线程访问UI是不安全的(学过操作系统的盆友应该都了解线程互斥,这里我就不详细介绍了)。有人就会说了,可以通过设置信号量来解决啊。这中方法不是不可以,因为这种方法会使访问UI的逻辑变得复杂;其次这会降低UI的访问效率。而使用Handler就比较简单高效。Handler是同个Message来通讯的。

Handler的用法

使用Handler时,需要重写handleMessage方法,在handleMessage中接受新线程发来的Message,并做相应的处理。在新线程中则是通过Message来传递消息,Message中往往也携带着需要传递的数据以及消息的类型。还要强调一点,如果当前线程有Looper就不需要执行Looper.prepare(),如果没有,就需要在新线程内执行Looper.prepare(),否则会报错。具体使用代码如下:

public class MainActivity extends AppCompatActivity {    private Handler mHandler=new Handler(){        @Override        public void handleMessage(Message msg) {            switch (msg.what)            {                case 1:                    //执行需要修改的UI操作                    break;                default:                    break;            }        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        new Thread(new Runnable() {            @Override            public void run() {//在新线程中执行耗时操作                //如果当前线程有Looper就不需要执行Looper.prepare();                Looper.prepare();                try {                    Thread.sleep(1000);//睡眠1秒                } catch (InterruptedException e) {                    e.printStackTrace();                }                //操作完成之后通过发送Message,来通知Handler进行UI操作                Message msg=new Message();                msg.what=1;                /*这部分是伪代码,value 是想通过Message传递的值                Bundle data=new Bundle();                data.putSerializable("key",value);                msg.setData(data);                */                //设置好数据后,发送消息                mHandler.sendMessage(msg);            }        }).start();    }}

当然,handler也可以在子线程中创建,代码如下:

    private TextView tv_test;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.handler_test_layout);        tv_test= (TextView) findViewById(R.id.tv_test);    }    //button点击的函数    public void click(View v)    {        new Thread(new Runnable() {            @Override            public void run() {                Looper.prepare();                Handler handler=new Handler(Looper.getMainLooper()){                    @Override                    public void handleMessage(Message msg) {                        switch (msg.what)                        {                            case 1:                                tv_test.setText("receive msg");                        }                    }                };                Message msg=new Message();                msg.what=1;                handler.sendMessage(msg);            }        }).start();    }

上面的代码是,当点击按钮后,就会创建一个新的线程,在新线程中创建handler,并发送消息、接受消息。这里需要注意的是,在新线程中创建handler需要使用Handler handler=new Handler(Looper.getMainLooper())这样的写法,Looper.getMainLooper()将主线程中的Looper传过去,这样handler才能访问UI。运行效果如下:
这里写图片描述

Handler的内部机制

Handler创建时会采用Looper来建立消息循环。所以,当前线程必须要有Looper。当Handler创建完成后,其内部的Looper以及MessageQueue既可以和Handler一起协同工作了。Handler通过sendMessage将消息发送给内部的MessageQueue,而MessageQueue会调用queue.enqueueMessage(msg, uptimeMillis)方法,它的源码如下:

boolean enqueueMessage(Message msg, long when) {        if (msg.target == null) {            throw new IllegalArgumentException("Message must have a target.");        }        if (msg.isInUse()) {            throw new IllegalStateException(msg + " This message is already in use.");        }        synchronized (this) {            if (mQuitting) {                IllegalStateException e = new IllegalStateException(                        msg.target + " sending message to a Handler on a dead thread");                Log.w(TAG, e.getMessage(), e);                msg.recycle();                return false;            }            msg.markInUse();            msg.when = when;            Message p = mMessages;            boolean needWake;            if (p == null || when == 0 || when < p.when) {                // New head, wake up the event queue if blocked.                msg.next = p;                mMessages = msg;                needWake = mBlocked;            } else {                // Inserted within the middle of the queue.  Usually we don't have to wake                // up the event queue unless there is a barrier at the head of the queue                // and the message is the earliest asynchronous message in the queue.                needWake = mBlocked && p.target == null && msg.isAsynchronous();                Message prev;                for (;;) {                    prev = p;                    p = p.next;                    if (p == null || when < p.when) {                        break;                    }                    if (needWake && p.isAsynchronous()) {                        needWake = false;                    }                }                msg.next = p; // invariant: p == prev.next                prev.next = msg;            }            // We can assume mPtr != 0 because mQuitting is false.            if (needWake) {                nativeWake(mPtr);            }        }        return true;    }

通过源码,我们发现,queue.enqueueMessage(msg, uptimeMillis)将消息放入了MessageQueue里。Looper则会一直处理MessageQueue中的消息。

1 0
原创粉丝点击