Talk about Android's message mechanism (Handler, Looper, Message...)

Talk about Android's message mechanism (Handler, Looper, Message...)

A very important point in Android development is to understand its message mechanism. Android's message mechanism involves a lot of concepts, such as HandlerThread, Handler, Looper, MessageQueue, Message, and MessagePool. Beginners are often dumbfounded when they see this, so many concepts. This article tries to make everyone understand the message mechanism of Android in a relatively easy-to-understand way. The reader must have some concept of java thread, this is the most basic requirement.

Why use a message mechanism

We can think of a thread as a "robot" that receives instructions and can do its own work. There is a very important thread in Android, it is responsible for interacting with the user, we call it the UI thread. The UI thread is responsible for receiving the user's operations on the interface and feeding back the processing results to the user in the interface. There is a problem here. If the UI thread has been processed for a long time and there is no feedback, the user will always feedback the processing result in the pending UI thread. The user can neither do anything else nor know when it will end, which is a very bad experience for the user.

To solve this problem, it is easy to think of a solution that we hire a lot of "robots". The UI thread responsible for dealing with the user is only responsible for receiving the user's operation instructions and feeding back the processing results to the user. Time-consuming processing is left to other "robots". So how do we construct other "robots"?

Construct a "robot"

HandlerThread is the "robot" we want to make. HandlerThread is a thread because it directly inherits from the Thread class:

public classHandlerThreadextendsThread

If you inherit from Thread, you must implement the run() method. I won't post the source code here. Some people will get a big head when they look at the source code. Let me tell you what the thread of HandlerThread mainly does:

  1. Generate a Looper instance, the Looper instance will generate another MessageQueue instance, and put the Looper instance into the thread local variable (ThreadLocal). If you have no concept of ThreadLoacl, you can understand it like this: ThreadLocal is a variable, which is bound to the thread of HandlerThread, and other threads cannot access this variable. Since no other threads can access it, ThreadLocal is thread-safe.
  2. Call the onLooperPrepared() method, which is implemented in the subclass. Usually we use this method to generate a Handler instance. We will talk about why the Handler is instantiated here later.
  3. Call Looper to start looping through messages.

Come here, let's see what this "robot" we made can do? This "robot" has a Looper, and there is a MessageQueue in the Looper. Looper's job is to always stare at the MessageQueue queue to see if there are messages to process. So who handles the message?

Handling messages with Handler

We first know a concept, each Message has its own Handler (except for the post method, which will be discussed later), and the relationship between Message and Handler will be discussed later when it comes to the construction of Message. As I said just now, Handler instances are generally generated in the onLooperPrepared() method. Because the onLooperPrepared() method is called before the loop gets the message for processing. If the Handler is instantiated after the onLooperPrepared() method, the message obtained by the Looper cannot be processed, which is embarrassing.

Let's talk about the instantiation process of Handler here, and don't paste the source code:

  1. First of all, Handler is just an ordinary class that does not inherit any classes and interfaces:

     public classHandler
    

    The message handling overrides the handleMessage method:

     public voidhandleMessage(Message msg){}
    
  2. It is very important to obtain the Looper bound to the current thread through ThreadLocal. The Handler constructed under which thread holds the Looper of the current thread . In this way, the interaction between the UI thread and other threads can be achieved.

  3. 通过Looper获得MessageQueue

到这里我们可以看出来,Handler持有MessageQueue,它能和MessageQueue进行沟通。消息就是通过Handler来进行入队列操作,将出队列的消息交给handleMessage处理。

构造一个消息

我们现在有很多图片需要下载,每个下载任务就是一个消息,我们要将下载任务的消息构造出来并放到MessageQueue中。首先我们需要构造Message。Message的构造不是简单的new,可能出于对移动设备内存使用的限制,Message的构造使用的是对象池的技术。下面这个图很好的解释了对象池的概念:  总的来说就是,Message对象不能无限创造,最大50个

private static final int MAX_POOL_SIZE = 50

Message的构造有两种方式:

  1. 最容易理解的方式,但是也是比较麻烦的方式:

     Message msg = Message.obtain();//从对象池中获取一个消息
     msg.what = 0;//指定一个消息类型
     msg.obj = obj;//消息中绑定的对象
     msg.setTarget(handler);//这个消息由哪个handler来处理
    
  2. 通过Handler构造消息

     Message msg = handler.obtainMessage(what, obj);
    

    这种通过handler构造的消息,消息的handler就是当前handler。

最后是将Message放入队列中,上面说过handler持有MessageQueue,那么将Hanlder放入队列的操作当然是由Hanlder来完成:

handler.sendMessage(msg)

handler既然能生成消息,也能将消息放到队列中,那么是不是可以一步就搞定呢,答案当然是可以:

handler.obtainMessage(what, obj).sendToTarget();

handler看上去是一个粘合剂,通过Handler生成消息并放入队列,Looper将取出的消息再交给Handler处理。

通过Handler的post处理消息

public final boolean post(Runnable r)

Handler有一个post方法也经常用到,它有啥特别的呢?看上去它传入了一个Runnable,好像是用线程处理消息的意思,实际上,并不是。

post方法也会生成一个Message,同时将Message放入队列中,生成的Message并没有what、obj、handler等信息,而是会携带一个callback,这个callback就是post的参数r。Looper在获得消息后会根据是否包含callback来决定采用哪种方式处理消息,这里有必要贴出源码,这段源码也比较好理解:

public voiddispatchMessage(Message msg){
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static voidhandleCallback(Message message){
    message.callback.run();
}

可以看到handleCallback(msg)并不是调用的线程的start(),而是调用的run()。也就是说这个Runnable不是启动异步线程去执行的。那么我们什么情况下使用post呢?如果无法在实例化Handler时明确handleMessage()方法的处理逻辑,我们通常先不定义handleMessage(),而是在其他地方动态的加入处理逻辑。

UI线程和HandlerThread的交互

这里我们先将自定义的HandlerThread线程称为myThread,其对应的Handler为myHandler。UI线程称为uiThread,其对应的Handler为uiHandler。

myThread线程获取消息后会调用myHandler.handleMessage()处理消息,这里我们认为这个任务是下载图片,图片下载完成后,需要通知uiThread将图片显示到界面上。所有我们通过uiHandler将更新界面的消息发送到队列中。这种情况下uiHandler事先并不知道要更新界面的哪个地方,无法实现在实例化uiHandler时定义handleMessage(),因此这里采用post的方式,也就是uiHandler.post(Runnable r)。在r里我们实现更新ui界面的操作。

总结一下

  1. 一个HandlerThread实例拥有一个Looper实例,一个Looper实例拥有一个MessageQueue实例;
  2. 在哪个线程实例化Handler,这个Handler就拥有哪个线程的MessageQueue;
  3. Message的实例化是通过对象池实现的,最大50个;
  4. Handler是一个粘合剂,生成Message并将消息发送到线程的MessageQueue中;
  5. Handler处理消息有两种方式,handleMessage()和post(Runnable r)
  6. 消息是单线程一个一个处理的,通过队列实现异步

彩蛋

看完这篇文章的同学,你们是否明白了?说实话我也不敢保证你能完全看明白,虽然我尽量用通俗的语言来解释。思考一下这其中有几个问题:

  1. 消息都是单线程处理,一个一个来,如果界面中有很多图片需要下载,也只能一个一个的下载。
  2. 使用起来过于繁琐,真的很繁琐……

还有别的简单方式吗?做过web开发的都知道ajax,异步获取数据返回后更新ui,其实利用Android消息机制异步更新UI的方式和ajax挺像的,是否能把Android的消息机制包装成类似ajax的方式呢?我写了个demo,代码开源在AndAjax,有兴趣的同学可以看看源代码,其实就一个类,依赖okhttp。代码的风格是这样的:

 

$.ajax("url", new AndAjax.Callback<String>() {
    @Override
    public voidonSuccess(String json){

    }

    @Override
    public voidonError(int state, Exception ex){

    }
});

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326972689&siteId=291194637