Android多线程

本文总结一下Android使用到的多线程,分别介绍Java原生线程,Java线程池,Handler

1 java原生线程

最直接的就是使用java原生线程,继承Thread类

	// 步骤1:创建线程类 (继承自Thread类)
   class MyThread extends Thread{

	// 步骤2:复写run(),内容 = 定义线程行为
    @Override
    public void run(){
    ... // 定义的线程行为
    }
}

	// 步骤3:创建线程对象,即 实例化线程类
  MyThread mt=new MyThread(“线程名称”);

	// 步骤4:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止
	// 此处采用 start()开启线程
  mt.start();

很多情况下,一种更加方便的方法去创建线程:匿名类

// 步骤1:采用匿名类,直接 创建 线程类的实例
 new Thread("线程名称") {
                 // 步骤2:复写run(),内容 = 定义线程行为
                    @Override
                    public void run() {       
                  // 步骤3:通过 线程对象 控制线程的状态,如 运行、睡眠、挂起  / 停止   
                      }.start();

2 java线程池

线程池的思路 就是我们可以新建一些线程,等待任务到来,可以直接把任务分配给线程,开始工作。这种机制可以避免因频繁创建和销毁线程而带来的性能开销,同时也能控制同时运行的线程数量,从而提高系统的性能和资源利用率。线程池的主要组成部分包括工作线程、任务队列、线程管理器等。线程池的设计有助于优化多线程程序的性能和资源利用,同时简化了线程的管理和复用的复杂性。

2.1 ThreadPoolExecutor

在这里插入图片描述
工作流程:
在这里插入图片描述
在这里插入图片描述

public class Test05 {
    public static void main(String[] args) {
        /*int corePollSize 核心线程数的个数  2
         * int maximumPoolSize 最大线程数量 6
         * long keepAliveTime 非核心线程允许空闲的时间 10
         * TimeUnit.SECONDS  时间单位
         * ArrayBlockingQueue 堵塞中的队列 5*/
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(5);
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, 6, 10, TimeUnit.SECONDS, blockingQueue);
        for (int i = 0; i < 10; i++) {
            poolExecutor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "==============");
            });
        }
    }
}

解释一下构造方法中涉及到的参数:

corePoolSize(必需): 核心线程数。即池中一直保持存活的线程数,即使这些线程处于空闲。但是将allowCoreThreadTimeOut参数设置为true后,核心线程处于空闲一段时间以上,也会被回收。
maximumPoolSize(必需): 池中允许的最大线程数。当核心线程全部繁忙且任务队列打满之后,线程池会临时追加线程,直到总线程数达到maximumPoolSize这个上限。
keepAliveTime(必需): 线程空闲超时时间。当非核心线程处于空闲状态的时间超过这个时间后,该线程将被回收。将allowCoreThreadTimeOut参数设置为true后,核心线程也会被回收。
unit(必需): keepAliveTime参数的时间单位。有:TimeUnit.DAYS(天)、TimeUnit.HOURS(小时)、TimeUnit.MINUTES(分钟)、TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)、TimeUnit.MICROSECONDS(微秒)、TimeUnit.NANOSECONDS(纳秒)
workQueue(必需): 任务队列,采用阻塞队列实现。当核心线程全部繁忙时,后续由execute方法提交的Runnable将存放在任务队列中,等待被线程处理。
threadFactory(可选): 线程工厂。指定线程池创建线程的方式。
handler(可选): 拒绝策略。当线程池中线程数达到maximumPoolSize且workQueue打满时,后续提交的任务将被拒绝,handler可以指定用什么方式拒绝任务。</font>

2.1.1 任务队列

使用ThreadPoolExecutor需要指定一个实现了BlockingQueue接口的任务等待队列。在ThreadPoolExecutor线程池的API文档中,一共推荐了三种等待队列,它们是:SynchronousQueue、LinkedBlockingQueue和ArrayBlockingQueue;

SynchronousQueue: 同步队列。这是一个内部没有任何容量的阻塞队列,任何一次插入操作的元素都要等待相对的删除/读取操作,否则进行插入操作的线程就要一直等待,反之亦然。

LinkedBlockingQueue: 无界队列(严格来说并非无界,上限是Integer.MAX_VALUE),基于链表结构。使用无界队列后,当核心线程都繁忙时,后续任务可以无限加入队列,因此线程池中线程数不会超过核心线程数。这种队列可以提高线程池吞吐量,但代价是牺牲内存空间,甚至会导致内存溢出。另外,使用它时可以指定容量,这样它也就是一种有界队列了。

ArrayBlockingQueue: 有界队列,基于数组实现。在线程池初始化时,指定队列的容量,后续无法再调整。这种有界队列有利于防止资源耗尽,但可能更难调整和控制。

另外,Java还提供了另外4种队列:
PriorityBlockingQueue: 支持优先级排序的无界阻塞队列。存放在PriorityBlockingQueue中的元素必须实现Comparable接口,这样才能通过实现compareTo()方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue不会保证优先级一样的元素的排序,也不保证当前队列中除了优先级最高的元素以外的元素,随时处于正确排序的位置。

DelayQueue: 延迟队列。基于二叉堆实现,同时具备:无界队列、阻塞队列、优先队列的特征。DelayQueue延迟队列中存放的对象,必须是实现Delayed接口的类对象。通过执行时延从队列中提取任务,时间没到任务取不出来。更多内容请见DelayQueue。

LinkedBlockingDeque: 双端队列。基于链表实现,既可以从尾部插入/取出元素,还可以从头部插入元素/取出元素。

LinkedTransferQueue: 由链表结构组成的无界阻塞队列。这个队列比较特别的时,采用一种预占模式,意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,那就生成一个节点(节点元素为null)入队,然后消费者线程被等待在这个节点上,后面生产者线程入队时发现有一个元素为null的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素。

2.1.2 拒绝策略

线程池有一个重要的机制:拒绝策略。当线程池workQueue已满且无法再创建新线程池时,就要拒绝后续任务了。拒绝策略需要实现RejectedExecutionHandler接口,不过Executors框架已经为我们实现了4种拒绝策略:

AbortPolicy(默认): 丢弃任务并抛出RejectedExecutionException异常。
CallerRunsPolicy: 直接运行这个任务的run方法,但并非是由线程池的线程处理,而是交由任务的调用线程处理。
DiscardPolicy: 直接丢弃任务,不抛出任何异常。
DiscardOldestPolicy: 将当前处于等待队列列头的等待任务强行取出,然后再试图将当前被拒绝的任务提交到线程池执行。

2.1.3 线程工厂指定创建线程的方式,这个参数不是必选项,Executors类已经为我们非常贴心地提供了一个默认的线程工厂:

/**
 * The default thread factory
 */
static class DefaultThreadFactory implements ThreadFactory {
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    private final ThreadGroup group;
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;

    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

2.1.4 线程池状态

线程池有5种状态:

volatile int runState;
// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

runState表示当前线程池的状态,它是一个 volatile 变量用来保证线程之间的可见性。
下面的几个static final变量表示runState可能的几个取值,有以下几个状态:

RUNNING: 当创建线程池后,初始时,线程池处于RUNNING状态;
SHUTDOWN: 如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
STOP: 如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
TERMINATED: 当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

使用线程池

下面看一个实例:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class MyTest {
 public static void main(String[] args) {
  // 创建线程池
  ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 5, 5, TimeUnit.SECONDS,
    new ArrayBlockingQueue<Runnable>(5));
  // 向线程池提交任务
  for (int i = 0; i < threadPool.getCorePoolSize(); i++) {
   threadPool.execute(new Runnable() {
    @Override
    public void run() {
     for (int x = 0; x < 2; x++) {
      System.out.println(Thread.currentThread().getName() + ":" + x);
      try {
       Thread.sleep(2000);
      } catch (InterruptedException e) {
       e.printStackTrace();
      }
     }
    }
   });
  }

  // 关闭线程池
  threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
  // threadPool.shutdownNow(); // 设置线程池的状态为STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,该方法要慎用,容易造成不可控的后果
 }
}

运行结果:

pool-1-thread-2:0
pool-1-thread-1:0
pool-1-thread-3:0
pool-1-thread-2:1
pool-1-thread-3:1
pool-1-thread-1:1

3 Handler

终于来到了handler,最耳熟能详的一个背景就是:Android UI的更新只能在主线程。而当子线程想要更新UI时,就需要依赖handler,给主线程发送一个消息,让主线程更新UI。

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/cfc5236fd3ab4886b1b598615a48fe79.png
在这里插入图片描述

3.1 使用方式

	 /** 
	  * 方式1:新建Handler子类(内部类)
	  */
    // 步骤1:自定义Handler子类(继承Handler类) & 复写handleMessage()方法
    class mHandler extends Handler {

        // 通过复写handlerMessage() 从而确定更新UI的操作
        @Override
        public void handleMessage(Message msg) {
         ...// 需执行的UI操作
            
        }
    }

    // 步骤2:在主线程中创建Handler实例
        private Handler mhandler = new mHandler();

    // 步骤3:创建所需的消息对象
        Message msg = Message.obtain(); // 实例化消息对象
        msg.what = 1; // 消息标识
        msg.obj = "AA"; // 消息内容存放

    // 步骤4:在工作线程中 通过Handler发送消息到消息队列中
    // 可通过sendMessage() / post()
    // 多线程可采用AsyncTask、继承Thread类、实现Runnable
        mHandler.sendMessage(msg);

    // 步骤5:开启工作线程(同时启动了Handler)
    // 多线程可采用AsyncTask、继承Thread类、实现Runnable

如何知道Handler的handleMessage方法运行在哪个线程,那自然是看构造函数传入的looper属于哪个线程

3.2 工作原理

Handler的工作原理如下图所示:
在这里插入图片描述

1 一个线程对应一个Looper,一个Looper对应一个MessageQueue。
2 Looper的loop方法就是不断从MessageQueue中取出Message,执行对应handler的handleMessage方法。
3 handler的sendMessage就是将消息放入消息队列,并唤醒loop取消息。
4 底层实现就是监听eventFd

需要注意的是:

线程默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。
我们经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
ActivityThread,并且在main方法中我们会看到主线程也是通过Looper方式来维持一个消息循环

3.2.1 在分析原理之前,先简单记录一下epoll的使用

epoll能够让一个线程 “同时监听多个流读写事件” ,这个机制就被称为 I/O 多路复用!

//epoll_create() 用于创建一个 epoll 池
int epoll_create(int size);

/*epoll_ctl() 用来执行 fd 的 “增删改” 操作,最后一个参数 event 是告诉内核 需要监听什么事件。
还是以网络请求举例, socketfd 监听的就是 可读事件,一旦接收到服务器返回的数据,监听 socketfd 的对象将会收到 回调通知,表示 socket 中有数据可以读了*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

//epoll_wait() 是 使用户线程阻塞 的方法,它的第二个参数 events 接受的是一个 集合对象,如果有多个事件同时发生,events对象可以从内核得到发生的事件的集合
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

简单使用步骤:
1 新建epoll描述符==epoll_create()
2 epoll_ctrl(epoll描述符,添加或者删除所有待监控的连接)
3 返回的活跃连接 ==epoll_wait( epoll描述符 )

3.2.2 理解 Linux eventfd

理解了 epoll 我们再来看 Linux eventfd ,eventfd 是专门用来传递事件的 fd ,它提供的功能也非常简单:累计计数

int efd = eventfd();
write(efd, 1);//写入数字1
write(efd, 2);//再写入数字2
int res = read(efd);
printf(res);//输出值为 3

通过 write() 函数,我们可以向 eventfd 中写入一个 int 类型的值,并且,只要没有发生 读 操作,eventfd 中保存的值将会一直累加

通过 read() 函数可以将 eventfd 保存的值读了出来,并且,在没有新的值加入之前,再次调用 read() 方法会发生阻塞,直到有人重新向 eventfd 写入值

eventfd 实现的是计数的功能,只要 eventfd 计数不为 0 ,那么表示 fd 是可读的。再结合 epoll 的特性,我们可以非常轻松的创建出 生产者/消费者模型

epoll + eventfd 作为消费者大部分时候处于阻塞休眠状态,而一旦有请求入队(eventfd 被写入值),消费者就立刻唤醒处理,Handler 机制的底层逻辑就是利用 epoll + eventfd

3.2.3 Handler底层原理

相关类图

在这里插入图片描述

我们先来看看ActivityThread,是如何创建Looper并开始消息队列的处理的:

public static void main(String[] args) {
	Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

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

	ActivityThread thread = new ActivityThread();

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

	if (sMainThreadHandler == null) {
		sMainThreadHandler = thread.getHandler();
	}

	Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

	Looper.loop();

//如果能执行下面方法,说明应用崩溃或者是退出了...

throw new RuntimeException("Main thread loop unexpectedly exited");

}

整个的时序图如图:
在这里插入图片描述

总的来说:
Loop.prepare

  • Java层为当前线程创建一个looper对象,线程单例模式;
  • 为looper对象创建一个MessageQueue;
  • native层创建nativeMessageQueue,looper对象;
  • 创建一个eventfd(mWakeEventFd),epoll池(mEpollFd),将mWakeEventFd加入epoll池,mWakeEventFd的值没有太多的具体含义,主要用来唤醒主线程;

Loop.loop

  • 监听mWakeEventFd。如果消息队列有消息,就会唤醒主线程
  • 取出Message,依次执行 message.callbak, handler.callback, handler.handleMessage

关键函数:

native层looper构造函数
/**
 * Looper构造函数,用于初始化一个Looper对象。Looper是Android中负责消息队列调度和处理的核心组件。
 * 它允许线程在不阻塞的情况下等待和处理消息和事件。
 *
 * @param allowNonCallbacks 如果设置为`true`,则允许处理非回调类型的事件。通常,非回调事件是指直接通过文件描述符
 * 传递的事件,而不是通过消息机制分发的事件。
 */
Looper::Looper(bool allowNonCallbacks)
    : mAllowNonCallbacks(allowNonCallbacks),  // 初始化是否允许非回调事件
      mSendingMessage(false),                 // 表示当前是否正在发送消息
      mPolling(false),                        // 表示当前是否正在进行轮询操作
      mEpollRebuildRequired(false),           // 标志是否需要重建epoll结构
      mNextRequestSeq(WAKE_EVENT_FD_SEQ + 1), // 请求序列号初始化,从常量WAKE_EVENT_FD_SEQ+1开始
      mResponseIndex(0),                      // 响应索引初始化为0
      mNextMessageUptime(LLONG_MAX) {         // 下一条消息的执行时间初始化为最大值,表示没有消息待处理
    
    // 创建一个用于唤醒事件的文件描述符(就是eventfd)
    mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
    
    // 如果文件描述符创建失败,记录致命错误并终止程序
    LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0, "Could not make wake event fd: %s", strerror(errno));

    // 锁住mLock,确保在多线程环境中安全重建epoll结构
    AutoMutex _l(mLock);
    
    // 重建epoll事件监听结构,用于监听文件描述符的事件
    rebuildEpollLocked();
}

/**
 * 重新构建`epoll`实例,并注册需要监听的事件(如唤醒事件和文件描述符事件)。
 * 该方法在Looper需要更新其监听的文件描述符时调用。
 * 
 * 这个方法会首先关闭旧的`epoll`实例,然后创建一个新的`epoll`实例,并将需要监听的
 * 文件描述符重新添加到新的`epoll`实例中。关键事件如唤醒事件会在这里注册。
 */
void Looper::rebuildEpollLocked() {
    // 如果已经存在一个旧的epoll实例,首先将其关闭。
    if (mEpollFd >= 0) {
#if DEBUG_CALLBACKS
        ALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this);
#endif
        mEpollFd.reset();  // 关闭并释放旧的epoll文件描述符
    }

    // 创建一个新的epoll实例,并确保创建成功。
    // EPOLL_CLOEXEC标志确保在创建子进程时关闭该文件描述符。
    mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));

    // 创建一个`epoll_event`,表示我们要监听的唤醒事件(EPOLLIN)。
    epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
    
    // 将mWakeEventFd添加到新的`epoll`实例中,以确保`Looper`可以通过
    // 该文件描述符被唤醒。
    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &wakeEvent);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
                        strerror(errno));

    // 遍历所有已经注册的请求(mRequests),并将它们的文件描述符重新添加到epoll实例中。
    for (const auto& [seq, request] : mRequests) {
        // 根据请求的事件类型创建对应的`epoll_event`。
        epoll_event eventItem = createEpollEvent(request.getEpollEvents(), seq);

        // 将每个请求的文件描述符添加到新的`epoll`实例中。
        int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, request.fd, &eventItem);
        
        // 如果添加文件描述符失败,记录错误日志。
        if (epollResult < 0) {
            ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
                  request.fd, strerror(errno));
        }
    }
}

int Looper::pollInner(int timeoutMillis) {
#if DEBUG_POLL_AND_WAKE
    // 如果启用了调试模式,打印当前对象指针和超时时间。
    ALOGD("%p ~ pollOnce - 等待中: timeoutMillis=%d", this, timeoutMillis);
#endif

    // 调整超时:根据当前时间和队列中下一个消息的时间,来确定合适的超时时间。
    if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);  // 获取当前系统时间(单调时钟)
        int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);  // 计算下一个消息的超时
        // 如果下一个消息的超时值有效且比当前超时短,则调整超时时间为下一个消息的超时值。
        if (messageTimeoutMillis >= 0
                && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
            timeoutMillis = messageTimeoutMillis;
        }
#if DEBUG_POLL_AND_WAKE
        // 如果启用了调试模式,打印下一个消息的到期时间和调整后的超时值。
        ALOGD("%p ~ pollOnce - 下一个消息在 %" PRId64 "ns 后到达,调整后的超时: timeoutMillis=%d",
                this, mNextMessageUptime - now, timeoutMillis);
#endif
    }

    // 开始轮询,初始化结果值为 `POLL_WAKE`,并清空之前的响应列表和索引。
    int result = POLL_WAKE;
    mResponses.clear();  // 清空之前的响应队列
    mResponseIndex = 0;  // 重置响应索引

    // 即将进入轮询空闲状态,设置 `mPolling` 为 `true` 表示当前正在轮询。
    mPolling = true;

    // 初始化用于接收 epoll 事件的数组 `eventItems`,并使用 epoll_wait 进行等待。
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);  // 等待事件

    // 轮询结束,标记 `mPolling` 为 `false` 表示已不再处于轮询状态。
    mPolling = false;

    // 获取锁,因为接下来可能会修改共享的数据结构(如响应队列和 epoll 集合)。
    mLock.lock();

    // 如果需要重建 epoll 集合(例如某些文件描述符发生了变化),则执行重建。
    if (mEpollRebuildRequired) {
        mEpollRebuildRequired = false;  // 重置标志
        rebuildEpollLocked();  // 重建 epoll 集合
        goto Done;  // 跳转到 Done 标签,退出当前轮询循环
    }

    // 处理 epoll 失败的情况。如果 `eventCount` 小于 0,则可能是系统中断引起的轮询失败。
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;  // 如果是中断错误,则直接退出,返回重新轮询
        }
        ALOGW("轮询发生意外错误: %s", strerror(errno));  // 打印错误信息
        result = POLL_ERROR;  // 标记轮询结果为错误
        goto Done;  // 退出轮询
    }

    // 处理超时的情况:如果 `eventCount` 为 0,则表示轮询超时,没有事件发生。
    if (eventCount == 0) {
#if DEBUG_POLL_AND_WAKE
        // 如果启用了调试模式,打印超时日志。
        ALOGD("%p ~ pollOnce - 超时", this);
#endif
        result = POLL_TIMEOUT;  // 标记轮询结果为超时
        goto Done;  // 退出轮询
    }

    // 处理所有发生的事件。
#if DEBUG_POLL_AND_WAKE
    // 如果启用了调试模式,打印处理事件的文件描述符数量。
    ALOGD("%p ~ pollOnce - 处理来自 %d 个文件描述符的事件", this, eventCount);
#endif

    // 遍历所有事件,处理每个事件。
    for (int i = 0; i < eventCount; i++) {
        const SequenceNumber seq = eventItems[i].data.u64;  // 获取事件的序列号
        uint32_t epollEvents = eventItems[i].events;  // 获取事件类型
        // 如果事件是唤醒事件(特殊文件描述符的事件),则处理唤醒逻辑。
        if (seq == WAKE_EVENT_FD_SEQ) {
            if (epollEvents & EPOLLIN) {
                awoken();  // 处理唤醒事件,通常用于唤醒线程
            } else {
                ALOGW("忽略唤醒事件文件描述符上的意外 epoll 事件 0x%x。", epollEvents);
            }
        } else {
            // 如果事件是普通文件描述符事件,查找对应的请求。
            const auto& request_it = mRequests.find(seq);
            if (request_it != mRequests.end()) {
                const auto& request = request_it->second;
                int events = 0;
                // 根据 epoll 事件类型,确定相应的事件标志。
                if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
                // 将响应加入到响应列表中,以便稍后处理。
                mResponses.push({.seq = seq, .events = events, .request = request});
            } else {
                // 如果没有找到对应的请求,打印警告并忽略该事件。
                ALOGW("忽略序列号 %" PRIu64 " 上的意外 epoll 事件 0x%x,不再注册。", epollEvents, seq);
            }
        }
    }

Done: ;
    // 调用所有待处理的消息回调(如定时器消息或延时消息)。
    mNextMessageUptime = LLONG_MAX;  // 重置下一个消息时间
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);  // 获取当前系统时间
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);  // 获取第一个消息
        if (messageEnvelope.uptime <= now) {
            // 如果消息到期,处理该消息。
            {
                sp<MessageHandler> handler = messageEnvelope.handler;  // 获取消息处理器
                Message message = messageEnvelope.message;  // 获取消息
                mMessageEnvelopes.removeAt(0);  // 从队列中移除该消息
                mSendingMessage = true;  // 标记正在发送消息
                mLock.unlock();  // 释放锁,允许其他线程操作

#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
                // 如果启用了调试模式,打印正在发送的消息信息。
                ALOGD("%p ~ pollOnce - 发送消息: handler=%p, what=%d", this, handler.get(), message.what);
#endif
                handler->handleMessage(message);  // 调用消息处理器,处理消息
            }

            mLock.lock();  // 重新获取锁
            mSendingMessage = false;  // 标记消息发送完毕
            result = POLL_CALLBACK;  // 标记轮询结果为回调
        } else {
            // 如果消息未到期,更新下一个消息的到期时间并退出循环。
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }

    // 释放锁,允许其他线程继续执行。
    mLock.unlock();

    // 处理所有响应的回调函数。
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);  // 获取响应
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;  // 获取文件描述符
            int events = response.events;  // 获取事件类型
            void* data = response.request.data;  // 获取附加数据
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
            // 如果启用了调试模式,打印正在处理的回调信息。
            ALOGD("%p ~ pollOnce - 调用文件描述符事件回调 %p: fd=%d, events=0x%x, data=%p",
                    this, response.request.callback.get(), fd, events, data);
#endif
            // 调用回调函数处理文件描述符事件。注意,回调可能会关闭文件描述符,因此需要小心处理。
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                AutoMutex _l(mLock);  // 获取锁
                removeSequenceNumberLocked(response.seq);  // 移除已处理的序列号
            }

            // 清除回调引用,因为下次轮询之前不会清除响应列表。
            response.request.callback.clear();
            result = POLL_CALLBACK;  // 标记轮询结果为回调
        }
    }
    return result;  // 返回轮询结果
}

子线程sendMessage

在这里插入图片描述
Handler.sendMessage

  • 将Message放入主线程的消息队列
  • 通过write(mWakeEventFd),唤醒主线程
/**
 * 将消息插入到消息队列中,按照时间顺序处理。消息可以插入到队列头或中间,
 * 根据消息的执行时间 `when` 决定消息插入的位置。
 *
 * @param msg 需要插入的消息对象 {@link Message},该消息包含处理目标和数据。
 * @param when 消息的处理时间,单位为毫秒,基于 {@link android.os.SystemClock#uptimeMillis}。
 *
 * @return 返回 true 表示消息成功插入队列,false 表示失败(例如队列已关闭或线程已经退出)。
 */
boolean enqueueMessage(Message msg, long when) {
    // 如果消息没有目标Handler,抛出异常。
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }

    // 同步锁保护消息队列的操作,确保线程安全。
    synchronized (this) {
        // 如果消息已经被使用(正在队列中),抛出异常。
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        // 如果消息队列正在退出状态,记录警告日志并回收消息。
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle(); // 回收消息,避免内存泄漏
            return false; // 消息未能插入队列
        }

        // 标记消息为已使用状态,避免重复插入。
        msg.markInUse();
        // 设置消息的执行时间。
        msg.when = when;

        Message p = mMessages; // 当前消息队列的头部
        boolean needWake; // 是否需要唤醒消息处理线程

        // 如果队列为空或消息的执行时间早于当前头部消息,将消息插入队列头部。
        if (p == null || when == 0 || when < p.when) {
            // 新的头部消息,可能需要唤醒消息队列
            msg.next = p; // 当前队列头部变为新消息的下一个
            mMessages = msg; // 新消息成为队列头部
            needWake = mBlocked; // 如果消息处理线程被阻塞,设置为需要唤醒
            if (p == null) {
                mLast = mMessages; // 如果这是队列中的唯一消息,设置为队列尾部
            }
        } else {
            // 将消息插入到队列的中部或尾部,通常不需要唤醒队列,除非是异步消息
            needWake = mBlocked && p.target == null && msg.isAsynchronous();

            // 根据系统标志判断是否启用尾部追踪
            if (Flags.messageQueueTailTracking()) {
                // 如果消息的执行时间晚于当前队列的最后一条消息,插入到尾部
                if (when >= mLast.when) {
                    needWake = needWake && mAsyncMessageCount == 0; // 如果没有异步消息,则需要唤醒
                    msg.next = null; // 新消息是队列的最后一条消息
                    mLast.next = msg; // 设置新消息为最后一个
                    mLast = msg; // 更新最后一个消息指针
                } else {
                    // 插入到队列中间
                    Message prev;
                    for (;;) {
                        prev = p;
                        p = p.next;
                        if (p == null || when < p.when) {
                            break; // 找到适合插入的位置
                        }
                        if (needWake && p.isAsynchronous()) {
                            needWake = false; // 阻止唤醒
                        }
                    }
                    if (p == null) {
                        mLast = msg; // 如果插入到队列尾部,更新最后一条消息指针
                    }
                    msg.next = p; // 将新消息插入到prev之后,p之前
                    prev.next = msg;
                }
            } else {
                // 不启用尾部追踪的情况,插入队列中间
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break; // 找到适合插入的位置
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false; // 阻止唤醒
                    }
                }
                msg.next = p; // 将新消息插入到prev之后,p之前
                prev.next = msg;

                // 如果不启用尾部追踪,避免尾部消息泄漏,mLast设置为null。
                mLast = null;
            }
        }

        // 如果消息是异步的,增加异步消息计数。
        if (msg.isAsynchronous()) {
            mAsyncMessageCount++;
        }

        // 唤醒被阻塞的消息处理线程,确保消息及时处理。
        if (needWake) {
            nativeWake(mPtr); // 本地方法,用于唤醒阻塞的消息队列
        }
    }
    return true; // 成功将消息插入队列
}

Android内存泄漏

对于Android java层来讲,内存泄漏就是指对象不再使用但却无法被GC回收的情况,通常发生在长生命周期对象(如单例、静态变量)持有短生命周期对象的引用(如Activity、Fragment)。

内存泄漏检查工具

Android Profiler:Android Studio自带的工具,可以实时监控应用的内存、CPU、网络等性能状况,可以查看内存使用详情,帮助发现潜在的内存泄漏。
LeakCanary:一个强大的开源工具,用于检测内存泄漏。它可以自动检测应用中可能的内存泄漏,并提供详细的堆栈信息,帮助开发者分析和修复泄漏。
MAT (Memory Analyzer Tool):一种Java内存分析工具,可以分析内存堆转储文件(hprof),帮助识别和定位内存泄漏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值