2024-11-21 16:13:07
最近把本地的Android源码升级到了最新的Android 15,用于看Android源码的Android Studio for Platform也升级到了最新版本,Google的Cuttlefish最近发布了1.0版本,也顺便折腾了一下使用Cuttlefish来运行自己编译的Android系统,这里就介绍一下如何使用和遇到的问题。
Cuttlefish是谷歌推出的一种可以配置的虚拟Android设备,它可以运行在我们本地设备上,也可以运行在服务器上面,官方也提供了Docker运行的支持,理论上可以运行在本地或者服务器的Debian设备上,或者运行在Google Compute Engine上。
用官方的化来说,它是一个更接近真实设备的Android模拟器,除了硬件抽象层(HAL)之外,它和实体设备的功能表现基本上是一致的。使用它来做CTS测试,持续集成测试会有更高的保真度。
在命令行中运行它,是没有类似模拟器的UI的,我们可以通过两种方式看到它的UI,一种是通过ADB连接,另一种则是开启它的webrtc功能,在浏览器中查看和交互。而他的虚拟硬件功能,可以让我们模拟多个屏幕,测试蓝牙wifi等各种功能。
首先我们需要检查我们的设备是否支持KVM虚拟化,使用下面的命令:
|
|
如果得到一个非0的值,就是支持的。
之后我们需要有一个Android固件,可以选择去Android持续集成网站下载他们编译好的固件,也可以自己编译固件。下载固件要注意下载设备目标中带cf的,并且下载的目标CPU需要和需要运行的宿主机CPU架构一样,ARM就下载ARM的,X86就下载X86_64的,具体的操作可以看官方教程。我这里则是自己编译,使用如下代码设备我要编译的选项:
|
|
这样有了固件,还是不能够运行的。我们还需要去编译Cuttlefish,在https://github.com/google/android-cuttlefish下载源码后,在cuttlefish源码目录下执行如下代码编译和Android:
|
|
如果你很幸运的化,上面会一次成功,但是我不是个幸运儿。于是了类似如下的错误:
|
|
这个错误的原因呢,就是因为编译cuttlefish的时候使用了bazel这个构建工具,它依赖JDK,而我没有设置JAVA_HOME
这个环境变量,因此把它加入到环境变量中就好了。类似如下:
export JAVA_HOME=/usr/lib/jvm/zulu-17-amd64
设置完成之后在Cuttlefish项目目录用如下命令检查一下,看看JAVA_HOME是否设置正确:
|
|
但是搞完之后,在安装这两个deb文件的时候又遇到了问题,告诉我我电脑上的grub-common
签名有错误,这个呢是因为我之前添加了铜豌豆的软件源,grub升级的时候升级了铜豌豆的grub软件包,它和ubuntu官方的不同,于是卸载掉铜豌豆软件源,grub-common也重新安装,之后就没问题了。
这些做完之后,我们执行下面的命令设置环境,并且重启电脑就好了。
|
|
在我们的已经编译完Android系统目录中首先执行如下代码让环境初始化好:
|
|
随后执行如下的命令就可以启动Cuttlefish运行Android了:
|
|
如果你是从Android官方下载的,那么会和我这有一些区别,可以去看一下官方教程。
这个时候我们就可以通过adb看看设备是否已经启动了,也可以在浏览器中打开,在本机浏览其打开使用如下地址和端口:
https://localhost:8443/
地址一定要使用https,点击左侧的swtich按钮就可以看到UI了。 webrtc是默认打开的,关于它的命令行更多使用方式可以查看官方文档,可以使用如下的命令查看。
|
|
而关闭Cuttlefish,也很简单,使用如下的命令:
|
|
2023版本的Android Studio for Platform(以下简称Asfp)在打开的时候是有一个单独的Open Aosp project
选项的,而新版本的这个选项去掉了。刚刚使用它的时候我还一脸懵逼,测试了Import和Open都不行,结果最后发现新版的New
选项就直接是导入Aosp工程了。
使用方式如下图。
我们可以根据上图选择我们需要导入的Module,选择Asfp给我们生成的项目文件存放的位置,之后Asfp会执行lunch
的操作和它需要的一些依赖构建。在我们选定的目录下面也会生成一个asfp-config.json
文件,它就是我们的项目设置,如果我们以后有变化了(比如想看不同的模块的代码),也可以直接修改这个文件。
|
|
参考内容和资料:
2024-11-19 23:40:52
最近因为孟岩在无人知晓播客中推荐了鱼不存在这本书,于是在微信读书中把它很快给读完了。
书不长,挺快就看完了。作者露露·米勒因为自己出轨和男友分手,自己的生活一团糟,所以她才开始研究起大卫·斯塔尔·乔丹。
从书的前半部分我知道了大卫是一名分类学家,同时还是斯坦福大学的首任校长。他的一生在追求建立秩序,小的时候他研究植物的分类,长大了开始研究鱼的分类,给鱼命名。经历了地震之后,他顽强的恢复,并通过用针把鱼和铭牌缝到一起来恢复秩序。
而书的后半部分,则转到了大卫的另一面,他推崇优生学,参与推动美国把优生绝育写到法律当中,作者崇拜的大卫完全变成了另一个人,而这要比希特勒的纳粹理论还要早,并且给纳粹提供了理论。而很多人因为大卫倡导的优生理论被进行绝育或者被歧视,作者访问了其中的两位。而大卫本人却获得了成功的一生。 到书的最后,作者又发现了鱼这个分类被最新的支序分类学判定为不存在,如果这么说的话,大卫的工作是否就不存在了。 书中穿插着对于大卫的描述,也包含了作者自己生活的描述,以及她对于大卫痕迹的追寻。
世界是无序和混沌的,我们每个人都很渺小,大卫用他的力量去构建他认为的秩序,而这也是他做很多事情的动力。做为个体的我们需要接受自己的渺小和日常的混沌。
以下为内容摘抄:
◆ 科学价值与美学趣味不同,前者的特质之一就是关注隐秘角落里微不足道的事物。
◆ 生命没有意义,无所谓意义
◆ 混乱是我们唯一的统治者。
◆ 这就是大卫·斯塔尔·乔丹吸引我的原因。我想知道,是什么驱使他不断举起缝衣针修补世界的混乱,罔顾所有告诫他不会成功的警示。他是否偶然发现了一些技巧,一剂充满希望的解药,用以消除世界的漠然?他是个科学家,所以他的坚持不懈背后也许有什么东西,能够与爸爸的世界观契合,我紧紧抓住这一丝微弱的可能性。或许他发现了关键:如何在毫无希望的世界里拥有希望,如何在黑暗的日子里继续前行,如何在没有上帝支持的时候坚持信念。
◆ 那是舌尖上的蜜糖、无所不能的幻想、秩序带来的愉悦感
◆ 科学世界观的问题在于,当你用它来探寻生活的意义时,它只会告诉你一件事:徒劳无功。
◆ “由此观之,生命何等壮丽恢宏。”
◆ 不可摧毁之物与乐观毫无关系,相反,它比乐观更深刻,处于意识的更深处。不可摧毁之物是我们用各种符号、希望和抱负粉饰的东西,并不要求我们看清它真实的模样。
◆ 学会换一种方式看待发生在自己身上的事情之后,那些经历创伤的人能够更快获得内心的平静。
◆ 我们行走在人世间,心里明白这个世界根本不在意我们的死活,不管我们如何努力,都不一定能够成功。
◆ 我们时刻在同数十亿人竞争,在自然灾害面前毫无还手之力,而我们热爱的每一件事物最终都会走向毁灭的结局
◆ 其中最重要的特质就是遭遇挫折后继续前进的能力,即便没有任何证据显示你的目标有可能实现,你也能不断地奋勇向前
◆ 在混乱的旋涡中,那残酷无情的真相昭然若揭:你无关紧要。
◆ 从星辰、永恒或优生学视角下的完美状态来看,一个人的生命似乎无关紧要,我们不过是一颗微粒上的一颗微粒上的一颗微粒,转瞬即逝。但这也只是无尽观点中的一个观点而已。在弗吉尼亚州林奇堡的一套公寓里,一个看似无关紧要的人会变得意义重大。她是替身母亲,是欢笑之源,她支撑着另一个人度过最黑暗的时光。
◆ 鱼不存在,“鱼类”并不存在。这个对大卫至关重要的分类,他陷入困境之时寻求慰藉的种类,他穷尽一生想看清的物种,根本不存在。
◆ 我们对周围的世界知之甚少,即便对脚边最简单的事物也缺乏了解。我们曾经犯过错,之后还会继续犯错。真正的发展之路并非由确定无疑铺就,而是由疑问筑成,因此需要保持“接受更正”的状态。
关于我的读后感,自认为写的不好,这本书的题材很吸引人,内容既有科普,又有关于大卫的传记,又有关于人生意义和哲理的思考,感兴趣还是要自己去读一读这本书。
最后附上孟岩这期播客的地址:https://www.xiaoyuzhoufm.com/episode/6720836fbad346ebe6399017
2024-11-03 21:40:24
本来准备一号就写的十月月报,然而在路上写了几笔就放下了,拖到了3号才写出来。本月主要介绍国庆出行,观看小宇宙播客漫游日,买了新NAS等。
国庆节回了一趟媳妇老家,在安庆附近,周边玩的很多,之前已经去过了不少地方,也是担心人多,这次只去了安庆看了博物馆和附近新开的集贤时空,之前的文章都写过.另外去亲戚家的时候,发现周围有个石屋寺,便开车去看了一下顺便爬了那边的大青山,听家长说这个寺庙不是每天都开的,因此我们过去的时候并不太热闹,正殿的门也都是关上的.大青山名字很大,其实也只是一个小山,爬到山顶可以看到远处的长江和长江大桥风景倒是还不错。以下是在那边拍的几张照片。
国庆回去之前就已经购买了一些木工的工具,于是国庆在家的几天用家里的旧木头做了个丑木凳,木工还未能入门,下次回家继续练习。
其余的几个周末,上海基本都是阴雨天,本来计划的外出爬山也只能作罢。而其中的某一个周末,小宇宙在上生新所举行播客漫游日,这个地方没有去过,便带着老婆小孩前往打卡,即参与了小宇宙的活动,也打卡了这边有名的茑屋书店。
小宇宙的活动搞得很不错,现场气氛很浓,有播客主播现场开讲,围了许多观众,非常热闹,而一些专场因为没有提前预约因此没有机会去旁听。
这个活动利用手机的NFC功能来进行现场互动,包括展示个人信息,交换贴纸等,很有意思。另外还设置了几个打卡点进行电子印章打开收集贴纸,这个形式也挺新颖的。在现场通过NFC获取个人数据大屏展示,很有意思,我的播客收听记录如下。
趁着双十一终于入手了一台成品NAS,型号为TS464C2,详情点击链接了解,除了稳文中提到的服务外,另外和搭建了Alist
和Sun-Panel
,前者主要为了接入阿里云盘到NAS,后者则是将众多的内网服务统一放大一个页面上去,它还支持内外网不同的链接,目前使用下来体验不错,下面是我的导航页展示。
另外为了给娃腾个书桌,我在用的桌子给娃了,自己换了一个更大一点的松木桌,不过要提醒大家,淘宝上面400以下的松木桌子购买需慎重,这个油漆气味是真不小。弄了新桌子,电脑,路由器等等各种线路也重新整理了一下。
之前使用Google Sheets也只是简单使用了它的表格功能,最近想要做一个数据统计功能,就问了一下ChatGpt,结果发现Google Sheets可以自己写脚本来更新数据以及和表格进行交互,而最后借助于GPT也实现了相关功能,只能大呼一声牛逼。虽然最近几年各种的airtable,多维表格很流行,看起来他们能够实现的功能,大多数通过Google Sheets依然能够实现,如果你感兴趣也可以去看看Google Apps Script
,语法基本和JavaScript差不多。
之前立的学习英语的Flag,这个月坚持了十天就放弃了,其中一个原因是多邻国不太合适,过于简单并且这个方式感觉不太适合我,后面需要找找别的方式学习。
Android源码方面,这个月分析了广播接收器相关和消息循环相关的代码,后面因为更新Android源码到Android 15以及更新Asfp导致新代码阅读有点问题,后面10天内就没有看代码了。
另外业余时间,这个学还开始看了看Rust,这是一门性能媲美C++的语言,它又通过所有权来解决了内存回收的问题,目前还只是了解了一些基础语法、一些基本的使用,后面需要继续学习。为什么要学它呢,因为现在很多地方都引入了它,Android系统源码中也能够看到Rust的身影,很多的系统模块未来也会采用它来进行开发。
这个月影视的主题是漫威,周末下雨待在家的时间把复仇者联盟四部,钢铁侠两部,雷神四部,重新看了一遍,大人小孩都喜欢。之前有的部分并没看,有些剧情因为隔了很久都忘记了,这次算是重新温习了一遍。
《逆行人生》上线了流媒体平台,也抽空看了一下,同样作为大龄程序员,有家要养,看得我鸭梨山大。但是电影部分的内容倒是不大真实,现在的就业环境确实差,但也不至于找不到工作的程度。另外外卖这个行业也不如电影中这么卷,在某天路上与外卖员闲聊后,甚至我都有了去体验一下的冲动,但是听说众包都是垃圾单,还没开始就直接放弃了。
《读库2404》中的文章还有许多没看,这个月看了其中的《我在郑州跑代驾》和《我在上海开出租》,《我在郑州跑代驾》为作者找不到工作后通过代驾赚钱的经历,其中了解到了赚钱的艰辛,《我在上海开出租》则更多是关于从司机的视角看到的乘客众生相。对于我们来说,虽然没有从事相关职业,不过也是了解行业侧面的机会。
上个月看了一半的《简读中国史》看完之后,这个月开始看桥水基金创始人达利欧创作的《原则》一书。这本书也算是一本脍炙人口的畅销书了。目前已经看完他的经历介绍和生活原则的部分,从他的经历了解了他如何白手起家以及他经历的挫折,以及他如达达成现在的成就。这本书就是他介绍的他的帮助他成功的一些践行原则,从目前已经看的部分我的最大感悟就是,做人做事要保持谦逊和心胸开阔,做事情可以遵循五步流程,从而不断的通过失败和问题来驱动自己进步,与人相处要理解人与人的不同。而他所说的原则,其实很多方面可以看到和国内的很多互联网大厂所提倡的文化观或者企业价值其实有很多相似点。目前我对于本书的理解还比较粗浅,仍需要等待读完一遍之后再次进行研读。
在农村待在的国庆节是放松闲适的,回到城市后又进入紧张的工作当中。月初的股市对于我们所有人来说都是当头棒喝,而其后则算是恢复其常态。折腾没有尽头,入了NAS,算是集齐了中年人三宝(NAS,路由,充电头)。
这是我的第五个月月报,感谢你的浏览,下月再会。
2024-10-29 21:57:17
之前是用树莓派连个两盘位硬盘盒运行一些服务,由于它的稳定性加上容量不够,一直想弄一个NAS,趁着双十一到来,就入手了威联通的NAS,本文介绍 一下购入的抉择以及NAS的初始化和相关的设置。
NAS这个东西知道了很多年了,一直想要搞一个,迫于家里花费的紧张,之前一直是使用一台树莓派4B,其中刷了Openwrt系统,挂载了两块盘的硬盘盒,其中开启了Webdav, Samba,Jellyfin相关的东西。不过因为Jellyfin挂载阿里云盘速度不太理想,有不少视频还是下载到自己的硬盘里面的。同时内,硬盘也出现了拷贝大文件就出现问题,需要重启硬盘盒和系统的问题,这个后续会继续说。
DIY NAS硬件或者成品的NAS也关注了有挺长一段时间,迫于以上问题,以及文件越来越多,当时买的这两块2T的硬盘,容量已经不够用了,想要购买一个NAS的想法更加加强,终于决定今年双十一搞个NAS。
购买NAS是有两个选择,自己组装硬件,安装飞牛或者黑群晖等NAS系统,又或者购买群晖、威联通等成品NAS。在V2EX发帖求助,以及自己的纠结中,最终在性价比和稳定性等各种因素比较之后,选择入手了威联通TS464C2。
威联通的系统虽然被大家诟病许久,但是它也算是市场上除了群晖之外NAS系统做的最久的厂家了,考虑到文件的安全可靠在文件系统和系统稳定性上面,这两家还是要比国内的新起之辈更加值得信赖的。而我选择的这一款,支持内存扩展,如果以后服务比较多,可以再增加一根内存。4个3.5寸硬盘位加上两个NVME 硬盘位,对于容量的扩展应该很多年都不存在问题了。双十一这块机器只要2000块钱就拿下,而群晖同配置的4盘位差不多要四千,只能说高攀不起。
另外下单了一块国产的NVME 2T硬盘,加入Qtier存储池,希望能提高一些速度。为了拥有更大的容量,经过一些研究,淘宝购入了一块2手服务器硬盘,型号为HC550, 16TB,回来看Smart信息,已经运行了786天,不过其他信息看着都不错。
收到机器,插上硬盘,参照指南开始初始化。威联通提供了比较友好的初始化方法,可以通过网页或者应用对它进行初始化,不过一定要插上硬盘才能开始这一切。
根据指南初始化之后,开始了硬盘的初始化和存储池的设置。之前使用的openwrt中对于硬盘的管理是比较简单的,基本就是实现了基础的Linux功能,把磁盘挂载到指定的目录,硬盘初始化之类的。而QNAP中,“存储与快照总管应用”中,对于硬盘和存储卷的设置则全面,可以设置各种raid的存储池,Qtier,快照,卷等等,也有硬盘的运行情况的显示。我想这就是选择大厂成品NAS的原因,毕竟docker之类的东西大家都很容易做,但是这种积累了很多年的东西不是那么快能够做出来的。
在威联通NAS中安装软件可以选择从QNAP的应用中心安装应用,也可以安装Container Station之后通过docker来安装。不过官方的应用中心中的应用中主要是一些官方提供的应用,这个时候我们可以选择第三方的应用中心,这里我推荐一个: https://www.myqnap.org/repo.xml,官方应用商店没有的可以来这里试试。不过这个应用商店中的部分应用是收费的,比如Jellyfin,它提供的版本需要依赖Apache,这个时候你需要去它的网站上面购买,价格还不便宜,当然我是不会去购买的。
除了应用中心安装之外,我们还可以去网上找QPKG文件,比如Jellyfin,我就是使用的pdulvp
为QNAP定制的版本,下载地址在:https://github.com/pdulvp/jellyfin-qnap/releases。Jellyfin我不使用官方的docker版本有两个原因,一是使用这个定制版本,可以方便的使用英特尔的集成显卡进行视频的硬解,另一方面是使用docker的化,默认只能映射一个媒体目录到Docker中,想要映射多个目录会麻烦一点,因此使用QPKG更方便。
对于其他的应用,比如FreshRss, VaultWarden则是选择了使用Docker进行部署,Container Station的Docker部署为先写一个compose文件,之后软件会帮助下载相关的容器进行启动,这个有个问题就是创建完compose之后,容器启动起来之后,在web界面上就没法编辑compose文件了,想要编辑的需要用ssh进终端去看,比如我创建的app-freshrss
它的compose文件就在/share/Container/container-station-data/application/app-freshrss
当中。
另外威联通自带的一些应用,文件管理,QuMagie,特别要说一下QuMagie,它已经可以帮我把相片按照人物,地点,物品等等分类好了,配合上手机App使用流畅很多,再也不用之前那样使用SMB手动同步了。
目前用了这个二手服务其硬盘加上新买的固态硬盘组了一个Qtier池作为主要存储区,家里有块旧的sata固态硬盘就把他搞成高速缓存加速了。原来的两块酷狼硬盘都是EXT4格式,但是插到QNAP上却不能识别,只好放在原来的设备上,新NAS通过Samba访问原先的设备,把文件拷贝过来。
之后把旧的硬盘插上来使用才发现,其中一个硬盘出现了坏道,数量还不少,感觉它应该命不久矣,不敢放什么东西上来了。 而另一块好的硬盘,准备把它作为备份盘,相片,笔记和其他的一些重要文件都定期备份到这个盘上面。因为硬盘数量优先,并没有组RAID还是空间优先,只把重要的文件备份但愿以后不会踩坑。
以上就是这个新NAS的初体验了,后面还要继续增加新的应用,仍然需要摸索,外网访问仍然沿用家里的DDNS和端口转发。目前才用了不到一个星期,还有很多东西没有用到没有涉及,后面有新的体验来再继续写文章分享,也欢迎玩NAS网友一起交流分享,如果有好玩的应用也欢迎评论推荐给我。
2024-10-17 19:40:16
广播,顾名思义就是把一个信息传播出去,在Android中也提供了广播和广播接收器BroadcastReceiver,用来监听特定的事件和发送特定的消息。不过广播分为全局广播和本地广播,本地广播是在Android Jetpack库中所提供,其实现也是基于Handler和消息循环机制,并且这个类Android官方也不推荐使用了。我们这里就来看看Android全局的这个广播。
应用开发者可以自己发送特定的广播,而更多场景则是接收系统发送的广播。注册广播接收器有在AndroidManifest文件中声明和使用代码注册两种方式,在应用的target sdk大于等于Android 8.0(Api Version 26)之后,系统会限制在清单文件中注册。通过清单方式注册的广播,代码中没有注册逻辑,只有PMS中读取它的逻辑,我们这里不进行分析。
首先是注册广播接收器,一般注册一个广播接收器的代码如下:
|
|
使用上面的代码就能注册一个广播接收器,当手机开始充电就会收到通知,会去执行MyBroadcastReceiver
的onReceive
方法。
那我们就从这个registerReceiver
来时往里面看,因为Activity是Context的子类,这个注册的方法的实现则是在ContextImpl
当中,其中最终调用的方法为registerReceiverInternal
,代码如下:
|
|
我们在注册广播的时候只传了两个参数,但是实际上它还可以传不少的参数,这里userId就是注册的用户id,会被自动 填充成当前进程的用户Id,broadcastPermission
表示这个广播的权限,也就是说需要有该权限的应用发送的广播,这个接收者才能接收到。scheduler
就是一个Handler
,默认不传,在第8行可以看到,会拿当前进程的主线程的Handler
,flag
是广播的参数,这里比较重要的就是RECEIVER_NOT_EXPORTED
,添加了它则广播不会公开暴露,其他应用发送的消息不会被接收。
在第10行,这里创建了一个广播的分发器,在24行,通过AMS去注册广播接收器,只有我们的broadcast会用到contentprovider或者有sticky广播的时候,30行才会执行到,这里跳过。
首先来看如何获取广播分发器,这块的代码在LoadedApk.java
中,代码如下:
|
|
先来说一下mReceivers
,它的结构为ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>>
,也就是嵌套了两层的ArrayMap
,外层是以Context为key,内层以Receiver
为key,实际存储的为ReceiverDispatcher
。ReceiverDispatcher
内部所放的IIntentReceiver
比较重要,也就是我们这个方法所返回的值,它实际是IIntentReceiver.Stub
,也就是它的Binder实体类。
这段代码的逻辑也比较清晰,就是根据Context
和Receiver
到map中去查找看是否之前注册过,如果注册过就已经有这个Dispatcher
了,如果没有就创建一个,并且放到map中去,最后返回binder对象出去。
在AMS注册的代码很长,我们这里主要研究正常的普通广播注册,关于黏性广播,instantApp的广播,以及广播是否导出等方面都省略不予研究。以下为我们关注的核心代码:
|
|
在前面ContextImpl中调用AMS注册Reciever的地方,我们传的就是Receiver的Binder实体,这里拿到的是binder引用。在代码中我们可以看到,首先会以我们传过来的receiver的binder对象为key,到mRegisterReceivers
当中去获取ReceiverList
,这里我们就知道receiver在System_server中是怎样存储的了。如果AMS当中没有,会去创建一个ReceiverList
并放置到这个map当中去,如果存在则不需要做什么事情。但是这一步只是放置了Receiver
,而我们的Receiver对应的关心的IntentFilter
还没使用,这里就需要继续看31行的代码了。在这里这是使用了我们传过来的IntentFilter
创建了一个BroadcastFilter
对象,并且把它放到了ReceiverList
当中,同时还放到了mReceiverResolver
当中,这个对象它不是一个Map而是一个IntentResolver,其中会存储我们的BroadcastFilter
,具体这里先不分析了。
到这里我们就看完了广播接收器的注册,在App进程和System_Server中分别将其存储,具体两边的数据结构如上图所示。这里可以继续看看发送广播的流程了。
一般我们发送广播会调用如下的代码:
|
|
我们通过设置Action来匹配对应的广播接收器,通过设置Data或者Extra,这样广播接收器中可以接收到对应的数据,最后调用sendBroadcast
来发送。而sendBroadcast
的实现也是在ContextImpl
中,源码如下:
|
|
这里代码比较简单,就是直接调用AMS的broadcastIntentWithFeature
来发送广播。
这里我们可以直接看AMS中的broadcastIntentWithFeature
的源码:
|
|
第10行代码,主要验证Intent,比如检查它的Flag,检查它是否传文件描述符之类的,里面的代码比较简单清晰,这里不单独看了。后面则是获取调用者的进程,uid,pid之类的,最后调用broadcastIntentLocked
,这个方法的代码巨多,接近1000行代码,我们同样忽略sticky的广播,也忽略顺序广播,然后来一点一点的看:
|
|
首先这里的代码是对Intent做一下封装,并且如果系统还在启动,不允许启动应用进程,以及获取当前的用户ID,大部分情况下,我们只需要考虑一个用户的情况。
|
|
对于一些系统的广播事件,除了要发送广播给应用之外,在AMS中,还会根据其广播,来调用相关的服务或者执行相关的逻辑,也会在这里调用其代码。这里我罗列了清除应用数据和时区变化两个广播,其他的感兴趣的可以自行阅读相关代码。
|
|
以上代码为根据前面拿到的userId,来决定广播要发送给所有人还是仅仅发送给当前用户,并且把userId保存到users
数组当中。
|
|
以上为获取我们注册的所有的接收器的代码,其中FLAG_RECEIVER_REGISTERED_ONLY
意味着仅仅接收注册过的广播,前面在判断当前系统还未启动完成的时候有添加这个FLAG,其他情况一般不会有这个Flag,这里我们按照没有这个flag处理。那也就会执行第4行的代码。另外下面还有从mReceiverResolver
从获取注册的接收器的代码,因为大部分情况不是从shell中执行的,因此也忽略了其代码。
首先看collectReceiverComponents
的代码:
|
|
以上就根据信息通过PMS获取所有通过Manifest静态注册的广播接收器,对其有一些处理,详见上面的注释。
对于我们在代码中动态注册的接收器,则需要看mReceiverResolver.queryIntent
的代码:
|
|
以上代码中,这个mActionToFilter
就是我们前面注册广播时候,将BroadcastFilter
添加进去的一个ArrayMap,这里会根据Action去其中取出所有的BroadcastFilter
,之后调用buildResolveList
将其中的不符合本次广播接收要求的广播接收器给过滤掉,最后按照IntentFilter的优先级降序排列。
到这里我们就有两个列表receivers
存放Manifest静态注册的将要本次广播接收者,和registeredReceivers
通过代码手动注册的广播接收者。
首先来看通过代码注册的接收器不为空,并且不是有序广播的情况,代码如下:
|
|
在这里,第4行会首先根据intent
的flag获取对应的BroadcastQueue
,这里有四个Queue,不看其代码了,不过逻辑如下:
FLAG_RECEIVER_OFFLOAD_FOREGROUND
标记,则使用mFgOffloadBroadcastQueue
。mEnableOffloadQueue
,并且有FLAG_RECEIVER_OFFLOAD
标记,则使用mBgOffloadBroadcastQueue
。FLAG_RECEIVER_FOREGROUND
,也就是前台时候才接收广播,则使用mFgBroadcastQueue
。mBgBroadcastQueue
。
拿到queue
之后,会创建一条BroadcastRecord
,其中会记录传入的参数,intent,以及接收的registeredReceivers
,调用queue
的入队方法,最后把registeredReceivers
设置为null,计数也清零。具体入队的代码,我们随后再看,这里先看其他情况下的广播入队代码。
|
|
以上的代码所做的事情就是首先移除静态注册的广播当中需要忽略的广播接收器,随后将静态注册和动态注册的广播接收器,按照优先级合并到同一个列表当中,当然如果动态注册的前面已经入队过了,这里实际上是不会在合并的。关于合并的代码,就是经典的两列表合并的算法,具体请看代码和注释。
|
|
以上的代码,跟前面入队的代码也差不多,不过这里如果采用的方法是enqueueOrderedBroadcastLocked
,并且多了关于已经发送的广播的替换的逻辑,这里我们先不关注。如果receivers为空,并且符合条件的隐式广播,系统也会对其进行记录,具体,我们这里也不进行分析了。
我们知道前面入队的时候有两个方法,分别是enqueueParallelBroadcastLocked
和enqueueOrderedBroadcastLocked
,我们先来分析前者。
|
|
这里就是将BroadcastRecord
放到mParallelBroadcasts
列表中,随后执行enqueueBroadcastHelper
,我们先看继续看一下enqueueOrderedBroadcastLocked
方法。
|
|
这里跟上面很类似,差别是这里把BroadcastRecord
入队了mDispatcher
,对于普通广播,其内部是把这个记录放到了mOrderedBroadcasts
列表。
而enqueueBroadcastHelper
方法仅仅用于trace,我们这里不需要关注。
到了这里,我们把广播放到对应的列表了,但是广播还是没有分发出去。
以上是代码入了BroadcastQueu
,接下来就可以看看队列中如何处理它了。首先需要注意一下,记录在入队的同时还调用了BroadcastQueue
的scheduleBroadcastsLock
方法,代码如下:
|
|
这里使用了Handler
发送了一条BROADCAST_INTENT_MSG
消息,我们可以去看一下BroadcastHandler
的handleMessage
方法。其中在处理这个消息的时候调用了processNextBroadcast
方法,我们可以直接去看其实现:
|
|
这里开启了同步块调用了processNextBroadcastLocked
方法,这个方法依然很长,其中涉及到广播的权限判断,对于静态注册的广播,可能还涉及到对应进程的启动等。
动态注册的无序广播相对比较简单,这里我们仅仅看一下其中无序广播的分发处理:
|
|
在这个方法中有大段的代码是判断是否需要跳过当前这个广播,我这里仅仅保留了几句权限检查的代码。对于跳过的记录会将其BroadcastRecord
的delivery[index]
值设置为DELIVERY_SKIPPED
, 而成功分发的会设置为DELIVERY_DELIVERED
。对于有序广播的分发我们这里也不予分析,直接看无序广播的分发,在分发之前会尝试给对应的接收进程添加后台启动Activity的权限,这个会在分发完成之后恢复原状,调用的是maybeAddAllowBackgroundActivityStartsToken
,就不具体分析了。
之后会调用performReceiveLocked
去进行真正的分发,代码如下:
|
|
在执行分发的代码中,如果我们的ProcessRecord
不为空,并且ApplicationThread也存在的情况下,会调用它的scheduleRegisterReceiver
方法。如果进程记录为空,则会直接使用IIntentReceiver
的performReceiver
方法。我们在App中动态注册的情况,ProcessRecord
一定是不为空的,我们也以这种情况继续向下分析。
|
|
在应用进程中,首先也只是根据AMS传过来的processState
更新一下进程的状态,随后还是调用了IIntentReceiver
的performReceive
方法,performReceive
在LoadedApk
当中,为内部类InnerReceiver
的方法:
|
|
在应用进程中,首先会获取ReceiverDisptcher
,这个一般不会为空。但是系统代码比较严谨,也考虑了,不存在的情况会调用AMS的finishReceiver
完成整个流程。
对于存在的情况,会调用ReceiverDispatcher
的performReceive
方法继续分发。
|
|
这里的代码有点绕,不过也还比较清晰,首先是创建了一个Args
对象,之后根据java的语法,如果intent不为空的时候会执行如下代码:
|
|
当这个执行失败的时候,才会看情况执行8行到第10行的代码。而这个Runnable
就是应用端真正分发的逻辑,其代码如下:
|
|
这里的receiver就是我们注册时候的那个BroadcastReceiver
,这里将当前的Args
对象作为它的PendingResult
,在这里调用了它的onReceive
方法 ,最后看pendingResult
是否为空,不为空则调用PendingResult
的finish()
方法。当我们在onReceive
中编写代码的时候,如果调用了goAsync
的话,那这里的PendingResult
就会为空。
另外就是我们这个Runnable是使用的mActivityThread
的post方法投递出去的,它是一个Handler对象,它是在注册广播接收器的时候指定的,默认是应用的主线程Handler,也就是说广播的执行会在主线程。
但是即使是我们使用goAsync
的话,处理完成之后也是需要手动调用finish
的,我们后面在来看相关的逻辑。
在前面分析的BroadcastQueue
的processNextBroadcastLocked
方法中,我们只分析了动态广播的发送,这里再看一下静态广播的发送,首先仍然是看processNextBroadcastLocked
中的相关源码:
|
|
在第3行,会从mDispatcher
中拿BroadcastRecord
的记录,我们之前在AMS端入队的代码,对于静态注册的广播和有序广播都是放在mDispatcher
当中的,这里拿到动态注册的有序广播也会从这里拿,它的后续逻辑跟前面分析的是一样的,这里不再看了。对于静态注册的广播,在调用后续的方法之前,需要先获取对应进程的ProcessRecord,和ApplicationThread,并且进行广播权限的检查,进程是否存活检查这些在我们11行的位置,都省略不看了。如果App进程存活则会走到我们12行的部分,否则会去创建对应的进程,创建完进程会再去分发广播。
动态注册的广播,会传一个IIntentReceiver
的Binder到AMS,而静态注册的广播,我们跟着第18行代码processCurBroadcastLocked
方法进去一览究竟:
|
|
在这个方法中,把App的ProcessRecord
放到了BroadcastRecord
当中,并且把ApplicationThread
设置为receiver
,最后是调用了ApplicationThread
的scheduleReceiver
,从而通过binder调用App进程。
通过Binder调用,在App的ApplicationThread
代码中,调用的是如下方法:
|
|
这里是创建了一个ReceiverData
把AMS传过来数据包裹其中,并且通过消息发出去,之后会调用ActivityThread
的handleReceiver
方法, 代码如下:
|
|
这个代码中主要有两个try-catch的代码块,分别是两个主要的功能区。因为静态注册的广播,我们的广播接收器是没有构建的,AMS传过来的只是广播的类名,因此,第一块代码的功能就是创建广播接收器对象。第二块代码则是去调用广播接收器的onReceive
方法,从而传递广播。另外这里会调用PendingResult
的finish
去执行广播处理完成之后的逻辑,以及告知AMS,不过这里的PendingResult
就是前面创建的ReceiverData
。
在分析前面的动态注册广播分发和静态注册广播分发的时候,最终在App进程它们都有一个Data,静态为ReceiverData
, 动态为Args
,他们都继承了PendingResult
,最终都会调用PendingResult
的finish
方法来完成后面的收尾工作,代码如下:
|
|
这里的QueuedWork
主要用于运行SharedPreferences
写入数据到磁盘,当然这个如果其中有未运行的task则会添加一个Task到其中来运行sendFinished
,这样做的目的是为了保证如果当前除了广播接收器没有别的界面或者Service运行的时候,AMS不会杀掉当前的进程。否则会直接运行sendFinished
方法。
|
|
这里就是调用AMS的finishReceiver
方法,来告诉AMS广播接收的处理已经执行完了。
|
|
相关的逻辑从13行开始,首先仍然是根据广播的flag找到之前的BroadcastQueue
,之后根据IBinder
找到发送的这一条BroadcastRecord
,调用Queue的finishReceiverLocked
方法。根据它的返回值,再去处理队列中的下一个广播记录。最后的trimApplicationsLocked
里面会视情况来决定是否停止App进程,我们这里就不进行分析了。
processNextBroadcastLocaked
前面已经分析过了,这里只需要来看finishReceiverLocked
方法,代码如下:
|
|
在这里,我们最关注的代码就是17行开是的代码,从mReceivers
列表中移除BroadcastRecord
,并且把ReceiverList
的curBroadcast
设置为空,并且其他几个参数也设置为空,这样才算完成了广播的分发和处理。
以上就是广播接收器的注册,以及动态、静态广播分发的分析了。关于取消注册是跟注册相关的过程,理解了注册的逻辑,取消注册也可以很快的搞清楚。关于sticky的广播,限于篇幅先不分析了。而有序广播,它在AMS端其实和静态注册的广播是差不多,不过它在调用App进程的时候是有差别的。另外关于权限相关的逻辑,以后在权限代码的分析中可以再进行关注。