Master these basic questions about Handler, so your interview more with less!

Master these basic questions about Handler, so your interview more with less!

Foreword

Handler is a commonplace problem, I believe that almost all of the Android developers will use Handler, then Handler on what is there to say? Handler if only to use it, really nothing to talk about, but it is a Handler almost all the interviewer will ask questions, ask different requirements of depth is not the same, and today I will take you to learn something about what you have to Handler knowledge.

Handler message mechanism

First, there are four objects we have to look at Handler , Looper , ThreadLocal as well as of the MessageQueue .

Handler

We need to understand Handler message mechanism is used to doing? Handler is the other thread to switch to the thread where the Handler , note that this is not the UI thread. Although we use the most Handler scenes, we are doing a bit in the sub-thread time-consuming operations (IO or database), when we finished after the child thread execution may need to update the UI, this time we have to deal with Handler (sendMessage () or POST ()) switch to put the child thread the UI thread. If we say that we now have such a demand, thread A send a message to the thread B (thread A, thread B is not the main thread), this time we Handler can still do, only need to thread B, create good Handler can the (Handler on how to create a child thread I will explain in detail below). So, Handler not to switch to another thread the main thread (UI thread), but switched to it in the thread, be sure to clarify this point.

Looper

Handler消息机制里面最核心的类,消息轮询。Handler要想切换线程(或者说发送消息),必须要先获得当前线程的Looper,然后调用Looper.loop()方法把MessageQueue里面的message轮询出来"交"给Handler来处理,至于如何“交”给Handler的,下面我会通过源码带大家分析。

ThreadLocal

ThreadLocal是Looper内部的一个,它虽然前面有个“Thread”,但其实它并不是线程,它相当于一个线程内部的存储类。刚才在讲Looper的时候我们说到,“Handler要想切换线程(或者说发送消息),必须要先获得当前线程的Looper”,那如何获得当前线程的Looper呢?正是通过ThreadLocal,它可以在不同线程之间互不干扰地存储数据。ThreadLocal里面有一个内部静态类对象ThreadLocalMap,通过其set()和get()方法对数据对象进行保存和读取。

MessageQueue

MessageQueue——消息队列,虽然它叫消息队列,但实际上的结构并不是一个队列,而是一种单链表的数据结构,只是使用列队的形式对数据进场做添加或者移除。MessageQueue是在Looper被创建的时候由Looper生成的。同一线程下,Handler发送的所有message都会被加入到MessageQueue里面,当Message被loop()以后,就会从消息队列中移除。Message我没有单独拿出来,因为确实比较简单,就是消息本身,它可以携带两个int型参数,如果要传比较复杂的参数就用obj属性,what属性用来区分是哪个Handler发送的信息。

小结一下

Handler是把其他线程切换到它所在的线程,使用Handler发消息之前要先创建Looper对象,创建和读取Looper都需要使用ThreadLocal,它可以在不同线程之间互不干扰地保存数据。在创建Looper对象的同时也把MessageQueue创建好了,Handler所发的message会被添加到MessageQueue对象里面,Looper.loop()方法以后会把MessageQueue上的Message轮询出来。连接这几个对象的还有一条很重要的线,那就是线程,记住,以上的过程都要保证Handler、Looper、MessageQueue在同一线程,这样,才能保证Handler的消息机制被正确使用。

子线程创建Handler

注意:我们这里是为了让大家更好地学习Handler,所以要在子线程创建Handler,现实使用场景,很少会在子线程创建Handler

在主线程(UI线程)创建Handler,我相信所有人都会

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mainHandler = new Handler();
        mainHandler.post(new Runnable() {
            @Override
            public void run() {
                Log.e("qige_test", "thread_name=" + Thread.currentThread().getName());
            }
        });
    }

我们先按照主线程的方式在子线程创建一个Handler试一下,看看会有什么样的结果

 new Thread(){
            @Override
            public void run() {
                childHandler=new Handler();
                childHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e("qige_test","thread_name="+Thread.currentThread().getName());
                    }
                });

            }
        }.start();

Master these basic questions about Handler, so your interview more with less!

没错,如图所示还没有创建Looper,那么如何创建Looper呢?图中有句话已经给出了答案——Looper.prepare(),同时为了让我们发送的消息被轮询到,还必须要调用Looper.loop(); 所以在完整的在子线程创建Handler的代码如下:

 new Thread(){
            @Override
            public void run() {
                //创建Looper,同时创建MessageQueue
                Looper.prepare();

                childHandler=new Handler();
                childHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e("qige_test","thread_name="+Thread.currentThread().getName());
                    }
                });

                //轮询
                Looper.loop();

            }
        }.start();

这样就可以在子线程里面创建Handler了,看到这里,你可能会问,为什么我在主线程创建Handler的时候,没有调用Looper.prepare()和Looper.loop()?这个问题我们先放一下,我们先从源码角度把Handler消息机制分析一下。

从源码角度分析

先看一下创建Looper和MessageQueue的方法Looper.prepare()

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

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

这里就出现了我刚才说的ThreadLocal对象。Android规定每个线程有且仅有一个Looper,所以为了防止不同的线程之间的Looper相互影响,使用ThreadLocal对象来存储Looper。

再看一眼new Looper()的源码

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

MessageQueue也出来了,创建Looper的同时创建MessageQueue。

下面我们看Handler.post()或者是Handler.sendMessage()他们本质是一样的,把消息加入到消息队列当中

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

post()或者sendMessagexxx()最终都会调用sendMessageAtTime(Message msg, long uptimeMillis)方法,我们直接看这个方法的源码

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        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);
    }

都很简单,最后一步enqueueMessage(queue, msg, uptimeMillis);是把消息加入队列,咱们再点开看一下

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //把当前的Handler赋值给msg.target
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

关键要看的地方是 msg.target = this刚才讲Message的时候没有提,Message的target属性就是一个Handler对象,这里直接把当前的Handler对象赋值过去了。最后一行:queue.enqueueMessage(msg, uptimeMillis)是把message加入到消息队列里面,具体的实现我抓不到了,知道这句话是把message加入到MessageQueue里面就行。
下面分析* 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.");
        }
        **标注1**
        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.
       ........................
       ......................
       **标注2**
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ....................................
            ...................................
            try {
               **标注3**
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
           ........................................
           ........................................
             **标注4**
            msg.recycleUnchecked();
        }
    }

看源码的时候注意看一下标注,一个个地来讲;

  • 标注1:保证了Handler、Looper和MessageQueue在同一线程
  • 标注2:没有看错,就是一个无线死循环,Looper会不停地对MessageQueue进行轮询。这时候,你可能会问,这种无限死循环会不会很浪费资源?其实并不会,因为当没有message被添加到队列的时候,程序会进入阻塞状态,对资源的消耗是很小的。
  • 标注3:还记得刚才讲地msg.target吧,这里 msg.target.dispatchMessage(msg);就是handler.dispatchMessage(msg),直接去看Handler的dispatchMessage(msg)方法
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

这里面msg.callback是个Runnable对象,也就是当我们使用handler.post(Runnable r)的方法的话,这时候就直接去调用Runnable对象里面的东西了,如果使用的是handler.sendMessagexxx(),就是去调用我们重写的handleMessage(msg)方法了。
如果调用post()方法发送消息

mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        //这里会被调用
                        Log.e("qige_test","thread_name="+Thread.currentThread().getName());
                    }
                });

如果调用sendMessagexxx()

 mainHandler.sendEmptyMessage(0);
        mainHandler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                //这里会被调用
                Log.e("qige_test","what="+msg.what);
            }
        };

我们再回到上一级源码

  • 标注4, msg.recycleUnchecked();其实这里就是msg完成了它的使命,被释放了以后又放回缓存池里面去了。

以上基本就是一个完整的Handler消息机制的过程,我再带大家好好回顾一下:

1.Looper.prepare();//这一步创建了Looper和MessageQueue
2.handler.sendMessagexxxx(); // 把message添加到MessageQueue上
3.Looper.loop();//轮询消息
4.handler.dispatchMessage(msg);//处理消息

关于在主线程创建Handler的疑问

好了,到了该解决历史遗留问题的时候了,为什么我们在主线程创建handler不需要调用Looper.prepare()Looper.loop()呢?
这是因为,主线程已经给我们创建好了,在哪里创建好的呢?
在Java里面,有一个程序的主入口,就是静态main方法

public class Test {
    //程序入口
     public static void main(String... args){

     }

在Android里面呢,同样有这么一个main方法入口,它在ActivityThread类中。在我们App启动过程中,会调用到ActivityTread的main()方法(由于篇幅问题,该过程没有办法详细讲,后期会推文章单独说一下),下面我们直接看ActivityTread的main()方法的源码
为了方便理解,还是只看关键部分代码

public static void main(String[] args) {
        ....

        //创建Looper和MessageQueue对象,用于处理主线程的消息
        Looper.prepareMainLooper();

        //创建ActivityThread对象
        ActivityThread thread = new ActivityThread(); 

        //建立Binder通道 (创建新线程)
        thread.attach(false);

        //消息轮询
        Looper.loop();

    }

看到这里,我想你应该明白了为啥在主线程里创建Handler不需要调用Looper.prepare()和Looper.loop()方法了吧,因为在App启动的时候,ActivityThread已经给我们创建好了。不过,需要注意的是,我刚才在讲Looper.loop()源码的时候说过这里面会有一个无限死循环,那么程序运行到这里岂不是要永远卡在这了呢,那我们的Activity、Service都是咋执行的呢? 看关键的源码这一句thread.attach(false),注释上其实解释的很清楚,通过Binder创建了新的线程,在这个新的线程里面运行了Activity、Service等组件,而之所以Android为什么采用这种方式,让我们留在以后再说,你只需要知道,Looper.loop()不会造成卡顿,主线程已经给我们创建好了Looper和MessageQueue对象就可以了。

Why write this article

Opening I said, Handler almost everyone will use, but will only use is not enough to know these better know why. Many interviewers are willing to ask questions related to the Handler, a good read this article, it will make you more effective in the interview.

Some things you should not only understand, but also to be able to express it very well, allowing the interviewer recognized your understanding, for example Handler mechanism, this interview will be asked the questions. Some obscure point, perhaps live only in the interview among the practical work you did not use it, but you know what it is.

End of the last article I prepared a set of fine architects Android tutorials for everyone, guarantee that you learn to ensure wage rises to a higher level. (The following is a small part, for more advanced architecture succinctly and other video material can add me wx: X1524478394 free access )

What is today to share some exclusive dry goods:

①Android development of core knowledge notes

Master these basic questions about Handler, so your interview more with less!

② benchmarking "Ali P7" 40W + annual salary of senior enterprise architect grow Learning Roadmap

Master these basic questions about Handler, so your interview more with less!

③ interview Boutique Collection Summary

Master these basic questions about Handler, so your interview more with less!

④ full set of advanced system architecture video

Android learn more succinctly video after you have received is even more powerful! Enter BATJ manufacturers, etc. (preparing for)! Now say the Internet winter, in fact, nothing more than you get on the wrong bus, and wear less (skills), and if you get on to the car, their technology is robust enough to replace the cost of large companies, how could be laid off, have Curd is out of the business end of it! Now flooding the market junior programmer, set of tutorials for the Android development engineers 1--6 years of staff, is in a bottleneck, after years want to break their own salary increase, advanced Android senior architect for you is themselves, and receive it as soon as possible!

[HD brain diagram above], and [supporting] PDF technology architecture can add me wx: X1524478394 free access

Guess you like

Origin blog.51cto.com/14332859/2458986