欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android开发之Handler消息机制

程序员文章站 2022-07-14 15:06:00
...

概述

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进行识别。