当前位置: 首页 > article >正文

安卓基础组件Looper - 02 native层面的剖析

文章目录

    • native使用
      • 使用总结
      • 创建Looper
        • 构造函数创建(不推荐)
          • 使用举例
          • 源代码
        • Looper::prepare
      • 获取Looper
      • 可忽略
        • 初始化Looper
        • 主动休眠 `pollAll`
        • 主动唤醒 `wake`
      • 发送消息 sendMessage
      • 轮询消息

native使用

Android Native Looper 机制 - 掘金 (juejin.cn)

/system/core/libutils/include/utils/Looper.h
/system/core/libutils/Looper.cpp

使用总结

总结一下 Native 层 Looper 的使用:

// 初始化Looper对象
sp<Looper> mLooper = Looper::prepare(false /*allowNonCallbacks*/);// 可选:文件描述符
// - 添加要检测的文件描述符,
//   当对应事件发生时,调用回调对象中的回调函数
mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
// - 删除要检测的文件描述符
mLooper->removeFd(inputChannel->getFd());// 进入休眠状态, 等待超时或唤醒的回调
mLooper->pollAll(timeoutMillis);// 主动唤醒
mLooper->wake();// 发送消息 mLooper->sendMessage(handler, message);
// 删除消息 mLooper->removeMessages(handler, message);
void IrisService::runInThread(std::function<void()> handler, std::string tag, bool log) {mLooper->sendMessage(new LambdaMessage(handler, tag), Message());void ReportHandler::scheduleStreamingReport() {mHandlerLooper->removeMessages(this, WHAT_TAKE_REPORT);mHandlerLooper->sendMessage(this, Message(WHAT_TAKE_REPORT));

Android Native 层的 Looper 机制,关注的重点是:

  • 如何实现休眠与唤醒
  • 如何封装通知

创建Looper

构造函数创建(不推荐)
使用举例

可以使用裸指针,但建议仍然保证一个线程只有一个Looper的要求。

  • 看一下 安卓系统服务surfaceflinger中的做法:

这里在进程的开头,在主线程里创造了一个Looper。所以没有其他子线程,满足 一个线程只有一个Looper

// frameworks/native/services/surfaceflinger/tests/vsync/vsync.cpp
int main(int /*argc*/, char** /*argv*/)
{sp<Looper> loop = new Looper(false);loop->addFd(myDisplayEvent.getFd(), 0, ALOOPER_EVENT_INPUT, receiver,&myDisplayEvent);
  • 其他看起来无保护的场景

其他一些地方,在构造函数或init过程中,确确实实是new Looper了。但也不会多于一个,仍然保证了线程(主线程)只有一个Looper。

// frameworks/base/core/jni/android_os_MessageQueue.cpp
NativeMessageQueue::NativeMessageQueue() :mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {mLooper = Looper::getForThread();if (mLooper == NULL) {mLooper = new Looper(false);Looper::setForThread(mLooper);}
}

为什么没用Looper::Prepare Looper::xxThread可能出于性能的考虑?

sp<::android::Looper> mLooper;
mLooper = new Looper(false);
源代码

构造函数

# /system/core/libutils/include/utils/Looper.h
# /system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks): mAllowNonCallbacks(allowNonCallbacks),mSendingMessage(false),mPolling(false),mEpollRebuildRequired(false),mNextRequestSeq(WAKE_EVENT_FD_SEQ + 1),mResponseIndex(0),mNextMessageUptime(LLONG_MAX) {mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));rebuildEpollLocked();
}
Looper::prepare

更推荐的做法:Looper::prepare

  • 如果当前线程已经通过初始化过 Looper(使用构造函数且未使用setForThread指定除外),会直接返回对应Looper
  • 设置Looper到当前线程,使用 RefBase::sp<Looper>智能指针管理Looper对象
// 初始化Looper对象
sp<Looper> mLooper = Looper::prepare(false /*allowNonCallbacks*/);
# /system/core/libutils/include/utils/Looper.h
# /system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks): mAllowNonCallbacks(allowNonCallbacks),mSendingMessage(false),mPolling(false),mEpollRebuildRequired(false),mNextRequestSeq(WAKE_EVENT_FD_SEQ + 1),mResponseIndex(0),mNextMessageUptime(LLONG_MAX) {mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));rebuildEpollLocked();
}
sp<Looper> Looper::prepare(int opts) {bool allowNonCallbacks = opts & PREPARE_ALLOW_NON_CALLBACKS;sp<Looper> looper = Looper::getForThread();if (looper == nullptr) {looper = new Looper(allowNonCallbacks);Looper::setForThread(looper);}return looper;
}
void Looper::setForThread(const sp<Looper>& looper) {
sp<Looper> Looper::getForThread() {int result = pthread_once(& gTLSOnce, initTLSKey);LOG_ALWAYS_FATAL_IF(result != 0, "pthread_once failed");return (Looper*)pthread_getspecific(gTLSKey);
}

@TOC

获取Looper

获取当前线程已经绑定的Looper。

如果当前线程已经被 Looper::setForThread 方法指定了 Looper / 通过Looper::prepare初始化了Looper,那么可以通过 Looper::getForThread 方法获取

# /system/core/libutils/include/utils/Looper.h
# /system/core/libutils/Looper.cpp
static pthread_once_t gTLSOnce = PTHREAD_ONCE_INIT;
static pthread_key_t gTLSKey = 0;sp<Looper> Looper::getForThread() {int result = pthread_once(& gTLSOnce, initTLSKey);Looper* looper = (Looper*)pthread_getspecific(gTLSKey);return sp<Looper>::fromExisting(looper);
}void Looper::initTLSKey() {int error = pthread_key_create(&gTLSKey, threadDestructor);LOG_ALWAYS_FATAL_IF(error != 0, "Could not allocate TLS key: %s", strerror(error));
}void Looper::setForThread(const sp<Looper>& looper) {sp<Looper> old = getForThread(); // also has side-effect of initializing TLSif (looper != nullptr)looper->incStrong((void*)threadDestructor);pthread_setspecific(gTLSKey, looper.get());if (old != nullptr) {old->decStrong((void*)threadDestructor);}
}

可忽略

初始化Looper

Looper 的初始化,主要完成以下两个工作:

  • 构建一个 epoll 池

  • 维护epoll池

    • 构造一个 eventfd,放到 epoll 池
    • 把 mRequests 中保存的 fd 放到 epoll 池

    初始化时 mRequests 为空,这步可暂时忽略

(可选)文件描述符管理 fd

looper->addFd 添加需要关注的 fd (可选)

addfd 添加一个额外的 fd (eventfd 以外的)给 epoll 监听,主要完成了三项工作:

  • 根据插入的 fd 回调对象等参数构建一个 Request 对象
  • 把参数中的 fd 加入到 epoll 池中
  • 把新构建的 Request 对象插入到 mRequests 中
// 可选:文件描述符
// - 添加要检测的文件描述符,
//   当对应事件发生时,调用回调对象中的回调函数
mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
// - 删除要检测的文件描述符
mLooper->removeFd(inputChannel->getFd());
主动休眠 pollAll
主动唤醒 wake

looper->pollAll 进入休眠状态等待回调、超时或唤醒

核心功能都聚集于 pollInner 函数中:

  • 调用 epoll_wait 进入休眠状态

  • 当 IO 事件到来时,被唤醒时

    • 读取事件,将发生的事件包装为 Response 对象,并保存到 mResponses 中

    • 处理所有的 Response,并回调 addfd 时传入的回调函数

    • 处理收到的 message,并调用回调函数

// 进入休眠状态, 等待超时或唤醒的回调
mLooper->pollAll(timeoutMillis);// 主动唤醒
mLooper->wake();

发送消息 sendMessage

其他线程是可以通过 Looper::sendMessage 给 Looper 所在工作线程发送消息

  • looper 线程从休眠中唤醒,处理收到的 message,调用回调函数

  • 主要完成了两项工作:

    • message 截止时间 回调对象 包装为 MessageEnvelope 对象,并插入 mMessageEnvelopes。Looper::pollInner轮询时,就是从中取出Msg对象的。

      Vector<MessageEnvelope> mMessageEnvelopes; // guarded by mLock
      
    • 调用 wake 函数,给 eventfd 写数据,唤醒 epoll

void IrisService::runInSendingThread(std::function<void()> handler, std::string tag) {mSendingLooper->sendMessage(new LambdaMessage(handler, tag), Message());
}
# /system/core/libutils/include/utils/Looper.h
# /system/core/libutils/Looper.cpp
void Looper::sendMessage(const sp<MessageHandler>& handler, const Message& message) {nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);sendMessageAtTime(now, handler, message);
}
void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,const Message& message) {size_t i = 0;size_t messageCount = mMessageEnvelopes.size();// 在按照时间顺序,寻找队列中要插入的地方while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) {i += 1;}// 封装可执行对象MessageEnvelope messageEnvelope(uptime, handler, message);mMessageEnvelopes.insertAt(messageEnvelope, i, 1);

轮询消息

native层面传入的message其实就是封装后的std::function——天然的可执行对象。

轮询实际上是自动完成的,会逐个执行这些可执行对象。

# /system/core/libutils/include/utils/Looper.h
# /system/core/libutils/Looper.cpp
Looper::pollAllLooper::pollOnceLooper::pollInner # 阻塞,轮询
int Looper::pollInner(int timeoutMillis) {// ...// Invoke pending message callbacks.mNextMessageUptime = LLONG_MAX;while (mMessageEnvelopes.size() != 0) {nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);if (messageEnvelope.uptime <= now) {// Remove the envelope from the list.// We keep a strong reference to the handler until the call to handleMessage// finishes.  Then we drop it so that the handler can be deleted *before*// we reacquire our lock.{ // obtain handlersp<MessageHandler> handler = messageEnvelope.handler;Message message = messageEnvelope.message;mMessageEnvelopes.removeAt(0);mSendingMessage = true;mLock.unlock();handler->handleMessage(message);} // release handlermLock.lock();mSendingMessage = false;result = POLL_CALLBACK;} else {// The last message left at the head of the queue determines the next wakeup time.mNextMessageUptime = messageEnvelope.uptime;break;}}

相关文章:

安卓基础组件Looper - 02 native层面的剖析

文章目录 native使用使用总结创建Looper构造函数创建(不推荐)使用举例源代码 Looper::prepare 获取Looper可忽略初始化Looper主动休眠 pollAll主动唤醒 wake 发送消息 sendMessage轮询消息 native使用 Android Native Looper 机制 - 掘金 (juejin.cn) /system/core/libutils/…...

用大白话解释搜索引擎Elasticsearch是什么,有什么用,怎么用

Elasticsearch是什么&#xff1f; Elasticsearch&#xff08;简称ES&#xff09;就像一个“超级智能的图书馆管理系统”&#xff0c;专门帮你从海量数据中快速找到想要的信息。它底层基于倒排索引技术&#xff08;类似书籍的目录页&#xff09;&#xff0c;能秒级搜索和分析万…...

机器学习的三个基本要素

机器学习的基本要素包括模型、学习准则&#xff08;策略&#xff09;和优化算法三个部分。机器学习方法之间的不同&#xff0c;主要来自其模型、学习准则&#xff08;策略&#xff09;、优化算法的不同。 模型 机器学习首要考虑的问题是学习什么样的模型&#xff08;Model&am…...

二十三种设计模式

2 工厂方法模式 工厂模式&#xff08;Factory Pattern&#xff09;是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 在工厂模式中&#xff0c;我们在创建对象时不会对客户端暴露创建逻辑&#xff0c;并且是通…...

Spring Boot 异步编程深入剖析

Spring Boot 异步编程深入剖析 1. 异步方法的使用 原理深度解析 Spring Boot 的异步方法基于 Spring 的 AOP&#xff08;面向切面编程&#xff09;实现。当在方法上添加 Async 注解时&#xff0c;Spring 会为该方法所在的类创建一个代理对象。当调用该异步方法时&#xff0c…...

SqlSugar 语法糖推荐方式

//方式1&#xff1a;var dd _repository._Db.Queryable<ConfigAggregateRoot, UserRoleEntity>((o, p) > o.Id p.Id).Select((o, p) > new{o.Id,o.Remark,p.RoleId,});//方式2&#xff1a;不推荐使用&#xff0c;建议优先使用 Lambda 表达式&#xff0c;因为它更…...

SQL 全面指南:从基础语法到高级查询与权限控制

SQL&#xff1a;全称 Structured Query Language&#xff0c;结构化查询语言。操作关系型数据库的编程语言&#xff0c;定义了一套操作关系型数据库统一标准 。 一、SQL通用语法 在学习具体的SQL语句之前&#xff0c;先来了解一下SQL语言的同于语法。 1). SQL语句可以单行或多…...

Spring Cloud Gateway 网关的使用

在之前的学习中&#xff0c;所有的微服务接口都是对外开放的&#xff0c;这就意味着用户可以直接访问&#xff0c;为了保证对外服务的安全性&#xff0c;服务端实现的微服务接口都带有一定的权限校验机制&#xff0c;但是由于使用了微服务&#xff0c;就需要每一个服务都进行一…...

【Spring Cloud Alibaba】基于Spring Boot 3.x 搭建教程

目录 前言一、开发环境二、简介 1.主要功能2.组件 三、搭建过程 1 - 主体工程搭建2 - 服务注册与发现组件 —— Nacos的安装3 - 服务注册与发现 —— 服务提供者4 - 服务注册与发现 —— 服务消费者5 - 服务配置中心6 - OpenFeign服务接口调用7 - OpenFeign高级特性8 - Spring…...

JavaWeb-jdk17安装

下载jdk17 地址&#xff1a;https://www.oracle.com/java/technologies/downloads/#jdk17-windows 安装jdk 配置环境变量 右键点击我的电脑>属性>高级系统设置>环境变量 在系统变量Path变量中添加 测试 java -version javac -version...

docker关闭mysql端口映射的使用

需求 项目中的数据库为mysql&#xff0c;如果将端口映射到宿主机上&#xff0c;容易被工具扫描出&#xff0c;且随着国产化的进程推进&#xff0c;mysql将不被允许。为了提高安全性与满足项目需求&#xff0c;这里采用隐藏mysql端口方式&#xff0c;不映射宿主机端口&#xff…...

【银河麒麟高级服务器操作系统】服务器测试业务耗时问题分析及处理全流程分享

更多银河麒麟操作系统产品及技术讨论&#xff0c;欢迎加入银河麒麟操作系统官方论坛 https://forum.kylinos.cn 了解更多银河麒麟操作系统全新产品&#xff0c;请点击访问 麒麟软件产品专区&#xff1a;https://product.kylinos.cn 开发者专区&#xff1a;https://developer…...

算法1-4 蜜蜂路线

题目描述 一只蜜蜂在下图所示的数字蜂房上爬动,已知它只能从标号小的蜂房爬到标号大的相邻蜂房,现在问你&#xff1a;蜜蜂从蜂房 m 开始爬到蜂房 n&#xff0c;m<n&#xff0c;有多少种爬行路线&#xff1f;&#xff08;备注&#xff1a;题面有误&#xff0c;右上角应为 n−…...

Android 常见View的防抖

在开发Android应用时&#xff0c;我们经常会遇到用户快速点击按钮或者频繁触发某个事件的情况。这种行为可能会导致不必要的重复操作&#xff0c;例如多次提交表单、重复加载数据等。为了避免这些问题&#xff0c;我们需要对这些事件进行防抖处理。本文将详细介绍如何在Kotlin中…...

数据库原理SQL查询(习题+知识点)

一、查询学生表所有学生记录 1.题目内容代码编写 select * from stu; 2.知识点提醒 1&#xff09;选择表中的所有属性列有两种方法 在select关键字后列出所有列名若列的显示顺序与其在表中的顺序相同&#xff0c;则也可用 * 表示所有列 二、查询学生表中部分信息 1.题目内…...

安路FPGA开发入门:软件安装与点灯与仿真(TangDynasty ModelSim)

文章目录 前言软件安装开发软件仿真软件 点灯测试代码编写与编译引脚分配固件下载 仿真测试ModelSim添加仿真库TangDynasty仿真设置进行仿真 后记 前言 最近因为工作需要用安路的FPGA&#xff0c;这里对安路FPGA开发相关流程做个记录。作为测试只需要一个核心板&#xff08;我这…...

Java 导出大数据到 Excel 表格

背景 之前的项目一直是用XSSFWorkbook来做 Excel 导出&#xff0c;在遇到大数据导出时&#xff0c;经常会遇到 OOM。在 Apache Poi 3.8 之后的版本提供的 SXSSFWorkbook 可以优雅的解决这个问题。 原理 SXSSFWorkbook 被称为流式 API&#xff0c;主要是因为它采用了流式写入…...

浅克隆与深克隆区别

package d12_api_object;public class Test2 {public static void main(String[] args) throws CloneNotSupportedException {//目标&#xff1a;掌握Object类提供的对象克隆方法//1、protected Object clone():对象克隆User u1 new User(1,"min","1120",…...

关于服务器cpu过高的问题排查

1.定位是哪个程序造成的cpu过高 如果有云服务器&#xff0c;就用云服务器自带的监控功能&#xff0c;查时间段 如果没有&#xff0c;则使用&#xff1a; ps -eo pid,comm,pcpu,pmem,cputime --sort-cputime | head -n 100 2.定位到问题 发现是uwsgi的cpu消耗过高&#xff0…...

【缓冲区】数据库备份的衍生问题,缓冲区在哪里?JVMor操作系统?(二)

【缓冲区】数据库备份的衍生问题&#xff0c;缓冲区在哪里&#xff1f;JVMor操作系统&#xff1f;&#xff08;二 完结&#xff09; 缓冲区既属于操作系统&#xff0c;也属于 JVM&#xff0c;具体取决于你讨论的是哪个层面的缓冲区。下面我会详细解释这两者的区别和联系。 1. …...

RPA 职业前景:个人职场发展的 “新机遇”

1. RPA职业定义与范畴 1.1 RPA核心概念 机器人流程自动化&#xff08;RPA&#xff09;是一种通过软件机器人模拟人类操作&#xff0c;自动执行重复性、规则性任务的技术。RPA的核心在于其能够高效、准确地处理大量数据和流程&#xff0c;减少人工干预&#xff0c;从而提高工作…...

如何实现小数据的大智能?

大数据可以通过从态到势、从感到知的态势感知过程计算出可能性&#xff0c;如各种大模型&#xff0c;而要通过小数据、小样本获得好的预测结果&#xff0c;可以通过从势到态、从知到感的势态知感过程算计出可能性。 一般情况下&#xff0c;大家常常会提到了大数据和小数据在态势…...

打开 Windows Docker Desktop 出现 Docker Engine Stopped 问题

一、关联文章: 1、Docker Desktop 安装使用教程 2、家庭版 Windows 安装 Docker 没有 Hyper-V 问题 3、安装 Windows Docker Desktop - WSL问题 二、问题解析 打开 Docker Desktop 出现问题,如下: Docker Engine Stopped : Docker引擎停止三、解决方法 1、检查服务是否…...

基于单片机和蓝牙通讯的简易钢琴控制装置设计

摘要&#xff1a;本文设计了一个基于单片机和蓝牙通讯的简易钢琴演奏控制装置&#xff0c;在 Proteus 中设计绘制了系统电路原理图&#xff0c;在 Keil 中编写了单片机控制程序并导入 Proteus电路原理图中进行了软、硬件交互仿真&#xff0c;设置了手机蓝牙串口调试的键盘设置及…...

Linux常见操作命令以及编辑器VI命令

一.复制(cp)和移动(mv) 1.复制文件 格式&#xff1a;cp 源文件 目标文件 2.复制目录 格式&#xff1a;cp -r 源文件夹 目标文件夹 3.重命名和移动 重命名格式&#xff1a;mv 源文件 目标文件 移动格式&#xff1a;mv 源文件 目录/源文件 二.查看文件内容 1.cat命令 格式&#x…...

React Native从入门到进阶详解

React Native知识框架从入门到进阶的问题。首先需要结合我搜索到的资料来整理出结构化的内容。证据中有多本书籍和文章&#xff0c;可能会涉及不同的章节和重点&#xff0c;需要仔细梳理。 首先&#xff0c;根据邱鹏源的《React Native精解与实战》将知识分为入门和进阶两大部分…...

STL——list的介绍和模拟实现

前言 本篇博客我们将要开始介绍list这个容器&#xff0c;list是带头双向循环链表&#xff0c;STL标准模板库中实现了list这样方便我们去使用&#xff0c;那么本篇博客我们将脱下list的神秘外衣&#xff0c;介绍它的使用以及模拟实现。 list的介绍 list的底层是带头双向循环链…...

go前后端开源项目go-admin,本地启动

https://github.com/go-admin-team/go-admin 教程 1.拉取项目 git clone https://github.com/go-admin-team/go-admin.git 2.更新整理依赖 go mod tidy会整理依赖&#xff0c;下载缺少的包&#xff0c;移除不用的&#xff0c;并更新go.sum。 # 更新整理依赖 go mod tidy 3.编…...

go 分布式redis锁的实现方式

go 语言以高并发著称。那么在实际的项目中 经常会用到锁的情况。比如说秒杀抢购等等场景。下面主要介绍 redis 布式锁实现的两种高并发抢购场景。其实 高并发 和 分布式锁 是一个互斥的两个状态&#xff1a; 方式一 setNX&#xff1a; 使用 redis自带的API setNX 来实现。能解决…...

深入理解递归:从原理到C++实践

什么是递归&#xff1f; 递归&#xff08;Recursion&#xff09;是编程中一种强大的技术&#xff0c;其核心思想是&#xff1a;函数直接或间接地调用自身。如同俄罗斯套娃一般&#xff0c;每个函数调用都会解开问题的一个层级&#xff0c;直到达到基础条件。 递归三要素&…...