Handler,Looper,MessageQueue,ThreadLocal的关联

濮阳旭东
2023-12-01

一.前言

本以为这些东西是老生常谈,并且作为Android开发的基础入门知识,是必须熟练掌握的,但是发现每每提起Handler及Looper,ThreadLocal的原理,总是要去翻阅资料,或者查看源码再捋一遍,干脆花一点时间整理一下,等下次再忘记拿出来稍稍翻阅一下就ok了。

二.原理浅析

分析一件事情往往需要带着问题去分析,下面我们将从以下几个问题进行分析:

  1. 为什么主线程直接new Handler就可以,子线程直接new Handler会报异常?报什么异常?子线程创建handler应该需要什么样的步骤?
  2. 消息是怎么进行跨线程传递的,最后是怎么又传回给了handler?
  3. Looper是怎么做到每个线程都存在一个实例的?并且在不同的线程中取到的是对应线程的实例?ThreadLocal的原理是什么样的?

相信你如果能够很详尽的回答这三个问题,那么本篇文章可能对你来说只是一个查漏补缺的过程,不过如果你对这三个问题回答模棱两可甚至回答不出来,那么请务必好好阅读此篇文章。

首先我们先看下怎么在主线程中创建Handler

 private void initMainThreadHandler() {
        mMainThreadHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Log.d(TAG, "mainThread \n"   msg.toString());
            }
        };
    }

如果同样按照这种方式在子线程中创建Handler如下

 private void initOtherThreadHandler() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                mOtherThreadHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        Log.d(TAG, "otherThread \n"   msg.toString());
                    }
                };
            }
        }).start();
    }

那么此时,调用initOtherThreadHandler一定会报异常,报的异常为:

   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)

那为什么会报这个异常呢,我们可以追溯到handler源码中

 public Handler(Callback callback, boolean async) {
        //省略部分代码
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
                //注意这里有个mQueue的赋值,是Handler的成员变量,后面会用到
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

可以看到Looper.myLooper()得到的对象是null,所以就抛出了一个异常,那么我们此时就要保证这个方法得到的对象不能为空,至于为什么为空呢?后面将ThreadLocal源码会讲到,优化后的子线程初始化handler如下:

private void initOtherThreadHandler() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                            //准备该线程对应的looper
                Looper.prepare();
                mOtherThreadHandler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        Log.d(TAG, "otherThread \n"   msg.toString());
                    }
                };
                                //循环遍历messageQueue来读取消息队列中的消息,从而能够处理消息并保证线程在一直执行
                Looper.loop();
            }
        }).start();
    }

那有的同学可能会问了,为什么主线程不会抛异常? 那是因为在应用App启动的时候,会在执行程序的入口ActivityThread.class类中主函数public static void main(String[] args)里面创建一个Looper对象:Looper.prepareMainLooper(),然后调用Looper.loop();完成Looper对象的创建。实际上Looper.prepareMainLooper()方法还是调用了Looper的prepare()方法完成Looper对象的创建。因此在主线程中通过关键字new创建的Handler对象之前,Looper对象已经存在并始终存在。

至此,我们解决了第一个问题,下面解决第二个问题。

上面我们创建了handler,那么怎么发消息呢:

@Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.test1:
                //给主线程发消息
                Message obtain = Message.obtain();
                obtain.obj = "主线程收到消息";
                mMainThreadHandler.sendMessage(obtain);
                break;
            case R.id.test2:
                //给子线程发消息
                Message obtain2 = Message.obtain();
                obtain2.obj = "子线程收到消息";
                mOtherThreadHandler.sendMessage(obtain2);
                break;
        }
    }

分别点击两个按钮,打印日志:

11-20 13:52:04.196 31131-31131/com.huli.hulitestapplication D/HandlerActivity: mainThread 
    { when=-1ms what=0 obj=主线程收到消息 target=com.huli.hulitestapplication.activitys.HandlerActivity$1 }
11-20 13:52:14.166 31131-31163/com.huli.hulitestapplication D/HandlerActivity: otherThread 
    { when=-1ms what=0 obj=子线程收到消息 target=com.huli.hulitestapplication.activitys.HandlerActivity$2$1 }

我们可以看到,给对应线程发消息,就是使用对应线程里面创建的handler发消息,给主线程发消息,就是使用mMainThreadHandler,给子线程发消息,就是使用mOtherThreadHandler,那这个消息怎么进到对应线程并处理,我们来看发消息的源码:

 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
             //这个mQueue是Handler的成员变量,是Looper的成员变量赋值过来的
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this   " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
        
        private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
           //这里将消息的target属性赋值为handler,后续会用这个target来处理消息
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
                //然后将消息塞进队列中
        return queue.enqueueMessage(msg, uptimeMillis);
    }

可以看到,对应的发消息,其实就是将message的target指向当前发消息的handler,然后将message塞入消息队列中,这个消息队列是通过Looper.loop()来进行遍历的,我们继续看源码:

public static void loop() {
        final Looper me = myLooper();
      
            //这个队列跟发消息的Handler里面的队列是一个对象,Handler里面队列添加了消息,这里就能取到消息
        final MessageQueue queue = me.mQueue;

        for (;;) {
            Message msg = queue.next(); // might block
        
            try {
                         //关键代码 使用调用msg.target.dispatchMessage方法处理message
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
        }
    }

由上面代码,我们可以看到,最终就转到了发消息的Handler来处理

 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
                        //由于上面都为空,所以走到这里,调用下面的方法
            handleMessage(msg);
        }
    }
        //这个方法就是我们重写的方法,最终打印出了日志
         public void handleMessage(Message msg) { }

所以我们了解到,消息是由handler进行发出,塞进对应线程的messageQueue中,对应线程的Looper又进行了遍历,取出消息,交由对应发消息的handler来处理,进行了消息的发送和处理,那么新的问题又来了,这个对应线程是怎么来的?怎么根据线程来获得对应的Looper和messageQueue?这就是接下来我们要分析的第三个问题了。

我们会发现我们是通过Looper.prepare()来进行对应线程Looper的初始化的,后面又通过Looper.myLooper()进行对应线程的Looper的获取的,这里面肯定有猫腻,这就涉及到了ThreadLocal的原理了,接下来我们看源码:

//Looper类

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
           //从这也可以看出,每个线程Looper.prepare()只能调用一次,否则会抛异常
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

从上面就看到ThreadLocal的身影了,我们先解释下ThreadLocal:

由于内容比较多,这里贴一篇详细的文章,我下面做简单的总结:

ThreadLocal原理分析与使用场景

其实每个线程Thread类里面都有一个成员变量ThreadLocal.ThreadLocalMap threadLocals,该map的key为ThreadLocal,value为要创建的副本,当我们第一次调用Looper.prepare()的时候,首先会调用sThreadLocal.get方法,进到get方法里面得到当前线程的map,然后获取key为sThreadLocal的value值,得到的为null, 然后调用 sThreadLocal.set(new Looper(quitAllowed));, 进到set方法,同样的获取当前线程的map,然后给当前map设置key为sThreadLocal,value为新创建的Looper,此时当前线程就存在Looper了

接下来我们创建Handler的时候,就会调用Looper.myLooer()获取当前线程的Looper,就是通过Looper的sThreadLocal.get得到的

后面进行消息遍历的时候,也是通过Looper.myLooer()获取当前线程的Looper,即通过Looper的sThreadLocal.get得到的

所以这样我们就回答了第3个问题,Looper能够做到每个线程都有一个Looper副本,就是通过Looper的静态成员变量sThreadLocal做到的,它是通过获取当前线程的ThreadLocalMap来判断对应线程是否有副本,有就能取到当前线程的Looper副本,所以我们并没有手动给Handler赋值Looper,它就能正常取到其值,就是通过ThreadLocal来做的。

如果在开发过程中你也有不同线程都存在一个副本的需求,就可以尝试使用ThreadLocal来实现。

至此,大功告成。

本文由博客一文多发平台 OpenWrite 发布!

 类似资料: