Android开发之Handler消息机制

概述

Android的消息机制主要指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程,这三者实际上是一个整体。

Handler的作用是将一个任务切换到某个制定的线程中去执行;MessageQueue是一个用单链表的数据结构实现的消息队列,用来来存储消通过Handler发送的消息;Looper是一个消息循环处理类,Looper会以无限循环的形式去查找是否有新消息,有的话就处理,否则就一直等待。


handler的使用

每次你新创建一个Handle对象,它会绑定于创建它的线程(也就是UI线程)以及该线程的消息队列, Handler可以把一个Message对象或者Runnable对象压入到消息队列中,进而在UI线程中获取Message或者执行Runnable对象,Handler把压入消息队列有两类方式

(1)post方式

对于Handler的Post方式来说,它会传递一个Runnable对象到消息队列中,在这个Runnable对象中,重写run()方法。一般在这个run()方法中写入需要在UI线程上的操作。
public class MainActivity extends AppCompatActivity {
    private Handler handler = new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView textView = (TextView) findViewById(R.id.textView);

        new Thread(){
            @Override
            public void run() {
                //在此执行耗时操作,如网络请求,文件读写等
                //...
                //耗时操作执行完毕后,更新UI
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText("耗时任务执行完毕,在主线程更新UI");
                        textView.setText("耗时任务执行完毕,在主线程更新U2");
                        textView.setText("耗时任务执行完毕,在主线程更新U3");
                    }
                });
                super.run();
            }
        }.start();
    }
}

(2)sendMessage方式

handler类需在主线程中重写handleMessage方法,用于获取工作线程传递过来的数据。在工作线程中,耗时操作执行完毕后,会实例化一个Message对象,然后调用handler.sendMessage(Message)方法将消息传输到主线程。
 super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView textView = (TextView) findViewById(R.id.textView);

        final Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what){
                    case 1:
                        textView.setText("更新主线程UI");
                        break;
                }
            }
        };
        new Thread(){
            @Override
            public void run() {
                //在此执行耗时操作,如网络请求,文件读写等
                //...
                //耗时操作执行完毕后,更新UI
                Message message = new Message();
                message.obj = 1;
                message.arg1 = 100;
                handler.sendMessage(message);
            }
        }.start();
    }

ThreadLocal的工作原理

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储后,只能在指定线程中获取到存储的数据。一般来说,当数据是以线程为作用域并且不同线程具有不同的数据副本的时候,可以考虑采用ThreadLocal。

对于Handler来说,他需要获取当前线程的Looper,而Looper的作用域就是线程并且不同线程有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。

注意:ThreadLocal的另一个使用场景是负责逻辑下的对象传递。

MessageQueue的工作原理

MessageQueue主要包含两个操作:插入(enqueMessage)和读取(next)

其中next()方法是一个无限循环的方法,如果队列中没有消息,next方法就一直阻塞,当有新的消息来时,next()方法会返回这条消息并将其从队列中删除。

注意:MessageQueue内部有一个boolean类型的mQuitting变量,在每次查询是否有消息存在之后,都会判断下mQuitting变量是否为true,如果为true表示消息队列要终止,则next()方法会返回null。

Looper的工作原理

Looper在Android的消息机制中扮演着消息循环的角色,它会不停地从MessageQueue中查看是否有新消息,如果有就会立刻处理,否则就一直阻塞在那里。

在Android的UI线程中,已经为我们创建好了Looper实例,如果想在非UI线程中Handler与Looper,可用如下代码实现:
new Thread(){
    @Override
    public void run() {
        //1.创建Looper实例
        Looper.prepare();
        //2.创建Handler实例
        Handler handler = new Handler();
        //3.开启消息循环
        Looper.loop();
    }
}.start();

在Looper.prepare()方法中会先调用ThreadLocal类查询该线程是否已有其他Looper实例,如果有则抛出异常,因为一个线程只能有一个Looper实例。如果没有,就调用Looper的构造方法,生成一个Looper实例。Looper的构造方法如下:
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
} 
在Looper的构造方法中会生成一个MessageQueue的消息实例,用于存储handler生成的消息。

注意:只有在创建了Looper实例之后,才能创建Handler实例,因为Handler在创建的时候,会判断Looper是否存在,不存在的话会抛出异常。

Looper.prepare()方法只是产生了消息队列的实例,并没有真正进行消息循环,只有调用了Looper.loop()之后,消息循环才正式开启。
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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            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.recycleUnchecked();
        }
    }
在Looper的loop()方法中,会开启一个for (;;) 的死循环,利用这个循环不断调用MessageQueue.next()方法查询消息队列中的消息,next()方法是一个阻塞方法,如果没有消息就会一直阻塞,有消息就会返回消息msg,这也导致looper()会一直阻塞在那里。获得消息msg后,就调用 msg.target.dispatchMessage(msg)处理这条消息,这里的msg.target是发送这条消息的Handler,这样Handler发送的消息最终又交给它的dispatchMessage方法来处理了。

所以说 Handler与MessageQueue、Looper的关系是N:1:1。

对于loop()方法内的死循环,唯一跳出循环的方法是消息队列返回null,为此需要调用Looper的quit()方法或者quitSafety()方法,二者的区别是quit()方法是直接退出Looper,而quitSafety()会等消息队列中的消息处理完后再退出。

public void quit() {
    mQueue.quit(false);
}
public void quitSafely() {
    mQueue.quit(true);
}

主线程的消息循环

Android的主线程是ActivityThread,在ActivityThread的入口方法main中,系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MwssageQueue,并通过Looper.loop()来开启主线程的消息循环。

并且在主线程也定义了和消息队列进行交互的Handler,它内部定义了一组消息类型,主要包括四大组件的启动和停止等过程。

所以我们在主线程创建Handler的时候,其实主线程就存在多个Handler了,不同Handler发出的消息最终还是发给另外特定的Handler,主要是通过handler.target进行识别。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值