本文总结一下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),帮助识别和定位内存泄漏。