Looper Handler MessageQueued 概念及关系及问题

网上关于这三者的博客太多了,都非常详尽,有些博客深入源码分析,非常不错,但难免有些晦涩难懂。本篇文章从概念层面讲解这三者关系,熟读本篇文章后会让你对这三者概念及关系有比较清晰的了解,再去看其他源码分析文章会有事半功倍的效果。

问题

1、一个应用的运行过程是怎样的(消息层面)?
2、Service和Handler都运行在主线程,是怎样避免同步问题的?
3、到底能不能在非UI线程操作UI?
4、子线程怎样创建Handler?意义在哪?
5、多个Handler工作机制是怎样的?

概念

MessageQueued

消息队列,这里的“消息”可以理解为事务,例如一次点击、一次刷新都是一个“消息”

Looper

可以理解为消息泵,内部维护了一个消息队列,不断从消息队列里面取出消息,交给不同的handler

Handler

从Looper的MessageQueued中取出消息并执行,主要用于线程间通信。如果不考虑线程间通信,那其实Looper和MessageQueued就能实现一个事务顺序执行的队列了,Handler的存在就是为了解决线程间通信问题。

Activity

这个大家都理解,运行在主线程(UI线程)。从ActivityThread的main方法可知,创建了Looper循环,而Looper循环是一个死循环,用户所有的操作都是一个消息(即事务),在这个死循环中不断被读取执行,如果没有消息,则会阻塞,此时主线程会进入休眠状态,直到有新的消息来唤醒。注意,这里所有执行都和Handler无关,因为所有事务执行都在主线程,没有涉及到线程间通信,所以不需要用到Handler。

Service

这个大家也熟悉,运行在主线程,相对于Activity来说一个前台可见一个后台默默运行。

解决问题

1、一个应用的运行过程是怎样的(消息层面)?

1、默认Activity过程:
ActivityThread创建Looper实例,由此创建了消息队列MessageQueued,Looper.loop()会进入一个死循环,不断从MessageQueued中读取消息出来执行
2、子线程执行过程:
在Activity中创建Handler,子线程中执行自己的任务(默认没有自己的Looper,但可新建Looper),如果有需要,则通过Activity的Handler把消息发送到主线程,在主线程中执行消息(这里发送到主线程的消息,其实是加入到了主线程的消息队列里面,接下来的执行过程就和上面1相同)。

2、Service和Handler都运行在主线程,是怎样避免同步问题的?

由上面的默认Activity运行过程可知,主线程维护了一个消息队列,这样不管是Activity还是Service,所有的事务处理都是往这个消息队列里面添加消息。站在系统的角度来说,不用管是Activity还是Service,不用管是前台(Activity)的消息还是后台(Service)的消息,所有东西都是一个消息,只要在消息队列里面取出消息执行就行了,对于系统来说根本没有区别,这也就是为什么service不能做阻塞线程的操作,因为阻塞了线程会导致Activity的消息在消息队列里面一直排队执行不了,最后ANR了。这也就解释了同步问题——消息队列的存在不会出现同步问题。

3、到底能不能在非UI线程操作UI?

众所周知,在Android中是不能在子线程更新UI的,否则会报错如下:

android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

但是这段代码却可以正常运行:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.main_text);
        new Thread(new Runnable() {
            @Override
            public void run() {
                textView.setText("hello everyone");
            }
        }).start();
    }

为什么:
首先上面报错的地方在ViewRootImpl的checkThread方法

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

但是这段代码写在onCreate里面,而这时候onCreate还没有初始化UI完成,而ViewRootImpl的checkThread还没有被调用,所以没有报错,可以这样验证一下:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.main_text);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                textView.setText("hello everyone");
            }
        }).start();
  }

如愿以偿的报错:

 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
                                                                     at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6556)
                                                                     at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:907)
                                                                     at android.view.View.requestLayout(View.java:18722)
                                                                     at android.view.View.requestLayout(View.java:18722)
                                                                     at android.view.View.requestLayout(View.java:18722)
                                                                     at android.view.View.requestLayout(View.java:18722)
                                                                     at android.view.View.requestLayout(View.java:18722)
                                                                     at android.view.View.requestLayout(View.java:18722)
                                                                     at android.support.constraint.ConstraintLayout.requestLayout(ConstraintLayout.java:1959)
                                                                     at android.view.View.requestLayout(View.java:18722)
                                                                     at android.widget.TextView.checkForRelayout(TextView.java:7172)
                                                                     at android.widget.TextView.setText(TextView.java:4342)
                                                                     at android.widget.TextView.setText(TextView.java:4199)
                                                                     at android.widget.TextView.setText(TextView.java:4174)
                                                                     at com.twsm.testdemo.MainActivity$1.run(MainActivity.java:32)
                                                                     at java.lang.Thread.run(Thread.java:818)
结论:可以在非UI线程操作UI,但这是不安全的,也不是官方推荐的,所以尽量不要在非UI线程操作UI。

PS:查看checkThread代码可知:判断的是该UI是否是创建的线程,那么如果我在子线程创建的UI然后在子线程操作它呢?可以试试。

4、子线程怎样创建Handler?意义在哪?

先说怎么在子线程创建Handler:

public class TestThread extends Thread{
    public static Handler handler;
    public TestThread() {
    }

    @Override
    public void run() {
        Looper.prepare();
        handler = new Handler(Looper.myLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what){
                    case 0:
                        Log.e("tag", "这是来自子线程的消息");
                        break;
                    case 1:
                        Log.e("tag", "这是来自主线程的消息");
                        break;
                }
            }
        };
        handler.sendEmptyMessage(0);
        Looper.loop();
    }
}

注意前后的 Looper.prepare()和Looper.loop(),如果没有这两句则会报错:

 java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
                                                at android.os.Handler.(Handler.java:200)
                                                at android.os.Handler.(Handler.java:114)

因为没有Looper的话,Handler也没地方取数据,所以在Handler的构造函数里面强制判断了是否有Looper。
为什么在Activity创建Handler不需要添加Looper.prepare()和Looper.loop()呢?在问题1里面就说了,ActivityThread的Main方法(整个APP的入口方法)中就创建了Looper,所以在Activity创建Handler不需要添加。
子线程创建Handler的意义在哪?
1、子线程和主线程通信,是通过Handler的,大部分时候我们都是从子线程发送消息到主线程,这时候使用的是主线程的handler来发送消息,当主线程需要发送消息给子线程时,调用子线程的handler发送消息。
2、消息队列可是个好东西,可以避免很多线程安全问题,所有东西都由于执行,不会产生同步问题。

5、多个Handler工作机制是怎样的?

如果一个Activity创建了多个Handler,Looper是怎样把消息交给对应的handler的呢?看下Looper的源码就知道了:

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();
        }
}

注意有一句代码:msg.target.dispatchMessage(msg),这个msg.target就是handler对象,这样就和对应的handler关联起来了,dispatchMessage代码:

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

可以看到熟悉的handleMessage,这样就可以在你发送消息的那个handler的handleMessage执行这个消息。
站在系统的角度来看,一个Handler和多个Handler并没有区别,还是上面那句话:

系统只是从消息队列里取出消息执行,所有的Handler操作的消息最终还是会添加到主线程的消息队列中

只是在开发者看来,消息会被发送到不同Handler中最终在对应的handleMessage被执行,“看起来好像”在不同地方执行了,其实都是同一个消息队列,只是代码写在不同地方而已,这个概念不要弄混淆。

你可能感兴趣的:(Looper Handler MessageQueued 概念及关系及问题)