首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >深入理解 Android 消息机制原理

深入理解 Android 消息机制原理

原创
作者头像
汪毅雄
修改于 2017-11-08 01:42:19
修改于 2017-11-08 01:42:19
2.1K0
举报
文章被收录于专栏:汪毅雄的专栏汪毅雄的专栏

导语: 本文讲述的是Android的消息机制原理,从Java到Native代码进行了梳理,并结合其中使用到的Epoll模型予以介绍。

Android的消息传递,是系统的核心功能,对于如何使用相信大家都已经相当熟悉了,这里简单提一句。我们可以粗糙的认为消息机制中关键的几个类的功能如下:

Handler:消息处理者

Looper:消息调度者

MessageQueue:存放消息的地方

使用过程:

Looper.prepare > #$%^^& > Looper.loop(死循环) --- loop到一个消息 > Handler处理

好了,我们直接看源码吧。

Java层

消息机制是伴随线程的,也就是说上面的几个类在可以在任何一个线程中都有实例的。

先看Looper吧。以主线程为例,Android进程在初始化,会调用prepareMainLooper

代码语言:txt
AI代码解释
复制
public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            ...
            sMainLooper = myLooper();
        }
    }

代码语言:txt
AI代码解释
复制
 private static void prepare(boolean quitAllowed) {
        ...
        sThreadLocal.set(new Looper(quitAllowed));
    }

代码语言:txt
AI代码解释
复制
 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

以上几个方法就是Looper初始化,如果是主线程Looper会创建一个不可退出的MessageQueue,并把looper实例放入线程独立(ThreadLocal)变量中。

Looper#loop

代码语言:txt
AI代码解释
复制
public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next(); 
            if (msg == null) {
                return;
            }
            ...
            try {
                msg.target.dispatchMessage(msg);
            } 
            ...
            msg.recycleUnchecked();
        }
    }

Looper prepare后就可以loop了,loop非常简单,一直去queue中拿消息就好了,拿到了交给target也就是Handler处理。大家有可能会奇怪这种死循环,执行起来不会太sb粗暴了吗?其实这个解决方式在queue.next!!!后面再讲。

Handler#dispatchMessage

代码语言:txt
AI代码解释
复制
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

handler收到后,如果发现message的callback不为空,则只处理callback。(提一句,我们用的很多的handler.post(Runnable),其实这个Runnable就是这里的callback,也就是说post的Runnable实质上是一个优先级很高的Message),如果没有则尝试交给handler本身的callback处理(handler初始化的时候可以用callback方式构造),再没有才到我们常用的handleMessage方法,这里就是我们经常重写的方法。

再说说消息的发送,一般handler会调用sendMessage方法,但是最终这个方法还是会跑到这里

代码语言:txt
AI代码解释
复制
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

再交给MessageQueue

代码语言:txt
AI代码解释
复制
boolean enqueueMessage(Message msg, long when) {
        。。。
        synchronized (this) {
            。。。
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    。。。
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            if (needWake){
               nativeWake(mPtr);
            }
        }
        return true;
    }

MessageQueue会把消息插入队列,并依次改变队列中各个消息的指针。

咦,好像只用Java层貌似就能把整个消息机制说通了,native代码在哪儿?有何用呢?

但是,刚才提到了Looper初始化的时候也会新建一个MessageQueue

代码语言:txt
AI代码解释
复制
MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

好了,我们第一个native方法出来了。这时候我们可以猜得到,MessageQueue才是整个消息机制的核心!

Native层

接上面Java层的代码,MessageQueue构造的时候会调一个nativeInit。

代码语言:txt
AI代码解释
复制
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    。。。
}

代码语言:txt
AI代码解释
复制
 NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

native层调用init方法后,会在native层构建一个native Looper!来看看native Looper的初始化

代码语言:txt
AI代码解释
复制
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    ...
    rebuildEpollLocked();
}

这里创建了一个eventfd,代码来自最新的8.0,这部分和5.0 pipe管道的mWakeReadPipeFd和mWakeWritePipeFd稍微有点不一样,前者是等待/响应,后者是读取/写入。只是android选取方式的不同而已,这块就不细说。

代码语言:txt
AI代码解释
复制
void Looper::rebuildEpollLocked() {
    。。。
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); 
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    。。。
}

再到rebuildEpollLocked这个方法中,可以看到通过epoll_create创建了一个epoll专用的文件描述符,EPOLL_SIZE_HINT表示mEpollFd上能监控的最大文件描述符数。最后调用epoll_ctl监控mWakeEventFd文件描述符的Epoll事件,即当mWakeEventFd中有内容可读时,就唤醒当前正在等待的线程.。

这里不了解的人可能听着晕,上面这么一大段一句话概括就是:Android native层用了Epoll模型。什么是Epoll模型呢?我先简单介绍一下。

Epoll(必看!!!)

为什么要引入呢?

在Looper.loop的时候提到了,android不会简单粗暴地真的执行啥都没干的死循环。刚才说了,问题出在queue.next。Epoll干的事就是: 如果你的queue中没有消息可执行了,好了你可以歇着了,等有消息的我再告诉你。这个queue.next就是“阻塞”(休眠)在这里。

Epoll简单介绍

1、传统的阻塞型I/O(一边写,一边读),一个线程只能处理一个一个IO流。

2、如果一个线程想要处理多个流,可以采用了非阻塞、轮询I/O方式,但是传统的非阻塞处理多个流的时候,会遍历所有流,但是如果所有流都没数据,就会白白浪费CPU。

。。。

于是出现了select和epoll两种常见的代理方式。

3、select就是那种无差别轮询的代理方式。epoll可以理解为Event poll,也就是说代理者会代理流的时候也伴随着事件,因此有了对应事件,就可以避免无差别轮询了。

4、其通常的操作有:epoll_create(创建一个epoll)、epoll_ctl(往epoll中增加/删除某一个流的某一个事件)、epoll_wait(在一定时间内等待事件的发生)

代码语言:txt
AI代码解释
复制
 eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);

好了,我们结合Looper初始化的代码来读一下epoll在这里干了什么吧。

我直接翻译了:往mEpollFd代理中、注册、一个叫mWakeEventFd流、的数据流入事件(EPOLLIN)

这样大家应该懂了吧。。。

接上MessageQueue在初始化后,在native创建了一个Looper。

我们继续消息的发送和提取在native层的表现。其实native层主要负责的是消息的调度,比如说何时阻塞、何时唤醒线程,避免CPU浪费。

native发送

发送在native比较简单,handler发送消息后,会到MessageQueue的enqueueMessage,此时在线程阻塞的情况下,会调用nativeWake来唤起线程。

代码语言:txt
AI代码解释
复制
void NativeMessageQueue::wake() {
    mLooper->wake();
}

代码语言:txt
AI代码解释
复制
 void Looper::wake() {
    。。。
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
      。。。。
    }
}

这里TEMP_FAILURE_RETRY是一个宏定义,顾名思义,就是不断地尝试往mWakeEventFd流里面写一个无用数据直到成功,以此来唤醒queue.next。这部分就不多说了。

native消息提取

也就是queue.next

代码语言:txt
AI代码解释
复制
 Message next() {
        。。。
        for (;;) {
           。。。
            nativePollOnce(ptr, nextPollTimeoutMillis);
           。。。
        }
    }

可以看到,又是一个死循环(阻塞)。继续往下看

代码语言:txt
AI代码解释
复制
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    。。。
    mLooper->pollOnce(timeoutMillis);
    。。。
}

mLooper->pollOnce

mLooper->pollInner

代码语言:txt
AI代码解释
复制
int Looper::pollInner(int timeoutMillis) {
    。。。
    int result = POLL_WAKE;
    mPolling = true;
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    
    mPolling = false;
    mLock.lock();

    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } 
        。。。
    }
    。。。
    mLock.unlock();
    。。。
    return result;
}

在这里,我们注意到epoll_wait方法,这里会得到一段时间内(结合消息计算得来的)收到的事件个数,这里对于queue来说就是空闲(阻塞)状态。过了这个时间后,看看事件数,如果为0,则意味着超时。否则,遍历所有的事件,看看有没有mWakeEventFd,且是EPOLLIN事件的,有的话就真正唤醒线程、解除空闲状态。

消息机制在native层的主要表现就是这些。

最后,画了一个粗糙、且不太准确图仅供参考学习

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
2026python实战——如何利用海外代理ip爬取海外数据
家人们!随着跨境电商的发展,是不是越来越多的小伙伴们也开始搞海外的数据分析了?不过虽然我们已经整天爬虫、数据采集打交道了,但一到海外数据,还是有不少人掉进坑里。你们是不是也遇到过以下情况:花了一堆时间结果被网站拦截、IP被封、爬虫跑几次就挂掉……海外数据采集分析起来远没有那么轻松简单。别慌,今天就手把手教你用海外代理IP高效爬取 Zillow 房产数据,看完保准能上手!
阿秋数据采集
2025/07/18
4310
用海外代理IP监测YouTube广告效果:一看就会的实操指南
咱先来说个问题:为啥要监测这些YouTube的广告投放效果呢?其实,YouTube早已经不止是一个视频平台啦,它更像是一个全球营销战场。从大品牌推广、新兴产品到个人创作者,他们都活跃在这片“无限广告屏”里。你会发现什么“快速挑战”、什么“产品开箱”,很多营销趋势都源于这些广告视频的互动数据。
阿秋数据采集
2025/08/22
4453
Python实战:如何利用海外代理IP采集加拿大机票酒店价格
在全球市场中,机票和酒店价格往往存在显著差异。以加拿大多伦多、温哥华、蒙特利尔、卡尔加里和渥太华五大城市为例,同一家连锁酒店在不同城市的价格差可能高达40-60%,而机票价格在不同预订平台和不同时间点的差异也可达到30%以上。
阿秋数据采集
2025/09/30
1630
Python爬虫进阶实战:用海外代理ip批量采集 eBay 爆款商品
如果说跨境电商是一场没有硝烟的战争,那么数据就是最锋利的武器。 在 eBay 这样的国际电商平台上,爆款商品的背后,往往意味着:
阿秋数据采集
2025/09/16
2300
用Python采集CBC新闻:如何借助青果网络海外代理IP构建稳定采集方案
CBC 新闻作为加拿大广播公司旗下的媒体平台,在全球新闻传播领域占据重要地位。它以深入的报道、广泛的覆盖面以及专业的视角,对国际政治格局、全球经济走势、科技创新动态、文化交流融合等多方面进行全方位呈现,已成为追踪北美政策动向与国际关系的重要信息源。在当前复杂多变的国际形势下,CBC 新闻所提供的信息能够帮助我们洞察国际政治势力的博弈,分析经济政策调整对全球市场的影响,跟踪前沿科技突破带来的产业变革,理解不同文化间的碰撞与交流。
阿秋数据采集
2025/06/30
4060
Python爬虫实战:利用代理IP获取招聘网站信息
免费「python+pycharm」 链接:https://pan.quark.cn/s/48a86be2fdc0
富贵软件
2025/09/24
1420
从信息捕获到多维研判的链路解析
在资讯快速流动的环境中,热点话题的形成往往只需要几分钟。对机构或个人而言,真正的挑战并不是单纯获取页面内容,而是如何 及时感知突发信息,并从海量报道中提炼出趋势与焦点。
jackcode
2025/08/27
2170
从信息捕获到多维研判的链路解析
Ownips+Coze海外社媒数据分析实战指南
在当今数字化浪潮中,社交媒体已深深融入人们的日常生活,并为企业、研究机构及个体研究者提供了宝贵的数据资源。从Twitter、Facebook、Instagram、LinkedIn等多元化平台高效采集数据,并进行深入分析,我们能够洞察用户行为、市场动向、公众情感等关键信息。这些信息对于市场分析、社会研究、品牌监控及其他各种分析工作至关重要。
中杯可乐多加冰
2024/05/29
6990
Python爬虫实战:如何利用代理IP高效爬取Bilibili视频评论数据(附完整代码)
在当今时代,Bilibili(哔哩哔哩)作为国内知名的弹幕视频网站,以内容创作为载体,拥有海量活跃的用户,其评论数据也承载了用户的真实观看体验,这些评论数据对于了解用户喜好、分析内容创作趋势以及进行市场调研具有重要价值。但众所周知,Bilibili的反爬虫机制较为严格,有用相对完善的反爬虫体系(如IP检测、频率限制及登录验证等),一旦爬取任务超出阈值,IP很容易被限制或封禁。
阿秋数据采集
2025/03/26
4210
​AI+代理IP手把手教你爬取某度
最近Deepseek火遍大江南北,很多朋友都会用它制作一些小型程序商用,其中不乏有网络爬虫。以前对于中小企业来说,“写个爬虫”似乎是一件遥不可及的事情。
袁袁袁袁满
2025/04/18
2640
python爬取Boss直聘,分析北京招聘市场
在当前的经济环境下,整体市场发展出现了低迷的趋势,许多求职者面临着找工作困难的局面。尤其是在深圳这样的大城市,竞争异常激烈,求职者需要更加精准地寻找与自己能力相匹配的工作岗位。让自己的能力和需求得到最大化的满足,需要我们了解市场需求和招聘动态,从这场激烈的求职竞争中脱颖而出。
阿秋数据采集
2025/01/17
5700
如何用Python搭建一个好用的海外代理IP池子?
作为一名深耕爬虫领域的工程师,你一定深有体会:在全球化背景下,数据采集的需求早已不再局限于本地。当目标网站部署在海外服务器上时,IP访问限制、速度瓶颈以及资源稳定性的挑战,常常让任务复杂化。而一个高效的海外代理IP池,便是解决此类难题的重要技术手段。
阿秋数据采集
2025/04/16
4720
Python爬取闲鱼价格趋势并可视化分析
一、项目背景与目标 闲鱼作为国内领先的二手交易平台,拥有海量的商品信息和价格数据。这些数据蕴含着丰富的市场信息,但平台本身并不提供直接的价格趋势分析功能。通过Python爬虫技术,我们可以自动化地收集这些数据,并利用数据分析和可视化工具,揭示商品价格的动态变化规律。 本文的目标是实现以下功能: 1使用Python爬虫技术爬取闲鱼上特定商品的价格数据。 2对爬取的数据进行清洗和预处理。 3利用数据可视化工具(如Matplotlib或Seaborn)绘制价格趋势图。 4分析价格趋势,为买卖双方提供决策支持。
小白学大数据
2025/07/09
4240
2025python实战:利用海外代理IP验证广告投放效果
你有没有遇到这种场景:团队投放了一个海外广告,明明预算烧了不少,却心里七上八下,担心广告到底在目标区域是否好好展示?可能东南亚的消费者该看到折扣广告,美国那边应该秀新品发布……但问题是,如果你不在海外,怎么验证这些广告在其他国家的人是否能看到,广告被正常展示呢?
阿秋数据采集
2025/05/29
4040
Python用代理IP获取抖音电商达人主播数据
在当今数字化时代,电商直播已成为一种重要的销售模式,吸引了大量消费者和商家。抖音电商汇聚了众多的大人主播,他们通过直播带货,创造了一次次惊人的销售额。对于品牌和商家来说,了解抖音电商达人主播的数据对于制定营销策略、选择合作伙伴以及分析市场趋势至关重要。因为平台的机制,了解这些主播达人的销售表现、粉丝互动等关键信息要直接从抖音平台获取这些数据并非易事。但,如果我们使用代理IP结合Python爬虫技术,就可以高效地获取所需数据。
阿秋数据采集
2025/01/15
1K2
如何利用海外代理IP分析Instagram网红穿搭博主的潮流趋势?
不知道你们有没有这种感觉,每次刷到那些外网的时尚博主,总觉得他们的风格特别抓眼球,像欧美的优雅复古、日本的清新干净,或者日韩那种甜酷风,每一套穿搭都特别有灵感!那问题来了,作为对时尚或者数据分析感兴趣的你,怎么才能从这些博主的内容中提取时尚密码,去分析出这些内容背后的流行趋势呢?
阿秋数据采集
2025/07/31
4180
Python爬虫实战:快手数据采集与舆情分析
在短视频时代,快手作为国内领先的短视频平台之一,积累了海量的用户数据、视频内容和互动信息。这些数据对市场分析、用户行为研究、舆情监测等具有重要价值。本文将介绍如何使用Python爬虫技术采集快手数据,并基于NLP(自然语言处理)进行简单的舆情分析。
小白学大数据
2025/06/13
4661
[爬虫+数据分析] 分析北京Python开发的现状|文末送书5本
相信各位同学多多少少在拉钩上投过简历,今天突然想了解一下北京Python开发的薪资水平、招聘要求、福利待遇以及公司地理位置。既然要分析那必然是现有数据样本。本文通过爬虫和数据分析为大家展示一下北京Python开发的现状,希望能够在职业规划方面帮助到大家!!!
龙哥
2019/07/30
7900
[爬虫+数据分析] 分析北京Python开发的现状|文末送书5本
用Python爬取Twitter数据的挑战与解决方案
你是一个数据分析师,你想用Python爬取Twitter上的一些数据,比如用户的昵称、头像、发言、点赞、转发等等。你觉得这应该是一件很简单的事情,只要用requests库和BeautifulSoup库就可以轻松搞定。但是,当你真正开始写代码的时候,你发现事情并没有那么顺利。你遇到了以下几个问题:
jackcode
2023/07/10
7.3K0
用Python爬取Twitter数据的挑战与解决方案
Python爬虫实战:快手数据采集与舆情分析
在短视频时代,快手作为国内领先的短视频平台之一,积累了海量的用户数据、视频内容和互动信息。这些数据对市场分析、用户行为研究、舆情监测等具有重要价值。本文将介绍如何使用Python爬虫技术采集快手数据,并基于NLP(自然语言处理)进行简单的舆情分析。
小白学大数据
2025/06/12
3820
推荐阅读
相关推荐
2026python实战——如何利用海外代理ip爬取海外数据
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档