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

什么是Handler?

Handler是Android中的一个音讯处理器,它可以接纳并处理其他线程发来的音讯。简略来说,Handler便是一个用来处理音讯的东西类,它可以将音讯发送给其他线程,也可以接纳其他线程发送的音讯进行处理。

Handler的运用办法

运用Handler的根本流程为:创立Handler方针 -> 发送音讯 -> 处理音讯。

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

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

创立Handler方针

在运用Handler之前,需求先创立一个Handler方针。创立Handler方针的办法有两种:

  • 在主线程中创立Handler方针:

    在主线程中创立Handler方针非常简略,只需求在主线程中创立一个Handler方针即可:

    Handler handler = new Handler();
    
  • 在子线程中创立Handler方针:

    在子线程中创立Handler方针需求先获取到主线程的Looper方针,然后运用Looper方针来创立Handler方针:

    Handler handler = new Handler(Looper.getMainLooper());
    

发送音讯

创立Handler方针之后,就可以运用它来发送音讯了。发送音讯的办法有两种:

  • 运用Handler的post()办法:

    运用Handler的post()办法可以将一个Runnable方针发送到Handler地点的音讯行列中。Runnable方针中的代码会在Handler地点的线程中履行。

    handler.post(new Runnable() {
        @Override
        public void run() {
            // 在Handler地点的线程中履行的代码
        }
    });
    
  • 运用Handler的sendMessage()办法:

    运用Handler的sendMessage()办法可以将一个Message方针发送到Handler地点的音讯行列中。Message方针中可以带着一些数据,用于在Handler中进行处理。

    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地点的线程中履行。

    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地点的线程中履行。

    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) 的功用。

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 的办法向音讯行列中发送音讯。

Handler handler = new Handler() ;
handler.post(new Runnable(){
    @Override
    public void run() {
        // 在其他线程发送音讯
    }
});

1.2. 创立 Message 方针

将需求传输的数据封装成 Message 类型的方针,然后将该方针塞入音讯行列中。

Message msg = new Message();
msg.obj = "音讯内容";
handler.sendMessage(msg);

1.3. 将音讯参加音讯行列

Handler 将音讯放入音讯行列中。

在 Handler 内部,新构建的音讯经过 enqueueMessage() 办法被参加到 MessageQueue 相应的内存块中,而且会在该内存块的标记 next 表明下一个内存块的索引号。

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() 办法的核心逻辑,便是紧接着找到音讯行列中最近的一个时刻戳比当时时刻小的音讯,将新音讯插入到这个音讯之后。

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秒钟来发动上下文中的音讯循环。

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() 办法中有这样一行代码:

msg.target.dispatchMessage(msg);

1.6. 传递 Message 方针

从音讯中获取到 target 特点,它便是当时这个Message方针所属的 Handler,并履行该Handler的 handleMessage(Message) 办法。

dispatchMessage(Message) 的核心代码是判断 Message.target 是否为 null,不为 null 则将音讯传递给方针 Handler,假如为 null,则直接抛出异常。

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 这三个关键组件的协同作业。

推荐

android_startup: 提供一种在运用发动时可以愈加简略、高效的办法来初始化组件,优化发动速度。不仅支撑Jetpack App Startup的悉数功用,还提供额外的同步与异步等待、线程控制与多进程支撑等功用。

AwesomeGithub: 根据Github的客户端,纯操练项目,支撑组件化开发,支撑账户密码与认证登陆。运用Kotlin语言进行开发,项目架构是根据JetPack&DataBinding的MVVM;项目中运用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技能。

flutter_github: 根据Flutter的跨渠道版别Github客户端,与AwesomeGithub相对应。

android-api-analysis: 结合具体的Demo来全面解析Android相关的知识点, 帮助读者可以更快的掌握与了解所论述的关键。

daily_algorithm: 每日一算法,由浅入深,欢迎参加一起共勉。