Handler运行机制中必须明白的几个问题

概述

我在看完Handler的源码后有两个感觉,一是貌似明白了很多东西,二是当问到具体问题时感觉还是模模糊糊。下面我们就带着问题再看一次源码,力争把这块知识点搞的明明白白。

问题有:

  1. 在UI线程中有几个Looper对象?有几个MessageQueue对象?有几个Handler对象?有几个Message对象?
  2. 怎么保证只有一个Looper对象的?
  3. 怎么保证只有一个MessageQueue对象的?
  4. 为什么发送消息在子线程,而处理消息就变成主线程了,在哪儿跳转的?
  5. looper对象只有一个,在分发消息时怎么区分不同的handler?
  6. 能不能在子线程中创建Handler对象?
  7. 怎么在子线程中得到主线程中handler对象?

如果上面七个问题你都能清晰的回答,那么恭喜你,你对Handler的理解已经很透彻了。如果你有时间,还是希望你能继续往下看,可以给我找错误,找到我理解偏差的地方,找到有奖哦。或者您也可以提出问题让我回答,大家共同学习。

在UI线程中有几个Looper对象?有几个MessageQueue对象?有几个Handler对象?有几个Message对象?

在UI线程中只有一个Looper对象,只有一个MessageQueue对象。但可以有很多个handler对象。可以有很多个Message对象。

怎么保证只有一个Looper对象的?

在Handler机制中使用Looper对象的地方有三个,一是在ActivityThread类中使用Looper.prepareMainLooper()创建Looper对象,二是在hanlder类中使用Looper对象,三是在Looper的loop方法中使用Looper对象处理消息。若这三个Looper对象是同一个就证明了在UI线程只有一个Looper对象。

一,Looper.prepareMainLooper()创建Looper对象

prepareMainLooper方法的原码是:

    public static void prepareMainLooper() {
        prepare(false);//创建Looper对象,并保存到ThreadLocal中
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();//得到Looper对象,并保存到Looper类的静态字段中
        }
    }

下面看prepare方法的原码:

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

分析:在第一次调用sThreadLocal.get()方法得到的一定是null,所以此时的重点是创建Looper对象,并放入sThreadLocal中储存起来。
这儿要明确两点:
1. sThreadLocal是Looper类的静态字段,所以只有一个sThreadLocal对象。
2. prepare方法在UI线程被调用,所以只有在Ui线程才能从sThreadLocal对象中获取到looper对象。
下面看myLooper方法的原码:

    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

分析:这个方法很简单,目的就是得到UI线程中的Looper对象。注意这个方法是静态方法,得到的Looper对象就是在UI线程中创建的Looper对象。

二,在Hanlder类中使用Looper对象

我们知道在创建Handler对象时使用的是无参构造,但无参构造方法调用了下面的构造方法:

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

分析:由原码可知,Handler类中有mLooper字段和mQueue字段。mLooper字段赋值时通过Looper.myLooper()方法,我们从上面知道这个方法返回的值就是UI线程中创建的Looper对象,所以此时的Looper对象和Ui线程中创建的Looper对象是同一个。

三,在Looper的loop方法中使用Looper对象

在Looper类的loop方法中获取Looper对象的代码是:

final Looper me = myLooper();

很明显通过myLooper方法得到的Looper对象就是UI线程中创建的Looper对象。

怎么保证只有一个MessageQueue对象的?

首先我们找一下MessageQueue对象是在哪儿创建的?
我们知道在在Looper的prepare方法中创建了Looper对象,并放入到ThreadLocal中,具体代码是:

sThreadLocal.set(new Looper(quitAllowed));

那么我们来看一下Looper的构造方法:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

在Looper的构造方法中创建了MessageQueue对象,并赋值给mQueue字段。因为Looper对象只有一个,那么Messagequeue对象肯定只有一个。

下面我们再多学习一点,找到使用MessageQueue使用的地方。MessageQueue被两个地方使用,一是在handler的sendMessage中发送消息。二是在looper类的loop方法中取出消息。

  1. 在看Handler的构造方法中我们知道,在构造方法中通过Looper.myPrepare方法得到mLooper对象,又通过mLooper.mQueue得到Messagequeue对象。所以此时这个MessageQueue对象就是在Looper的构造方法中创建的对象。
  2. 在Looper的loop方法中也是首先通过Looper.myPrepare()方法得到Looper对象,然后得到MessageQueue对象。

为什么发送消息在子线程,而处理消息就变成主线程了,在哪儿跳转的?

发送消息使用的是Handler的sendMessage方法,这个方法最终调用的是enqueueMessage方法,enqueueMessage方法的原码是:

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

这个方法中调用的是MessageQueue的enqueueMessage方法,只要queue对象是UI线程中的MessageQueue对象,那么就能被Ui线程中的Looper从消息队列中取出来,然后就在主线程执行了。我们知道Handler对象中的mQueue就是UI线程中的消息队列对象,所以在处理消息时就是主线程了。

looper对象只有一个,在分发消息时怎么区分不同的handler?

关于这个问题主要看三点:
一是Message类中有个字段target,这个字段是Handler类型的。
二在Handler的enqueueMessage方法中有这么一句代码:msg.target = this;即把handler对象与Message绑定在了一起。
三在Looper类的looper方法中分发消息的代码是:msg.target.dispatchMessage(msg);

此时我们就明白了:在发送消息时handler对象与Message对象绑定在了一起。在分发消息时首先取出Message对象,然后就可以得到与它绑定在一起的Handler对象了。

能不能在子线程中创建Handler对象?

这个问题并不是简单的能不能的问题,考察的是对Handler的深入理解。答案肯定是可以的,但是仅仅使用无参构造是不可以的,还需要做其他的操作。

一,仅仅使用无参构造为什么不能创建Handler对象?

我们知道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()");
        }
    }

分析:在上面代码中我们看到,如果得到的Looper对象是null就会抛出异常。为什么在子线程中Looper.myLooper方法会返回null呢?原因就是ThreadLocal的特性了。Looper.myLooper方法的原码是从ThreadLocal中得到Looper对象,而在Looper.prepare方法中Looper对象是在UI线程中放入到ThreadLocal中的,所以在子线程中是得不到Looper对象的。在handler中没有looper对象就会抛出异常。

为什么在handler中没有looper对象就会抛出异常?

我们知道handler发送消息最终调用的是MessageQueue的enqueueMessage方法,如果没有Looper对象,肯定得不到MessageQueue对象,在发送消息时一定会抛出nullPointException。所以系统在前面就进行了拦截,只要没有Looper对象就不让代码继续执行。

怎么在子线程中创建Handler对象?

在UI线程之所以可以直接创建创建Handler对象,是因为在Ui线程已经有了Looper对象,所以只要我们在子线程中创建Looper对象后就可以创建handler对象了。使用示例如下:

 new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();//创建looper对象
                Looper.loop();//开启子线程中的消息循环
                Handler handler = new Handler(){//创建Handler对象
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                    }
                };
            }
        }).start();

此时就在子线程中维护了一套消息循环系统。
注意:这套消息循环系统与UI线程中的Handler机制没有任何关系,这儿的消息不能与Ui线程中的消息队列进行通信。但子线程中的消息循环系统也有很大的作用,当需要处理很多消息时可以让消息按序列执行。在图片加载框架Picasso中就在子线程中维护了一套消息循环系统,感兴趣的小伙伴可以自行查看Picasso的原码。

怎么在子线程中得到主线程中handler对象?

其实handler对象没有主线程和子线程之分,有区分的是Looper对象,如果Looper对象是主线程中的,那么handler就是主线程中的。
Handler有下面一个构造方法:

    public Handler(Looper looper) {
        this(looper, null, false);
    }

这个构造方法中接收一个Looper对象。只要我们能到子线程中得到主线程的Looper对象,那么就可以实现在子线程中得到主线程中的handler对象了。
在Looper类中有getMainLooper方法,这个方法的源码是:

    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

这个方法返回的是在主线程中创建过的Looper对象。所以这个方法无论在哪儿调用得到的都是主线程中的Looper对象,此时我们就可以在子线程中创建主线程的handler对象了,代码如下:

new Thread(new Runnable() {
     @Override
     public void run() {
         Handler handler = new Handler(Looper.getMainLooper());//得到UI线程中的handler对象
         handler.post(new Runnable() {
            @Override
             public void run() {
                //这儿写逻辑代码
             }
         });
     }
 }).start();

注:使用这种方法可以轻松的从子线程跳转到UI线程,完全不依赖于Activity或Application。图片加载框架Picasso,网络请求控件Volley都是通过这种方法在工具类内部实现了更新UI。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值