从零到一手搓安卓handler简化版

2024-10-10 11:49:28 浏览数 (4)

前言

在Android开发的多线程应用场景中,Handler机制十分常用。使用Handler 主要有以下原因:

一方面,安卓的主线程(UI 线程)负责处理界面相关的操作,如果一些耗时的任务在主线程中执行,就会导致界面卡顿,影响用户体验。Handler 可以让我们在其他线程中执行耗时任务,然后在合适的时候把结果传递回主线程进行界面更新等操作,确保界面的流畅性。

另一方面,Handler 可以实现线程之间的通信。比如在一个线程中发送消息,在另一个线程中接收并处理这个消息,从而协调不同线程的工作。这样可以让安卓应用在多线程环境下更加高效、稳定地运行。

开始手搓handler

从场景入手

我们先从一个场景入手。在电影《无间道》中,琛哥派傻强去海滩对接货物。我们把这个过程分为三步:

1.傻强告诉琛哥他已经到对接地点

2.琛哥通知傻强开始交易

3.傻强等人开始交易

接下来把这个过程在程序中简单模拟一下:

我们可以把整个事件看成一个进程,接着可以创建两个线程分别代表傻强和琛哥。那么这两个线程该如何交流呢?最简单容易想到的办法就是设置两个全局变量:message1代表傻强传递给琛哥的消息,message2表示琛哥传递给傻强的消息。

按照这个思路,我们就可以编写简单的代码了

代码如下:

代码语言:java复制
package com.demo.myhandler;

public class ActivityThread {
    //傻强->琛哥
    private static String message1;
    //琛哥->傻强
    private static String message2;

    public static void main(String[] args){
        new Thread(){
            @Override
            public void run() {
                message1 = "I have arrived";
                for(;;){
                    if(message2 == "Start trading"){
                        trade();
                    }
                }
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                if (message1 == "I have arrived"){
                    message2 = "Start trading";
                }
            }
        }.start();
    }
    public static void trade(){
        System.out.println("trading....................");
    }
}

运行效果

存在问题

上面的例子已经实现了简单的线程之间的通信,但是存在很大的问题

1.安全性问题

静态变量message1和message2在多线程环境下被读写,没有同步机制保证数据的一致性。可能出现一个线程正在写入变量时,另一个线程正在读取,导致不可预期的结果

2.无法实现线程切换

代码中只是简单地启动了两个线程,但没有任何机制来协调它们的执行顺序和确保在合适的时候进行线程切换。例如,第一个线程在无限循环中不断检查message2的值,这可能会导致该线程一直占用 CPU 资源而不主动让出执行权给其他线程

更好的方案

明确职责划分

我们需要先创建一个Handler类,并创建两个方法sendMessage和handleMessage。sendMessage方法明确负责将消息传递给处理逻辑,它的作用单一,就是发起消息的传递。

而handleMessage专门用于处理接收到的消息。

这样使得每个方法的职责更加清晰,相较于之前的代码可读性和可维护性。

代码语言:java复制
public class Handler {

    public  void sendMessage(String message){
        handleMessage(message);
    }
    public void handleMessage(String message){

    }
}

接着我们就可以在ActivityThread中使用这个类了

代码语言:java复制
public class ActivityThread {
    public static void main(String[] args){
        Handler handler = new Handler(){
            @Override
            public void handleMessage(String message) {
                if (message.equals("Start trade")){
                    trade();
                }
            }
        };
        handler.sendMessage("Start trade");
    }
    public static void trade(){
        System.out.println("trading....................");
    }
}

但是当前只有一个主线程,并没有实现多线程通信。要想实现多线程通信,根据前面的经验,有一个线程要监听是否受到消息,同时还要避免使用全局变量。于是我们设想一个架构

更好的架构

设想一个场景

每个线程产生的消息通过Handler的sendMessage发送,传入Looper这个条传送带上等待处理。所有在传送带上的消息构成了消息队列(messageQueue)。传送带Looper属于监听状态(loop),并通过Handler的handleMessage交给主线程处理

这个设想有6个主体,分别是:主线程,子线程,Message,MessageQueue,Looper,Handler

下面引用carson.ho大佬的一张图进行介绍

确定这个架构的几个主体后,我们开始逐一编写代码

message

先从最简单的message开始

Message类中应该有消息字符串和构造方法

但是这样还存在不足,就是无法将消息(Message)和处理者(Handler)关联在一起。

一个多线程环境或者事件驱动的系统中,一个消息可能需要特定的处理逻辑,而将Handler与Message关联起来可以确保消息被正确地处理。于是我们再补充一个属性Handler target

所以Message类最终代码如下

代码语言:java复制
package com.demo.myhandler;

public class Message {
    String obj;
    Handler target;
    public Message(){}
    public Message(String obj){
        this.obj = obj;
    }

    @Override
    public String toString() {
        return obj.toString();
    }
}

messageQueue

代码语言:java复制
    BlockingQueue<Message>  queue = new ArrayBlockingQueue<>(500);

先创建了一个容量为 500 的阻塞队列(BlockingQueue),用于存储Message类型的对象

再创建负责将一个Message对象放入阻塞队列queue中enqueueMessage方法和负责从阻塞队列中取出一个Message对象的next方法

所以MessageQueue类最终代码如下:

代码语言:java复制
package com.demo.myhandler;

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.BlockingQueue;

public class MessageQueue {
    BlockingQueue<Message>  queue = new ArrayBlockingQueue<>(500);
 
    public void enqueueMessage(Message message){
        try {
            queue.put(message);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    
    public Message next(){
        Message msg = null;

        try {
            msg = queue.take();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return  msg;
    }
}

Looper

接下来编写架构中的传送带了

先编写Looper类的属性

静态的 ThreadLocal 对象 sThreadLocal,用于存储和管理与线程相关的 Looper 对象。

messageQueue使得Looper实例都有自己的消息队列

代码语言:java复制
static ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
MessageQueue messageQueue;

这样可以通过 sThreadLocal 可以将 Looper 对象与特定的线程关联起来

接着编写一个myLooper方法通过访问静态的ThreadLocal变量sThreadLocal并调用其get()方法获取当前线程的Looper对象

代码语言:java复制
public static Looper myLooper(){
    return sThreadLocal.get();
}

然后是Looper类的构造方法,在此初始化成员变量messageQueue

代码语言:java复制
private Looper(){
    messageQueue = new MessageQueue();
}

但是这样还不行,ThreadLocal还没有Looper实例,这个类还缺乏初始化工作,于是我们编写静态方法prepare在当前线程中进行Looper的初始化。如果当前线程没有Looper对象,它会创建一个新的Looper实例,并通过sThreadLocal.set(new Looper())将其存储在当前线程的ThreadLocal变量中

代码语言:java复制
    public static void prepare(){
        if(sThreadLocal.get()!=null){
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }

最后,我们要编写looper方法,实现消息循环机制,不断从消息队列中取出消息并交给对应的处理者进行处理。

代码语言:java复制
public static void looper(){
    final Looper mLooper = myLooper();
    final MessageQueue queue = mLooper.messageQueue;
    for(;;){
        Message msg = queue.next();
        msg.target.handleMessage(msg);
    }
}

Looper类最终代码如下

代码语言:java复制
package com.demo.myhandler;

public class Looper {
    
    static ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
    MessageQueue messageQueue;
    
    private Looper(){
        messageQueue = new MessageQueue();
    }
    public static Looper myLooper(){
        return sThreadLocal.get();
    }
    
    public static void prepare(){
        if(sThreadLocal.get()!=null){
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
    
    public static void looper(){
        final Looper mLooper = myLooper();
        final MessageQueue queue = mLooper.messageQueue;
        for(;;){
            Message msg = queue.next();
            msg.target.handleMessage(msg);
        }
    }
}

Handler

我们首先使用成员变量mLooper存储与当前Handler关联的Looper实例

再通过构造方法调用Looper.myLooper()获取当前线程的Looper实例,并将其赋值给mLooper。这确保了每个Handler对象都与创建它的线程的消息循环相关联

代码语言:java复制
public Handler(){
    mLooper = Looper.myLooper();
}

最后就是发送和处理消息的逻辑

代码语言:java复制
public  void sendMessage(Message message){
    enqueueMessage(message);
}
private void enqueueMessage(Message message){
    message.target = this;
    mLooper.messageQueue.enqueueMessage(message);
}
public void handleMessage(Message message){
}

Handler类最终代码如下:

代码语言:java复制
package com.demo.myhandler;

public class Handler {
    Looper mLooper;
    public Handler(){
        mLooper = Looper.myLooper();
    }
    public  void sendMessage(Message message){
        enqueueMessage(message);
    }
    private void enqueueMessage(Message message){
        message.target = this;
        mLooper.messageQueue.enqueueMessage(message);
    }
    public void handleMessage(Message message){

    }
}

ActivityThread

最后我们编写ActivityThread实现对上面架构的使用

代码语言:java复制
package com.demo.myhandler;

public class ActivityThread {
    public static void main(String[] args){
        Looper.prepare();

        final Handler handler1 = new Handler(){
            @Override
            public void handleMessage(Message message) {
                if (message.obj.equals("Start trading")){
                    trade();
                }
            }
        };

        new Thread(){
            @Override
            public void run() {
                handler1.sendMessage(new Message("Start trading"));
            }
        }.start();
        
        Looper.looper();
    }
    public static void trade(){
        System.out.println("Thread: " Thread.currentThread().getName() " trading....................");
    }
}

运行结果如下:

总结

这样子就实现了安卓handler组件的简化版本。

实际Android 中的Handler功能更加丰富和复杂,支持多种消息类型、延迟消息发送、处理异步任务的回调等,同时它也经过了大量的优化,以确保在不同的设备和负载情况下都能高效地运行。想要深入探索可以进行源码分析

0 人点赞