Handler真的难?看完这篇文章你就懂了!

2023-08-31 14:18:33 浏览数 (2)

在Android开发中,Handler是一个非常重要的组件,它可以用来实现线程之间的通信和任务调度。本篇文章将介绍Handler的使用方式和原理,帮助读者更好地理解Android开发中的线程处理。

什么是Handler?

Handler是Android中的一个消息处理器,它可以接收并处理其他线程发来的消息。简单来说,Handler就是一个用来处理消息的工具类,它可以将消息发送给其他线程,也可以接收其他线程发送的消息进行处理。

Handler的使用方式

使用Handler的基本流程为:创建Handler对象 -> 发送消息 -> 处理消息。

在使用 Handler 之前,需要了解一些相关概念:

  • 线程:是独立运行的程序段,执行的代码是一个单独的任务。
  • 消息队列:是一种存储消息的数据结构,支持先进先出的队列操作。
  • Looper:可以让线程不停地从消息队列中取出消息并处理,是线程与消息队列交互的桥梁。
  • Message:是 Android 中处理消息的基本类,可以携带一些数据,用于在 Handler 中进行处理。

创建Handler对象

在使用Handler之前,需要先创建一个Handler对象。创建Handler对象的方式有两种:

在主线程中创建Handler对象:

在主线程中创建Handler对象非常简单,只需要在主线程中创建一个Handler对象即可:

代码语言:javascript复制
Handler handler = new Handler();

在子线程中创建Handler对象:

在子线程中创建Handler对象需要先获取到主线程的Looper对象,然后使用Looper对象来创建Handler对象:

代码语言:javascript复制
Handler handler = new Handler(Looper.getMainLooper());

发送消息

创建Handler对象之后,就可以使用它来发送消息了。发送消息的方式有两种:

使用Handler的post()方法:

使用Handler的post()方法可以将一个Runnable对象发送到Handler所在的消息队列中。Runnable对象中的代码会在Handler所在的线程中执行。

代码语言:javascript复制
handler.post(new Runnable() {
    @Override
    public void run() {
        // 在Handler所在的线程中执行的代码
    }
});

使用Handler的sendMessage()方法:

使用Handler的sendMessage()方法可以将一个Message对象发送到Handler所在的消息队列中。Message对象中可以携带一些数据,用于在Handler中进行处理。

代码语言:javascript复制
Message message = new Message();
message.what = 1;
message.obj = "Hello World!";
handler.sendMessage(message);

除了基本用法,Handler还有一些高级用法,下面列举了几个常用的:

  • 使用HandlerThread创建带有消息队列的线程,避免频繁地创建线程;
  • 使用Message.obtain()来获取Message对象,避免频繁地创建对象;
  • 使用Handler的sendEmptyMessage()方法来发送空消息。

处理消息

当其他线程发送消息到Handler所在的消息队列中时,Handler就会接收到这些消息并进行处理。处理消息的方式有两种:

重写Handler的handleMessage()方法:

重写Handler的handleMessage()方法可以处理其他线程发送的消息。handleMessage()方法中的代码会在Handler所在的线程中执行。

代码语言:javascript复制
Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                String message = (String) msg.obj;
                // 处理消息的代码
                break;
            default:
                break;
        }
    }
};

实现Handler.Callback接口:

实现Handler.Callback接口可以处理其他线程发送的消息。Callback接口中的方法会在Handler所在的线程中执行。

代码语言:javascript复制
Handler.Callback callback = new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                String message = (String) msg.obj;
                // 处理消息的代码
                break;
            default:
                break;
        }
        return true;
    }
};
Handler handler = new Handler(callback);

Handler的原理

在Handler的背后,实际上是使用了消息队列和线程通信的机制。当其他线程发送消息时,消息会被加入到Handler所在的消息队列中。然后,Handler会从消息队列中取出消息进行处理。

消息队列和Looper

消息队列和Looper是 Handler 实现的基础。每个线程都有一个消息队列(Message Queue),消息队列中存储着队列的所有消息(Message)。线程通过一个 Looper 来管理它的消息队列,通过不断地从消息队列中读取消息,实现了线程的消息循环 (Message Loop) 的功能。

代码语言:javascript复制
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;
    for (;;) {
        Message msg = queue.next(); // might block
        // 一旦有消息,就会返回Message对象
        msg.target.dispatchMessage(msg);
    }
}

如上所示,一个线程中的消息无限循环直到队列里没有消息为止(MessageQueue.next())。消息通过 Message.target 属性来找到它想要执行的 Handler,从而被分配到正确的线程中并且得到执行。一旦有消息,就会调用 dispatchMessage(Message) 方法进行分发。

消息分发

消息分发是Handler 的核心部分,在它的内部逻辑中,也是最为关键的部分。

在 Handler 中,消息分发的流程如下:

1.1. 发送消息

由其他线程调用 Handler 的方法向消息队列中发送消息。

代码语言:javascript复制
Handler handler = new Handler() ;
handler.post(new Runnable(){
    @Override
    public void run() {
        // 在其他线程发送消息
    }
});

1.2. 创建 Message 对象

将需要传输的数据封装成 Message 类型的对象,然后将该对象塞入消息队列中。

代码语言:javascript复制
Message msg = new Message();
msg.obj = "消息内容";
handler.sendMessage(msg);

1.3. 将消息加入消息队列

Handler 将消息放入消息队列中。

在 Handler 内部,新构建的消息通过 enqueueMessage() 方法被加入到 MessageQueue 相应的内存块中,并且会在该内存块的标记 next 表示下一个内存块的索引号。

代码语言:javascript复制
public void 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;
    }
    msg.target = this;
    queue.enqueueMessage(msg, uptimeMillis);
}

enqueueMessage() 方法的核心逻辑,就是紧接着找到消息队列中最近的一个时间戳比当前时间小的消息,将新消息插入到这个消息之后。

代码语言:javascript复制
boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        if (msg.isInUse()) {
            throw new IllegalStateException(msg   " This message is already in use.");
        }

        boolean needWake;
        if (mBlocked) {
            // If the queue is blocked, then we don't need to wake
            // any waiters since there can be no waiters.
            msg.markInUse();
            needWake = false;
        } else {
            msg.markInUse();
            needWake = mMessagesForQueue.enqueueMessage(msg, when);
        }

        if (needWake) {
            nativeWake(mPtr);
        }
    }

    return true;
}

1.4. Looper 开启消息循环

Looper 不断轮询内部 MessageQueue 中的消息,获取消息后在 Handler 中进行分发处理。

在 Looper 类中,强制让当前线程创建一个 Looper 对象,并通过调用 QualityLooper 构造函数 create 方法捕获该对象(一般用于构建线程的消息循环)。接下来,通过调用 run 方法被延迟1秒钟来启动上下文中的消息循环。

代码语言:javascript复制
public static void prepare() {
    prepare(true);
}

public 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));
}

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;
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            continue;
        }
        msg.target.dispatchMessage(msg);
    }
}

这个方法是一个无限循环方法,在每个循环中,Looper 都会从自己的消息队列中获取一个消息,如果队列为空,则一直循环等待新的消息到来,直到被调用 quit() 方法,才终止循环。在获取到消息之后,调用 msg.target.dispatchMessage(msg) 进行消息的分发处理。

1.5. 查找目标 Handler

Looper 不断轮询消息队列,获取消息后,注意到 MessageQueue.next() 方法中有这样一行代码:

代码语言:javascript复制
msg.target.dispatchMessage(msg);

1.6. 传递 Message 对象

从消息中获取到 target 属性,它就是当前这个Message对象所属的 Handler,并执行该Handler的 handleMessage(Message) 方法。

dispatchMessage(Message) 的核心代码是判断 Message.target 是否为 null,不为 null 则将消息传递给目标 Handler,如果为 null,则直接抛出异常。

代码语言:javascript复制
void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        // 消息带有回调方法,如果 callback 不为空,那么就直接执行
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            // 尝试将消息抛给 mCallback
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg); // 如果消息中没有 callback,那就执行 handleMessage(msg)
    }
 }

// 处理具体的 Message
public void handleMessage(Message msg) {
    switch (msg.what) {
        // 根据消息类型分发处理
        default:
            break;
    }
}

当 Handler 接收到消息时,它会回调自己的 handleMessage(Message) 方法处理消息。

handleMessage(Message) 方法中,我们可以编写各种不同的逻辑,并对当前情况下的消息进行处理。这通常包括对消息类型的检查以及消息携带的数据的解析和操作。

当我们在 handleMessage(Message) 方法中完成了所有处理后,我们就可以将数据发送回发送消息的线程,或将数据传递给其他线程进行进一步处理。

总结

本篇文章深入探讨了 Handler 的原理,主要包括了消息队列和 Looper 的相关概念,以及消息的发送和处理。除此之外,还讲了当不同线程的消息需要在 Handler 中处理时,需要用到 Looper、MessageQueue 和 Handler 这三个关键组件的协同工作。

0 人点赞