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.<init>(Handler.java:200)
                                                at android.os.Handler.<init>(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被执行,“看起来好像”在不同地方执行了,其实都是同一个消息队列,只是代码写在不同地方而已,这个概念不要弄混淆。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容