阅读视图

发现新文章,点击刷新页面。

儿子入托 时间得以宽裕

小家伙在幼儿园

这周是儿子在幼儿园托班的第七周,以幼儿园学期来讲是第八周,因生病推迟了几天入园,不过为期两天的开学活动有幸带他去体验一番。儿子入园算是顺利,与我们的预想一样,入园当天不哭不闹,背着小书包开开心心就去了,且能与其他小朋友玩成一片。带他入园的当天,还是看到有蛮多小朋友因来到陌生环境而哭闹;托班中的小朋友基本没有达到3岁,幼师、生活老师的配备数量相比其他班多得多,抱的抱哄的哄玩的玩。

老师说一两周后小朋友应该会有所厌倦,话说以幼师的经验还是挺了解这些小朋友的,慢慢发现要送他去幼儿园时会出现一些不情愿的小情绪,不过仅此而已,到了班里还是开开心心的;BB分离焦虑是不存在的,反倒我们起初会担心与不舍,当然现在我和他妈已是无感了~

这段时间下来,起初的一些顾虑也打消了,老婆坚持送他去托班是对的;小家伙在幼儿园刷的经验不少,学会东西挺多,沟通、自理和认知能力都有小升级,话变得多且尚有逻辑,儿歌能唱个几首,不过尿片还没戒掉,还需努力引导。老师每天会在班级群里发小朋友们的照片视频,几乎涵盖小朋友的所有活动;还有本《家园联系手册》,老师会在里面记录儿子的情况,家长也要在里头每周写一次,写下孩子在家里的变化表现以及意愿要求,当然,都是孩子妈在写。

在幼儿园的这段期间也没少折腾,儿子得了两次感冒,第一次感冒鼻涕鼻塞,加上自己不会擤鼻涕,导致双分泌性中耳炎,治疗期间每周耳镜复查,两周后好了结果又感冒~不管是老师还是我们都有教他如何擤鼻涕,教的过程中可能会一点,过后就不会了且不主动要求清理鼻涕,没办法孩子还小,为此专门买了擤鼻器和喷雾帮他清理,搞得他对擤鼻器有些心里阴影~

幼儿园小朋友多病原体也多,短期内反复感染,常带他往医院跑,人家儿科医生都能认出他来了。这段时间接触这么多病原体,免疫系统交这么多学费学习防御,免疫系统得董事生产抗体呀,希望儿子的防御库丰富强大起来呀!~玩笑~

孩子白天交给幼儿园老师,且离家近接送起来也方便,因此我们的时间精力方面也宽裕许多。这个年龄段就送他去托班的原因之一是没人带孩子,虽然父母会过来帮忙但有时老家有什么事他们需要回去,为带小孩又要急着赶过来,俩老来回太折腾了;而现在他们可以自由意愿,可在我这也可在我弟那,更可以在老家麻将钓鱼广场舞;话说我爸妈在这边几乎没什么圈子,娱乐方面便少许多,所以老爸把他的雅马哈电子琴带了过来,我妈则结识了跳广场舞的“舞者”们。讲真,如果我们这一代没有长辈帮忙,真的会没辙! 

再迎新成员 得宝

自从进宝来到家里,老婆正式成为了铲屎官,真的唯独她任此职,对进宝悉心照顾呵护有佳,而我们只是负责撸兔。“花在它身上的时间比你儿子还多”,这是玩笑话,但也算是非常贴切;关于兔圈的知识她已非常了解且透彻,加了不少微信、小红书各路兔友群,看群人数还挺多。

前不久她帮进宝剃毛时不小心弄伤了它的腿(电动剃毛器靠太近剃秃了一个口子,弄破了些皮组织但没有出血,不过进宝受到惊吓),为此她对我说很内疚很难过很想哭,得一顿安慰,可见她对进宝真的很上心。自岳母走后,不难看出她的一部分心思落在了进宝身上;我特地去网上搜罗,在心理学上被称为”情感转移”,所以在一段时间里进宝将成为寄托情感的替代承载者。

上周老婆给进宝布置了一个比原来更大三倍的窝房,原来是为了迎接新伙伴——得宝,英文 Tempo;是一只侏儒兔,2月26日生日,怪可爱的,就在今天上午我和她去十二公里外的宠物医院,买卖双方约定的地点,把它接了回来;顺便给进宝做了一次体检,并询问了兽医何时可以把进宝给阉了,答复是不急,蛋蛋还没长大。

进宝生日是去年11月7日,转眼尽五个月大,已经开始稍有安哥拉成年兔的样子了,耳朵慢慢变垂耳毛变长,变化还是挺大的。进宝以前样貌传送门~

Mate70 Pro 优享版

老婆年初说过要把手机换成 Android,原因是 iPhone 15 Pro 电池不耐用。自我N年前认识她时一直用的就是苹果,迭代了好几部,这牌子手机就这样心里应该有点数,可能是被谁安利了吧。

我向她推荐过几家品牌旗舰,其中最中意华为 Mate70 Pro ,有那么一段时间每天定时在京东抢,根本抢不到,线下体验店也是始终缺货。很多人认为是饥饿营销,我觉得这种大众化的电子产品没必要,根本原因更趋于产能和芯片供应链问题,迫于无奈。

因为买不到,换手机念头就不了了之,倒是趁国补买了华为 WATCH GT5,不到1100元。直到本周三逛万达顺便去华为体验店,问了圈说是有 Mate70 Pro 优享版,比标准版便宜300块,店员说参数保持一致,区别是处理器降频,网上搜罗了一下,给的信息也大致如此。对于老婆来说,性能基本过剩,处理器降不降频对她毫无影响,6699元拿下512G 云杉绿!

老婆对 Mate70 Pro 优享版初体验下来,对其评价是设置和广告让她眼花缭乱,这评价中肯;其实我已经帮关掉很多不必要的东西,包括她用不到的功能、信息流和广告,试了小会还是可以接受的,慢慢习惯吧。还有个事,居然配备100W充电头,在各路手机厂商不给充电头的当下,这操作挺让我意想不到的。

我的手机也顺理地从 iPhone 14 Pro 深空黑 荣升至 iPhone 15 Pro 白色钛金属,接盘侠合理且稳定!闲置的128G 14Pro进行了回收处理,找了咸鱼、转转和京东爱回收,最终在爱回收下单;收官回收价格是我意想不到的,自己喜欢半包手机壳,没包住的边框都掉漆了,而且有一角细小块磕陷,在京东下单在线评估价格为3260元也欣然接受,待回收员上门验机后告诉我,属于正常使用痕迹,其他都很完美,敲定价格3560元;不知是亏还是赚了,反正当时心挺乐的,罢了~

Folo 移动客户端

自从有了 Follow 后,就全靠它来看博友们的更新,自建的 FreshRSS 都荒废了,有一纳闷就是何时出移动端,这下不就来了吗;Follow 在三月底的一次版本更新中把名称改为了 Folo,便好奇搜索起来,并翻看 X 上的 Follow 官方帖子,发现已经推出了移动端且上架了苹果 App Store,目前只有 iOS 版,Android 版看样子要晚些时候再推出;不过大佬们可以参考 Folo Mobile App in GitHub 来尝鲜安卓版本。

整体体验下来还不错,目前语言项只有英文一项,不过问题不大,有个功能特别喜欢就是“AI Summary”,开启该功能后,进入文章就能看到 AI 生成的当前文章概要,感兴趣的各位小伙伴们可以去体验一下。

纯粹直播TV v1.8.1 重构版,斗鱼B站虎牙抖音快手网易七合一,支持手机PC

纯粹直播TV v1.8.1 重构版
纯粹直播TV v1.8.1 重构版

纯粹直播支持平台

哔哩哔哩/虎牙/斗鱼/快手/抖音/网易cc/M38自定义源 可以选择喜爱的分区展示,或者全隐藏,只看关注,节省流量与内存

纯粹直播功能

  • 使用supabase 完成登录注册功能,邮箱为真实邮箱 白名单使用(发送注册账号到我的邮箱认证 - 点击联系) 您可自己fork项目去supabase控制台生成远程服务,具体不在赘述,只提供表字段。
  • 平台管理:多种平台选择喜欢的展示
  • 已实现Android/TV/Windows
  • 已实现WevServer(局域网连接使用手机可控制设备)
  • 已实现倒计时关闭应用
  • Android/TV 端多种播放器随意切换
  • M3u8 自定义导入网络/本地直播源,可直接使用APP打开,观看自定义内容。(导入请先打开App) 设置-本地导入-导入m3u源 部分资源请云盘自行导入
  • 弹幕过滤,弹幕合并

纯粹直播软件介绍

对于喜欢看游戏、户外、娱乐等直播的朋友,使用ASeam、纯粹直播以及SimpleLive来观看是非常好的选择,这类聚合型app都整合了目前热门的,如B站、某鱼、虎牙、某抖等平台,一个app就可以看多个平台。

在这三者中,ASeam和纯粹直播是自适应屏幕并适配了遥控,所以基本上也能在电视上使用,但毕竟不是专为电视开发的,所以还是存在很多问题,比如纯粹app在电视使用时无法全屏等。

为此,纯粹直播的开发大佬直接重构了一个TV版,专门为电视端打造,使用体验真心可以——纯粹重构电视版

纯粹直播TV v1.8.1 重构版 看7大平台 - 首页
纯粹直播TV v1.8.1 重构版 看7大平台 - 首页

纯粹直播是一款开源软件,目前已经整合了包括自定义在内的7大平台直播,可以说直接覆盖所有短视频直播平台。

这次的版本是专为电视端重新打造的版本,完全符合电视端操作习惯,颇为专业。

纯粹直播TV v1.8.1 重构版 看7大平台 - 分类类别
纯粹直播TV v1.8.1 重构版 看7大平台 - 分类类别

软件没有登录系统,所以不用登录就可以看到各个平台的热门内容和分类内容,且支持关注。

纯粹直播TV v1.8.1 重构版 看7大平台
纯粹直播TV v1.8.1 重构版 看7大平台

内容界面简单大气,可以看到各个直播间,顶部可以切换平台。

纯粹直播TV v1.8.1 重构版 看7大平台 - 直播页面
纯粹直播TV v1.8.1 重构版 看7大平台 - 直播页面

在播放界面可以设置弹幕,如大小、速度、显示区域等,也支持关注。

纯粹直播TV v1.8.1 重构版 看7大平台
纯粹直播TV v1.8.1 重构版 看7大平台

另外软件有个『网络』栏目,该栏目没有任何内容,完全交由用户自定义,支持导入网络/本地M3u8源。

在软件的设置界面可以进行一些相关播放设置和数据同步设置,数据同步主要是备份/恢复软件相关设置和关注数据。

纯粹直播TV v1.8.1 重构版 看7大平台 - 设置
纯粹直播TV v1.8.1 重构版 看7大平台 - 设置

更新日志

v1.8.1

  • FIx:电视直播源(播放失败请刷新或删除缓存)
  • 电脑加载失败 请在 C:\Windows\System32\drivers\etc 修改hostst添加:185.199.111.133 raw.githubusercontent.com

v1.8.0

  • Fixed: 弹幕速度
  • Removed:删除弹幕合并
  • Fixed: 虎牙线路无法播放以及修复自动全屏
  • New: 添加电视直播(需要网络支持IPV6)

v1.7.4

  • Fix:修复Bilibili

v1.7.3

  • Fix:修复斗鱼分类.
  • Add: Tv添加MPV播放器,VLC播放器.
  • Intro: 如果无法播放请切换播放器后重启App重试即可。

下载地址

文武科技社
文武科技社
为尊重作者劳动成果,请输入验证码查看隐藏内容
微信扫码关注本站微信公众号(文武科技社/wwkejishe),回复 验证码 获取。

使用Cuttlefish运行自编译Android固件

最近把本地的Android源码升级到了最新的Android 15,用于看Android源码的Android Studio for Platform也升级到了最新版本,Google的Cuttlefish最近发布了1.0版本,也顺便折腾了一下使用Cuttlefish来运行自己编译的Android系统,这里就介绍一下如何使用和遇到的问题。

Cuttlefish是什么

Cuttlefish是谷歌推出的一种可以配置的虚拟Android设备,它可以运行在我们本地设备上,也可以运行在服务器上面,官方也提供了Docker运行的支持,理论上可以运行在本地或者服务器的Debian设备上,或者运行在Google Compute Engine上。

用官方的化来说,它是一个更接近真实设备的Android模拟器,除了硬件抽象层(HAL)之外,它和实体设备的功能表现基本上是一致的。使用它来做CTS测试,持续集成测试会有更高的保真度。

在命令行中运行它,是没有类似模拟器的UI的,我们可以通过两种方式看到它的UI,一种是通过ADB连接,另一种则是开启它的webrtc功能,在浏览器中查看和交互。而他的虚拟硬件功能,可以让我们模拟多个屏幕,测试蓝牙wifi等各种功能。

安装Cuttlefish,编译Android固件

首先我们需要检查我们的设备是否支持KVM虚拟化,使用下面的命令:

1
grep -c -w "vmx\|svm" /proc/cpuinfo

如果得到一个非0的值,就是支持的。

之后我们需要有一个Android固件,可以选择去Android持续集成网站下载他们编译好的固件,也可以自己编译固件。下载固件要注意下载设备目标中带cf的,并且下载的目标CPU需要和需要运行的宿主机CPU架构一样,ARM就下载ARM的,X86就下载X86_64的,具体的操作可以看官方教程。我这里则是自己编译,使用如下代码设备我要编译的选项:

1
lunch aosp_cf_x86_64_phone-trunk_staging-eng

这样有了固件,还是不能够运行的。我们还需要去编译Cuttlefish,在https://github.com/google/android-cuttlefish下载源码后,在cuttlefish源码目录下执行如下代码编译和Android:

1
2
3
tools/buildutils/build_packages.sh
sudo dpkg -i ./cuttlefish-base_*_*64.deb || sudo apt-get install -f
sudo dpkg -i ./cuttlefish-user_*_*64.deb || sudo apt-get install -f

如果你很幸运的化,上面会一次成功,但是我不是个幸运儿。于是了类似如下的错误:

1
While resolving toolchains for target //src/tools/ak/generatemanifest:generatemanifest (6312974): invalid registered toolchain '@local_jdk//:bootstrap_runtime_toolchain_definition': no such target '@local_jdk//:bootstrap_runtime_toolchain_definition': target 'bootstrap_runtime_toolchain_definition' not declared in package '' defined by /home/sam/.cache/bazel/_bazel_jcater/ddb4e20e0e2e6bca92f5deeef02ce168/external/local_jdk/BUILD.bazel (Tip: use `query "@local_jdk//:*"` to see all the targets in that package)

这个错误的原因呢,就是因为编译cuttlefish的时候使用了bazel这个构建工具,它依赖JDK,而我没有设置JAVA_HOME这个环境变量,因此把它加入到环境变量中就好了。类似如下:

export JAVA_HOME=/usr/lib/jvm/zulu-17-amd64

设置完成之后在Cuttlefish项目目录用如下命令检查一下,看看JAVA_HOME是否设置正确:

1
bazel info java-home

但是搞完之后,在安装这两个deb文件的时候又遇到了问题,告诉我我电脑上的grub-common签名有错误,这个呢是因为我之前添加了铜豌豆的软件源,grub升级的时候升级了铜豌豆的grub软件包,它和ubuntu官方的不同,于是卸载掉铜豌豆软件源,grub-common也重新安装,之后就没问题了。 这些做完之后,我们执行下面的命令设置环境,并且重启电脑就好了。

1
2
sudo usermod -aG kvm,cvdnetwork,render $USER
sudo reboot

使用Cuttlefish

在我们的已经编译完Android系统目录中首先执行如下代码让环境初始化好:

1
2
source ./build/envsetup.sh
lunch aosp_cf_x86_64_phone-trunk_staging-eng

随后执行如下的命令就可以启动Cuttlefish运行Android了:

1
launch_cvd --daemon

如果你是从Android官方下载的,那么会和我这有一些区别,可以去看一下官方教程。

这个时候我们就可以通过adb看看设备是否已经启动了,也可以在浏览器中打开,在本机浏览其打开使用如下地址和端口:

https://localhost:8443/

地址一定要使用https,点击左侧的swtich按钮就可以看到UI了。 webrtc是默认打开的,关于它的命令行更多使用方式可以查看官方文档,可以使用如下的命令查看。

1
launch --help

而关闭Cuttlefish,也很简单,使用如下的命令:

1
stop_cvd

新版Android Studio for Platform使用

2023版本的Android Studio for Platform(以下简称Asfp)在打开的时候是有一个单独的Open Aosp project选项的,而新版本的这个选项去掉了。刚刚使用它的时候我还一脸懵逼,测试了Import和Open都不行,结果最后发现新版的New选项就直接是导入Aosp工程了。

使用方式如下图。

我们可以根据上图选择我们需要导入的Module,选择Asfp给我们生成的项目文件存放的位置,之后Asfp会执行lunch的操作和它需要的一些依赖构建。在我们选定的目录下面也会生成一个asfp-config.json文件,它就是我们的项目设置,如果我们以后有变化了(比如想看不同的模块的代码),也可以直接修改这个文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
 "repoRoot" : "/home/sam/android/android-source",
 "modulePaths" : [
 "frameworks",
 "packages/inputmethods"
 ],
 "lunchTarget" : "aosp_cf_x86_64_phone-trunk_staging-eng",
 "nativeConfig" : {
 "excludePaths" : [ ],
 "excludeGenPaths" : [ ]
 },
 "syncConfig" : {
 "environmentVars" : { },
 "buildFlags" : [ ]
 }}

参考内容和资料:

  1. Cuttlefish 官方文档: https://source.android.com/docs/devices/cuttlefish
  2. Cuttlefish官方Repo: https://github.com/google/android-cuttlefish
  3. Bazel用户指南:https://bazel.build/docs/user-manual
  4. Android Cuttlefish emulator: https://2net.co.uk/blog/cuttlefish-android12.html

看完评论一下吧

Android源码分析:广播接收器注册与发送广播流程解析

广播,顾名思义就是把一个信息传播出去,在Android中也提供了广播和广播接收器BroadcastReceiver,用来监听特定的事件和发送特定的消息。不过广播分为全局广播和本地广播,本地广播是在Android Jetpack库中所提供,其实现也是基于Handler和消息循环机制,并且这个类Android官方也不推荐使用了。我们这里就来看看Android全局的这个广播。

应用开发者可以自己发送特定的广播,而更多场景则是接收系统发送的广播。注册广播接收器有在AndroidManifest文件中声明和使用代码注册两种方式,在应用的target sdk大于等于Android 8.0(Api Version 26)之后,系统会限制在清单文件中注册。通过清单方式注册的广播,代码中没有注册逻辑,只有PMS中读取它的逻辑,我们这里不进行分析。

注册广播接收器

首先是注册广播接收器,一般注册一个广播接收器的代码如下:

1
2
3
val br: BroadcastReceiver = MyBroadcastReceiver()
val filter = IntentFilter(ACTION_CHARGING)
activity.registerReceiver(br, filter)

使用上面的代码就能注册一个广播接收器,当手机开始充电就会收到通知,会去执行MyBroadcastReceiveronReceive方法。

那我们就从这个registerReceiver来时往里面看,因为Activity是Context的子类,这个注册的方法的实现则是在ContextImpl当中,其中最终调用的方法为registerReceiverInternal,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
 IntentFilter filter, String broadcastPermission,
 Handler scheduler, Context context, int flags) {
 IIntentReceiver rd = null;
 if (receiver != null) {
 if (mPackageInfo != null && context != null) {
 if (scheduler == null) {
 scheduler = mMainThread.getHandler();
 }
 rd = mPackageInfo.getReceiverDispatcher(
 receiver, context, scheduler,
 mMainThread.getInstrumentation(), true);
 } else {
 ...
 }
 }
 try {
 ActivityThread thread = ActivityThread.currentActivityThread();
 Instrumentation instrumentation = thread.getInstrumentation();
 if (instrumentation.isInstrumenting()
 && ((flags & Context.RECEIVER_NOT_EXPORTED) == 0)) {
 flags = flags | Context.RECEIVER_EXPORTED;
 }
 final Intent intent = ActivityManager.getService().registerReceiverWithFeature(
 mMainThread.getApplicationThread(), mBasePackageName, getAttributionTag(),
 AppOpsManager.toReceiverId(receiver), rd, filter, broadcastPermission, userId,
 flags);
 if (intent != null) {
 intent.setExtrasClassLoader(getClassLoader());
 intent.prepareToEnterProcess(
 ActivityThread.isProtectedBroadcast(intent),
 getAttributionSource());
 }
 return intent;
 } catch (RemoteException e) {
 ...
 }
}

我们在注册广播的时候只传了两个参数,但是实际上它还可以传不少的参数,这里userId就是注册的用户id,会被自动 填充成当前进程的用户Id,broadcastPermission表示这个广播的权限,也就是说需要有该权限的应用发送的广播,这个接收者才能接收到。scheduler就是一个Handler,默认不传,在第8行可以看到,会拿当前进程的主线程的Handlerflag是广播的参数,这里比较重要的就是RECEIVER_NOT_EXPORTED,添加了它则广播不会公开暴露,其他应用发送的消息不会被接收。

在第10行,这里创建了一个广播的分发器,在24行,通过AMS去注册广播接收器,只有我们的broadcast会用到contentprovider或者有sticky广播的时候,30行才会执行到,这里跳过。

获取广播分发器

首先来看如何获取广播分发器,这块的代码在LoadedApk.java中,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
 Context context, Handler handler,
 Instrumentation instrumentation, boolean registered) {
 synchronized (mReceivers) {
 LoadedApk.ReceiverDispatcher rd = null;
 ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
 if (registered) {
 map = mReceivers.get(context);
 if (map != null) {
 rd = map.get(r);
 }
 }
 if (rd == null) {
 rd = new ReceiverDispatcher(r, context, handler,
 instrumentation, registered);
 if (registered) {
 if (map == null) {
 map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
 mReceivers.put(context, map);
 }
 map.put(r, rd);
 }
 } else {
 rd.validate(context, handler);
 }
 rd.mForgotten = false;
 return rd.getIIntentReceiver();
 }
}

先来说一下mReceivers,它的结构为ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>>,也就是嵌套了两层的ArrayMap,外层是以Context为key,内层以Receiver为key,实际存储的为ReceiverDispatcherReceiverDispatcher内部所放的IIntentReceiver比较重要,也就是我们这个方法所返回的值,它实际是IIntentReceiver.Stub,也就是它的Binder实体类。

这段代码的逻辑也比较清晰,就是根据ContextReceiver到map中去查找看是否之前注册过,如果注册过就已经有这个Dispatcher了,如果没有就创建一个,并且放到map中去,最后返回binder对象出去。

AMS注册广播接收器

在AMS注册的代码很长,我们这里主要研究正常的普通广播注册,关于黏性广播,instantApp的广播,以及广播是否导出等方面都省略不予研究。以下为我们关注的核心代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public Intent registerReceiverWithFeature(IApplicationThread caller, String callerPackage,
 String callerFeatureId, String receiverId, IIntentReceiver receiver,
 IntentFilter filter, String permission, int userId, int flags) {
 ...
 synchronized(this) {
 ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
 if (rl == null) {
 rl = new ReceiverList(this, callerApp, callingPid, callingUid,
 userId, receiver);
 if (rl.app != null) {
 final int totalReceiversForApp = rl.app.mReceivers.numberOfReceivers();
 if (totalReceiversForApp >= MAX_RECEIVERS_ALLOWED_PER_APP) {
 throw new IllegalStateException("Too many receivers, total of "
 + totalReceiversForApp + ", registered for pid: "
 + rl.pid + ", callerPackage: " + callerPackage);
 }
 rl.app.mReceivers.addReceiver(rl);
 } else {
 try {
 receiver.asBinder().linkToDeath(rl, 0);
 } catch (RemoteException e) {
 return sticky;
 }
 rl.linkedToDeath = true;
 }
 mRegisteredReceivers.put(receiver.asBinder(), rl);
 } else {
 // 处理userId, uid,pid 等不同的错误
 }

 BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, callerFeatureId,
 receiverId, permission, callingUid, userId, instantApp, visibleToInstantApps,
 exported);
 if (rl.containsFilter(filter)) {
 } else {
 rl.add(bf);
 mReceiverResolver.addFilter(getPackageManagerInternal().snapshot(), bf);
 }
 }
 ...
}

在前面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,具体这里先不分析了。 BroadcastReceiver 存放结构

到这里我们就看完了广播接收器的注册,在App进程和System_Server中分别将其存储,具体两边的数据结构如上图所示。这里可以继续看看发送广播的流程了。

发送广播

一般我们发送广播会调用如下的代码:

1
2
3
4
5
Intent().also { intent -> 
 intent.setAction("com.example.broadcast.MY_NOTIFICATION") 
 intent.putExtra("data", "Nothing to see here, move along.") 
 activity.sendBroadcast(intent)
}

我们通过设置Action来匹配对应的广播接收器,通过设置Data或者Extra,这样广播接收器中可以接收到对应的数据,最后调用sendBroadcast来发送。而sendBroadcast的实现也是在ContextImpl中,源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Override
public void sendBroadcast(Intent intent) {
 warnIfCallingFromSystemProcess();
 String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
 try {
 intent.prepareToLeaveProcess(this);
 ActivityManager.getService().broadcastIntentWithFeature(
 mMainThread.getApplicationThread(), getAttributionTag(), intent, resolvedType,
 null, Activity.RESULT_OK, null, null, null, null /*excludedPermissions=*/,
 null, AppOpsManager.OP_NONE, null, false, false, getUserId());
 } catch (RemoteException e) {
 throw e.rethrowFromSystemServer();
 }
}

这里代码比较简单,就是直接调用AMS的broadcastIntentWithFeature来发送广播。

AMS发送广播

这里我们可以直接看AMS中的broadcastIntentWithFeature的源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public final int broadcastIntentWithFeature(IApplicationThread caller, String callingFeatureId,
 Intent intent, String resolvedType, IIntentReceiver resultTo,
 int resultCode, String resultData, Bundle resultExtras,
 String[] requiredPermissions, String[] excludedPermissions,
 String[] excludedPackages, int appOp, Bundle bOptions,
 boolean serialized, boolean sticky, int userId) {
 enforceNotIsolatedCaller("broadcastIntent");
 synchronized(this) {
 intent = verifyBroadcastLocked(intent);

 final ProcessRecord callerApp = getRecordForAppLOSP(caller);
 final int callingPid = Binder.getCallingPid();
 final int callingUid = Binder.getCallingUid();

 final long origId = Binder.clearCallingIdentity();
 try {
 return broadcastIntentLocked(callerApp,
 callerApp != null ? callerApp.info.packageName : null, callingFeatureId,
 intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
 requiredPermissions, excludedPermissions, excludedPackages, appOp, bOptions,
 serialized, sticky, callingPid, callingUid, callingUid, callingPid, userId);
 } finally {
 Binder.restoreCallingIdentity(origId);
 }
 }
}

第10行代码,主要验证Intent,比如检查它的Flag,检查它是否传文件描述符之类的,里面的代码比较简单清晰,这里不单独看了。后面则是获取调用者的进程,uid,pid之类的,最后调用broadcastIntentLocked,这个方法的代码巨多,接近1000行代码,我们同样忽略sticky的广播,也忽略顺序广播,然后来一点一点的看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//ActivityManagerService.java 
//final int broadcastIntentLocked(...)
intent = new Intent(intent);
intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
if (!mProcessesReady && (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0) {
 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
}
userId = mUserController.handleIncomingUser(callingPid, callingUid, userId, true,
 ALLOW_NON_FULL, "broadcast", callerPackage);
final String action = intent.getAction();

首先这里的代码是对Intent做一下封装,并且如果系统还在启动,不允许启动应用进程,以及获取当前的用户ID,大部分情况下,我们只需要考虑一个用户的情况。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
if (action != null) {
 ...
 switch (action) {
 ...
 case Intent.ACTION_PACKAGE_DATA_CLEARED:
 {
 Uri data = intent.getData();
 String ssp;
 if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
 mAtmInternal.onPackageDataCleared(ssp, userId);
 }
 break;
 }
 case Intent.ACTION_TIMEZONE_CHANGED:
 mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
 break;
 ...
 }
}

对于一些系统的广播事件,除了要发送广播给应用之外,在AMS中,还会根据其广播,来调用相关的服务或者执行相关的逻辑,也会在这里调用其代码。这里我罗列了清除应用数据和时区变化两个广播,其他的感兴趣的可以自行阅读相关代码。

1
2
3
4
5
6
int[] users;
if (userId == UserHandle.USER_ALL) {
 users = mUserController.getStartedUserArray();
} else {
 users = new int[] {userId};
}

以上代码为根据前面拿到的userId,来决定广播要发送给所有人还是仅仅发送给当前用户,并且把userId保存到users数组当中。

获取广播接收者

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
List receivers = null;
List<BroadcastFilter> registeredReceivers = null;
if ((intent.getFlags() & Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
 receivers = collectReceiverComponents(
 intent, resolvedType, callingUid, users, broadcastAllowList);
}
if (intent.getComponent() == null) {
 final PackageDataSnapshot snapshot = getPackageManagerInternal().snapshot();
 if (userId == UserHandle.USER_ALL && callingUid == SHELL_UID) {
 ...
 } else {
 registeredReceivers = mReceiverResolver.queryIntent(snapshot, intent,
 resolvedType, false /*defaultOnly*/, userId);
 }
}

以上为获取我们注册的所有的接收器的代码,其中FLAG_RECEIVER_REGISTERED_ONLY意味着仅仅接收注册过的广播,前面在判断当前系统还未启动完成的时候有添加这个FLAG,其他情况一般不会有这个Flag,这里我们按照没有这个flag处理。那也就会执行第4行的代码。另外下面还有从mReceiverResolver从获取注册的接收器的代码,因为大部分情况不是从shell中执行的,因此也忽略了其代码。

首先看collectReceiverComponents的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,
 int callingUid, int[] users, int[] broadcastAllowList) {
 int pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;

 List<ResolveInfo> receivers = null;
 HashSet<ComponentName> singleUserReceivers = null;
 boolean scannedFirstReceivers = false;
 for (int user : users) {
 List<ResolveInfo> newReceivers = mPackageManagerInt.queryIntentReceivers(
 intent, resolvedType, pmFlags, callingUid, user, true /* forSend */); //通过PMS,根据intent和uid读取Manifest中注册的接收器
 if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
 for (int i = 0; i < newReceivers.size(); i++) {
 ResolveInfo ri = newReceivers.get(i);
 //如果调用不是系统用户,移除只允许系统用户接收的接收器
 if ((ri.activityInfo.flags & ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
 newReceivers.remove(i);
 i--;
 }
 }
 }
 // 把别名替换成真实的接收器 
 if (newReceivers != null) {
 for (int i = newReceivers.size() - 1; i >= 0; i--) {
 final ResolveInfo ri = newReceivers.get(i);
 final Resolution<ResolveInfo> resolution =
 mComponentAliasResolver.resolveReceiver(intent, ri, resolvedType,
 pmFlags, user, callingUid, true /* forSend */);
 if (resolution == null) {
 // 未找到对应的接收器,删除这个记录 
 newReceivers.remove(i);
 continue;
 }
 if (resolution.isAlias()) {
 //找到对应的真实的接收器,就把别名的记录替换成真实的目标
 newReceivers.set(i, resolution.getTarget());
 }
 }
 }
 if (newReceivers != null && newReceivers.size() == 0) {
 newReceivers = null;
 }

 if (receivers == null) {
 receivers = newReceivers;
 } else if (newReceivers != null) {
 if (!scannedFirstReceivers) {
 //查找单用户记录的接收器,并且保存
 scannedFirstReceivers = true;
 for (int i = 0; i < receivers.size(); i++) {
 ResolveInfo ri = receivers.get(i);
 if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
 ComponentName cn = new ComponentName(
 ri.activityInfo.packageName, ri.activityInfo.name);
 if (singleUserReceivers == null) {
 singleUserReceivers = new HashSet<ComponentName>();
 }
 singleUserReceivers.add(cn);
 }
 }
 }
 for (int i = 0; i < newReceivers.size(); i++) {
 ResolveInfo ri = newReceivers.get(i);
 if ((ri.activityInfo.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
 ComponentName cn = new ComponentName(
 ri.activityInfo.packageName, ri.activityInfo.name);
 if (singleUserReceivers == null) {
 singleUserReceivers = new HashSet<ComponentName>();
 }
 if (!singleUserReceivers.contains(cn)) {
 //对于单用户的接收器,只存一次到返回结果中
 singleUserReceivers.add(cn);
 receivers.add(ri);
 }
 } else {
 receivers.add(ri);
 }
 }
 }
 }
 ...
 return receivers;
}

以上就根据信息通过PMS获取所有通过Manifest静态注册的广播接收器,对其有一些处理,详见上面的注释。

对于我们在代码中动态注册的接收器,则需要看mReceiverResolver.queryIntent的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
protected final List<R> queryIntent(@NonNull PackageDataSnapshot snapshot, Intent intent,
 String resolvedType, boolean defaultOnly, @UserIdInt int userId, long customFlags) {
 ArrayList<R> finalList = new ArrayList<R>();
 F[] firstTypeCut = null;
 F[] secondTypeCut = null;
 F[] thirdTypeCut = null;
 F[] schemeCut = null;

 if (resolvedType == null && scheme == null && intent.getAction() != null) {
 firstTypeCut = mActionToFilter.get(intent.getAction());
 }

 FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
 Computer computer = (Computer) snapshot;
 if (firstTypeCut != null) {
 buildResolveList(computer, intent, categories, debug, defaultOnly, resolvedType,
 scheme, firstTypeCut, finalList, userId, customFlags);
 }
 sortResults(finalList); //按照IntentFilter的priority优先级降序排序
 return finalList;
}

以上代码中,这个mActionToFilter就是我们前面注册广播时候,将BroadcastFilter添加进去的一个ArrayMap,这里会根据Action去其中取出所有的BroadcastFilter,之后调用buildResolveList将其中的不符合本次广播接收要求的广播接收器给过滤掉,最后按照IntentFilter的优先级降序排列。

到这里我们就有两个列表receivers存放Manifest静态注册的将要本次广播接收者,和registeredReceivers通过代码手动注册的广播接收者。

广播入队列

首先来看通过代码注册的接收器不为空,并且不是有序广播的情况,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
if (!ordered && NR > 0) {
 ...
 final BroadcastQueue queue = broadcastQueueForIntent(intent);
 BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
 callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
 requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
 registeredReceivers, resultTo, resultCode, resultData, resultExtras, ordered,
 sticky, false, userId, allowBackgroundActivityStarts,
 backgroundActivityStartsToken, timeoutExempt);
 ...
 final boolean replaced = replacePending
 && (queue.replaceParallelBroadcastLocked(r) != null);
 if (!replaced) {
 queue.enqueueParallelBroadcastLocked(r);
 queue.scheduleBroadcastsLocked();
 }
 registeredReceivers = null;
 NR = 0;
}

在这里,第4行会首先根据intent的flag获取对应的BroadcastQueue,这里有四个Queue,不看其代码了,不过逻辑如下:

  1. 如果有FLAG_RECEIVER_OFFLOAD_FOREGROUND 标记,则使用mFgOffloadBroadcastQueue
  2. 如果当前开启了offloadQueue,也就是mEnableOffloadQueue,并且有FLAG_RECEIVER_OFFLOAD标记,则使用mBgOffloadBroadcastQueue
  3. 如果有FLAG_RECEIVER_FOREGROUND,也就是前台时候才接收广播,则使用mFgBroadcastQueue
  4. 如果没有上述标记,则使用mBgBroadcastQueue。 拿到queue之后,会创建一条BroadcastRecord,其中会记录传入的参数,intent,以及接收的registeredReceivers,调用queue的入队方法,最后把registeredReceivers设置为null,计数也清零。具体入队的代码,我们随后再看,这里先看其他情况下的广播入队代码。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
int ir = 0;
if (receivers != null) {
 String skipPackages[] = null;
 //对于添加应用,删除应用数据之类的广播,不希望变化的应用能够接收到对应的广播
 //这里设置忽略它们
 if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
 || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
 || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
 Uri data = intent.getData();
 if (data != null) {
 String pkgName = data.getSchemeSpecificPart();
 if (pkgName != null) {
 skipPackages = new String[] { pkgName };
 }
 }
 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
 skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
 }
 if (skipPackages != null && (skipPackages.length > 0)) {
 //如果Manifest注册的广播接收器的包名和skip的一样,那就移除它们
 for (String skipPackage : skipPackages) {
 if (skipPackage != null) {
 int NT = receivers.size();
 for (int it=0; it<NT; it++) {
 ResolveInfo curt = (ResolveInfo)receivers.get(it);
 if (curt.activityInfo.packageName.equals(skipPackage)) {
 receivers.remove(it);
 it--;
 NT--;
 }
 }
 }
 }
 }

 int NT = receivers != null ? receivers.size() : 0;
 int it = 0;
 ResolveInfo curt = null;
 BroadcastFilter curr = null;
 while (it < NT && ir < NR) {
 if (curt == null) {
 curt = (ResolveInfo)receivers.get(it);
 }
 if (curr == null) {
 curr = registeredReceivers.get(ir);
 }
 if (curr.getPriority() >= curt.priority) {
 //如果动态注册的广播优先级比静态注册的等级高,就把它添加到静态注册的前面。
 receivers.add(it, curr);
 ir++;
 curr = null;
 it++;
 NT++;
 } else {
 // 如果动态注册的广播优先级没有静态注册的等级高,那就移动静态注册的游标,下一轮在执行相关的判断。
 it++;
 curt = null;
 }
 }
}
while (ir < NR) { //如果registeredReceivers中的元素没有全部放到receivers里面,就一个一个的遍历并放进去。
 if (receivers == null) {
 receivers = new ArrayList();
 }
 receivers.add(registeredReceivers.get(ir));
 ir++;
}

以上的代码所做的事情就是首先移除静态注册的广播当中需要忽略的广播接收器,随后将静态注册和动态注册的广播接收器,按照优先级合并到同一个列表当中,当然如果动态注册的前面已经入队过了,这里实际上是不会在合并的。关于合并的代码,就是经典的两列表合并的算法,具体请看代码和注释。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
if ((receivers != null && receivers.size() > 0)
 || resultTo != null) {
 BroadcastQueue queue = broadcastQueueForIntent(intent);
 BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
 callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
 requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
 receivers, resultTo, resultCode, resultData, resultExtras,
 ordered, sticky, false, userId, allowBackgroundActivityStarts,
 backgroundActivityStartsToken, timeoutExempt);

 final BroadcastRecord oldRecord =
 replacePending ? queue.replaceOrderedBroadcastLocked(r) : null;
 if (oldRecord != null) {
 if (oldRecord.resultTo != null) {
 final BroadcastQueue oldQueue = broadcastQueueForIntent(oldRecord.intent);
 try {
 oldRecord.mIsReceiverAppRunning = true;
 oldQueue.performReceiveLocked(oldRecord.callerApp, oldRecord.resultTo,
 oldRecord.intent,
 Activity.RESULT_CANCELED, null, null,
 false, false, oldRecord.userId, oldRecord.callingUid, callingUid,
 SystemClock.uptimeMillis() - oldRecord.enqueueTime, 0);
 } catch (RemoteException e) {

 }
 }
 } else {
 queue.enqueueOrderedBroadcastLocked(r);
 queue.scheduleBroadcastsLocked();
 }
}else {
 //对于无人关心的广播,也做一下记录
 if (intent.getComponent() == null && intent.getPackage() == null
 && (intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {
 addBroadcastStatLocked(intent.getAction(), callerPackage, 0, 0, 0);
 }
}

以上的代码,跟前面入队的代码也差不多,不过这里如果采用的方法是enqueueOrderedBroadcastLocked,并且多了关于已经发送的广播的替换的逻辑,这里我们先不关注。如果receivers为空,并且符合条件的隐式广播,系统也会对其进行记录,具体,我们这里也不进行分析了。

BroadcastQueue 入队

我们知道前面入队的时候有两个方法,分别是enqueueParallelBroadcastLockedenqueueOrderedBroadcastLocked,我们先来分析前者。

1
2
3
4
5
6
7
public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
 r.enqueueClockTime = System.currentTimeMillis();
 r.enqueueTime = SystemClock.uptimeMillis();
 r.enqueueRealTime = SystemClock.elapsedRealtime();
 mParallelBroadcasts.add(r);
 enqueueBroadcastHelper(r);
}

这里就是将BroadcastRecord放到mParallelBroadcasts列表中,随后执行enqueueBroadcastHelper,我们先看继续看一下enqueueOrderedBroadcastLocked方法。

1
2
3
4
5
6
7
public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
 r.enqueueClockTime = System.currentTimeMillis();
 r.enqueueTime = SystemClock.uptimeMillis();
 r.enqueueRealTime = SystemClock.elapsedRealtime();
 mDispatcher.enqueueOrderedBroadcastLocked(r);
 enqueueBroadcastHelper(r);
}

这里跟上面很类似,差别是这里把BroadcastRecord入队了mDispatcher,对于普通广播,其内部是把这个记录放到了mOrderedBroadcasts列表。 而enqueueBroadcastHelper方法仅仅用于trace,我们这里不需要关注。

到了这里,我们把广播放到对应的列表了,但是广播还是没有分发出去。

AMS端广播的分发

以上是代码入了BroadcastQueu,接下来就可以看看队列中如何处理它了。首先需要注意一下,记录在入队的同时还调用了BroadcastQueuescheduleBroadcastsLock方法,代码如下:

1
2
3
4
5
6
7
public void scheduleBroadcastsLocked() {
 if (mBroadcastsScheduled) {
 return;
 }
 mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
 mBroadcastsScheduled = true;
}

这里使用了Handler发送了一条BROADCAST_INTENT_MSG消息,我们可以去看一下BroadcastHandlerhandleMessage方法。其中在处理这个消息的时候调用了processNextBroadcast方法,我们可以直接去看其实现:

1
2
3
4
5
private void processNextBroadcast(boolean fromMsg) {
 synchronized (mService) {
 processNextBroadcastLocked(fromMsg, false);
 }
}

这里开启了同步块调用了processNextBroadcastLocked方法,这个方法依然很长,其中涉及到广播的权限判断,对于静态注册的广播,可能还涉及到对应进程的启动等。

动态广播的分发

动态注册的无序广播相对比较简单,这里我们仅仅看一下其中无序广播的分发处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
if (fromMsg) {
 mBroadcastsScheduled = false; //通过handleMessage过来,把flag设置为false
}
while (mParallelBroadcasts.size() > 0) {
 r = mParallelBroadcasts.remove(0);
 r.dispatchTime = SystemClock.uptimeMillis();
 r.dispatchRealTime = SystemClock.elapsedRealtime();
 r.dispatchClockTime = System.currentTimeMillis();
 r.mIsReceiverAppRunning = true;
 final int N = r.receivers.size();

 for (int i=0; i<N; i++) {
 Object target = r.receivers.get(i);

 deliverToRegisteredReceiverLocked(r,
 (BroadcastFilter) target, false, i); //分发
 }
 addBroadcastToHistoryLocked(r); //把广播添加的历史记录中
}


这里就是遍历`ParallelBroadcasts`中的每一条`BroadcastRecord`,其中会再分别遍历每一个`BroadcastFilter`,调用`deliverToRegisteredReceiverLocked`来分发广播
```java
private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
 BroadcastFilter filter, boolean ordered, int index) {
 boolean skip = false;
 ...

 if (filter.requiredPermission != null) {
 int perm = mService.checkComponentPermission(filter.requiredPermission,
 r.callingPid, r.callingUid, -1, true);
 if (perm != PackageManager.PERMISSION_GRANTED) {
 skip = true;
 } else {
 final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission);
 if (opCode != AppOpsManager.OP_NONE
 && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid,
 r.callerPackage, r.callerFeatureId, "Broadcast sent to protected receiver")
 != AppOpsManager.MODE_ALLOWED) {
 skip = true;
 }
 }
 }
 ...
 if (skip) {
 r.delivery[index] = BroadcastRecord.DELIVERY_SKIPPED;
 return;
 }

 r.delivery[index] = BroadcastRecord.DELIVERY_DELIVERED;
 ...
 try {

 if (filter.receiverList.app != null && filter.receiverList.app.isInFullBackup()) {
 if (ordered) {
 skipReceiverLocked(r);
 }
 } else {
 r.receiverTime = SystemClock.uptimeMillis();
 maybeAddAllowBackgroundActivityStartsToken(filter.receiverList.app, r);
 maybeScheduleTempAllowlistLocked(filter.owningUid, r, r.options);
 maybeReportBroadcastDispatchedEventLocked(r, filter.owningUid);
 performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
 new Intent(r.intent), r.resultCode, r.resultData,
 r.resultExtras, r.ordered, r.initialSticky, r.userId,
 filter.receiverList.uid, r.callingUid,
 r.dispatchTime - r.enqueueTime,
 r.receiverTime - r.dispatchTime);
 if (filter.receiverList.app != null
 && r.allowBackgroundActivityStarts && !r.ordered) {
 postActivityStartTokenRemoval(filter.receiverList.app, r);
 }
 }
 if (ordered) {
 r.state = BroadcastRecord.CALL_DONE_RECEIVE;
 }
 } catch (RemoteException e) {
 ...
 if (ordered) {
 r.receiver = null;
 r.curFilter = null;
 filter.receiverList.curBroadcast = null;
 }
 }
}

在这个方法中有大段的代码是判断是否需要跳过当前这个广播,我这里仅仅保留了几句权限检查的代码。对于跳过的记录会将其BroadcastRecorddelivery[index]值设置为DELIVERY_SKIPPED, 而成功分发的会设置为DELIVERY_DELIVERED。对于有序广播的分发我们这里也不予分析,直接看无序广播的分发,在分发之前会尝试给对应的接收进程添加后台启动Activity的权限,这个会在分发完成之后恢复原状,调用的是maybeAddAllowBackgroundActivityStartsToken,就不具体分析了。

之后会调用performReceiveLocked去进行真正的分发,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
 Intent intent, int resultCode, String data, Bundle extras,
 boolean ordered, boolean sticky, int sendingUser,
 int receiverUid, int callingUid, long dispatchDelay,
 long receiveDelay) throws RemoteException {
 if (app != null) {
 final IApplicationThread thread = app.getThread();
 if (thread != null) {
 try {
 thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
 data, extras, ordered, sticky, sendingUser,
 app.mState.getReportedProcState());
 } catch (RemoteException ex) {
 ...
 throw ex;
 }
 } else {
 ...
 throw new RemoteException("app.thread must not be null");
 }
 } else {
 receiver.performReceive(intent, resultCode, data, extras, ordered,
 sticky, sendingUser);
 }
 ...
}

在执行分发的代码中,如果我们的ProcessRecord不为空,并且ApplicationThread也存在的情况下,会调用它的scheduleRegisterReceiver方法。如果进程记录为空,则会直接使用IIntentReceiverperformReceiver方法。我们在App中动态注册的情况,ProcessRecord一定是不为空的,我们也以这种情况继续向下分析。

动态注册广播分发App进程逻辑

1
2
3
4
5
6
7
public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
 int resultCode, String dataStr, Bundle extras, boolean ordered,
 boolean sticky, int sendingUser, int processState) throws RemoteException {
 updateProcessState(processState, false);
 receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
 sticky, sendingUser);
}

在应用进程中,首先也只是根据AMS传过来的processState更新一下进程的状态,随后还是调用了IIntentReceiverperformReceive方法,performReceiveLoadedApk当中,为内部类InnerReceiver的方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void performReceive(Intent intent, int resultCode, String data,
 Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
 final LoadedApk.ReceiverDispatcher rd;
 if (intent == null) {
 rd = null;
 } else {
 rd = mDispatcher.get(); //获取ReceiverDispatcher
 }
 if (rd != null) {
 rd.performReceive(intent, resultCode, data, extras,
 ordered, sticky, sendingUser);
 } else {
 IActivityManager mgr = ActivityManager.getService();
 try {
 if (extras != null) {
 extras.setAllowFds(false);
 }
 mgr.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());
 } catch (RemoteException e) {
 throw e.rethrowFromSystemServer();
 }
 }
}

在应用进程中,首先会获取ReceiverDisptcher,这个一般不会为空。但是系统代码比较严谨,也考虑了,不存在的情况会调用AMS的finishReceiver完成整个流程。

对于存在的情况,会调用ReceiverDispatcherperformReceive方法继续分发。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public void performReceive(Intent intent, int resultCode, String data,
 Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
 final Args args = new Args(intent, resultCode, data, extras, ordered,
 sticky, sendingUser);
 ..
 if (intent == null || !mActivityThread.post(args.getRunnable())) {
 if (mRegistered && ordered) {
 IActivityManager mgr = ActivityManager.getService();
 ..
 args.sendFinished(mgr);
 }
 }
}

这里的代码有点绕,不过也还比较清晰,首先是创建了一个Args对象,之后根据java的语法,如果intent不为空的时候会执行如下代码:

1
mActivityThread.post(args.getRunnable())

当这个执行失败的时候,才会看情况执行8行到第10行的代码。而这个Runnable就是应用端真正分发的逻辑,其代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public final Runnable getRunnable() {
 return () -> {
 final BroadcastReceiver receiver = mReceiver;
 final boolean ordered = mOrdered;


 final IActivityManager mgr = ActivityManager.getService();
 final Intent intent = mCurIntent;

 mCurIntent = null;
 mDispatched = true;
 mRunCalled = true;
 if (receiver == null || intent == null || mForgotten) {
 ...
 return;
 }
 try {
 ClassLoader cl = mReceiver.getClass().getClassLoader();
 intent.setExtrasClassLoader(cl);
 intent.prepareToEnterProcess(ActivityThread.isProtectedBroadcast(intent),
 mContext.getAttributionSource());
 setExtrasClassLoader(cl);
 receiver.setPendingResult(this);
 receiver.onReceive(mContext, intent);
 } catch (Exception e) {
 if (mRegistered && ordered) {
 sendFinished(mgr);
 }
 if (mInstrumentation == null ||
 !mInstrumentation.onException(mReceiver, e)) {
 throw new RuntimeException(
 "Error receiving broadcast " + intent
 + " in " + mReceiver, e);
 }
 }

 if (receiver.getPendingResult() != null) {
 finish();
 }
 };
}

这里的receiver就是我们注册时候的那个BroadcastReceiver,这里将当前的Args对象作为它的PendingResult,在这里调用了它的onReceive方法 ,最后看pendingResult是否为空,不为空则调用PendingResultfinish()方法。当我们在onReceive中编写代码的时候,如果调用了goAsync的话,那这里的PendingResult就会为空。

另外就是我们这个Runnable是使用的mActivityThread的post方法投递出去的,它是一个Handler对象,它是在注册广播接收器的时候指定的,默认是应用的主线程Handler,也就是说广播的执行会在主线程。

但是即使是我们使用goAsync的话,处理完成之后也是需要手动调用finish的,我们后面在来看相关的逻辑。

静态广播的发送

在前面分析的BroadcastQueueprocessNextBroadcastLocked方法中,我们只分析了动态广播的发送,这里再看一下静态广播的发送,首先仍然是看processNextBroadcastLocked中的相关源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
BroadcastRecord r;
do {
 r = mDispatcher.getNextBroadcastLocked(now);
 if (r == null) {
 ...
 return;
 }
 ...

} while(r === null);
...
if (app != null && app.getThread() != null && !app.isKilled()) {
 try {
 app.addPackage(info.activityInfo.packageName,
 info.activityInfo.applicationInfo.longVersionCode, mService.mProcessStats);
 maybeAddAllowBackgroundActivityStartsToken(app, r);
 r.mIsReceiverAppRunning = true;
 processCurBroadcastLocked(r, app);
 return;
 } catch(RemoteException e) {
 ...
 }
}
...

在第3行,会从mDispatcher中拿BroadcastRecord的记录,我们之前在AMS端入队的代码,对于静态注册的广播和有序广播都是放在mDispatcher当中的,这里拿到动态注册的有序广播也会从这里拿,它的后续逻辑跟前面分析的是一样的,这里不再看了。对于静态注册的广播,在调用后续的方法之前,需要先获取对应进程的ProcessRecord,和ApplicationThread,并且进行广播权限的检查,进程是否存活检查这些在我们11行的位置,都省略不看了。如果App进程存活则会走到我们12行的部分,否则会去创建对应的进程,创建完进程会再去分发广播。

动态注册的广播,会传一个IIntentReceiver的Binder到AMS,而静态注册的广播,我们跟着第18行代码processCurBroadcastLocked方法进去一览究竟:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private final void processCurBroadcastLocked(BroadcastRecord r,
 ProcessRecord app) throws RemoteException {
 final IApplicationThread thread = app.getThread();
 ...
 r.receiver = thread.asBinder();
 r.curApp = app;
 final ProcessReceiverRecord prr = app.mReceivers;
 prr.addCurReceiver(r);
 app.mState.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER);
 ...
 r.intent.setComponent(r.curComponent);

 boolean started = false;
 try {
 mService.notifyPackageUse(r.intent.getComponent().getPackageName(),
 PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
 thread.scheduleReceiver(new Intent(r.intent), r.curReceiver,
 mService.compatibilityInfoForPackage(r.curReceiver.applicationInfo),
 r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId,
 app.mState.getReportedProcState());
 started = true;
 } finally {
 if (!started) {
 r.receiver = null;
 r.curApp = null;
 prr.removeCurReceiver(r);
 }
 }

}

在这个方法中,把App的ProcessRecord放到了BroadcastRecord当中,并且把ApplicationThread设置为receiver,最后是调用了ApplicationThreadscheduleReceiver,从而通过binder调用App进程。

静态注册广播分发App进程逻辑

通过Binder调用,在App的ApplicationThread代码中,调用的是如下方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public final void scheduleReceiver(Intent intent, ActivityInfo info,
 CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
 boolean sync, int sendingUser, int processState) {
 updateProcessState(processState, false);
 ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
 sync, false, mAppThread.asBinder(), sendingUser);
 r.info = info;
 r.compatInfo = compatInfo;
 sendMessage(H.RECEIVER, r);
}

这里是创建了一个ReceiverData把AMS传过来数据包裹其中,并且通过消息发出去,之后会调用ActivityThreadhandleReceiver方法, 代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private void handleReceiver(ReceiverData data) {
 String component = data.intent.getComponent().getClassName();

 LoadedApk packageInfo = getPackageInfoNoCheck(
 data.info.applicationInfo, data.compatInfo);

 IActivityManager mgr = ActivityManager.getService();

 Application app;
 BroadcastReceiver receiver;
 ContextImpl context;
 try {
 app = packageInfo.makeApplicationInner(false, mInstrumentation);
 context = (ContextImpl) app.getBaseContext();
 if (data.info.splitName != null) {
 context = (ContextImpl) context.createContextForSplit(data.info.splitName);
 }
 if (data.info.attributionTags != null && data.info.attributionTags.length > 0) {
 final String attributionTag = data.info.attributionTags[0];
 context = (ContextImpl) context.createAttributionContext(attributionTag);
 }
 java.lang.ClassLoader cl = context.getClassLoader();
 data.intent.setExtrasClassLoader(cl);
 data.intent.prepareToEnterProcess(
 isProtectedComponent(data.info) || isProtectedBroadcast(data.intent),
 context.getAttributionSource());
 data.setExtrasClassLoader(cl);
 receiver = packageInfo.getAppFactory()
 .instantiateReceiver(cl, data.info.name, data.intent);
 } catch (Exception e) {
 data.sendFinished(mgr);
 ...
 }

 try {

 sCurrentBroadcastIntent.set(data.intent);
 receiver.setPendingResult(data);
 receiver.onReceive(context.getReceiverRestrictedContext(),
 data.intent);
 } catch (Exception e) {
 data.sendFinished(mgr);
 } finally {
 sCurrentBroadcastIntent.set(null);
 }

 if (receiver.getPendingResult() != null) {
 data.finish();
 }
}

这个代码中主要有两个try-catch的代码块,分别是两个主要的功能区。因为静态注册的广播,我们的广播接收器是没有构建的,AMS传过来的只是广播的类名,因此,第一块代码的功能就是创建广播接收器对象。第二块代码则是去调用广播接收器的onReceive方法,从而传递广播。另外这里会调用PendingResultfinish去执行广播处理完成之后的逻辑,以及告知AMS,不过这里的PendingResult就是前面创建的ReceiverData

完成广播的发送

在分析前面的动态注册广播分发和静态注册广播分发的时候,最终在App进程它们都有一个Data,静态为ReceiverData, 动态为Args,他们都继承了PendingResult,最终都会调用PendingResultfinish方法来完成后面的收尾工作,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public final void finish() {
 if (mType == TYPE_COMPONENT) {
 final IActivityManager mgr = ActivityManager.getService();
 if (QueuedWork.hasPendingWork()) {
 QueuedWork.queue(new Runnable() {
 @Override public void run() {
 sendFinished(mgr);
 }
 }, false);
 } else {
 sendFinished(mgr);
 }
 } else if (mOrderedHint && mType != TYPE_UNREGISTERED) {
 final IActivityManager mgr = ActivityManager.getService();
 sendFinished(mgr);
 }
}

这里的QueuedWork主要用于运行SharedPreferences写入数据到磁盘,当然这个如果其中有未运行的task则会添加一个Task到其中来运行sendFinished,这样做的目的是为了保证如果当前除了广播接收器没有别的界面或者Service运行的时候,AMS不会杀掉当前的进程。否则会直接运行sendFinished方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public void sendFinished(IActivityManager am) {
 synchronized (this) {
 if (mFinished) {
 throw new IllegalStateException("Broadcast already finished");
 }
 mFinished = true;
 try {
 if (mResultExtras != null) {
 mResultExtras.setAllowFds(false);
 }
 if (mOrderedHint) {
 am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras,
 mAbortBroadcast, mFlags);
 } else {
 am.finishReceiver(mToken, 0, null, null, false, mFlags);
 }
 } catch (RemoteException ex) {
 }
 }
}

这里就是调用AMS的finishReceiver方法,来告诉AMS广播接收的处理已经执行完了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public void finishReceiver(IBinder who, int resultCode, String resultData,
 Bundle resultExtras, boolean resultAbort, int flags) {
 if (resultExtras != null && resultExtras.hasFileDescriptors()) {
 throw new IllegalArgumentException("File descriptors passed in Bundle");
 }

 final long origId = Binder.clearCallingIdentity();
 try {
 boolean doNext = false;
 BroadcastRecord r;
 BroadcastQueue queue;

 synchronized(this) {
 if (isOnFgOffloadQueue(flags)) {
 queue = mFgOffloadBroadcastQueue;
 } else if (isOnBgOffloadQueue(flags)) {
 queue = mBgOffloadBroadcastQueue;
 } else {
 queue = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0
 ? mFgBroadcastQueue : mBgBroadcastQueue;
 }

 r = queue.getMatchingOrderedReceiver(who);
 if (r != null) {
 doNext = r.queue.finishReceiverLocked(r, resultCode,
 resultData, resultExtras, resultAbort, true);
 }
 if (doNext) {
 }
 trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
 }

 } finally {
 Binder.restoreCallingIdentity(origId);
 }
}

相关的逻辑从13行开始,首先仍然是根据广播的flag找到之前的BroadcastQueue,之后根据IBinder找到发送的这一条BroadcastRecord,调用Queue的finishReceiverLocked方法。根据它的返回值,再去处理队列中的下一个广播记录。最后的trimApplicationsLocked里面会视情况来决定是否停止App进程,我们这里就不进行分析了。

processNextBroadcastLocaked前面已经分析过了,这里只需要来看finishReceiverLocked方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public boolean finishReceiverLocked(BroadcastRecord r, int resultCode,
 String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) {
 final int state = r.state;
 final ActivityInfo receiver = r.curReceiver;
 final long finishTime = SystemClock.uptimeMillis();
 final long elapsed = finishTime - r.receiverTime;
 r.state = BroadcastRecord.IDLE;
 final int curIndex = r.nextReceiver - 1;
 if (curIndex >= 0 && curIndex < r.receivers.size() && r.curApp != null) {
 final Object curReceiver = r.receivers.get(curIndex);

 }
 ...

 r.receiver = null;
 r.intent.setComponent(null);
 if (r.curApp != null && r.curApp.mReceivers.hasCurReceiver(r)) {
 r.curApp.mReceivers.removeCurReceiver(r);
 mService.enqueueOomAdjTargetLocked(r.curApp);
 }
 if (r.curFilter != null) {
 r.curFilter.receiverList.curBroadcast = null;
 }
 r.curFilter = null;
 r.curReceiver = null;
 r.curApp = null;
 mPendingBroadcast = null;

 r.resultCode = resultCode;
 r.resultData = resultData;
 r.resultExtras = resultExtras;
 ....
 r.curComponent = null;

 return state == BroadcastRecord.APP_RECEIVE
 || state == BroadcastRecord.CALL_DONE_RECEIVE;
}

在这里,我们最关注的代码就是17行开是的代码,从mReceivers列表中移除BroadcastRecord,并且把ReceiverListcurBroadcast设置为空,并且其他几个参数也设置为空,这样才算完成了广播的分发和处理。

总结

以上就是广播接收器的注册,以及动态、静态广播分发的分析了。关于取消注册是跟注册相关的过程,理解了注册的逻辑,取消注册也可以很快的搞清楚。关于sticky的广播,限于篇幅先不分析了。而有序广播,它在AMS端其实和静态注册的广播是差不多,不过它在调用App进程的时候是有差别的。另外关于权限相关的逻辑,以后在权限代码的分析中可以再进行关注。

看完评论一下吧

Android源码分析:再读消息循环源码

Android消息循环在应用开发中会经常涉及,我以前也分析过。不过那个时候分析的还是以很老的Android源码来进行的,并且只是分析了Java层的代码,当时的文章为:Android消息循环分析。而Native层,以及一些新增的功能,都没有涉及,今天再读源码,对其进行再次分析。

消息循环简化版本

对于应用层的开发者来说,虽然已经过了10年,java层的Api还是跟之前一样的,依然是通过Handler发送消息,Looper会中消息队列中取消息,消息会根据Handler中的callback或者消息自己的callback执,如上图所示。我之前分析的发送消息和处理消息已经比较清楚了,这块不再看了。这里主要分析一下从MessageQueue取消息,之前涉及的文件描述符的监控和Native层的一些实现等进行分析。

java层loop取消息

首先来看java层如何从消息队列取消息的,Looper中有如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public static void loop() {
 final Looper me = myLooper();
 ...
 me.mInLoop = true;
 Binder.clearCallingIdentity();
 final long ident = Binder.clearCallingIdentity();
 ...
 for (;;) {
 if (!loopOnce(me, ident, thresholdOverride)) {
 return;
 }
 }
}

以上代码核心就是拿到当前线程的Looper然后,在无限循环当中取调用loopOnceloopOnce代码很长,但是忽略错误处理和Log,核心代码如下:

1
2
3
4
5
6
7
8
9
private static boolean loopOnce(final Looper me,
 final long ident, final int thresholdOverride) {
 Message msg = me.mQueue.next(); //从消息队列中取消息
 ...
 msg.target.dispatchMessage(msg); //分发消息
 ...
 msg.recycleUnchecked(); //回收消息,方便下一次发送消息使用
 return true;
}

loopOnce中主要就是去通过MessageQueue取消息,之后在分发消息,并且回收消息。再来看MessageQueuenext方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Message next() {
 final long ptr = mPtr;
 ...
 int nextPollTimeoutMillis = 0;
 for (;;) {
 nativePollOnce(ptr, nextPollTimeoutMillis);
 synchronized (this) {
 Message prevMsg = null;
 Message msg = mMessages;
 if (msg != null && msg.target == null) {
 do {
 prevMsg = msg;
 msg = msg.next;
 } while (msg != null && !msg.isAsynchronous());
 }
 if (msg != null) {
 if (now < msg.when) {
 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
 } else {
 mBlock = false;
 if (preMsg != null) {
 prevMsg.next = msg.next;
 } else {
 mMessages = msg.next;
 }
 msg.next = null;
 msg.markInUse();
 return msg;
 }
 } else {
 nextPollTimeoutMillis = -1;
 }
 ...
 }
 ...
 }
}

以上为next方法的简化,在Java层的MessageQueue的实现就是一个链表,因此向其中发送消息或者取消息的过程就是链表添加或者删除的过程。在第21行到第26行就是从链表中删除msg的过程。其中这个链表它的头节点是存放在mMessages这个变量,Message在插入链表的时候,也是按照事件先后运行放到链表当中的。

在这个方法的开头,我们看到mPtr,它就是MessageQueue在native层对应的对象,不过Native的Message和Java层的Message是相互独立的,在读取next的时候,也会通过nativePollOnce来native层来读取一个消息,另外在这里还传了一个nextPollTimeoutMillis,用来告诉native需要等待的时间,具体后面在来具体分析相关代码。

因为我们的消息循环中除了放置我们通过Handler所发送的消息之外,还会存在同步信号的屏障,比如ViewRootImpl就会在每一次scheduleTraversals的时候发送一个屏障消息。屏障消息和普通消息的区别就是没有targetHandler。因此在第10行,当我们检查到是屏障消息的时候,会跳过它, 并且查找它之后的第一条异步消息。 另外就是在这个do-while的循环条件中,我们可以看到它还有判断消息是否为Asynchronous的,我们正常创建的Handler一般async都是false,也就是说消息的这个值也是为false。而异步的,一般会被IMS,WMS,Display,动画等系统组件使用,应用开发者无法使用。

这里我们只要知道,如果有异步消息,就会先执行异步消息。在第17行,这里还会判断消息的事件,如果消息的when比当前事件大的化,那么这个消息还不能够执行,这时候需要去等待,这里就会给nextPollTimeoutMillis去赋值。

Native层的MessageQueue和Looper

我们刚刚看MessageQueue的代码时候,看到mPtr,它对应native层的MessageQueue的指针。它的初始化在MessageQueue的构造方法中,也就是调用nativeInit,其内部源码为调用NativeMessageQueue的构造方法,源码在android_os_MessageQueue.cpp中:

1
2
3
4
5
6
7
8
NativeMessageQueue::NativeMessageQueue() :
 mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
 mLooper = Looper::getForThread();
 if (mLooper == NULL) {
 mLooper = new Looper(false);
 Looper::setForThread(mLooper);
 }
}

这里我们可以看到在Native层,创建MessageQueue的时候,也会创建Looper,当然如果当前线程存在Looper则会直接使用。Native层的Looper跟Jav层一样,是存放在ThreadLocal当中的,可以看如下代码:

1
2
3
4
5
sp<Looper> Looper::getForThread() {
 int result = pthread_once(& gTLSOnce, initTLSKey);
 Looper* looper = (Looper*)pthread_getspecific(gTLSKey);
 return sp<Looper>::fromExisting(looper);
}

到这里,我们知道对于一个启动了消息循环的线程,它在Java层和Native层分别会有各自的MessageQueue和Looper,java层通过mPtr来引用Native层的对象,从而使得两层能够产生联系。

Native层pollOnce

之前分析Java层获取消息的时候,会有一个地方调用nativePollOnce,它在native拿到NativeMessageQueue之后会调用它的pollOnce方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
 mPollEnv = env;
 mPollObj = pollObj;
 mLooper->pollOnce(timeoutMillis);
 mPollObj = NULL;
 mPollEnv = NULL;

 if (mExceptionObj) {
 env->Throw(mExceptionObj);
 env->DeleteLocalRef(mExceptionObj);
 mExceptionObj = NULL;
 }
}

这里的pollObj为我们java层的MessageQueue, 这里继续调用了native层的pollOnce,代码如下:

1
2
3
4
5
6
7
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) { //我们的调用流程只会传timeoutMillis
 ...
 for (;;) {
 ...
 result = pollInner(timeoutMillis);
 }
}

这里省略了一些结果处理的代码,我们可以回头在看,这可以看到开启了一个无限循环,并调用pollInner, 这个方法比较长,我们先分块看其中的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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;
 }
}
int result = POLL_WAKE;
mResponses.clear(); //清除reponses列表和计数
mResponseIndex = 0;
mPolling = true;

struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

mPolling = false;

这里timeoutMillis是我们从java层传过来的下一个消息的执行事件,而mNextMessageUptime是native层的最近一个消息的执行事件,这个根据这两个字段判断需要等待的事件。

在之后调用epoll_wait来等待I/O事件,或者到设置的超时时间结束等待,这样做可以避免Java层和Native层的循环空转。此处的epoll_wait除了避免循环空转还有另一个作用,我们之前在分析IMS也使用过LooperaddFd,这里如果对应的文件描述符有变化,这里就会拿到,并反应在eventCount上,这里我们先不具体分析,后面再看。

Native消息的读取和处理

当等待完成之后,就会去native的消息队列中取消息和处理,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
 handler->handleMessage(message);
 }

 mLock.lock();
 mSendingMessage = false;
 result = POLL_CALLBACK;
 } else {
 mNextMessageUptime = messageEnvelope.uptime;
 break;
 }
 }

在Native中消息是放在mMessageEnvelope当中,这是一个verctor也就是一个动态大小的数组。不过不看这个的化,我们可以看到这里读取消息,以及读取它的执行时间uptime跟java层的代码是很像是的,甚至比java层还要简单许多,就是直接拿数组的第一条。之后使用MessageHandler执行handleMessage。这里的MessageHandler跟java层的也是很像,这里再列一下MessageEnvelopeMessage的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
struct MessageEnvelope {
 MessageEnvelope() : uptime(0) { }

 MessageEnvelope(nsecs_t u, sp<MessageHandler> h, const Message& m)
 : uptime(u), handler(std::move(h)), message(m) {}

 nsecs_t uptime;
 sp<MessageHandler> handler;
 Message message;
};

struct Message {
 Message() : what(0) { }
 Message(int w) : what(w) { }

 /* The message type. (interpretation is left up to the handler) */
 int what;
};

这里和java层的区别是,拆分成了两个结构体,但是呢比java层的还是要简单很多。到这里Native层和Java层对应的消息循环体系就分析完了。但是Native层除了这个消息循环还有一些其他东西,就是前面说到的文件描述符的消息传递。

文件描述符消息读取和处理

前面在pollOnce中还是有关于文件描述符消息的处理,这里继续分析。前面的epoll_wait就会读取相关的事件,读取完事件之后的处理如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
if (eventCount < 0) { //如果读出来的eventCount小于0,则说明有错误
 if (errno == EINTR) { //处理错误,并且跳转到Done去读取native层的消息
 goto Done;
 }
 result = POLL_ERROR;
 goto Done;
}

if (eventCount == 0) { //直接超时,没有读到事件
 result = POLL_TIMEOUT;
 goto Done;
}

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 {
 }
 } else {
 const auto& request_it = mRequests.find(seq);
 if (request_it != mRequests.end()) {
 const auto& request = request_it->second;
 int events = 0;
 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 {
 ...
 }
 }
}

前面的错误处理我们直接看我的注释即可。后面会根据返回的eventCount来一次对每一个eventItem做处理,其他它的u64为序列号,这些为注册到LoopermRequests的序列号,其中1为WAKE_EVENT_FD_SEQ,也就是mWakeEventFd的序列,这里唤醒我们先不管了,直接看后面的正常的文件描述符事件监听。 这里首先会通过seq找到对应的Request,并根据epollEvents来设置他们的事件类型,之后封装成为Response放到mResponses当中。在这些做完,后面同样是跳转到Done后面的代码块,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Done: ;
 ...
 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;
 int callbackResult = response.request.callback->handleEvent(fd, events, data);
 if (callbackResult == 0) {
 AutoMutex _l(mLock);
 removeSequenceNumberLocked(response.seq);
 }

 response.request.callback.clear(); //移除response对与callback的引用
 result = POLL_CALLBACK;
 }
 }

这里则是遍历刚刚我们填充的mResponses数组,从其中取出每一个Response,并调用它的Request的Callback回调的handleEvent方法,它的使用我们之前分析IMSServiceManager启动的时候已经见到过了。

以上说的是Java层会初始化Handler和Looper的情况,如果只是Native层使用的话,一般怎么用的呢。我们以BootAnimation中的使用为例,它是在BootAnimation.cpp当中,在初始化BootAnimation对象的时候,会创建一个Looper,代码如下:

1
new Looper(false)

readyToRun中添加文件描述符的监听:

1
2
3
4
5
status_t BootAnimation::readyToRun() {
 ...
 mLooper->addFd(mDisplayEventReceiver->getFd(), 0, Looper::EVENT_INPUT,
 new DisplayEventCallback(this), nullptr);
}

最后去循环调用pollOnce,来获取消息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
bool BootAnimation::android() {
 do {
 processDisplayEvents();
 ...
 } while (!exitPending());
}

void BootAnimation::processDisplayEvents() {
 mLooper->pollOnce(0);
}

这就是Android Framework当中,大部分的Native场景使用消息循环的方式。而Native中,想要跟Java层一样发送消息,则是调用Looper的sendMessage方法。而Native层的Handler我们可以理解为只是一个Message的回调,和java层的Handler功能不可同日而语。

异步消息

在Java层的消息循环中,消息是有同步和异步之分的,异步消息一般都会伴随则屏障消息,我们之前分析的获取next消息中可以看到,如果第一个消息是屏障消息,会找后面的第一条异步消息来执行。

同时在enqueueMessage的代码中也有如下逻辑:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//MessageQueue.java
needWake = mBlocked && p.target == null && msg.isAsynchronous();
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.next = msg;

插入异步消息会改变唤醒等待的状态,如果链表头是屏障消息,且之前调用next的时候mBlocked设置为了true,且当前是异步消息会设置成唤醒,但是如果当前的消息队列中已经有了比当前消息更早执行的消息,则不会唤醒。

到这就完成了消息循环的所有分析了。也欢迎读者朋友交流探讨。

看完评论一下吧

Android消息循环分析

我们的常用的系统中,程序的工作通常是有事件驱动和消息驱动两种方式,在Android系统中,Java应用程序是靠消息驱动来工作的。

消息驱动的原理就是:
1. 有一个消息队列,可以往这个队列中投递消息;
2. 有一个消息循环,不断从消息队列中取出消息,然后进行处理。
在Android中通过Looper来封装消息循环,同时在其中封装了一个消息队列MessageQueue。
另外Android给我们提供了一个封装类,来执行消息的投递,消息的处理,即Handler。

在我们的线程中实现消息循环时,需要创建Looper,如:

class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare(); //1.调用prepare
......
Looper.loop(); //2.进入消息循环
}
}

看上面的代码,其实就是先准备Looper,然后进入消息循环。

  1. 在prepare的时候,创建一个Looper,同时在Looper的构造方法中创建一个消息队列MessageQueue,同时将Looper保存到TLV中(这个是关于ThreadLocal的,不太懂,以后研究了再说)
  2. 调用loop进入消息循环,此处其实就是不断到MessageQueue中取消息Message,进行处理。

然后再看我们如何借助Handler来发消息到队列和处理消息

Handler的成员(非全部):

final MessageQueue mQueue;
final Looper mLooper;
final Callback mCallback;

Message的成员(非全部):

Handler target;
Runnable callback;

可以看到Handler的成员包含Looper,通过查看源代码,我们可以发现这个Looper是有两种方式获得的,1是在构造函数传进来,2是使用当前线程的Looper(如果当前线程无Looper,则会报错。我们在Activity中创建Handler不需要传Handler是因为Activity本身已经有一个Looper了),MessageQueue也就是Looper中的消息队列。

然后我们看怎么向消息队列发送消息,Handler有很多方法发送队列(这个自己可以去查),比如我们看sendMessageDelayed(Message msg, long delayMillis)

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
// SystemClock.uptimeMillis() 获取开机到现在的时间
}
//最终所有的消息是通过这个发,uptimeMillis是绝对时间(从开机那一秒算起)
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
return sent;
}

看上面的的代码,可以看到Handler将自己设为Message的target,然后然后将msg放到队列中,并且指定执行时间。

消息处理

处理消息,即Looper从MessageQueue中取出队列后,调用msg.target的dispatchMessage方法进行处理,此时会按照消息处理的优先级来处理:

  1. 若msg本身有callback,则交其处理;
  2. 若Handler有全局callback,则交由其处理;
  3. 以上两种都没有,则交给Handler子类实现的handleMessage处理,此时需要重载handleMessage。

我们通常采用第三种方式进行处理。

注意!!!!我们一般是采用多线程,当创建Handler时,LooperThread中可能还未完成Looper的创建,此时,Handler中无Looper,操作会报错。

我们可以采用Android为我们提供的HandlerThread来解决,该类已经创建了Looper,并且通过wait/notifyAll来避免错误的发生,减少我们重复造车的事情。我们创建该对象后,调用getLooper()即可获得Looper(Looper未创建时会等待)。

补充

本文所属为Android中java层的消息循环机制,其在Native层还有消息循环,有单独的Looper。并且2.3以后MessageQueue的核心向Native层下移,native层java层均可以使用。这个我没有过多的研究了!哈哈

PS:本文参考《深入理解Android:卷I》

看完评论一下吧

搞机!给红米 K40 刷一个 PixelExperience

随着国产手机系统日益完善,各大厂商对 Bootloader(BL)解锁的限制也愈发严格。这导致越来越少的用户愿意尝试对安卓手机进行 root 或刷机等深度定制。我使用了几个月的澎湃 OS 后,最终还是决定将我的红米 K40 刷入 PixelExperience(简称 PE)系统。在这篇文章中,我将为大家详细记录这次"搞机"的全过程。

搞机!给红米 K40 刷一个 PixelExperience最先出现在Jack‘s Space

Android源码分析:系统进程中事件的读取与分发

之前分析的是从InputChannel中读取Event,并且向后传递,进行消费和处理的过程。但是之前的部分呢,事件如何产生,事件怎么进入到InputChanel当中的,事件又是如何跨进程到达App进程,这里继续来分析。

以上为system进程的流程的简化图,这里我们可以看到几个重要的组件,这里以触摸事件来进行分析(后文的分析也将会以触摸事件为主进行分析)并且简单的描绘了事件从EventHub到服务端的InputChannel发送事件的全部过程。具体内容一起来看下面的代码。

InputManagerService的创建

因为事件的分发涉及到不少类,我们先从InputManagerService(IMS)的初始化出发,进行分析。入口代码在SystemServer.java中,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
WindowManagerService wm = null;
InputManagerService inputManager = null;
inputManager = new InputManagerService(context);
wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
 new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
 DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
 /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);

inputManager.setWindowManagerCallbacks(wm.getInputManagerCallback());
inputManager.start();
}

这里我们可以看到WMS的创建我们传入了IMS,并且IMS也依赖WindowMnagerCallbacks,我们先看一下IMS的构造方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public InputManagerService(Context context) {
 this(new Injector(context, DisplayThread.get().getLooper()));
}

@VisibleForTesting
InputManagerService(Injector injector) {
 ...
 mHandler = new InputManagerHandler(injector.getLooper());
 mNative = injector.getNativeService(this);
 ...
}

我们主要关注这个mNative的构建,它是NativeImpl,它的创建过程如下:

1
new NativeInputManagerService.NativeImpl(service, mContext, mLooper.getQueue());

这里的Looper是前面传进来的DisplayThread的Looper。在NativeImpl的构造方法中调用了init方法,并获取到了它的native指针,这里需要看com_android_server_input_InputManagerService.cpp中的natvieInit方法,代码如下:

1
2
3
4
5
6
7
8
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
 jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
 sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
 NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
 messageQueue->getLooper());
 im->incStrong(0);
 return reinterpret_cast<jlong>(im);
}

这里创建了NativeInputManager

NativeInputManager初始化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
NativeInputManager::NativeInputManager(jobject contextObj,
 jobject serviceObj, const sp<Looper>& looper) :
 mLooper(looper), mInteractive(true) {
 JNIEnv* env = jniEnv();

 mServiceObj = env->NewGlobalRef(serviceObj);

 {
 AutoMutex _l(mLock);
 mLocked.systemUiLightsOut = false;
 mLocked.pointerSpeed = 0;
 mLocked.pointerAcceleration = android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION;
 mLocked.pointerGesturesEnabled = true;
 mLocked.showTouches = false;
 mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
 }
 mInteractive = true;

 InputManager* im = new InputManager(this, this);
 mInputManager = im;
 defaultServiceManager()->addService(String16("inputflinger"), im);
}

这个构造方法中,传入的jobject为我们之前的NativeImpl,后面有需要调用java层的时候会用到它。除此之外我们看到又创建了一个InputManger,并且把它注册到了ServiceManger当中,名称为inputflinger

我们继续看InputManager的初始化代码,它传如的两个参数readerPolicydispatcherPolicy的实现都在NativeInputManager当中。它的代码如下:

1
2
3
4
5
6
7
8
InputManager::InputManager(
 const sp<InputReaderPolicyInterface>& readerPolicy,
 const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
 mDispatcher = createInputDispatcher(dispatcherPolicy);
 mClassifier = std::make_unique<InputClassifier>(*mDispatcher);
 mBlocker = std::make_unique<UnwantedInteractionBlocker>(*mClassifier);
 mReader = createInputReader(readerPolicy, *mBlocker);
}

这里首先创建了InputDispatcher,之后创建的mClassifiermBlockerInputDispatcher一样都是继承自InputListenerInterface,它们的作用为在事件经过InputDispatcher分发之前,可以做一些预处理。最后创建InputReader,事件会经由它传递到InputDispatcher,最后再由InputDispatcher分到到InputChannel。下面来详细分析。

事件源的初始化

因为InputDispatcher初始化代码比较简单,我们从createInputReader的源码开始看起来:

1
2
3
4
std::unique_ptr<InputReaderInterface> createInputReader(
 const sp<InputReaderPolicyInterface>& policy, InputListenerInterface& listener) {
 return std::make_unique<InputReader>(std::make_unique<EventHub>(), policy, listener);
}

我们可以看到在创建InputReader之前首先创建了一个EventHub,看名字我们就知道它是一个事件的收集中心。我们看它的构造方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
EventHub::EventHub(void)
 : mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD),
 mNextDeviceId(1),
 ...
 mPendingINotify(false) {
 ensureProcessCanBlockSuspend();

 mEpollFd = epoll_create1(EPOLL_CLOEXEC); //创建epoll实例,flag表示执行新的exec时候会自动关闭

 mINotifyFd = inotify_init1(IN_CLOEXEC); //创建inotify实例,该实例用于监听文件的变化

 if (std::filesystem::exists(DEVICE_INPUT_PATH, errorCode)) {
 addDeviceInputInotify();
 } else {
 addDeviceInotify();
 isDeviceInotifyAdded = true;

 }

 struct epoll_event eventItem = {};
 eventItem.events = EPOLLIN | EPOLLWAKEUP;
 eventItem.data.fd = mINotifyFd;
 int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
 int wakeFds[2];
 result = pipe2(wakeFds, O_CLOEXEC);

 mWakeReadPipeFd = wakeFds[0];
 mWakeWritePipeFd = wakeFds[1];

 result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);

 result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);

 eventItem.data.fd = mWakeReadPipeFd;
 result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);
}

从上面的代码我们可以看到这里主要为创建inotify并且通过epoll去监听文件的变化,其中还是用管道创建了wakeReadPipewakeReadPipe的文件描述,用于接收回调。我们先看一下addDeviceInputInotify()方法:

1
2
3
4
void EventHub::addDeviceInputInotify() {
 mDeviceInputWd = inotify_add_watch(mINotifyFd, DEVICE_INPUT_PATH, IN_DELETE | IN_CREATE);

}

其中DEVICE_INPUT_PATH的值为/dev/input,也就是说把这个path放到mINofiyFd的监控当中。对于了解Linux的人应该知道,在Linux中万物结尾文件,因此我们的输入也是文件,当事件发生的时候便会写入到/dev/input下面,文件变化我们也会得到通知。我这里使用ls命令打印了一下我的手机,/dev/input下面有如下文件:

1
event0 event1 event2 event3 event4 event5

具体这些文件的写入,那就是内核和驱动相关的东西了,我们这里不再讨论。而事件的读取,我们后面再进行分析。

IMS的启动

各个对象都构建完成之后,IMS要进行启动,才能够对事件进行处理并且分发。SystemServer中已经调用了IMS的start方法,它其中又会调用NativeInputMangerstart方法,最终会调用 native层的InputManagerstart方法。而其中分别又调用了Dispatcher的start方法和Reader的start方法。我们分别分析。

InputDispater 调用start

1
2
3
4
5
6
7
8
status_t InputDispatcher::start() {
 if (mThread) {
 return ALREADY_EXISTS;
 }
 mThread = std::make_unique<InputThread>(
 "InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });
 return OK;
}

这个方法中主要创建了InputThread,并且给它传了两个lambda,分别执行InputDispatchdispatchOnce方法和执行Looper的wake方法。我们看InputThread的构造方法:

1
2
3
4
5
InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake)
 : mName(name), mThreadWake(wake) {
 mThread = new InputThreadImpl(loop);
 mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
}

可以看到其中创建了InputThreadImpl,这个类才是真的继承的系统的Thread类,这里构建完成它就继续调用了它的run方法,这样它就会启动了。这里我们需要注意这个 线程的优先级,为PRIORITY_URGEN_DISPLAY,可以看到优先级是非常高了。

1
2
3
4
bool threadLoop() override {
 mThreadLoop();
 return true;
}

另外就是我们传进来的loop传入了这个对象,并且在它的threadLoop中会执行它。对于native中的线程,我们在threadLoop中实现逻辑就可以了,并且这里我们返回值为true,它会继续循环执行 。而我们传入的另一个lambda,则是在线程推出的时候调用。这个线程循环中执行的就是我们的InputDispatch中 的dispatchOnce方法,也就是消息的投递,后面再来分析。

InputReader调用start方法

1
2
3
4
5
6
7
8
status_t InputReader::start() {
 if (mThread) {
 return ALREADY_EXISTS;
 }
 mThread = std::make_unique<InputThread>(
 "InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
 return OK;
}

这里的初始化,我们可以看到跟前面的InputDispatch很类似,连InputThread用的都是同一个类,内部也就一样有InputThreadImpl了。这里则是调用了InputReader内部的loopOnce方法。到这里系统就完成了输入事件分发的初始化了。

我们在看事件的分发之前,先看一下应用中的接收和系统的InputDispatch进行连接的过程。

InputChannel的注册

我们之前分析应用层的事件传递的时候,只是谈到了InputChannel是在WMS调用如下代码生成的:

1
mInputChannel = mWmService.mInputManager.createInputChannel(name);

但是内部如何创建InputChannel的,以及 这个InputChannel是如何收到消息的我们都没有涉及,我们现在继续分析它一下。这个createInputChannel内部最终会调用到native层的InputDispatchercreateInputChannel方法, 代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputChannel(const std::string& name) {
 std::unique_ptr<InputChannel> serverChannel;
 std::unique_ptr<InputChannel> clientChannel;
 status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel);
 ...
 { // acquire lock
 std::scoped_lock _l(mLock);
 const sp<IBinder>& token = serverChannel->getConnectionToken();
 int fd = serverChannel->getFd();
 sp<Connection> connection =
 n1ew Connection(std::move(serverChannel), false /*monitor*/, mIdGenerator);
 ...
 mConnectionsByToken.emplace(token, connection);

 std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback, this, std::placeholders::_1, token);

 mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, new LooperEventCallback(callback), nullptr);
 } // release lock

 // Wake the looper because some connections have changed.
 mLooper->wake();
 return clientChannel;
}

首先是第4行代码,这里创建了InputChannel,而它又分为serverChannelclientChannel,返回调用方的是`clientChannel。

我们先进去看看其源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
status_t InputChannel::openInputChannelPair(const std::string& name,
 std::unique_ptr<InputChannel>& outServerChannel,
 std::unique_ptr<InputChannel>& outClientChannel) {
 int sockets[2];
 if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) { //创建socket对
 ..
 return result;
 }
 ..
 sp<IBinder> token = new BBinder();

 std::string serverChannelName = name + " (server)";
 android::base::unique_fd serverFd(sockets[0]); //获取server socket fd
 outServerChannel = InputChannel::create(serverChannelName, std::move(serverFd), token); //创建server InputChannel

 std::string clientChannelName = name + " (client)";
 android::base::unique_fd clientFd(sockets[1]);
 outClientChannel = InputChannel::create(clientChannelName, std::move(clientFd), token); //创建Client InputChannel
 return OK;
}

以上代码我们可以看到就是创建了一对socket,分别放到两个InputChannel当中,并且这里创建了一个BBinder作为两个InputChannel的token,具体用处我们后面会再提到。此时可以继续回看前面的createInputChannel方法,在11行,创建了一个 Connection对象,并且以前面创建的BBinder为key放到了mConnectionsByToken当中,Connection的用处留到后面继续讲。

在15行创建了一个callback,其中会执行InputDispatcherhandleReceiveCallback方法,并且这个callback被添加looper的addFd的时候设置进去了,这里的fd就是之前创建的ServerInputChannel的socket的文件描述符。到这里就完成了初始化,添加了服务端InputChannel的文件描述符监听。

事件触发

我们之前在分析InputManger的启动的时候,已经看到了事件是通过/dev/input来通知到EventHub,而InputReader通过Looper监听了/dev/input的文件描述符,从而让我们事件传递的系统动起来。那么我们首先就从InputReaderloopOnce开始看起来。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
void InputReader::loopOnce() {
 ...
 size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

 { // acquire lock
 std::scoped_lock _l(mLock);
 mReaderIsAliveCondition.notify_all();

 if (count) {
 processEventsLocked(mEventBuffer, count);
 }
 ...
 } // release lock
 ...
 mQueuedListener.flush();
}

我们这里省略了设备变化,超时等相关的代码,仅仅保留了事件读取相关的部分。我们看到,首先在第3行中,从EventHub中去获取新的事件,之后在第10行,去处理这些事件,第15行会清楚所有的事件,我们分别看看各个里面的逻辑。

从EventHub读取事件

首先是getEvents方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
 std::scoped_lock _l(mLock);
 struct input_event readBuffer[bufferSize];
 RawEvent* event = buffer;
 size_t capacity = bufferSize;
 bool awoken = false;
 for (;;) {
 nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);

 bool deviceChanged = false;
 //pendingIndex小于PendingCount,说明之前有事件还为处理完
 while (mPendingEventIndex < mPendingEventCount) {
 const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++];
 ...
 Device* device = getDeviceByFdLocked(eventItem.data.fd);
 if (device == nullptr) { //未能找到device,报错跳出
 continue;
 }

 // EPOLLIN表示有事件可以处理
 if (eventItem.events & EPOLLIN) {
 int32_t readSize =
 read(device->fd, readBuffer, sizeof(struct input_event) * capacity);
 if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
 // 接收到通知之前,设备以及不见了
 deviceChanged = true;
 closeDeviceLocked(*device);
 } else if() { //其中的错误情况,忽略掉
 } else {
 int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id;

 size_t count = size_t(readSize) / sizeof(struct input_event); //根据一个事件的大小,来算同一个设备上面读取到的事件的个数
 //以下为具体保存事件到event当中
 for (size_t i = 0; i < count; i++) {
 struct input_event& iev = readBuffer[i];
 event->when = processEventTimestamp(iev);
 event->readTime = systemTime(SYSTEM_TIME_MONOTONIC);
 event->deviceId = deviceId;
 event->type = iev.type;
 event->code = iev.code;
 event->value = iev.value;
 event += 1;
 capacity -= 1;
 }
 if (capacity == 0) { //缓冲区已经满了,无法在记录事件,跳出
 mPendingEventIndex -= 1;
 break;
 }
 }
 } else if (eventItem.events & EPOLLHUP) {
 ...
 } else {
 ...
 }
 }
 ...
 //event和buffer地址不同说明已经拿到事件了,可以跳出循环
 if (event != buffer || awoken) {
 break;
 }

 mPendingEventIndex = 0;
 mLock.unlock(); // poll之前先加锁
 int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis);
 mLock.lock(); // poll完之后从新加锁

 if (pollResult == 0) {
 // Timed out.
 mPendingEventCount = 0;
 break;
 }

 if (pollResult < 0) {
 mPendingEventCount = 0;
 if (errno != EINTR) {
 usleep(100000);
 }
 } else {
 mPendingEventCount = size_t(pollResult);
 }
 }

 // event为填充之后的指针地址,而buffer为开始的地址,相减获得count
 return event - buffer;
}

这个方法是很复杂的,但是我们主要分析事件的分发,因此其中关于设备变化,设备响应,错误处理等等相关的代码都省略了。这个方法,我们传入了一个RawEvent的指针用来接收事件,另外传了bufferSize来表示我们所能接收的事件数量。这个方法使用了两层循环来进行逻辑的处理,外层的为无限循环。当我们第一次进入这个方法当中,mPendingEventCountmPendingEventIndex都是0,因此不会进入第二层的循环,这个时候会执行到64行,调用epoll_wait系统调用,去读取事件,读取的结果会放到mPendingEventItems当中,之后会算出pendingCount。这样继续循环,我们就可以进入内存循环当中了。 在刚刚的PendingEventItem中并没有存储具体的事件,而是存储的事件发生的设备文件描述符,在内存的循环中,首先会根据设备的描述符查找设备,并对其进行检查。之后再从设备当中读取事件,拼装成为需要向后分发的事件。 这里的count有点让人迷糊,我画了个图如下所示:

其中我们真正读取的事件的数量,是要看有几个设备,每个设备有多少个事件,对其进行计算。 到这里我们就获取到了事件,这里可以回到InputReader中继续往下看了。

InputReader对事件进行处理

在这里的处理调用的是processEventsLocked,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
 for (const RawEvent* rawEvent = rawEvents; count;) {
 int32_t type = rawEvent->type;
 size_t batchSize = 1;
 //如果不是设备处理相关的事件,则执行。
 if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
 int32_t deviceId = rawEvent->deviceId;
 while (batchSize < count) {
 if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT ||
 rawEvent[batchSize].deviceId != deviceId) {
 //当遇到设备整删除事件,或者不是当前设备的事件,就不能进行批量处理,跳过。
 break;
 }
 batchSize += 1;
 }
 processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
 } else {
 //设备添加删除之类的事件处理,跳过
 }
 count -= batchSize;
 rawEvent += batchSize;
 }
}

这个方法中主要是对与设备增加删除事件和普通事件进行分别处理,如果是普通的事件,会对同一个设备上的事件进行批量处理,批量处理则会调用processEventsForDeviceLocked方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents,
 size_t count) {
 auto deviceIt = mDevices.find(eventHubId);
 if (deviceIt == mDevices.end()) {
 //没有找到设备,返回
 return;
 }

 std::shared_ptr<InputDevice>& device = deviceIt->second;
 if (device->isIgnored()) { //是被忽略的设备,跳过
 return;
 }

 device->process(rawEvents, count);
}

这个方法中主要是查找设备,找到未忽略的设备则会调用设备的process方法进行处理。 InputDevice只是设备的抽象,而其中的处理又会调用InputMapper的方法,InputMapper是抽象类,它有许多的实现,比如我们的触摸事件就会有TouchuInputMapperMultiTouchInputMapper,各种不同的InputMapper会对事件进行处理,拼装成符合相关类型的事件,其中逻辑我们就不继续进行追踪了。

对于touch事件,这个process处理完成,在TouchInputMapper中最终会调用dispatchMotion,这个方法代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
void TouchInputMapper::dispatchMotion(...) {
 PointerCoords pointerCoords[MAX_POINTERS];
 PointerProperties pointerProperties[MAX_POINTERS];
 uint32_t pointerCount = 0;
 ...
 const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
 const int32_t deviceId = getDeviceId();
 std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
 std::for_each(frames.begin(), frames.end(),
 [this](TouchVideoFrame& frame) { frame.rotate(this->mInputDeviceOrientation); });
 NotifyMotionArgs args(getContext()->getNextId(), when, readTime, deviceId, source, displayId,
 policyFlags, action, actionButton, flags, metaState, buttonState,
 MotionClassification::NONE, edgeFlags, pointerCount, pointerProperties,
 pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,
 downTime, std::move(frames));
 getListener().notifyMotion(&args);
}

其中有许多关于多点触控,事件处理的判断,这里只关注最后的部分,就是将事件组装成一个NotifyMotionArgs对象,并调用ListenernotifyMotion方法。这里的getListener()内部首先会调用getContenxt获取Context,而这个Context就是InputReader的内部成员mContext,这这个Listener也就是我们之前在初始化InputReader时候它的成员变量mQueuedListener,那我们下面继续去看它的notifyMotion

notifyMotion

1
2
3
4
void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
 traceEvent(__func__, args->id);
 mArgsQueue.emplace_back(std::make_unique<NotifyMotionArgs>(*args));
}

这里是直接把之前的那个变量放到mArgsQueue当中了。这个时候,我们需要留意一下之前InputReadeloopOnce的15行,这里调用的 flush方法,也是这个QueuedInputListener内部的:

1
2
3
4
5
6
void QueuedInputListener::flush() {
 for (const std::unique_ptr<NotifyArgs>& args : mArgsQueue) {
 args->notify(mInnerListener);
 }
 mArgsQueue.clear();
}

这里这是掉用了我们传进来的NotifyArgs的notify方法,并且传过来的参数mInnerListener是我们之前创建InputManager时候创建的,这里会有三层嵌套,首先是UnWantedInteractionBlocker先处理,之后它会按情况传递给InputClassifier处理,最后是在InputDispatcher当中处理。

我们先看看看notify当中做了什么,再继续往后看。

1
2
3
void NotifyMotionArgs::notify(InputListenerInterface& listener) const {
 listener.notifyMotion(this);
}

这里也是比较简单,就是直接调用了linster的notifyMotion方法,我们可以直接去看了。因为我们主要关注 传递,而不关注处理,这里就跳过,直接看InputDispatcher中的这个方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
 if (!validateMotionEvent(args->action, args->actionButton, args->pointerCount,
 args->pointerProperties)) {
 return; //不合法的触摸事件直接返回
 }

 uint32_t policyFlags = args->policyFlags;
 policyFlags |= POLICY_FLAG_TRUSTED;

 android::base::Timer t;

 bool needWake = false;
 { // acquire lock
 mLock.lock();
 ...
 std::unique_ptr<MotionEntry> newEntry =
 std::make_unique<MotionEntry>(args->id, args->eventTime, args->deviceId,
 args->source, args->displayId, policyFlags,
 args->action, args->actionButton, args->flags,
 args->metaState, args->buttonState,
 args->classification, args->edgeFlags,
 args->xPrecision, args->yPrecision,
 args->xCursorPosition, args->yCursorPosition,
 args->downTime, args->pointerCount,
 args->pointerProperties, args->pointerCoords);
 ...
 needWake = enqueueInboundEventLocked(std::move(newEntry));
 mLock.unlock();
 } // release lock

 if (needWake) {
 mLooper->wake();
 }
}

在这里则是执行完一些检查之后,把事件封装成为MotionEntry,调用enqueueInboundEventLocked,最后调用looperwake方法。enqueueInboundEventLocked代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newEntry) {
 bool needWake = mInboundQueue.empty();
 mInboundQueue.push_back(std::move(newEntry));
 EventEntry& entry = *(mInboundQueue.back());
 switch (entry.type) {
 case EventEntry::Type::MOTION: {
 if (shouldPruneInboundQueueLocked(static_cast<MotionEntry&>(entry))) { //返回true的时候,事件会被移除不处理
 mNextUnblockedEvent = mInboundQueue.back();
 needWake = true;
 }
 break;
 }
 ...
 }

 return needWake;
}

在这里,首先把事件放入mInboundQueue这个deque当中,最后根据事件的类型和信息要不要唤醒looper,如果事件不被移除needWake就为false,前面的wake也不会被调用。但是这个是否调用,不影响我们的后续分析,因为InputDispatch中的Thead会一直循环调用。

InputDispatcher分发消息

说到这里,我们就该来看看InputDispatcherdispatchOnce方法了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
void InputDispatcher::dispatchOnce() {
 nsecs_t nextWakeupTime = LONG_LONG_MAX;
 { // acquire lock
 std::scoped_lock _l(mLock);
 mDispatcherIsAlive.notify_all();

 if (!haveCommandsLocked()) {
 dispatchOnceInnerLocked(&nextWakeupTime);
 }

 if (runCommandsLockedInterruptable()) {
 nextWakeupTime = LONG_LONG_MIN;
 }
 ...
 } // release lock

 //等待下一次调用
 mLooper->pollOnce(timeoutMillis);
}

这里有不少处理下一次唤醒的逻辑,我们都跳过,主要就看一下第8行,进行这一次的实际执行内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
 ...
 if (!mPendingEvent) { //当前没有待处理的pending事件
 if (mInboundQueue.empty()) { //如果事件列表为空
 ...
 if (!mPendingEvent) {
 return;
 }
 } else {
 // 列表中拿一个事件
 mPendingEvent = mInboundQueue.front();
 mInboundQueue.pop_front();
 traceInboundQueueLengthLocked();
 }
 ...
 }

 bool done = false;
 ..
 switch (mPendingEvent->type) {
 ...
 case EventEntry::Type::MOTION: {
 std::shared_ptr<MotionEntry> motionEntry =
 std::static_pointer_cast<MotionEntry>(mPendingEvent);
 ...
 done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime);
 break;
 }
 ...
 }

 if (done) {
 ...
 releasePendingEventLocked();
 *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
 }
}

我们将这个方法进行了简化,仅仅保留了触摸事件的部分代码。首先判断mPendingEvent是否为空,为空的时候我们需要到mPendingEvent中去拿一个,我们之前插入的是尾部,这里是从头部取的。拿到事件进行完种种处理和判断之后,会调用dispatchMotionLocked进行触摸事件的分发:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<MotionEntry> entry,
 DropReason* dropReason, nsecs_t* nextWakeupTime) {
 if (!entry->dispatchInProgress) { //设置事件正在处理中
 entry->dispatchInProgress = true;

 }

 if (*dropReason != DropReason::NOT_DROPPED) {
 //对于要抛弃的事件这里进行处理,返回
 return true;
 }

 const bool isPointerEvent = isFromSource(entry->source, AINPUT_SOURCE_CLASS_POINTER); //读取是否为POINTER
 std::vector<InputTarget> inputTargets;

 bool conflictingPointerActions = false;
 InputEventInjectionResult injectionResult;
 if (isPointerEvent) {
 //如果屏幕触摸事件则去找到对应的window
 injectionResult =
 findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime,
 &conflictingPointerActions);
 } else {
 // Non touch event. (eg. trackball)
 injectionResult =
 findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);
 }
 if (injectionResult == InputEventInjectionResult::PENDING) {
 return false;
 }

 setInjectionResult(*entry, injectionResult);
 ...

 // Dispatch the motion.
 dispatchEventLocked(currentTime, entry, inputTargets);
 return true;
}

这个方法中依然是对于事件做很多的处理和判断,比如否要抛弃等。但是其中最终要的是调用findFocusedWIndowTargetsLocked来找到我们的事件所对应的Window,并且保存相关信息到inputTargets当中,这里获取inputTargets的过程比较复杂,但是简单来说呢就是从之前我们保存在InputDispatcher中的mConnectionsByToken中查找到对应的条目,这里暂不深入分析。拿到这个之后就是调用dispatchEventLocked去分发,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
 std::shared_ptr<EventEntry> eventEntry,
 const std::vector<InputTarget>& inputTargets) {
 ...
 for (const InputTarget& inputTarget : inputTargets) {
 sp<Connection> connection =
 getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
 if (connection != nullptr) {
 prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
 } else {

 }
 }
}

通过这里我们可以看到首先是通过inputTarget去拿到connectionToken,再通过它拿到Connection。最后通过调用prepareDispatchCycleLocked

1
2
3
4
5
6
7
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
 const sp<Connection>& connection,
 std::shared_ptr<EventEntry> eventEntry,
 const InputTarget& inputTarget) {
 ...
 enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}

这个方法简化的化,这是调用第6行的这个方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
 const sp<Connection>& connection,
 std::shared_ptr<EventEntry> eventEntry,
 const InputTarget& inputTarget) {

 bool wasEmpty = connection->outboundQueue.empty();

 // Enqueue dispatch entries for the requested modes.
 enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
 InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
 enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
 InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
 enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
 InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
 enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
 InputTarget::FLAG_DISPATCH_AS_IS);
 enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
 InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
 enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
 InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);

 // If the outbound queue was previously empty, start the dispatch cycle going.
 if (wasEmpty && !connection->outboundQueue.empty()) {
 startDispatchCycleLocked(currentTime, connection);
 }
}

其中对于消息会尝试按照每一种mode都调用enqueueDIspatchEntryLocked方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connection,
 std::shared_ptr<EventEntry> eventEntry,
 const InputTarget& inputTarget,
 int32_t dispatchMode) {

 int32_t inputTargetFlags = inputTarget.flags;
 if (!(inputTargetFlags & dispatchMode)) {
 return;
 }
 inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;

 std::unique_ptr<DispatchEntry> dispatchEntry =
 createDispatchEntry(inputTarget, eventEntry, inputTargetFlags);

 EventEntry& newEntry = *(dispatchEntry->eventEntry);
 // Apply target flags and update the connection's input state.
 switch (newEntry.type) {
 case EventEntry::Type::MOTION: {
 const MotionEntry& motionEntry = static_cast<const MotionEntry&>(newEntry);
 constexpr int32_t DEFAULT_RESOLVED_EVENT_ID =
 static_cast<int32_t>(IdGenerator::Source::OTHER);
 dispatchEntry->resolvedEventId = DEFAULT_RESOLVED_EVENT_ID;
 ...


 if ((motionEntry.flags & AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) &&
 (motionEntry.policyFlags & POLICY_FLAG_TRUSTED)) {
 break;
 }

 dispatchPointerDownOutsideFocus(motionEntry.source, dispatchEntry->resolvedAction,
 inputTarget.inputChannel->getConnectionToken());
 break;
 }
 ...
 }
 connection->outboundQueue.push_back(dispatchEntry.release());
 traceOutboundQueueLength(*connection);
}

在这个方法中,又把事件封装成为dispatchEntry,并放到Connection内部的outboundQueue这个队列当中。

到这里我们可以回看上面的enqueueDispatchEntriesLocked的最后一块代码,那里有判断了如果这个outboundQueue队列不为空,则会执行最后的startDispatchCycleLocked,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
 const sp<Connection>& connection) {

 while (connection->status == Connection::Status::NORMAL && !connection->outboundQueue.empty()) {
 DispatchEntry* dispatchEntry = connection->outboundQueue.front();
 dispatchEntry->deliveryTime = currentTime;
 ...
 // Publish the event.
 status_t status;
 const EventEntry& eventEntry = *(dispatchEntry->eventEntry);
 switch (eventEntry.type) {
 ...
 case EventEntry::Type::MOTION: {
 const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
 ...

 std::array<uint8_t, 32> hmac = getSignature(motionEntry, *dispatchEntry);

 status = connection->inputPublisher
 .publishMotionEvent(dispatchEntry->seq,
 dispatchEntry->resolvedEventId,
 motionEntry.deviceId, motionEntry.source,
 motionEntry.displayId, std::move(hmac),
 dispatchEntry->resolvedAction,
 motionEntry.actionButton,
 dispatchEntry->resolvedFlags,
 motionEntry.edgeFlags, motionEntry.metaState,
 motionEntry.buttonState,
 motionEntry.classification,
 dispatchEntry->transform,
 motionEntry.xPrecision, motionEntry.yPrecision,
 motionEntry.xCursorPosition,
 motionEntry.yCursorPosition,
 dispatchEntry->rawTransform,
 motionEntry.downTime, motionEntry.eventTime,
 motionEntry.pointerCount,
 motionEntry.pointerProperties, usingCoords);
 break;
 }
 }
 ...

 }
}

在这个方法中,则是从outboundQueue把所有的事件一条一条的取出来,解包成它要的类型,比如触摸事件就是MotionEntry,经过判断和一些处理之后,调用connection中的inputPublisherpublishMotionEvent方法,这里的inputPublisher我们之前分析创建InputChannel的时候有所了解,创建它所传的InputChannel为Server端的那个。 我们这里看一下它的publishMotionEvent方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
status_t InputPublisher::publishMotionEvent(...) {

 InputMessage msg;
 msg.header.type = InputMessage::Type::MOTION;
 msg.header.seq = seq;
 msg.body.motion.eventId = eventId;
 ...
 msg.body.motion.pointerCount = pointerCount;
 for (uint32_t i = 0; i < pointerCount; i++) {
 msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]);
 msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]);
 }

 return mChannel->sendMessage(&msg);
}

这里主要创建了InputMessage,将之前MotionEvent的所有参数放进去,通过ServerInputChannel调用sendMessage发送出去,sendMessage的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
status_t InputChannel::sendMessage(const InputMessage* msg) {
 const size_t msgLength = msg->size();
 InputMessage cleanMsg;
 msg->getSanitizedCopy(&cleanMsg);
 ssize_t nWrite;
 do {
 nWrite = ::send(getFd(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
 } while (nWrite == -1 && errno == EINTR);

 return OK;
}

我们知道,InputChannel内部的文件描述符为socket的标记,这里调用send方法,也就是通过socket把信息发送出去,这样的话,我们Client端的的socket也就会接收到,通过Looper,Client端的EventListener就可以接收到消息,我们的应用便可以接收到,到这里便把事件分发,完整的串起来了。

总结

以上就是事件在系统进程中的处理,包括它的事件获取,事件处理,最后通过socket发送,这样我们在客户端进程的InputChannel就能够接收到通知,客户端能够处理事件。配合我们之前分析过应用层的事件分发,到这里,不算事件的驱动相关的部分,事件分发的整个流程我们都有所了解了。

在这里事件从系统system_server通过Server的InputChannel发送的客户端的InputChannel,所采用的是unix的socket功能,而不是使用的binder或者其他的跨进程服务。这一块,结合我在网上查找的资料,以及我自己的想法,我想这里这样做的原因是,unix的sockt pair使用上很简单,并且运行效率很高效,不需要像binder一样涉及到进程和线程的切换。另外就是socket使用了fd,目标进程可以直接监听到事件的来临,而不是向binder一样需要有相应的接口涉及,可以更加实时的接收到事件,也不会因为binder线程阻塞而卡顿。

当然这是我的一些想法,也欢迎读者朋友说说你对于这块的想法。

看完评论一下吧

Android源码分析:从源头分析View事件的传递

对于应用开发者的我们来说,经常会处理按钮点击,键盘输入等事件,而我们的处理一般都是在Activity中或者View中去做的。我们在上一篇文章中分析了View和Activity与Window的关系,其中的ViewRootImpl和我们的事件传递息息相关,上文未能分析,本文将对其进行分析。

事件介绍

事件是什么呢,广义上事件的发生可能在软件也可能在硬件层,在Android设备当中,我们会有可能有键盘触发,触摸触发,鼠标触发的各种事件。我们关注的通常有两种事件: 按键事件(KeyEvent): 这种色包括物理的按键,Home键,音量键,也包括软键盘触发的事件。 触摸事件(TouchEvent): 手指在屏幕上触摸触发的事件,可能是点击,也可能是拖动。

对于按键事件,一般有ACTION_DOWNACTION_UP两种状态,对于KeyEvent所支持的所有keyCode,我们都可以在KeyEvent当中找到。

而对于触摸事件来说,除了DOWNUP两种状态之外,还有ACTION_MOVEACTION_CANCEL等状态。

应用层的事件类图如下图所示:

classDiagram
class Parcelable {
<<interface>>
}
class InputEvent {
<<abstract>>
}
class KeyEvent
class MotionEvent
InputEvent<|--KeyEvent
InputEvent<|--MotionEvent
Parcelable<|..KeyEvent
Parcelable<|..MotionEvent
Parcelable<|..InputEvent

事件传递到View

我们一般处理View的onClick事件,而这个事件是在View的onTouchEvent中进行处理并执行的,在View中我们可以向上追溯到dispatchPointerEvent方法当中,这个方法就是外部向View传递事件的调用。我们知道Android的UI界面中的所有View是一个树形的结构,因此这些事件也就会通过dispatchTouchEvent一层一层的往下传,从而每一个View都能够接收到事件,并决定是否处理。

dispatchPointerEvent是在ViewRootImpl当中调用,代码如下:

1
2
3
4
5
6
7
8
9
private int processPointerEvent(QueuedInputEvent q) {
 final MotionEvent event = (MotionEvent)q.mEvent;
 ...
 boolean handled = mView.dispatchPointerEvent(event);
 maybeUpdatePointerIcon(event);
 maybeUpdateTooltip(event);
 ...
 return handled ? FINISH_HANDLED : FORWARD;
}

在Activity中,它的根视图为DecorViewViewRootImpl在执行它的dispatchPointerEvent方法,它再向下把触摸事件依次向下传递。

除了触摸事件,按键事件也是类似,ViewRootImpl当中会调用View的dispatchKeyEvent方法,View当中会做相应的处理或者向下传递。

ViewRootImpl中对事件的处理

对于ViewRootImpl当中是如何获取事件,并且向后传递的,我们这里以触摸事件为主进行分析,其他事件也类似。

ViewRootImpl中,定义写一些内部类,大概如下:

classDiagram
class InputStage {
<<abstract>>
+InputStage mNext;
+deliver(QueuedInputEvent q)
#finish(QueuedInputEvent q, boolean handled)
#forward(QueuedInputEvent q)
#onDeliverToNext(QueuedInputEvent q)
#onProcess(QueuedInputEvent q)
}
class AsyncInputStage {
<<abstract>>
#defer(QueuedInputEvent q)
}
InputStage <|-- AsyncInputStage
AsyncInputStage <|--NativePreImeInputStage
InputStage <|-- ViewPreImeInputStage
AsyncInputStage <|-- ImeInputStage
InputStage <|-- EarlyPostImeInputStage
AsyncInputStage <|-- NativePostImeInputStage
InputStage <|-- ViewPostImeInputStage
InputStage <|--SyntheticInputStage

上面这几个类就ViewRootImpl中处理事件的类,其初始化代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//ViewRootImpl.java
public void setView(...) {
 ...
 CharSequence counterSuffix = attrs.getTitle();
 mSyntheticInputStage = new SyntheticInputStage();
 InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
 InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
 "aq:native-post-ime:" + counterSuffix);
 InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
 InputStage imeStage = new ImeInputStage(earlyPostImeStage,
 "aq:ime:" + counterSuffix);
 InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
 InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
 "aq:native-pre-ime:" + counterSuffix);

 mFirstInputStage = nativePreImeStage;
 mFirstPostImeInputStage = earlyPostImeStage;
}

以上代码创建了多个InputStage,它们一起组成了输入事件处理的流水线。其中ViewPostImeInputStage中就会处理与触摸相关的事件,它的onProcess方法代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
protected int onProcess(QueuedInputEvent q) {
 if (q.mEvent instanceof KeyEvent) {
 return processKeyEvent(q);
 } else {
 final int source = q.mEvent.getSource();
 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
 return processPointerEvent(q);
 } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
 return processTrackballEvent(q);
 } else {
 return processGenericMotionEvent(q);
 }
 }
}

可以看到,当我们的输入源为POINTER,触摸屏和鼠标的触发都是这一类。这个时候就会执行上面我们提到的 processPointerEvent方法,之后事件也就会传递到View当中。

这里我们知道了是通过InputStage的流水线拿到的事件,但是这个事件从何处来的呢,我们需要继续向上溯源。

ViewRootImpl从何处获得事件

关于这一点,我们仍然需要关注ViewRootImplsetView方法中的如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//VieRootImpl.java
InputChannel inputChannel = null;
if ((mWindowAttributes.inputFeatures
 & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
 inputChannel = new InputChannel();
}
...
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
 getHostVisibility(), mDisplay.getDisplayId(), userId,
 mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
 mTempControls, attachedFrame, compatScale);
...
if (inputChannel != null) {

 mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
 Looper.myLooper());

}

在这里,我们创建了一个InputChannel,但是我们创建的InputChannel仅仅是java层的一个类,没法去获取到事件,随后我们调用WindowSessionaddToDisplayAsUser他就会获得mPtr,也就是Native层的InputChannel,具体内容随后再看相关代码。在15行,这里创建了一个WindowInputEventReceiver,它的参数为inputChannelLooper,这里一起看一下InputEventReceiver的构造方法,代码如下:

1
2
3
4
5
6
7
8
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
 mInputChannel = inputChannel;
 mMessageQueue = looper.getQueue();
 mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
 inputChannel, mMessageQueue);

 mCloseGuard.open("InputEventReceiver.dispose");
}

InputEventReceiver的初始化

这里主要是调用了nativeInit方法,并且获取到mReceivePtr,native的代码在android_view_InputEventReceiver.cpp当中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
 jobject inputChannelObj, jobject messageQueueObj) {
 std::shared_ptr<InputChannel> inputChannel =
 android_view_InputChannel_getInputChannel(env, inputChannelObj); //获取Native成的InputChannel
 sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); //获取native层的消息队列

 sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
 receiverWeak, inputChannel, messageQueue);
 status_t status = receiver->initialize();
 receiver->incStrong(gInputEventReceiverClassInfo.clazz); // 增加引用计数
 return reinterpret_cast<jlong>(receiver.get());
}

在上面的代码中,先是分别获取了Native层的InputChannel和MessageQueue,之后创建了NativeInputEventReceiver,并且调用了它的initialize方法:

1
2
3
4
status_t NativeInputEventReceiver::initialize() {
 setFdEvents(ALOOPER_EVENT_INPUT);
 return OK;
}

内部调用了setFdEvents方法,参数ALOOPER_EVENT_INPUT,这个参数表示监听文件描述符的读操作,其内部代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void NativeInputEventReceiver::setFdEvents(int events) {
 if (mFdEvents != events) {
 mFdEvents = events;
 int fd = mInputConsumer.getChannel()->getFd();
 if (events) {
 mMessageQueue->getLooper()->addFd(fd, 0, events, this, nullptr);
 } else {
 mMessageQueue->getLooper()->removeFd(fd);
 }
 }
}

这里就是拿到InputChannel的文件描述符,并且添加到Looper中去监听它的输入事件。我们暂时不会去阅读硬件层面的触发,以及事件如何发送到InputChannel当中,这里就大胆的假设,InputChannel当中有一个文件描述符,当有事件发生时候,会写入到这个文件当中去。而文件变化,Looper就会收到通知,事件也就发送出来了。

NativeInputEventReceiver 接收事件并分发

这个时候我们可以看一下NativeInputEventReceiver所实现的LooperCallbackhandleEvent方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {

 constexpr int REMOVE_CALLBACK = 0;
 constexpr int KEEP_CALLBACK = 1;

 if (events & ALOOPER_EVENT_INPUT) {
 JNIEnv* env = AndroidRuntime::getJNIEnv();
 status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);
 mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
 return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK;
 }
 ...
 return KEEP_CALLBACK;
}

其中核心代码如上,就是判断如果事件为ALOOPER_EVENT_INPUT,则会调用consumeEvents方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
 bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
 ...
 ScopedLocalRef<jobject> receiverObj(env, nullptr);
 bool skipCallbacks = false;
 for (;;) {
 uint32_t seq;
 InputEvent* inputEvent;

 status_t status = mInputConsumer.consume(&mInputEventFactory,
 consumeBatches, frameTime, &seq, &inputEvent);
 ...
 assert(inputEvent);

 if (!skipCallbacks) {
 if (!receiverObj.get()) {
 receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
 if (!receiverObj.get()) {
 ...
 return DEAD_OBJECT;
 }
 }

 jobject inputEventObj;
 switch (inputEvent->getType()) {
 case AINPUT_EVENT_TYPE_MOTION: {
 MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
 if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
 *outConsumedBatch = true;
 }
 inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
 break;
 }
 ...
 default:
 assert(false); // InputConsumer should prevent this from ever happening
 inputEventObj = nullptr;
 }

 if (inputEventObj) {
 env->CallVoidMethod(receiverObj.get(),
 gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
 ...
 env->DeleteLocalRef(inputEventObj);
 } else {
 ...
 }
 }
 }
}

上面的代码做过简化,switch的case只保留了一个。首先在第10行,我们看到这里调用了mInputConsumerconsume方法。这个InputConsumer是在Receiver创建的时候创建它,它用于到InputChannel中获取消息,并且按照类型包装成InputEvent的具体子类,并写入到inputEvent当中。在后面的Switch判断处,就可以根据它的类型做处理,从而封装成java类型的InputEvent。而receiverObj在第17行,通过jniGetReferent拿到java层的InputEventReceiver的引用,在41行调用了它的dispatchInputEvent方法,从而调用了java层的同名方法,代码如下:

1
2
3
4
private void dispatchInputEvent(int seq, InputEvent event) {
 mSeqMap.put(event.getSequenceNumber(), seq);
 onInputEvent(event);
}

我们再到WindowInputEventReceiver中看onInputEvent方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public void onInputEvent(InputEvent event) {
 List<InputEvent> processedEvents;
 try {
 processedEvents =
 mInputCompatProcessor.processInputEventForCompatibility(event);
 } finally {
 }
 if (processedEvents != null) {
 if (processedEvents.isEmpty()) {
 finishInputEvent(event, true);
 } else {
 for (int i = 0; i < processedEvents.size(); i++) {
 enqueueInputEvent(
 processedEvents.get(i), this,
 QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
 }
 }
 } else {
 enqueueInputEvent(event, this, 0, true);
 }
}

其中第4行代码,是为了兼容低版本设计的,只有应用的TargetSDKVersion小于23才会生效,这里我们就不关注它了。因此这里就只会执行第19行的代码,其内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
void enqueueInputEvent(InputEvent event,
 InputEventReceiver receiver, int flags, boolean processImmediately) {
 QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
 if (event instanceof MotionEvent) {
 MotionEvent me = (MotionEvent) event;
 }
 QueuedInputEvent last = mPendingInputEventTail;
 if (last == null) {
 mPendingInputEventHead = q;
 mPendingInputEventTail = q;
 } else {
 last.mNext = q;
 mPendingInputEventTail = q;
 }
 mPendingInputEventCount += 1;
 if (processImmediately) {
 doProcessInputEvents();
 } else {
 scheduleProcessInputEvents();
 }
}

这里的代码,把我们的Event包装成一个QueuedInputEvent,并且放置到mQueuedInputEventPool这个链表中,具体可以自行看obtainQueuedInputEvent方法。而根据我们之前传递的参数,可以看到这里后面会调用到doProcessInputEvents方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void doProcessInputEvents() {
 // Deliver all pending input events in the queue. 
 while (mPendingInputEventHead != null) {
 QueuedInputEvent q = mPendingInputEventHead;
 mPendingInputEventHead = q.mNext;
 if (mPendingInputEventHead == null) {
 mPendingInputEventTail = null;
 }
 q.mNext = null;

 mPendingInputEventCount -= 1; mViewFrameInfo.setInputEvent(mInputEventAssigner.processEvent(q.mEvent));

 deliverInputEvent(q);
 }
 //除了我们收到调用来把事件队列的所有事件消费,还有一些消息本来是准备通过Handler发送消息来处理的,既然我们已经手动把所有消息都处理掉了,那么如果有等待处理的消息事件,也就不需要了,下面的代码就是把他们删掉
 if (mProcessInputEventsScheduled) {
 mProcessInputEventsScheduled = false;
 mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
 }
}

这里的代码主要就是遍历之前的链表,把每一条消息都取出来,并且调用deliverInputEvent方法来把它分发掉,同时会把它从链表中删除。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private void deliverInputEvent(QueuedInputEvent q) {
 try {
 if (mInputEventConsistencyVerifier != null) {
 try { //事件一致性检查,避免外面传过来应用无法处理的事件
 mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
 } finally {
 }
 }

 InputStage stage; //选择要使用的入口InputStage
 if (q.shouldSendToSynthesizer()) {
 stage = mSyntheticInputStage;
 } else {
 stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
 }
 ...
 if (stage != null) {
 handleWindowFocusChanged();
 stage.deliver(q); //InputStage 开始分发事件
 } else {
 finishInputEvent(q);
 }
 } finally {

 }
}

在这个方法中,主要就是根据事件的属性选择入口的InputStage,之后调用它的deliver方法,在这个方法中就会按照链式调用,最终能够处理掉的一个InputStage会将它处理,也就是把事件分发到应用中去。

到这里我们就完成了从InputChannel中获取事件,并且通过InputEventReceiver传递到Java层,并且通过InputStage转发到应用的View当中。

InputChannel的初始化

刚刚我们已经基本把事件处理在ViewRootImpl中的部分看完了,而我们在其中创建的InputChannel只是一个壳,想要看看它的真正的初始化,我们沿着之前调用的addToDisplayAsUser继续往后看。IWindowSession是一个AIDL定义的Binder服务,在它的定义中InputChannel使用了out进行修饰,表示它会被binder服务端修改,并写入数据。而这个addToDisplayAsUser方法内部最终会调用WMS的addWindow方法,其中和InputChannel相关代码如下:

1
2
3
4
5
6
7
8
final WindowState win = new WindowState(this, session, client, token, parentWindow,
 appOp[0], attrs, viewVisibility, session.mUid, userId,
 session.mCanAddInternalSystemWindow);
 final boolean openInputChannels = (outInputChannel != null
 && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
if (openInputChannels) {
 win.openInputChannel(outInputChannel);
}

这里outInputChannel就是我们从客户端传过来的那个InputChannel的壳,随后便调用了WindowStateopenInputChannel方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void openInputChannel(InputChannel outInputChannel) {
 String name = getName(); //获取window的name
 mInputChannel = mWmService.mInputManager.createInputChannel(name); //创建
 mInputChannelToken = mInputChannel.getToken();
 mInputWindowHandle.setToken(mInputChannelToken);
 mWmService.mInputToWindowMap.put(mInputChannelToken, this);
 if (outInputChannel != null) {
 mInputChannel.copyTo(outInputChannel); //将Native Channel写入我们传入的InputChannel
 } else {
 }
}

这里就是调用InputManager去创建InputChannel,并且把它和我们的WIndow关联,以及保存到我们传入的InputChannel当中,这样我们的View层面就可以通过InputChannel获取到事件了。InputManagerService创建InputChannel的部分这里就不讨论了,留待以后讨论。

总结

到此为止,就分析完了应用侧从WMS到View,如何初始化InputEventReceiver,InputEventReceiver和InputChannel关联起来,事件如何从InputChannel一直传递到我们的View的了。

sequenceDiagram
InputChannel-->>NativeInputEventReceiver: handleEvent
note right of InputChannel: notify has event via Looper
NativeInputEventReceiver->> NativeInputEventReceiver: consumeEvents
NativeInputEventReceiver->>+ InputChannel: consume
note right of InputChannel: get event from InputChannel
InputChannel-->>-NativeInputEventReceiver: return inputEvent
NativeInputEventReceiver->>InputEventReceiver: dispatchInputEvent
InputEventReceiver->>InputEventReceiver: onInputEvent
InputEventReceiver->>ViewRootImpl: enqueueInputEvent
ViewRootImpl->>ViewRootImpl: doProcessInputEvents
ViewRootImpl->>ViewRootImpl: deliverInputEvent
ViewRootImpl->>+ViewPostImeInputStage: deliver
ViewPostImeInputStage->>ViewPostImeInputStage:onProcess
ViewPostImeInputStage->>ViewPostImeInputStage: processPointerEvent
ViewPostImeInputStage-->>+View: dispatchPointerEvent
View->>View:dispatchTouchEvent
View->>View: onTouch
View-->>-ViewPostImeInputStage: return is consume it or not
ViewPostImeInputStage-->>-ViewRootImpl: finish deliver

之前的分析涉及到了InputChannel的初始化和InputEventReceiver的初始化,直接看可以会比较绕人,上面从事件分发角度画了一下事件从InputChannel一直流转到View的一个时序图,希望对于你理解这个流程有所理解。如果哪里存在疏漏,也欢迎读者朋友们评论指点。

看完评论一下吧

Android源码分析:Window与Activity与View的关联

Activity是四大组件中和UI相关的那个,应用开发过程中,我们所有的界面基本都需要使用Activity才能去渲染和绘制UI,即使是ReactNative,Flutter这种跨平台的方案,在Android中,也需要一个Activity来承载。但是我们的Activity内我们设置的View又是怎么渲染到屏幕上的呢,这背后又有WindowManager和SurfaceFlinger来进行工作。本文就来看看WindowManger如何管理Window,以及Window如何与Activity产生关系的呢。

Activity与Window的初见

Activity的创建是在ActivityThreadperformLaunchActivity中,这里会创建要启动的Activity,并且会调用Activity的attach方法,在这个方法当中就会创建Window,其中和Window相关的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
 mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
 mWindow.setUiOptions(info.uiOptions);
}
mWindow.setWindowManager(
 (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
 mToken, mComponent.flattenToString(),
 (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
 mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();

这里我们可以看到为Activity创建了Window,目前Android上面的Window实例为PhoneWindow,同时还给Window设置了WindowManager,不过这里的WindowManager仅仅是一个本地服务,它的实现为WindowManagerImpl,它的注册代码在SystemServiceRegister.java中,代码如下:

1
2
3
4
5
6
registerService(Context.WINDOW_SERVICE, WindowManager.class,
 new CachedServiceFetcher<WindowManager>() {
 @Override
 public WindowManager createService(ContextImpl ctx) {
 return new WindowManagerImpl(ctx);
 }});

而我们这个WindowManagerImpl内部持有持有了一个WindowManagerGlobal,看名字就知道它应该会涉及到跨进程通讯,去看它代码就知道它内部有两个成员,分别是sWindowManagerServicesWindowSession,这两个成员就用于跨进程通讯。这里我们先知道有这几个类,后面到用处再继续分析。

---
title: WindowManager相关类图
---
classDiagram
directioni TB
class ViewManager {
<<interface>>
addView(view, params)
updateViewLayout(view, params)
removeView(view)
}
class WindowManager {
<<interface>>
}
class WindowManagerImpl {
- WindowManagerGlobal mGlobal;
- IBinder mWindowContextToken;
- IBinder mDefaultToken;
}
ViewManager <|-- WindowManager
WindowManager <|.. WindowManagerImpl
class WindowManagerGlobal {
IwindowManager sWindowManagerService;
IWindowSession SwindowSession;
}
WindowManagerImpl ..> WindowManagerGlobal
class Window {
<<abstract>>
WindowManager mWindowManager;
}
Window <|.. PhoneWindow
Window ..> WindowManager
class IWindow {
<<Interface>>
}
IWindow <|--W
class ViewRootImpl {
W mWindow
IWindowSession mWindowSession
}
ViewRootImpl .. W
ViewRootImpl .. IWindowSession
IWindowSession .. W

这里只可出了App进程相关的一些类,System_Server相关未列出,后面涉及到相关部分的时候再进行分析。

Window与View的邂逅

我们一般情况下会在Activity的onCreate当中去调用setContentView,只有这样我们的View才能够显示出来。因此我们直接看这个方法的调用:

1
2
3
4
public void setContentView(@LayoutRes int layoutResID) {
 getWindow().setContentView(layoutResID);
 initWindowDecorActionBar();
}

其中就是调用了window的同名方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public void setContentView(int layoutResID) {
 if (mContentParent == null) {
 installDecor();
 } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
 mContentParent.removeAllViews();
 }

 if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
 final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
 getContext());
 transitionTo(newScene); //执行页面Transition动画
 } else {
 mLayoutInflater.inflate(layoutResID, mContentParent);
 }
 mContentParent.requestApplyInsets();
 final Callback cb = getCallback();
 if (cb != null && !isDestroyed()) {
 cb.onContentChanged();
 }
 mContentParentExplicitlySet = true;
}

这里我们主要是将我们的ContentView添加到mContentParent当中去,这个mContentParent有可能为空,需要我们通过installDecor来创建,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
private void installDecor() {
 mForceDecorInstall = false;
 if (mDecor == null) {
 mDecor = generateDecor(-1);
 mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
 mDecor.setIsRootNamespace(true);
 if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
 mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
 }
 } else {
 mDecor.setWindow(this);
 }
 if (mContentParent == null) {
 mContentParent = generateLayout(mDecor);

 // Set up decor part of UI to ignore fitsSystemWindows if appropriate. 
 mDecor.makeFrameworkOptionalFitsSystemWindows();

 final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
 R.id.decor_content_parent);

 if (decorContentParent != null) {
 mDecorContentParent = decorContentParent;
 mDecorContentParent.setWindowCallback(getCallback());
 if (mDecorContentParent.getTitle() == null) {
 mDecorContentParent.setWindowTitle(mTitle);
 }

 final int localFeatures = getLocalFeatures();
 for (int i = 0; i < FEATURE_MAX; i++) {
 if ((localFeatures & (1 << i)) != 0) {
 mDecorContentParent.initFeature(i);
 }
 }

 mDecorContentParent.setUiOptions(mUiOptions);

 ...

 PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
 if (!isDestroyed() && (st == null || st.menu == null) && !mIsStartingWindow) {
 invalidatePanelMenu(FEATURE_ACTION_BAR);
 }
 } else {
 mTitleView = findViewById(R.id.title);
 if (mTitleView != null) {
 //title view的设置
 }
 }

 if (mDecor.getBackground() == null && mBackgroundFallbackDrawable != null) {
 mDecor.setBackgroundFallback(mBackgroundFallbackDrawable);
 }

 if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
 ...
 //页面动画的读取和设置 
 }
 }
}

这里我们可以看到主要做的就是创建了decorView和ContentParent,还有一些动画,标题之类的初始化我们这里就跳过了。DecorView就是App Activity页面最底层的容器,它为我们封装了状态栏,底部导航栏,App页面的内容的展示。而ContentParent的初始化,则是根据Activity的设置,根据是否展示状态栏,是否展示标题栏等,进行加载相应的布局,加载到DecorView当中,最后com.android.internal.R.id.content对应的FrameLayout就会成为ContentParent。 当这一切做完,我们的页面View就成功的添加到Window当中了,但是它是如何展示出来的呢,还需要继续往后看。我们需要前往ActivityThread的handleResumeActivity方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//调用Activity的onResume方法
if (!performResumeActivity(r, finalStateRequest, reason)) {
 return;
}
//r为ActivityClientRecord
final Activity a = r.activity;
//检查当前的Activity是否能显示
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
 willBeVisible = ActivityClient.getInstance().willActivityBeVisible(
 a.getActivityToken());
}
if (r.window == null && !a.mFinished && willBeVisible) {
 r.window = r.activity.getWindow(); //把activity的window保存到r.window中
 View decor = r.window.getDecorView();
 decor.setVisibility(View.INVISIBLE);
 ViewManager wm = a.getWindowManager();
 WindowManager.LayoutParams l = r.window.getAttributes();
 a.mDecor = decor;
 l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 l.softInputMode |= forwardBit;
 if (r.mPreserveWindow) {
 a.mWindowAdded = true;
 r.mPreserveWindow = false;
 ViewRootImpl impl = decor.getViewRootImpl();
 if (impl != null) {
 impl.notifyChildRebuilt();
 }
 }
 if (a.mVisibleFromClient) {
 if (!a.mWindowAdded) {
 a.mWindowAdded = true;
 wm.addView(decor, l); //调用windowManager添加decorView
 } else {
 a.onWindowAttributesChanged(l);
 }
 }
} else if (!willBeVisible) {
 r.hideForNow = true;
}

可以看到上面的代码把window保存到了ActivityClientRecord当中,同时调用了WindowManager的addView方法,去添加view。我们继续往后看代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
 ViewRootImpl impl = r.window.getDecorView().getViewRootImpl();
 WindowManager.LayoutParams l = impl != null
 ? impl.mWindowAttributes : r.window.getAttributes();
 if ((l.softInputMode
 & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
 != forwardBit) {
 l.softInputMode = (l.softInputMode
 & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
 | forwardBit;
 if (r.activity.mVisibleFromClient) {
 ViewManager wm = a.getWindowManager();
 View decor = r.window.getDecorView();
 wm.updateViewLayout(decor, l);
 }
 }

 r.activity.mVisibleFromServer = true;
 mNumVisibleActivities++;
 if (r.activity.mVisibleFromClient) {
 r.activity.makeVisible();
 }

 if (shouldSendCompatFakeFocus) {
 if (impl != null) {
 impl.dispatchCompatFakeFocus();
 } else {
 r.window.getDecorView().fakeFocusAfterAttachingToWindow();
 }
 }
}

上面的代码中,我们看到主要做了两件事情,一个是调用updateViewLayout去更新视图的属性,但是updateViewLayout也要属性发生变化,并且有输入法的时候才会执行,另外就是调用activity的makeVisible方法去展示View。

这个过程我们需要分析如下两步。

  1. 调用addView添加decorView
  2. 调用activity.makeVisible来显示 我们分别看一下这两个方法的实现

WMS与ViewRootImpl的遇见:调用WindowManger的addView

1
2
3
4
5
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
 applyTokens(params);
 mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
 mContext.getUserId());
}

这里就是调用mGlobaladdView方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public void addView(View view, ViewGroup.LayoutParams params,
 Display display, Window parentWindow, int userId) {

 final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
 ....

 ViewRootImpl root;
 View panelParentView = null;

 synchronized (mLock) {

 int index = findViewLocked(view, false);
 ...

 if (windowlessSession == null) {
 root = new ViewRootImpl(view.getContext(), display);
 } else {
 root = new ViewRootImpl(view.getContext(), display,
 windowlessSession, new WindowlessWindowLayout());
 }

 view.setLayoutParams(wparams);

 mViews.add(view);
 mRoots.add(root);
 mParams.add(wparams);

 try {
 root.setView(view, wparams, panelParentView, userId);
 } catch (RuntimeException e) {
 final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
 // BadTokenException or InvalidDisplayException, clean up. 
 if (viewIndex >= 0) {
 removeViewLocked(viewIndex, true);
 }
 throw e;
 }
 }
}

在正常的App页面,windowlessSession会一直为空,这里就会创建一个ViewRootImpl,并且把我们的DecorView以及WindowParams都传进去。并且viewrootwparams都会按照顺序存到List当中。这里我们需要去看ViewRootImpl的setView方法,其中和添加到屏幕相关的代码如下:

1
2
3
4
5
6
7
requestLayout(); //测量布局
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
 getHostVisibility(), mDisplay.getDisplayId(), userId,
 mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
 mTempControls, attachedFrame, compatScale);
...
view.assignParent(this); //将ViewRootImpl设置为DecorView的parent

这里的mDisplay为外面从Context中所获取的,用于指定当前的UI要显示到哪一个显示器上去。这里的mWindowSession的获取代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//WindowManagerGlobal.java
@UnsupportedAppUsage
public static IWindowSession getWindowSession() {
 synchronized (WindowManagerGlobal.class) {
 if (sWindowSession == null) {
 try {
 @UnsupportedAppUsage
 InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
 IWindowManager windowManager = getWindowManagerService();
 sWindowSession = windowManager.openSession(
 new IWindowSessionCallback.Stub() {
 @Override
 public void onAnimatorScaleChanged(float scale) {
 ValueAnimator.setDurationScale(scale);
 }
 });
 } catch (RemoteException e) {
 throw e.rethrowFromSystemServer();
 }
 }
 return sWindowSession;
 }
}

可以看到就是通过IWindowManger这个Binder服务调用了openSession来获取了一个WindowSession。其代码如下:

1
2
3
4
//WindowManagerService.java
public IWindowSession openSession(IWindowSessionCallback callback) {
 return new Session(this, callback);
}

在System_Server端,创建了一个Session对象来提供相关的服务。它的addToDisplayAsUser方法又调用了WMSaddWindow方法,这个方法比较长我们只看其中和UI展示相关的部分,并且UI类型不是App的普通UI的也都给省略掉。

1
2
3
int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
 appOp);
final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);

第一行代码首先是去检查我们当前要展示的view,它的类型是否支持去展示。第3行代码的内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private DisplayContent getDisplayContentOrCreate(int displayId, IBinder token) {
 if (token != null) {
 final WindowToken wToken = mRoot.getWindowToken(token);
 if (wToken != null) {
 return wToken.getDisplayContent();
 }
 }

 return mRoot.getDisplayContentOrCreate(displayId);
}

mRoot为一个RootWindowContainer对象,之前我们在分析Activity的启动过程中已经见到了它,我们的ActivityRecord和Task都存在它当中。这里wToken初始情况一般为null因此会执行下面的getDisplayContentOrCreate方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
DisplayContent getDisplayContentOrCreate(int displayId) {
 DisplayContent displayContent = getDisplayContent(displayId);
 if (displayContent != null) {
 return displayContent;
 }
 ...
 final Display display = mDisplayManager.getDisplay(displayId);
 ...
 displayContent = new DisplayContent(display, this);
 addChild(displayContent, POSITION_BOTTOM);
 return displayContent;
}

这里就是根据displayId从列表中去拿DisplayContent如果不存在就去创建一个并且保存到列表中,方便下次使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//WindowManagerService.addWindow
WindowToken token = displayContent.getWindowToken(
 hasParent ? parentWindow.mAttrs.token : attrs.token);
if (token == null) {
{
 ...
 final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
 token = new WindowToken.Builder(this, binder, type)
 .setDisplayContent(displayContent)
 .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
 .setRoundedCornerOverlay(isRoundedCornerOverlay)
 .build();
}

继续看addWindow的内容,如果displayContent是新创建的,那么这里拿到的token就会为空,因此这里调用了client.asBinder来获取IBinder,或者直接拿’attr’中的token,这个client为IWindow类型,它在应用侧为W的实例,它是ViewRootImpl的一个内部类。这里创建完WindowToken之后,我们可以继续往后看。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//WindowManagerService.addWindow
final WindowState win = new WindowState(this, session, client, token, parentWindow,
 appOp[0], attrs, viewVisibility, session.mUid, userId,
 session.mCanAddInternalSystemWindow);
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
attrs.inputFeatures = sanitizeSpyWindow(attrs.inputFeatures, win.getName(), callingUid,
 callingPid);
win.setRequestedVisibilities(requestedVisibilities);

res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);

这里创建的WindowState用于保存Window的状态,可以说是Window在WMS中存储的一个章台。随后从DisplayContent中拿到了DisplayPolicy这个类主要是用于控制显示的一些行为,比如状态栏,导航栏的显示状态之类的。这里会根据WindowParams来调整DisplayPolicy的参数,以及调用validateAddingWindowLw检查当前的window是否能够添加的系统界面中,这个app普通type不涉及。

1
2
3
4
//WindowManagerService.addWindow
win.attach();
mWindowMap.put(client.asBinder(), win);
win.initAppOpsState();

win.attach方法如下:

1
2
3
void attach() {
 mSession.windowAddedLocked();
}

其中就调用了SessionwindowAddedLocked方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
void windowAddedLocked() {
 if (mPackageName == null) {
 final WindowProcessController wpc = mService.mAtmService.mProcessMap.getProcess(mPid);
 if (wpc != null) {
 mPackageName = wpc.mInfo.packageName;
 mRelayoutTag = "relayoutWindow: " + mPackageName;
 } else {
 }
 }
 if (mSurfaceSession == null) {
 mSurfaceSession = new SurfaceSession();
 mService.mSessions.add(this);
 if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
 mService.dispatchNewAnimatorScaleLocked(this);
 }
 }
 mNumWindow++;
}

对于每个进程第一次使用openSession创建的Session这个地方都会执行,主要就是创建了SurfaceSession,并且保存到WMSmSessions当中去。之后又把client作为key,WindowState为value存放到mWindowMap当中。

1
2
3
//WindowManagerService.addWindow
win.mToken.addWindow(win);
displayPolicy.addWindowLw(win, attrs);

先看这个WindowToken.addWindow方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void addWindow(final WindowState win) {
 if (mSurfaceControl == null) {
 createSurfaceControl(true /* force */);

 reassignLayer(getSyncTransaction());
 }
 if (!mChildren.contains(win)) {
 addChild(win, mWindowComparator);
 mWmService.mWindowsChanged = true;
 }
}

这里创建了一个SurfaceControl,并且保存到了WindowList当中去。随后再看displayyPolicy.addWindowLw,其中主要用于处理inset相关的处理,这里也先跳过。到此位置addView的代码基本就看完了。

调用activity.makeVisible来显示

1
2
3
4
5
6
7
8
void makeVisible() {
 if (!mWindowAdded) {
 ViewManager wm = getWindowManager();
 wm.addView(mDecor, getWindow().getAttributes());
 mWindowAdded = true;
 }
 mDecor.setVisibility(View.VISIBLE);
}

我们之前已经分析过addView了,这里mWindowAdded也是为true,这里的addView因此是不会被执行的。我们看一下下面的setVisibility,这个就是我们的普通View的方法,还是直接看源码:

1
2
3
4
//View.java
public void setVisibility(@Visibility int visibility) {
 setFlags(visibility, VISIBILITY_MASK);
}

这里是直接调用了setFlags方法,其中和设置显示相关的部分如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
final int newVisibility = flags & VISIBILITY_MASK;
if (newVisibility == VISIBLE) {
 if ((changed & VISIBILITY_MASK) != 0) {
 mPrivateFlags |= PFLAG_DRAWN;
 invalidate(true);

 needGlobalAttributesUpdate(true);
 shouldNotifyFocusableAvailable = hasSize();
 }
}

if ((changed & VISIBILITY_MASK) != 0) {
 if (mParent instanceof ViewGroup) {
 ViewGroup parent = (ViewGroup) mParent;
 parent.onChildVisibilityChanged(this, (changed & VISIBILITY_MASK),
 newVisibility);
 parent.invalidate(true);
 } else if (mParent != null) {
 mParent.invalidateChild(this, null);
 }
}

DecorView的parent为ViewRootImpl,因此上面会调用ViewRootImplinvalidateChild方法,内部会调用如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
 checkThread();

 if (dirty == null) {
 invalidate();
 return null;
 } else if (dirty.isEmpty() && !mIsAnimating) {
 return null;
 }

 if (mCurScrollY != 0 || mTranslator != null) {
 mTempRect.set(dirty);
 dirty = mTempRect;
 if (mCurScrollY != 0) {
 dirty.offset(0, -mCurScrollY);
 }
 if (mTranslator != null) {
 mTranslator.translateRectInAppWindowToScreen(dirty);
 }
 if (mAttachInfo.mScalingRequired) {
 dirty.inset(-1, -1);
 }
 }

 invalidateRectOnScreen(dirty);

 return null;
}

这段代码会检查需要从新绘制的区域,并且放在dirty当中,最后调用invalidateRectOnScreen方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
private void invalidateRectOnScreen(Rect dirty) {
 final Rect localDirty = mDirty;

 // Add the new dirty rect to the current one 
 localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
 final float appScale = mAttachInfo.mApplicationScale;
 final boolean intersected = localDirty.intersect(0, 0,
 (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
 if (!intersected) {
 localDirty.setEmpty();
 }
 if (!mWillDrawSoon && (intersected || mIsAnimating)) {
 scheduleTraversals();
 }
}

这里仍然检查dirty区域,并且去做Traversal。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void scheduleTraversals() {
 if (!mTraversalScheduled) {
 mTraversalScheduled = true;
 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
 mChoreographer.postCallback(
 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
 notifyRendererOfFramePending();
 pokeDrawLockIfNeeded();
 }
}

这里就是启动线程去不断的页面的刷新重绘,就不分析了。最终会执行到performTraversals方法,其中有如下代码我们比较关注:

1
2
3
4
if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
 || mForceNextWindowRelayout) {
 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
}

当首次执行这个方法的时候mFirst为true,除了这个条件之外,window需要从新计算size,view的可见性变化,windowParams变化等任一条件满足就会执行这里。我们在继续看里面的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
if (relayoutAsync) {
 mWindowSession.relayoutAsync(mWindow, params,
 requestedWidth, requestedHeight, viewVisibility,
 insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
 mLastSyncSeqId);
} else {
 relayoutResult = mWindowSession.relayout(mWindow, params,
 requestedWidth, requestedHeight, viewVisibility,
 insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
 mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
 mTempInsets, mTempControls, mRelayoutBundle);
 ...
}

当view为本地进行Layout且一些其他的条件符合,并且它的位置大小没有变化的时候,才会是relayoutAsync,不过两个最终的在服务端都会调用relayout方法,区别就是这里relayout的时候传过去了一个mSurfaceControl,这个接口是AIDL定义的,这个参数定义的为out,服务端会传输值到这个对象里,我们随后会看到,因为非异步是大多数情况的调用,这里也对他进行分析。在Session的relayout方法中调用了如下代码:

1
2
3
4
int res = mService.relayoutWindow(this, window, attrs,
 requestedWidth, requestedHeight, viewFlags, flags, seq,
 lastSyncSeqId, outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
 outActiveControls, outSyncSeqIdBundle);

这里就是调用了WMSrelayoutWindow方法,其中我们关注的有一下代码:

1
2
3
4
5
6
7
8
9
final WindowState win = windowForClientLocked(session, client, false);
if (shouldRelayout && outSurfaceControl != null) {
 try {
 result = createSurfaceControl(outSurfaceControl, result, win, winAnimator);
 } catch (Exception e) {
 ...
 return 0;
 }
}

为应用提供画布容器

这里看一下这个createSurfaceControl的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
WindowSurfaceController surfaceController;
try {
 surfaceController = winAnimator.createSurfaceLocked();
} finally {

}
if (surfaceController != null) {
 surfaceController.getSurfaceControl(outSurfaceControl);

} else {
 outSurfaceControl.release();
}

第三行主要是创建一个WindowSurfaceController对象,第8行则是使用这个对象去获取SurfaceControl,我们看一下它的代码:

1
2
3
void getSurfaceControl(SurfaceControl outSurfaceControl) {
 outSurfaceControl.copyFrom(mSurfaceControl, "WindowSurfaceController.getSurfaceControl");
}

SurfaceControlcopyFrom方法代码如下:

1
2
3
4
5
6
7
public void copyFrom(@NonNull SurfaceControl other, String callsite) {
 mName = other.mName;
 mWidth = other.mWidth;
 mHeight = other.mHeight;
 mLocalOwnerView = other.mLocalOwnerView;
 assignNativeObject(nativeCopyFromSurfaceControl(other.mNativeObject), callsite);
}

最主要的是最后的assignNativeObject赋值到我们从app进程传过来的SurfaceControl当中。native层的SurfaceControl有如下几个成员变量:

1
2
3
4
5
6
sp<SurfaceComposerClient> mClient;
sp<IBinder> mHandle;
sp<IGraphicBufferProducer> mGraphicBufferProducer;
mutable Mutex mLock;
mutable sp<Surface> mSurfaceData;
mutable sp<BLASTBufferQueue> mBbq;

其中就有Surface,而我们在服务端拿到的这个SurfaceControl随后会写回客户端,这样App进程就可以把UI元素绘制到这个Surface上面了。

前面有列过客户端WindowManager相关的类,这里在列一下system_server进程中相关的类:

classDiagram
class IWindowManager {
<<interface>>
}
class Stub["IWindowManager.Stub"]
IWindowManager <|..Stub
class WindowManagerService {
WindowManagerPolicy mPolicy
ArraySet~Session~ mSessions
HashMap~IBinder, WindowState~ mWindowMap
RootWindowContainer mRoot
}
Stub <|--WindowManagerService
class IWindowSession {
<<Interface>>
}
class SessionStub["IWindowSession.Stub"] {
<<abstract>>
}
class Session {
WindowManagerService mService
}
IWindowSession <|..SessionStub
SessionStub<|--Session
WindowContainer<|--RootWindowContainer
WindowContainer <|-- WindowToken
WindowContainer <|--WindowState
WindowToken .. WindowManagerService
Session .. WindowManagerService
RootWindowContainer .. WindowManagerService

总结

我们在调用WMS的addWindow的时候,并没有把View直接传过来,所传过来的WindowLayoutParams当中,宽和高是比较重要的信息,因为在对调用这个方法之前,代码中先是执行了requestLayout去测量的布局的尺寸,并且在返回参数中通过Rect返回了画布的尺寸。我们也知道通过SurfaceControl为我们提供了Surface,这样客户端就能够把UI数据写上去了。而这样,这个Window与View就能够与系统的其他服务一起,把我们的UI显示到屏幕上了。

在与WMS初始通信的时候,WMS服务端为App创建了Session这个对象,App通过这个对象来与服务端进行Binder通讯。同时,App进程在创建ViewRootImpl的时候创建了W这个对象,它是IWindow的binder对象,服务端可以通过这个对象来与app进程通讯。为了方便理解,关于服务端和客户端,我又画了如下图,希望对你理解它们有所帮助。

以上就应用的窗口与Activity相关的分析,整体流程还是比较复杂的,如果哪里存在疏漏,也欢迎读者朋友们评论指点。另外关于应用的事件分发也会涉及到WMS和ViewRootImpl,为了使得文章不至于太长,就留到下次再进行分析。

看完评论一下吧

Android源码分析:Activity启动流程Task相关分析

Activity的启动分析,很大一块需要了解的是Activity的Task管理,以及启动过程中Task的决策,在之前分析启动流程中,关于Task处理的部分,我这里是简化掉了很多的,今天再来分析一下。

入口与计算启动参数

在之前分析Activity的启动中,已经看到了关于处理Task的代码是在ActivityStart当中的startActivityInner方法当中,这个方法有不少入参,先捋一遍: resultTo为调用的Activity的mToken(IBinder)

ActivityRecord r, //新创建的Record,包含calling信息和要打开的ActivityInfo等
ActivityRecord sourceRecord, //resultTo不为空的时候才会去使用`ActivityRecord isInAnyTask`读取
IvoiceInteractionSession voiceSession, //startVoiceActivity的时候才会传
IvoiceInteractor voiceInteractor, //同上,一般为系统的语音助手界面
int startFlags, //客户端传过来的startFlags一般为0
boolean doResume, //是否需要去resume activity,对于启动Activity场景总是为true
ActivityOptions options, //Activity启动的一些参数,页面跳转动画等
Task inTask, //一般为通过AppTaskImpl启动Activity才会设置值,正常app启动不存在
TaskFragment inTaskFragment, //同上,一般情况为空
int balCode, //Activity后台启动的许可Code,默认为BAL_ALLOW_DEFAULT
NeededGrants intentGrants //Intent访问权限授权

有了所有的入参可以看看computeLaunchingTaskFlags,对于普通应用mInTask为空,mSourceRecord不为空,关注这个方法内的如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
if (mInTask == null) {
 if (mSourceRecord == null) {
 if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
 //如果获取不到启动来源的ActivityRecord,且当前要启动的Activity还没有设置NEW_TASK flag,则给他添加
 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
 }
 } else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
 //如果来源ActivityRecord是SINGLE INSTANCE,也就是说它是自己独立的任务栈,新启动Activity必须设置NEW_TASK 
 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
 } else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
 //如果新启动的Activity是SingleInstance或者SingleTask,也要添加NEW_TASK flag
 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
 }
}

if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
 && ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 || mSourceRecord == null)) {
 //如果要启动的Activity设置了分屏的FLAG,但是却没有设置NEW——FLAG或者没有源ActivityRecord,这个时候就需要忽略掉分屏的这个FLAG
 mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT;
}

简化版本的流程图如下:

获取当前的顶部Task: getFocusedRootTask

以上是针对LaunchFlag的一部分处理,但并不是全部,暂时继续往后看。随后就是获取task

1
2
3
final Task prevTopRootTask = mPreferredTaskDisplayArea.getFocusedRootTask();
final Task prevTopTask = prevTopRootTask != null ? prevTopRootTask.getTopLeafTask() : null;
final Task reusedTask = getReusableTask();

首先看这个mPreferredTaskDisplayArea,这个表示倾向的Activity显示的TaskDisplay,它的赋值是在前面的setInitialState方法中:

1
2
3
4
5
mSupervisor.getLaunchParamsController().calculate(inTask, r.info.windowLayout, r,
 sourceRecord, options, mRequest, PHASE_DISPLAY, mLaunchParams);
mPreferredTaskDisplayArea = mLaunchParams.hasPreferredTaskDisplayArea()
 ? mLaunchParams.mPreferredTaskDisplayArea
 : mRootWindowContainer.getDefaultTaskDisplayArea();

我们这里就以它是拿的DefaultTaskDisplayArea为例来分析,继续就是看它的getFocusedRootTask,看代码之前先看看这些类的关系图,之前画过Task,WindowContainer相关的,但是还不够全,这里再补充完整一点。

classDiagram
class ConfigurationContainer {
<<abstract>>
}
class WindowContainer {
List<WindowContainer> mChildren
}
class TaskFragment
class Task
class ActivityRecord {
Task task
TaskDisplayArea mHandoverTaskDisplayArea
}
ConfigurationContainer <|--WindowContainer
WindowContainer <|-- TaskFragment
TaskFragment <|--Task
WindowContainer <|--RootWindowContainer
WindowContainer <|-- DisplayArea
DisplayArea <|-- TaskDisplayArea
WindowContainer"0..*" <-- "1*"WindowContainer
WindowContainer <|-- WindowToken
WindowToken <|--ActivityRecord
ActivityRecord --> Task
ActivityRecord --> TaskDisplayArea

当然WindowContainer的子类远不止这些,包括WindowState等等都是它的子类,但是暂时不需要讨论他们,这里暂时先不列出来了。 我们还是先看getFocusedRootTask方法的源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Task getFocusedRootTask() {
 if (mPreferredTopFocusableRootTask != null) {
 return mPreferredTopFocusableRootTask;
 }

 for (int i = mChildren.size() - 1; i >= 0; --i) {
 final WindowContainer child = mChildren.get(i);
 if (child.asTaskDisplayArea() != null) {
 final Task rootTask = child.asTaskDisplayArea().getFocusedRootTask();
 if (rootTask != null) {
 return rootTask;
 }
 continue;
 }

 final Task rootTask = mChildren.get(i).asTask();
 if (rootTask.isFocusableAndVisible()) {
 return rootTask;
 }
 }

 return null;
}

如果说当前的TaskDisplayArea中,preferredTopFocusableRoot存在就会直接使用,这个会在postionChildTaskAt的时候,如果child放置到顶部,并且它是可获得焦点的,会把他赋值给这个preferredTopFocusableRoot。 我们这里先看它为空的情况。如果它为空,这回到树状结构中查找,遍历树节点如果也是TaskDisplayArea,则会 看他们的focusedRootTask是否存在,如果就返回。如果节点是Task,就会检查这个Task是否为可获得焦点并且可见的,则返回它。否则就返回空。因为我们当前已经打了Activity,这里一般是可以获得值的。

如果拿到了prevTopRootTask,就会去调用getTopLeafTask去获取叶子节点的Task,代码如下:

1
2
3
4
5
6
7
8
public Task getTopLeafTask() {
 for (int i = mChildren.size() - 1; i >= 0; --i) { //从大数开始遍历
 final Task child = mChildren.get(i).asTask();
 if (child == null) continue; //如果不是Task就跳过
 return child.getTopLeafTask(); //继续看它的子节点
 }
 return this; //没有孩子节点,那就是一个叶子节点 
}

以上是获取叶子节点的代码,典型的树的遍历代码。到目前是拿的当前在展示的页面的任务栈。

获取可复用的Task:getReusableTask

而之后的getReusableTask则是获取可以使用的任务Task:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
private Task getReusableTask() {
 //一般是从最近任务打开的页面才会执行这里,我们可以跳过
 if (mOptions != null && mOptions.getLaunchTaskId() != INVALID_TASK_ID) {
 Task launchTask = mRootWindowContainer.anyTaskForId(mOptions.getLaunchTaskId());
 if (launchTask != null) {
 return launchTask;
 }
 return null;
 }
 //如果启动的FLAG是 Single Instance或者SingleTask;又或者是虽然设置了NEW_TASK但是没有设置MULTIPLE_TASK。这些情况都会把新的Activity放到已有的任务栈。
 boolean putIntoExistingTask = ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
 (mLaunchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
 || isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK);
 //因为mInTask为空,后面的resultTo不为空,因此putIntoExistingTask结果为false。当通过startActivityForResult的且requestCode > 0 时候就不为空
 putIntoExistingTask &= mInTask == null && mStartActivity.resultTo == null;
 ActivityRecord intentActivity = null;
 if (putIntoExistingTask) {
 if (LAUNCH_SINGLE_INSTANCE == mLaunchMode) {
 //这种情况只有一个实例,就通过intent和activityInfo去找到它。
 intentActivity = mRootWindowContainer.findActivity(mIntent, mStartActivity.info,
 mStartActivity.isActivityTypeHome());
 } else if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
 //对于分屏的,如果历史栈中有才使用
 intentActivity = mRootWindowContainer.findActivity(mIntent, mStartActivity.info,
 !(LAUNCH_SINGLE_TASK == mLaunchMode));
 } else {
 // 查找最合适的Task给Activity用 
 intentActivity =
 mRootWindowContainer.findTask(mStartActivity, mPreferredTaskDisplayArea);
 }
 }

 if (intentActivity != null && mLaunchMode == LAUNCH_SINGLE_INSTANCE_PER_TASK
 && !intentActivity.getTask().getRootActivity().mActivityComponent.equals(
 mStartActivity.mActivityComponent)) {
 //对于singleInstancePreTask,如果Task的根Activity不是要启动的Activity那么还是不能够复用,因此需要把intentActivity设置为空。
 intentActivity = null;
 }

 if (intentActivity != null
 && (mStartActivity.isActivityTypeHome() || intentActivity.isActivityTypeHome())
 && intentActivity.getDisplayArea() != mPreferredTaskDisplayArea) {
 //
 intentActivity = null;
 }

 return intentActivity != null ? intentActivity.getTask() : null;
}

以上就是根据条件判断是否可以复用栈,如果可以会去拿已经存在的Activity,如果Activity存在,则回去拿它的Task。其中这里有一个singleInstancePreTask的启动模式,这个对于我们很多Android开发这是不熟悉的,它是Android12引入的,它可以说是加强版本的singleInstance,当它是Task栈的根Task的时候就复用,如果不是的就类似singleTask会去打开一个新的Task栈。

这里先来看一下这个findActivity,他也是到RootWindowContainer中去查找,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
ActivityRecord findActivity(Intent intent, ActivityInfo info, boolean compareIntentFilters) {
 ComponentName cls = intent.getComponent();
 if (info.targetActivity != null) {
 cls = new ComponentName(info.packageName, info.targetActivity);
 }
 final int userId = UserHandle.getUserId(info.applicationInfo.uid);

 final PooledPredicate p = PooledLambda.obtainPredicate(
 RootWindowContainer::matchesActivity, PooledLambda.__(ActivityRecord.class),
 userId, compareIntentFilters, intent, cls);
 final ActivityRecord r = getActivity(p);
 p.recycle();
 return r;
}

其中第8行就是创建了一个PooledPredicate,在我们调用test方法的时候就 会调用RootWindowContainer::matchesActivity这个方法,这个方法的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
private static boolean matchesActivity(ActivityRecord r, int userId,
 boolean compareIntentFilters, Intent intent, ComponentName cls) {
 if (!r.canBeTopRunning() || r.mUserId != userId) return false;

 if (compareIntentFilters) {
 if (r.intent.filterEquals(intent)) {
 return true;
 }
 } else {
 if (r.mActivityComponent.equals(cls)) {
 return true;
 }
 }
 return false;
}

首先检查,对应的ActivityRecord是否可以运行在topTask,是否与我们目标要启动的Activity是同样的用户Id,也就是在同一个进程。如果compareIntentFilters为true,还是检查他们的intent是否相同,之后会检查是否为同一个Activity类。对于这个有所了解,我们继续看getActivity的代码,它首先是会调用WindowContainer中的这个方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
 ActivityRecord boundary) {
 if (traverseTopToBottom) {
 for (int i = mChildren.size() - 1; i >= 0; --i) {
 final WindowContainer wc = mChildren.get(i);
 if (wc == boundary) return boundary;

 final ActivityRecord r = wc.getActivity(callback, traverseTopToBottom, boundary);
 if (r != null) {
 return r;
 }
 }
 } else {
 ...
 }

 return null;
}

如果单看上面的代码,我们似乎永远都拿不到ActivityRecord,但是呢ActivityRecord也是WindowContainer的子类,在它当中我们也有同名方法,代码如下:

1
2
3
4
5
@Override
ActivityRecord getActivity(Predicate<ActivityRecord> callback, boolean traverseTopToBottom,
 ActivityRecord boundary) {
 return callback.test(this) ? this : null;
}

这里可以看到,他就是调用了我们刚刚传入的那个PooledPredicate来测试自己是否符合要求,从而我们可以拿到对应的ActivityRecord

计算目标Task: computeTargetTask

到这里我们可以继续看startActivityInner方法中的如下代码:

1
2
3
final Task targetTask = reusedTask != null ? reusedTask : computeTargetTask();
final boolean newTask = targetTask == null;
mTargetTask = targetTask;

如果我们刚刚已经拿到reusedTask,那么目标的task就会使用它,如果拿不到则会调用computeTargetTask去获取Task,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private Task computeTargetTask() {
 if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
 && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
 // 同时满足这些条件的情况,不复用task,直接返回空
 return null;
 } else if (mSourceRecord != null) {
 //调用源ActivityRecord,直接复用调用源的Task
 return mSourceRecord.getTask();
 } else if (mInTask != null) {
 //inTask一般是AppTaskImpl指定的,就直接用它,它有可能还没创建这里去创建
 if (!mInTask.isAttached()) {
 getOrCreateRootTask(mStartActivity, mLaunchFlags, mInTask, mOptions);
 }
 return mInTask;
 } else {
 //获取或者创建Task
 final Task rootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, null /* task */,
 mOptions);
 final ActivityRecord top = rootTask.getTopNonFinishingActivity();
 if (top != null) {
 return top.getTask();
 } else {
 rootTask.removeIfPossible("computeTargetTask");
 }
 }
 return null;
}

这里我们继续去看一下getOrCreateRootTask,代码如下:

1
2
3
4
5
6
7
8
private Task getOrCreateRootTask(ActivityRecord r, int launchFlags, Task task,
 ActivityOptions aOptions) {
 final boolean onTop =
 (aOptions == null || !aOptions.getAvoidMoveToFront()) && !mLaunchTaskBehind;
 final Task sourceTask = mSourceRecord != null ? mSourceRecord.getTask() : null;
 return mRootWindowContainer.getOrCreateRootTask(r, aOptions, task, sourceTask, onTop,
 mLaunchParams, launchFlags);
}

这里还是先拿到调用端的sourceTask以及是否需要onTop,之后调用了RootWindowContainergetOrCreateRootTask方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
Task getOrCreateRootTask(@Nullable ActivityRecord r,
 @Nullable ActivityOptions options, @Nullable Task candidateTask,
 @Nullable Task sourceTask, boolean onTop,
 @Nullable LaunchParamsController.LaunchParams launchParams, int launchFlags) {
 ...
 TaskDisplayArea taskDisplayArea = null;

 final int activityType = resolveActivityType(r, options, candidateTask);
 Task rootTask = null;
 ...
 int windowingMode = launchParams != null ? launchParams.mWindowingMode
 : WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 ....
 if (taskDisplayArea == null) {
 taskDisplayArea = getDefaultTaskDisplayArea();
 }
 return taskDisplayArea.getOrCreateRootTask(r, options, candidateTask, sourceTask,
 launchParams, launchFlags, activityType, onTop);
}

因为我们没有设置什么参数,因此会执行到最后的fallback流程,我们只分析这一部分。默认我们拿到的activityTypeActivity_TYPE_STANDARDgetDefaultTaskDisplayArea会拿到默认的TaskDisplayArea这个之前已经分析过了,最后就是通过它去调用getOrCreateRootTask,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Task getOrCreateRootTask(int windowingMode, int activityType, boolean onTop,
 @Nullable Task candidateTask, @Nullable Task sourceTask,
 @Nullable ActivityOptions options, int launchFlags) {
 final int resolvedWindowingMode =
 windowingMode == WINDOWING_MODE_UNDEFINED ? getWindowingMode() : windowingMode;
 if (!alwaysCreateRootTask(resolvedWindowingMode, activityType)) {
 Task rootTask = getRootTask(resolvedWindowingMode, activityType);
 if (rootTask != null) {
 return rootTask;
 }
 } else if (candidateTask != null) {
 ....
 }
 return new Task.Builder(mAtmService)
 .setWindowingMode(windowingMode)
 .setActivityType(activityType)
 .setOnTop(onTop)
 .setParent(this)
 .setSourceTask(sourceTask)
 .setActivityOptions(options)
 .setLaunchFlags(launchFlags)
 .build();
}

因为我们传进来的windowingModeWINDOWING_MODE_UNDEFINED,因此这里会调用getWindowingMode来设置Mode,这里就是调用系统设置了,不需要看代码。

因为ActivityType是ACTIVITY_TYPE_STAND,所以这里alwaysCreateRootTask为true,因为我们传进来的candidateTask也是空,因此最后就是会创建一个新的Task。但是因为是创建的新task,这个Task里面没有运行中的Activity,因此computeTargetTask还是会返回空。

获取PriorAboveTask和task回收检查

继续回来看startActivityInner内部的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
if (targetTask != null) { //在DisplayArea中获取在targetTask Root上面的其他root task
 mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
}
//如果newTask为false,则看看目标task 顶部的未finish的ActivityRecord
final ActivityRecord targetTaskTop = newTask
 ? null : targetTask.getTopNonFinishingActivity();
if (targetTaskTop != null) {
 startResult = recycleTask(targetTask, targetTaskTop, reusedTask, intentGrants);
 if (startResult != START_SUCCESS) {
 return startResult;
 }
} else {
 mAddingToTask = true;
}

在可以复用栈的情况下,targetTaskTop不为空,比如singleTask的模式,这个时候会去执行recycleTask。其他情况设置mAddingToTask,表示我们的ActivityRecord需要添加到Task。

1
2
3
4
5
6
7
final Task topRootTask = mPreferredTaskDisplayArea.getFocusedRootTask();
if (topRootTask != null) {
 startResult = deliverToCurrentTopIfNeeded(topRootTask, intentGrants);
 if (startResult != START_SUCCESS) {
 return startResult;
 }
}

如果我们检查topRootTask不为空的情况,这里如果我们的启动模式是singleTask,首先会检查task栈顶未启动的Activity是否与当前要启动的相同,如果相同,则不启动当前Activity,仅仅去执行它的newIntent,具体代码就不分析了。

创建RootTask,处理新Activity的Task

再往后看代码,之后就该创建RootTask了,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if (mTargetRootTask == null) {
 mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, targetTask,
 mOptions);
}
if (newTask) {
 final Task taskToAffiliate = (mLaunchTaskBehind && mSourceRecord != null)
 ? mSourceRecord.getTask() : null;
 setNewTask(taskToAffiliate);
} else if (mAddingToTask) {
 addOrReparentStartingActivity(targetTask, "adding to task");
}

上面调用了getOrCreateRootTask,来创建了新的RootTask,与我们之前分析的类似。同时因为我们之前没有成功创建targetTask,因此这里会执行到setNewTask,而taskToAffiliate没有特殊参数,默认我们先按照空来对待吧。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
private void setNewTask(Task taskToAffiliate) {
 final boolean toTop = !mLaunchTaskBehind && !mAvoidMoveToFront;
 final Task task = mTargetRootTask.reuseOrCreateTask(
 mStartActivity.info, mIntent, mVoiceSession,
 mVoiceInteractor, toTop, mStartActivity, mSourceRecord, mOptions);
 task.mTransitionController.collectExistenceChange(task);
 //把新的ActivityRecord放置到Task列表的顶部
 addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask");
 if (taskToAffiliate != null) {
 mStartActivity.setTaskToAffiliateWith(taskToAffiliate);
 }
}

这里大多数情况,toTop会是true,我们去看一下这个reuseOrCreateTask方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Task reuseOrCreateTask(ActivityInfo info, Intent intent, IVoiceInteractionSession voiceSession,
 IVoiceInteractor voiceInteractor, boolean toTop, ActivityRecord activity,
 ActivityRecord source, ActivityOptions options) {

 Task task;
 if (canReuseAsLeafTask()) { //如果没有Task子节点或者不是组织创建的
 task = reuseAsLeafTask(voiceSession, voiceInteractor, intent, info, activity);
 } else {
 // 创建taskId
 final int taskId = activity != null
 ? mTaskSupervisor.getNextTaskIdForUser(activity.mUserId)
 : mTaskSupervisor.getNextTaskIdForUser();
 final int activityType = getActivityType();
 //创建task,并且当前Task设置为这个Task的Parent,在build当中,把当前的Task放置到Parent的mChildren当中,根据toTop决定是否放置到顶部
 task = new Task.Builder(mAtmService)
 .setTaskId(taskId)
 .setActivityType(activityType != ACTIVITY_TYPE_UNDEFINED ? activityType
 : ACTIVITY_TYPE_STANDARD)
 .setActivityInfo(info)
 .setActivityOptions(options)
 .setIntent(intent)
 .setVoiceSession(voiceSession)
 .setVoiceInteractor(voiceInteractor)
 .setOnTop(toTop)
 .setParent(this)
 .build();
 }

 int displayId = getDisplayId();
 if (displayId == INVALID_DISPLAY) displayId = DEFAULT_DISPLAY;
 final boolean isLockscreenShown = mAtmService.mTaskSupervisor.getKeyguardController()
 .isKeyguardOrAodShowing(displayId);
 if (!mTaskSupervisor.getLaunchParamsController()
 .layoutTask(task, info.windowLayout, activity, source, options)
 && !getRequestedOverrideBounds().isEmpty()
 && task.isResizeable() && !isLockscreenShown) {
 //设置task的布局边界
 task.setBounds(getRequestedOverrideBounds());
 }

 return task;
}

上面代码我们就可以复用或者创建新的task,详见注释。拿到Task,或者我们之前已经有Task的情况下(mAddingToTask为true)的时候,还需要执行addOrReparentStartingActivity,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private void addOrReparentStartingActivity(@NonNull Task task, String reason) {
 TaskFragment newParent = task;
 if (mInTaskFragment != null) {
 //我们的场景不涉及InTaskFragment不为空,忽略
 ...
 } else {
 //当clearTop的时候,并且是可嵌入的,这个时候会保存TaskFragment到mAddingToTaskFragment
 TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null;
 if (candidateTf == null) {
 //获取目标Task的topRunningActivity,新建的Task不存在
 final ActivityRecord top = task.topRunningActivity(false /* focusableOnly */,
 false /* includingEmbeddedTask */);
 if (top != null) {
 candidateTf = top.getTaskFragment();
 }
 }
 if (candidateTf != null && candidateTf.isEmbedded()
 && canEmbedActivity(candidateTf, mStartActivity, task) == EMBEDDING_ALLOWED) {
 //如果拿到了topTask,并且对应的Task是可嵌入的,并且要打开的ActivityRecord也可被嵌入,这把拿到的这个Task作为新的父Task
 newParent = candidateTf;
 }
 }
 //新的ActivityRecord的TaskFragment为空,或者和新的Parent一样,就把这个ActivityRecord放到Task的顶部
 if (mStartActivity.getTaskFragment() == null
 || mStartActivity.getTaskFragment() == newParent) {
 newParent.addChild(mStartActivity, POSITION_TOP);
 } else {
 mStartActivity.reparent(newParent, newParent.getChildCount() /* top */, reason);
 }
}

这里会检查如果新的父Task和我们可以复用的Task是否相同,如果相同,或者ActivityRecord中还没有parent,这个时候就把ActivityRecord添加到Task的孩子列表的顶部。而如果ActivityRecord已经存在了parent并且不是我们将要设置的这个,就需要做reparent,这个步骤代码比较复杂,前面调用检查判断的调用省略,直接看最后的调用,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//WindowContainer.java
void reparent(WindowContainer newParent, int position) {
 final DisplayContent prevDc = oldParent.getDisplayContent();
 final DisplayContent dc = newParent.getDisplayContent();

 mReparenting = true;
 //从旧的parent中移除自己,并把自己添加到新parent的指定位置
 oldParent.removeChild(this);
 newParent.addChild(this, position);
 mReparenting = false;

 // 重新布局layout
 dc.setLayoutNeeded();
 //如果新旧的DisplayContent不同,还需要做displayChange的处理
 if (prevDc != dc) {
 onDisplayChanged(dc);
 prevDc.setLayoutNeeded();
 }
 getDisplayContent().layoutAndAssignWindowLayersIfNeeded();

 onParentChanged(newParent, oldParent);
 onSyncReparent(oldParent, newParent);
}

以上的代码我们看到有做parent的替换,但是复杂点在后面的onParentChanged里面,这里会做SurfaceControl的创建或者reparent,这里就不深入了。除此之外,这里还涉及到动画的处理,我们这里也 不深入了。

继续往后看

1
2
3
4
if (!mAvoidMoveToFront && mDoResume) {
 mTargetRootTask.getRootTask().moveToFront("reuseOrNewTask", targetTask);
 ...
}

这里会在检查我们的TargetRootTask相比与它的RootTask如果不是在顶部的,需要把它移动到顶部。再往后面就是调用TargetRootTask去启动Activity,以及确认Activity显示出来。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
final boolean isTaskSwitch = startedTask != prevTopTask && !startedTask.isEmbedded();
//启动Activity
mTargetRootTask.startActivityLocked(mStartActivity, topRootTask, newTask, isTaskSwitch,
 mOptions, sourceRecord);
if (mDoResume) {
 final ActivityRecord topTaskActivity = startedTask.topRunningActivityLocked();
 if (!mTargetRootTask.isTopActivityFocusable()
 || (topTaskActivity != null && topTaskActivity.isTaskOverlay()
 && mStartActivity != topTaskActivity)) {
 //如果当前要启动的Activity还没有启动,没有在栈顶端,执行下面的代码
 mTargetRootTask.ensureActivitiesVisible(null /* starting */,
 0 /* configChanges */, !PRESERVE_WINDOWS);
 mTargetRootTask.mDisplayContent.executeAppTransition();
 } else {
 if (mTargetRootTask.isTopActivityFocusable()
 && !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) {
 mTargetRootTask.moveToFront("startActivityInner");
 }
 mRootWindowContainer.resumeFocusedTasksTopActivities(
 mTargetRootTask, mStartActivity, mOptions, mTransientLaunch);
 }
}
mRootWindowContainer.updateUserRootTask(mStartActivity.mUserId, mTargetRootTask); //更新用户的rootTask

// 更新系统的最近任务
mSupervisor.mRecentTasks.add(startedTask);

到此位置才算是完成了所有Task计算以及Activity的启动。

通过Adb shell看Activity Task栈

前面都是在解读Android的源码可能比较抽象,其中涉及到了挺多WindowContainer和Task等等相关的查找创建的,为了更加形象。我写了个小demo,主页面是普通的launchMode,另外一次打开了一个singleTask启动Mode的和一个singleInstance 启动Mode的页面,然后我们用一下命令进行Activity Task的dump:

1
adb shell dumpsys activity activities > ~/activitytasks.txt

我们就得到了如下的内容(为方便解读,做了删减):

Display #0 (activities from top to bottom):
* Task{8ed7532 #40 type=standard A=10116:com.example.myapplication U=0 visible=true visibleRequested=true mode=fullscreen translucent=false sz=1}
topResumedActivity=ActivityRecord{3653c00 u0 com.example.myapplication/.SimpleInstanceActivity} t40}
* Hist #0: ActivityRecord{3653c00 u0 com.example.myapplication/.SimpleInstanceActivity} t40}
* Task{ac77886 #39 type=standard A=10116:com.example.myapplication U=0 visible=false visibleRequested=false mode=fullscreen translucent=true sz=2}
mLastPausedActivity: ActivityRecord{6c019a5 u0 com.example.myapplication/.SingleTaskActivity} t39}
* Hist #1: ActivityRecord{6c019a5 u0 com.example.myapplication/.SingleTaskActivity} t39}
* Hist #0: ActivityRecord{ef92174 u0 com.example.myapplication/.MainActivity} t39}
* Task{d8527c1 #1 type=home U=0 visible=false visibleRequested=false mode=fullscreen translucent=true sz=1}
* Task{d60ff49 #33 type=home I=com.android.launcher3/.uioverrides.QuickstepLauncher U=0 rootTaskId=1 visible=false visibleRequested=false mode=fullscreen translucent=true sz=1}
mLastPausedActivity: ActivityRecord{868b56f u0 com.android.launcher3/.uioverrides.QuickstepLauncher} t33}
* Hist #0: ActivityRecord{868b56f u0 com.android.launcher3/.uioverrides.QuickstepLauncher} t33}
* Task{2c52978 #36 type=standard A=10044:com.android.documentsui U=0 visible=false visibleRequested=false mode=fullscreen translucent=true sz=1}
mLastPausedActivity: ActivityRecord{f9c48b6 u0 com.android.documentsui/.files.FilesActivity} t36}
mLastNonFullscreenBounds=Rect(338, 718 - 1103, 2158)
isSleeping=false
* Hist #0: ActivityRecord{f9c48b6 u0 com.android.documentsui/.files.FilesActivity} t36}
* Task{e38c1d6 #35 type=standard A=10044:com.android.documentsui U=0 visible=false visibleRequested=false mode=fullscreen translucent=true sz=1}
mLastPausedActivity: ActivityRecord{32a5344 u0 com.android.documentsui/.files.FilesActivity} t35}
mLastNonFullscreenBounds=Rect(338, 718 - 1103, 2158)
isSleeping=false
* Hist #0: ActivityRecord{32a5344 u0 com.android.documentsui/.files.FilesActivity} t35}
* Task{1d65c74 #3 type=undefined U=0 visible=false visibleRequested=false mode=fullscreen translucent=true sz=2}
mCreatedByOrganizer=true
* Task{41cb5e3 #5 type=undefined U=0 rootTaskId=3 visible=false visibleRequested=false mode=multi-window translucent=true sz=0}
mBounds=Rect(0, 2960 - 1440, 4440)
mCreatedByOrganizer=true
isSleeping=false
* Task{1cdca12 #4 type=undefined U=0 rootTaskId=3 visible=false visibleRequested=false mode=multi-window translucent=true sz=0}
mCreatedByOrganizer=true
isSleeping=false

这上面就是我们的mRootContainer它当中的的display下面的所有的Task记录,因为我的手机只有一块屏幕,这里只有一个display0, 并且展示了他们的存储关系,这里我们可以看到我们的SimpleInstanceActivity它是在独立的Task当中的。用图表简单描绘一下,结构如下所示:

总结

以上就是Activity Task管理的分析,因为这个流程真的是非常复杂,因此中间的很多步骤还是进行了部分省略。Android系统迭代了这么多年,作为UI展示的组件,Activity承载了太多东西,多屏幕,折叠屏什么的都要支持,因此引入的东西就越来越多。官方也是意识到了这一块的,Activity的管理从AMS抽出来单独的ATMS,ActivityTaskSupervisor的功能也在慢慢抽离到其他的代码中,当前代码里面也添加了很多注释,只要花时间还是能够给搞明白的。

本文仅为一家之言,因为个人疏忽,可能文中也会出现一些错误,欢迎大家指正。

看完评论一下吧

Android源码分析:Activity启动流程分析

Activity是Android中四大组件使用最多的一种,不准确的说,一个Activity就是一个独立页面的承载,因此看Android系统的源码,Activity的启动也是必须要去阅读的。今天的文章就来介绍Activity的启动。因为之前的文章已经分析了ClientTransaction,因此我们对于AMS调用Activity的生命周期和启动有所了解。并且我们也已经分析过了Binder,对于跨进程通讯我们也比较清楚了,不需要细看。我们也分析了应用进程的启动,我们分析Activity启动过程,就不需要去关注应用进程的启动了。有了这些基础,分析Activity的启动会容易一点点。

发起启动Activity

我们首先来看一下启动Activity的调用,我们通常会使用下面的显示调用来启动一个Activity。

1
2
Intent intent = new Intent(context, AActivity.class);
startActivity(intent);

当然也有可能会使用隐式调用来启动一个Activity,如下:

1
2
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://isming.me"));
startActivity(intent);

不过以上两者在启动过程中,仅仅是查找目标组件有区别,并且对于隐式调用,可能存在多个可以启动的Activity,这个时候需要让用户选择目标的页面。对于这一块,我们在后面这个地方会考虑部分略过。

Activity中最终会走到 startActivityForResult(intent, requestCode, options)方法中,这里传入的options我们可以用它设置一些东西,比如App跳转的动画等,我们前面的场景的options为空,并且手机上默认的parent activity也为空,因此会执行这一部分:

1
2
3
4
5
6
7
8
Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(
 this, mMainThread.getApplicationThread(), mToken, this,
 intent, requestCode, options);
if (ar != null) {
 mMainThread.sendActivityResult(
 mToken, mEmbeddedID, requestCode, ar.getResultCode(),
 ar.getResultData());
}

发起端的处理

在Instrumentation.execStartActivity中会调用如下代码:

1
2
3
4
5
6
int result = ActivityTaskManager.getService().startActivity(whoThread,
 who.getOpPackageName(), who.getAttributionTag(), intent,
 intent.resolveTypeIfNeeded(who.getContentResolver()), token,
 target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
 notifyStartActivityResult(result, options);
 checkStartActivityResult(result, intent);

早期是直接通过ActivityManagerService去启动新的页面的,在这个commit开始把Activity管理的拆分到ActivityTaskManagerService中去。这里我们看到是去获取ActivityTaskManagerService后面简称ATMS,获取ATMS的代码就不罗列了。

这里传到ATMS的参数,包括,发起应用的ApplicationThread,包名(对于普通应用来说opPackage和packageName是一样的),启动的Intent,token和target一般都是空。

ATMS执行startActivity

最终的执行实际是通过binder调用到ActivityTaskManagerService中的startActivity方法,这个方法中又直接调用了startActivityAsUser,其中会有一些检查,检查调用端的uid和packageName是否匹配和其他一些检查,这里不太关注,我们主要关注以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser,
 Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser");
 return getActivityStartController().obtainStarter(intent, "startActivityAsUser")
 .setCaller(caller)
 .setCallingPackage(callingPackage)
 .setCallingFeatureId(callingFeatureId)
 .setResolvedType(resolvedType)
 .setResultTo(resultTo)
 .setResultWho(resultWho)
 .setRequestCode(requestCode)
 .setStartFlags(startFlags)
 .setProfilerInfo(profilerInfo)
 .setActivityOptions(opts)
 .setUserId(userId)
 .execute();

从上面的逻辑可以看到,控制Activity启动的代码都放到ActivityStartController中了,首先是获取用户uid,因为每个应用的都会有一个uid,其后就是获取一个ActivityStarter,再通过构建者模式把启动Activity的参数都传到ActivityStarter中去,最后在ActivityStarter的execute()方法中去执行启动的逻辑。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
...
if (mRequest.activityInfo == null) { //如果还没有activitgyInfo去填充
 mRequest.resolveActivity(mSupervisor);
}
...
synchronized (mService.mGlobalLock) {
 res = resolveToHeavyWeightSwitcherIfNeeded(); //检查是否为heavy-weight 进程,系统会限制同一时间只有一个heavy-weight进程
 if (res != START_SUCCESS) {
 return res;
 }
 res = executeRequest(mRequest);
}

其中第2行代码就是根据我们的Intent去查询我们将要打开的目标Activity信息。

解析ActivityInfo

resolveActivity中的核心代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//ActivityStarter.Request
void resolveActivity(ActivityTaskSupervisor supervisor) {
 resolveInfo = supervisor.resolveIntent(intent, resolvedType, userId,
 0 /* matchFlags */,
 computeResolveFilterUid(callingUid, realCallingUid, filterCallingUid));
 activityInfo = supervisor.resolveActivity(intent, resolveInfo, startFlags,
 profilerInfo);
 if (activityInfo != null) {
 intentGrants = supervisor.mService.mUgmInternal.checkGrantUriPermissionFromIntent(
 intent, resolvedCallingUid, activityInfo.applicationInfo.packageName,
 UserHandle.getUserId(activityInfo.applicationInfo.uid));
 }
}

resolveActivity的工作主要由ActivityTaskSupervisor来完成,首先是resolveIntent来获取ResolveInfo,之后调用resolveActivity获取ActivityInfo,最后再去对Intent中的data Uri做权限检查,我们这里只需要分析前两步骤就可。

resolveIntent方法内部,我们看到是调用了PackageManagerServiceresolveIntent方法,代码如下,具体就不深入探究了。

1
2
3
4
//ActivityTaskSupervisor
return mService.getPackageManagerInternalLocked().resolveIntent(
 intent, resolvedType, modifiedFlags, privateResolveFlags, userId, true,
 filterCallingUid);

resolveActivity代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
ActivityInfo resolveActivity(Intent intent, ResolveInfo rInfo, int startFlags,
 ProfilerInfo profilerInfo) {
 final ActivityInfo aInfo = rInfo != null ? rInfo.activityInfo : null;
 if (aInfo != null) {
 intent.setComponent(new ComponentName(
 aInfo.applicationInfo.packageName, aInfo.name));
 ...
 }
 return aInfo;
}

这里所做的事情则比较简单,就是从前面拿到的ResolveInfo中拿到activityInfo,并且构建一个ComponentName放到Intent中去。到此为止就拿到了要打开的Activity信息。

ActivityStarter.executeRequest

在前面拿到ActivityInfo,并且我们还构建了一个Request,我们就会继续调用executeRequest方法,其中是有大段的代码 是检查权限,以及一些系统Activity逻辑的处理,不是我们流程关注的重点,重要的是以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage,
 callingFeatureId);
if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment,
 callingPid, callingUid, checkedOptions)) { //拦截器对要启动的Activity做预处理
 intent = mInterceptor.mIntent;
 rInfo = mInterceptor.mRInfo;
 aInfo = mInterceptor.mAInfo;
 resolvedType = mInterceptor.mResolvedType;
 inTask = mInterceptor.mInTask;
 callingPid = mInterceptor.mCallingPid;
 callingUid = mInterceptor.mCallingUid;
 checkedOptions = mInterceptor.mActivityOptions;

 intentGrants = null;
}

final ActivityRecord r = new ActivityRecord.Builder(mService) //构建ActivityRecord
 .setCaller(callerApp)
 .setLaunchedFromPid(callingPid)
 .setLaunchedFromUid(callingUid)
 .setLaunchedFromPackage(callingPackage)
 .setLaunchedFromFeature(callingFeatureId)
 .setIntent(intent)
 .setResolvedType(resolvedType)
 .setActivityInfo(aInfo)
 .setConfiguration(mService.getGlobalConfiguration())
 ...
 .build();

 mLastStartActivityRecord = r; //保存构建的ActivityRecord

 ...

 mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
 request.voiceInteractor, startFlags, true /* doResume */, checkedOptions,
 inTask, inTaskFragment, balCode, intentGrants); //执行启动Activity,并保存结果到mLastStartActivityResult中,以及结果中返回这个result

 if (request.outActivity != null) {
 request.outActivity[0] = mLastStartActivityRecord;
 }
 ...

这里我们有一些权限检查和系统处理之类的没有贴,不过还是贴了一下intercept方法,这里就是给了系统的其他代码来修改Intent的机会。之后就会利用我们传进来的信息去创建ActivityRecord,并且调用startActivityUnchecked去进入下一步:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,...,NeededUriGrants intentGrants) {
 int result = START_CANCELED;
 final Task startedActivityRootTask;

 ......
 try {
 ......
 try {
 .....
 result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
 startFlags, doResume, options, inTask, inTaskFragment, balCode,
 intentGrants);
 } finally {
 startedActivityRootTask = handleStartResult(r, options, result, newTransition,
 remoteTransition); //处理启动Activity的结果
 }
 } finally {
 mService.continueWindowLayout(); //wms处理
 }
 postStartActivityProcessing(r, result, startedActivityRootTask);

 return result;
 }

这里又走到了startActivityInner(),startActivityInner()会去计算launch falgs,去判断是否开创建新的Task还是可以复用task,以及调用启动的后续代码,这个方法的代码比较长我们先一点一点的看。

Task的处理

首先来看其中关于flag的处理,首先就是其中调用的computeLaunchingTaskFlags方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
private void computeLaunchingTaskFlags() {
 ...
 if (mInTask == null) {
 if (mSourceRecord == null) {
 if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
 }
 } else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
 } else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
 mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
 }
 }

 if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
 && ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 || mSourceRecord == null)) {
 mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT;
 }
}

这里就是对于我们的启动的LaunchFlag做处理,比如说LAUNCH_SIGLE_INSTANCELAUNCH_SINGLE_TASK都给添加FLAG_ACTIVITY_NEW_TASK等。

随后则是计算Task:

1
2
3
4
5
6
7
final Task prevTopRootTask = mPreferredTaskDisplayArea.getFocusedRootTask();
final Task prevTopTask = prevTopRootTask != null ? prevTopRootTask.getTopLeafTask() : null;
final Task reusedTask = getReusableTask();

final Task targetTask = reusedTask != null ? reusedTask : computeTargetTask();
final boolean newTask = targetTask == null;
mTargetTask = targetTask;

getFocusedRootTask会尝试去获取首选的Task,如果不存在也会从当前显示屏获取获取最顶部的可触摸并且在展示的Task。而这个preTopTask如果能够获取到,它又会去获取的它叶子节点。叶子节点的规则就是没有只节点。Task相关类的继承结果如下:

classDiagram
class ConfigurationContainer {
<<abstract>>
}
class WindowContainer
class TaskFragment
class Task
ConfigurationContainer <|--WindowContainer
WindowContainer <|-- TaskFragment
TaskFragment <|--Task
WindowContainer <|--RootWindowContainer
1
2
3
4
if (mTargetRootTask == null) {
 mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, targetTask,
 mOptions);
}

这里最终会拿到RootTask,如果没有也会创建,具体代码这里不分析了。

调用Task的 resumeFocusedTasksTopActivities

之后会调用如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//ActivityStarter
 ...
 mTargetRootTask.startActivityLocked(mStartActivity, topRootTask, newTask, isTaskSwitch,
 mOptions, sourceRecord); //这个名字是startActivityLock但并不是真的打开activity,而是把Activity对应的task放到列表的最前面,以及会展示window动画
 if (mDoResume) {
 if (!mTargetRootTask.isTopActivityFocusable()
 || (topTaskActivity != null && topTaskActivity.isTaskOverlay()
 && mStartActivity != topTaskActivity)) {
 //对样式pip页面或者其他一些情况的处理
 ...
 } else {
 ...
 //真正的启动Activity的代码这里是入口
 mRootWindowContainer.resumeFocusedTasksTopActivities(
 mTargetRootTask, mStartActivity, mOptions, mTransientLaunch);
 }
 ...

 }
 ...

 return START_SUCCESS;
 }

我们看到首先调用了startActivityLocked方法,这里主要做的就是把我们的ActivityReccord放到Task中去,并且展示Activity的启动动画。之后调用的RootContainerresumeFocsedTasksTopActivities才是真正的启动,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//RootWindowContainer.java
boolean resumeFocusedTasksTopActivities(Task targetRootTask, ActivityRecord target, ActivityOptions targetOptions,
boolean deferPause) {
 ...
 boolean result = false;
 if (targetRootTask != null && (targetRootTask.isTopRootTaskInDisplayArea()
 || getTopDisplayFocusedRootTask() == targetRootTask)) {
 result = targetRootTask.resumeTopActivityUncheckedLocked(
 target,targetOptions, deferPause); //执行启动
 }
 ...
}

后面会走到Task的resumeTopActivityUnCheckedLocked方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options,
 boolean deferPause) {
 ...
 if (isLeafTask()) {
 if (isFocusableAndVisible()) { //可触摸可见
 someActivityResumed = resumeTopActivityInnerLocked(prev, options, deferPause);
 }
 } else {
 int idx = mChildren.size() - 1;
 while (idx >= 0) {
 final Task child = (Task) getChildAt(idx--);
 if (!child.isTopActivityFocusable()) {
 continue;
 }
 if (child.getVisibility(null /* starting */)
 != TASK_FRAGMENT_VISIBILITY_VISIBLE) {
 if (child.topRunningActivity() == null) {
 continue;
 }
 break;
 }

 someActivityResumed |= child.resumeTopActivityUncheckedLocked(prev, options,
 deferPause);
 if (idx >= mChildren.size()) {
 idx = mChildren.size() - 1;
 }
 }
 }


}

此处如果当前的Task本来就是叶子节点,那么会调用resumeTopActivityInnerLocked方法,否则会遍历子的task列表,在子task列表中找到符合条件的去执行resumeTopActivityUncheckedLocked方法,如此最后还是会调用到resumeTopActivityInnerLocked方法,而我们再跟进去看,可以看到其中的核心逻辑是调用topFragment的resumeTopActivity方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
 boolean deferPause) {
 ...
 boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);
 if (mResumedActivity != null) {
 pausing |= startPausing(mTaskSupervisor.mUserLeaving, false /* uiSleeping */,
 next, "resumeTopActivity"); //首先把顶部处于Resumed状态的activity执行pausing
}
if (pausing) {
 //检查即将启动的Activity的Activity的进程有没有起来,如果没有进程去创建进程,创建进程的代码需要单独分析,此处略过
 if (next.attachedToProcess()) {
 next.app.updateProcessInfo(false /* updateServiceConnectionActivities */,
 true /* activityChange */, false /* updateOomAdj */,
 false /* addPendingTopUid */);
 } else if (!next.isProcessRunning()) {
 final boolean isTop = this == taskDisplayArea.getFocusedRootTask();
 mAtmService.startProcessAsync(next, false /* knownToBeDead */, isTop,
 isTop ? HostingRecord.HOSTING_TYPE_NEXT_TOP_ACTIVITY
 : HostingRecord.HOSTING_TYPE_NEXT_ACTIVITY);
 }
 ...
 return true;
 }
 if (next.attachedToProcess()) { //如何Activity已经在这个进程中了
 ...
 final ClientTransaction transaction =
 ClientTransaction.obtain(next.app.getThread(), next.token); //构建ClientTransaction,传入要打开的Activity对应的applicationThread和IBinder
 ...
 if (next.newIntents != null) { //把intent放进去,后面会把Activity吊起,并 调用onNewIntent()
 transaction.addCallback(
 NewIntentItem.obtain(next.newIntents, true /* resume */));
 }
 ...
 mAtmService.getLifecycleManager().scheduleTransaction(transaction);

 } else {
 ...
 mTaskSupervisor.startSpecificActivity(next, true, true); //启动Activity
 }


}

上面最后的代码可以看到,Activity已经存在的时候是走到onNewIntent, 调用的代码被包装成了ClientTransaction,通过ClientlifecycleManager 的scheduleTransaction方法,最终其实是调用了IApplicationThread的scheduleTransaction,最终通过binder调用到了app进程中的同名方法,这里要去看ActivityThread, ApplicationThread为它的内部类,看它的代码,它实际调用了ActivityThread的同名方法。而启动Activity,我们一路跟着startSpecificActivity()方法进去最终会看到也是通过ClientTransaction,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
 System.identityHashCode(r), r.info,
 // TODO: Have this take the merged configuration instead of separate global 
 // and override configs. 
 mergedConfiguration.getGlobalConfiguration(),
 mergedConfiguration.getOverrideConfiguration(), r.compat,
 r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
 proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
 results, newIntents, r.takeOptions(), isTransitionForward,
 proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
 r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken));
// Set desired final state. 
final ActivityLifecycleItem lifecycleItem;
if (andResume) {
 lifecycleItem = ResumeActivityItem.obtain(isTransitionForward,
 r.shouldSendCompatFakeFocus());
} else {
 lifecycleItem = PauseActivityItem.obtain();
}
clientTransaction.setLifecycleStateRequest(lifecycleItem);
mService.getLifecycleManager().scheduleTransaction(clientTransaction);

目标进程执行performLaunchActivity

我们之前已经分析过ClientTransaction,我们知道这个LaunchActivityItem的callback,最后client就是我们的ActivityThread,会执行它的handleLaunchActivity方法,其中最核心的就是如下这一句:

1
final Activity a = performLaunchActivity(r, customIntent);

我们继续往里面看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//content of performLaunchActivity() function
...
//为Activity创建Base Context
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
...
//创建Activity实例,就是通过反射来实例化一个Activity实例
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
 cl, component.getClassName(), r.intent);
...
//拿到Application的实例,如果缓存中有就用,没有就创建一个新的,这里也不看具体代码了
Application app = r.packageInfo.makeApplicationInner(false, mInstrumentation);
...
//为Activity创建配置,如里面有语言,屏幕设置等等参数,不具体分析了
Configuration config =
 new Configuration(mConfigurationController.getCompatConfiguration());
if (r.overrideConfig != null) {
 config.updateFrom(r.overrideConfig);
}
...
//把Activity和baseContext绑定,并且把一些参数附加到Activity实例上去
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
 r.ident, app, r.intent, r.activityInfo, title, r.parent,
 r.embeddedID, r.lastNonConfigurationInstances, config,
 r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
 r.assistToken, r.shareableActivityToken);
...
mInstrumentation.callActivityOnCreate(activity, r.state); //这一步完成,Activity里面会执行完onCreate()
...
r.setState(ON_CREATE);

再来具体看一看Activity的attach方法中做了什么

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
....
mWindow.setWindowManager(
 (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
 mToken, mComponent.flattenToString(),
 (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;

可以看到在里面绑定了baseContext,以及创建了我们的PhoneWindow,以及把window和windowManager进行绑定,还有其他一些Activity内部会用到的参数的传递。

而mInstrumentation里面最终是会调用Activity的performCreate,其中则会调用activity的onCreate。这里就不贴相关代码了。

这样我们的Activity才走完onCreate,而剩余步骤,我们之前还设置了LifecycleStateRequestResumeActivityItem,因此这是要让我们的Activity最终进入到Resume状态,具体的可以参看ClientTransaction分析。两篇文章配合着一起,就是完整的Activity启动流程了。

总结

从Activity调用startActivity,一直到A T M S调用ActivityStarter的调用时序图如下:

sequenceDiagram
Activity->>Activity: startActivity
Activity->>Instrumentation:execStartActivity
Instrumentation->>ATMS: startActivity
ATMS->>ActivityStarter: execute

从ActivityStarter调用到新的进程处理的时序图如下(省略了到Activity部分的流程):

sequenceDiagram
ActivityStarter->>ActivityStarter: resolveActivity
ActivityStarter->>ActivityStarter: executeRequest
ActivityStarter->>ActivityStarter: startActivityUnchecked
ActivityStarter->>RootWindowContainer: resumeFocusedTasksTopActivities
RootWindowContainer->>Task: resumeTopActivityUncheckedLocked
Task->>Task: resumeTopActivity
Task->>ActivityTaskSupervisor: startSpecificActivity
ActivityTaskSupervisor->>ActivityTaskSupervisor: realStartActivityLocked
ActivityTaskSupervisor->>ActivityThread: handleLaunchActivity
note right of ActivityTaskSupervisor: 通过ClientTransaction
ActivityThread->>ActivityThread: performLaunchActivity

以上就是一个较为精简的Activity启动的流程。其中省略了不少东西,关于startActivityForResult的情况需要获取到打开的Activity的结果的情况这里还没有讨论。

看代码可以发现Activity的启动过程是非常的复杂的,再加上新版本的Android支持多屏幕,折叠屏,分屏,画中画等等非常多的特性,因而Task的复用,新建就很复杂,因此本文这一部分暂时放下,等到以后在写。

如果你也对于Android系统源码感兴趣,欢迎与我交流。博文因为个人局限,也难免会出现差错,欢迎大家指正。

看完评论一下吧

Android源码分析: 应用进程启动分析

Android应用进程的启动,简单来说就是从zygot进程fork出来一个新进程,并对其进行一些初始化。这样做系统的一些代码和资源等等就不需要重复加载,一些环境变量也都不需要重新设置,可以说是很巧妙的设置。下面就来具体分析一下其初始化过程。

启动时机

应用进程的启动,一般是在创建四大组件,比如说启动Activity,Service,使用ContentProvider,有广播需要处理,这些情况需要创建进程。在我们分析的代码当中,除了这几种情况,BackupAngent也会涉及到创建App进程。

启动进程调用的为AMS当中的startProcessLocked方法, 我们注意看的话,AMS当中还有另一个方法startIsolatedProcess也是用来启动进程的,但是这个方法它启动的进程一般是给系统使用的,我们这里不会分析。

AMS调用启动进程

我们就从AMSstartProcessLocked这个方法开始看起来:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
final ProcessRecord startProcessLocked(String processName,
 ApplicationInfo info, boolean knownToBeDead, int intentFlags,
 HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting,
 boolean isolated) {
 return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,
 hostingRecord, zygotePolicyFlags, allowWhileBooting, isolated, 0 /* isolatedUid */,
 false /* isSdkSandbox */, 0 /* sdkSandboxClientAppUid */,
 null /* sdkSandboxClientAppPackage */,
 null /* ABI override */, null /* entryPoint */,
 null /* entryPointArgs */, null /* crashHandler */);
}

这里我们传入的参数intentFlags为0,zygotePolicyFlagsZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE, allowWhileBootingfalse, isolatedfalse。之后startProcessLocked方法内部用调用了ProcessList的同名方法,其中我们关注的核心语句如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
ProcessRecord app;
...
app = getProcessRecordLocked(processName, info.uid); //从缓存中获取ProcessRecord
...
if (app == null) {
 app = newProcessRecordLocked(info, processName, isolated, isolatedUid, isSdkSandbox,
 sdkSandboxUid, sdkSandboxClientAppPackage, hostingRecord);
} else {
 app.addPackage(info.packageName, info.longVersionCode, mService.mProcessStats);
}
...
final boolean success =
 startProcessLocked(app, hostingRecord, zygotePolicyFlags, abiOverride);

以上代码可以看到,会先去获取是否有现有的processRecord可用,有的话就拿出来使用,没有的话会创建新的,之后会调用startProcessLocked方法。ProcessList中使用mProcessNames来存储ProcessRecord与processName和uid的对应关系,查找的逻辑就是从map中查找不再关注。newProcessRecordLocked方法则是创建新的ProcessRecord,并且会把各种信息保存到这个record当中去。我们这里可以继续看startProcessLocked方法,,最终会调用这个方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
 int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks,
 String abiOverride) {
 //对于从缓存拿到的ProcessRecord,把原来的信息清掉
 if (app.getPid() > 0 && app.getPid() != ActivityManagerService.MY_PID) {
 mService.removePidLocked(app.getPid(), app);
 app.setBindMountPending(false);
 app.setPid(0);
 app.setStartSeq(0);
 }
 app.unlinkDeathRecipient();
 app.setDyingPid(0);
 ...
 final IPackageManager pm = AppGlobals.getPackageManager();
 permGids pm.getPackageGids(app.info.packageName, =
 MATCH_DIRECT_BOOT_AUTO, app.userId);
 StorageManagerInternal storageManagerInternal = LocalServices.getService(
 StorageManagerInternal.class);
 mountExternal = storageManagerInternal.getExternalStorageMountMode(uid,
 app.info.packageName); //检查外部存储访问权限
 externalStorageAccess = storageManagerInternal.hasExternalStorageAccess(uid,
 app.info.packageName);
 if (pm.checkPermission(Manifest.permission.INSTALL_PACKAGES,
 app.info.packageName, userId)
 == PackageManager.PERMISSION_GRANTED) { //检查安装应用的权限
 Slog.i(TAG, app.info.packageName + " is exempt from freezer");
 app.mOptRecord.setFreezeExempt(true);
 }
 if (app.processInfo != null && app.processInfo.deniedPermissions != null) {
 for (int i = app.processInfo.deniedPermissions.size() - 1; i >= 0; i--) {
 int[] denyGids = mService.mPackageManagerInt.getPermissionGids(
 app.processInfo.deniedPermissions.valueAt(i), app.userId);
 if (denyGids != null) {
 for (int gid : denyGids) {
 permGids = ArrayUtils.removeInt(permGids, gid);
 }
 }
 }
 }

 gids = computeGidsForProcess(mountExternal, uid, permGids, externalStorageAccess); //根据前面的权限和相关信息,计算新启动的进程 需要分配的用户
 ...
 //读取app的debuggable,profileable等标志位
 boolean debuggableFlag = (app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
 boolean isProfileableByShell = app.info.isProfileableByShell();
 boolean isProfileable = app.info.isProfileable();
 if (debuggableFlag) {
 runtimeFlags |= Zygote.DEBUG_ENABLE_JDWP;
 runtimeFlags |= Zygote.DEBUG_JAVA_DEBUGGABLE;
 runtimeFlags |= Zygote.DEBUG_ENABLE_CHECKJNI;


 if (android.provider.Settings.Global.getInt(mService.mContext.getContentResolver(), android.provider.Settings.Global.ART_VERIFIER_VERIFY_DEBUGGABLE, 1) == 0) {
 runtimeFlags |= Zygote.DISABLE_VERIFIER;
 }
 }
 if (isProfileableByShell) {
 runtimeFlags |= Zygote.PROFILE_FROM_SHELL;
 }
 if (isProfileable) {
 runtimeFlags |= Zygote.PROFILEABLE;
 } //把标志位信息保存到runtimeFlags中
 ...//其他一些flag写入到runtimeFlags中去
 if (debuggableFlag) {
 //debuggable时候使用wrap.sh去fork进程
 String wrapperFileName = app.info.nativeLibraryDir + "/wrap.sh";
 StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
 try {
 if (new File(wrapperFileName).exists()) {
 invokeWith = "/system/bin/logwrapper " + wrapperFileName;
 }
 } finally {
 StrictMode.setThreadPolicy(oldPolicy);
 }
 }
 String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;
 if (requiredAbi == null) { //设置 app native 库使用的abi,如arm或者x86或者armv8等等
 requiredAbi = Build.SUPPORTED_ABIS[0];
 }
 String instructionSet = null;
 if (app.info.primaryCpuAbi != null) {
 instructionSet = VMRuntime.getInstructionSet(requiredAbi);
 }

 app.setGids(gids);
 app.setRequiredAbi(requiredAbi);
 app.setInstructionSet(instructionSet); //把信息都设置到ProcessRecord中
 final String seInfo = app.info.seInfo
 + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser);
 final String entryPoint = "android.app.ActivityThread"; //设置进程入口位ActivityThread

 return startProcessLocked(hostingRecord, entryPoint, app, uid, gids,
 runtimeFlags, zygotePolicyFlags, mountExternal, seInfo, requiredAbi,
 instructionSet, invokeWith, startUptime, startElapsedTime);

}

以上代码主要是检查应用的各种权限,对其设置对应权限组的groupId,以及设置应用的Abi等信息。之后又会启动一个新的startProcessLocked方法,其中仍然是给ProcessRecord设置参数,其中很大篇幅的为设置debug和profilable相关的参数设置,这里就不列出参数设置的代码了,只列以下最后启动调用的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
if (mService.mConstants.FLAG_PROCESS_START_ASYNC) {
 mService.mProcStartHandler.post(() -> handleProcessStart(
 app, entryPoint, gids, runtimeFlags, zygotePolicyFlags, mountExternal,
 requiredAbi, instructionSet, invokeWith, startSeq));
 return true;
} else {
 final Process.ProcessStartResult startResult = startProcess(hostingRecord,
 entryPoint, app,
 uid, gids, runtimeFlags, zygotePolicyFlags, mountExternal, seInfo,
 requiredAbi, instructionSet, invokeWith, startUptime);
 handleProcessStartedLocked(app, startResult.pid, startResult.usingWrapper,
 startSeq, false);
 return app.getPid() > 0;
}

这里有两个分支,这个 FLAG_PROCESS_START_ASYNC 默认为True,是通过系统的Setting去设置的。第一个分支是通过Handle把任务抛出去执行,而直接返回了执行成功,另一个分支则是等待任务执行完成,在根据返回的UID检查是否成功。不过两个分支里面都是执行了startProcess方法,在这个方法中我们关注以下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
if (hostingRecord.usesWebviewZygote()) { //webview进程的创建
 startResult = startWebView(entryPoint,
 app.processName, uid, uid, gids, runtimeFlags, mountExternal,
 app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
 app.info.dataDir, null, app.info.packageName,
 app.getDisabledCompatChanges(),
 new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
} else if (hostingRecord.usesAppZygote()) {
 final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app);

 startResult = appZygote.getProcess().start(entryPoint,
 app.processName, uid, uid, gids, runtimeFlags, mountExternal,
 app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
 app.info.dataDir, null, app.info.packageName,
 /*zygotePolicyFlags=*/ ZYGOTE_POLICY_FLAG_EMPTY, isTopApp,
 app.getDisabledCompatChanges(), pkgDataInfoMap, allowlistedAppDataInfoMap,
 false, false,
 new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
} else {
 regularZygote = true;
 startResult = Process.start(entryPoint,
 app.processName, uid, uid, gids, runtimeFlags, mountExternal,
 app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
 app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
 isTopApp, app.getDisabledCompatChanges(), pkgDataInfoMap,
 allowlistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs,
 new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});
}

以上可以看到我们在创建新的进程的时候,会有三个分支,我们回看我们创建HostingRecord 时候是调用的如下的构造方法:

1
2
3
4
5
public HostingRecord(@NonNull String hostingType, ComponentName hostingName, boolean isTopApp) {
 this(hostingType, hostingName.toShortString(), REGULAR_ZYGOTE,
 null /* definingPackageName */, -1 /* mDefiningUid */, isTopApp /* isTopApp */,
 null /* definingProcessName */, null /* action */, TRIGGER_TYPE_UNKNOWN);
}

Process启动进程调用

因此上面的代码是走到了regular分支,它调用了Processstart方法, Process中又调用了ZYGOTE_PROCESSstart方法, ZYGOTE_PROCESS为一个ZygoteProcess常量,其中又会调用startViaZygoate方法,我们来看看这个方法的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private Process.ProcessStartResult startViaZygote(@NonNull final String processClass, @Nullable final String niceName, final int uid, final int gid, @Nullable final int[] gids,
 int runtimeFlags, int mountExternal,
 int targetSdkVersion,
 @Nullable String seInfo,
 @NonNull String abi,
 @Nullable String instructionSet,
 @Nullable String appDataDir,
 @Nullable String invokeWith,
 boolean startChildZygote,
 @Nullable String packageName,
 int zygotePolicyFlags,
 boolean isTopApp,
 @Nullable long[] disabledCompatChanges,
 @Nullable Map<String, Pair<String, Long>> pkgDataInfoMap,
 @Nullable Map<String, Pair<String, Long>> allowlistedDataInfoList,
 boolean bindMountAppsData,
 boolean bindMountAppStorageDirs,
 @Nullable String[] extraArgs) throws ZygoteStartFailedEx {
 ArrayList<String> argsForZygote = new ArrayList<>();
 argsForZygote.add("--runtime-args");
 argsForZygote.add("--setuid=" + uid);
 argsForZygote.add("--setgid=" + gid);
 argsForZygote.add("--runtime-flags=" + runtimeFlags);
 ....
 synchronized(mLock) {
 return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
 zygotePolicyFlags,
 argsForZygote);
}

上面的代码就是把我们之前所有的各种参数,都拼接起来放到一个字符数组中,后面的openZygoteSocketIfNeeded则是根据abi来于zygote进程建立socket连接,其他的我就要进入zygoteSendArgsAndGetResult方法中查看详情了。

1
2
3
4
5
if (shouldAttemptUsapLaunch(zygotePolicyFlags, args)) {
 return attemptUsapSendArgsAndGetResult(zygoteState, msgStr);
}

return attemptZygoteSendArgsAndGetResult(zygoteState, msgStr);

这里有一个判断是否要使用usap进程池(非专门app使用进程池),不过我看了这里mUsapPoolEnabled字段默认为false,那我们就不看这个分支了。而attemptZygoteSendArgsAndGetResult代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private Process.ProcessStartResult attemptZygoteSendArgsAndGetResult(
 ZygoteState zygoteState, String msgStr) throws ZygoteStartFailedEx {
 try {
 final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter;
 final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream;

 zygoteWriter.write(msgStr);
 zygoteWriter.flush();

 Process.ProcessStartResult result = new Process.ProcessStartResult();
 result.pid = zygoteInputStream.readInt();
 result.usingWrapper = zygoteInputStream.readBoolean();

 if (result.pid < 0) {
 throw new ZygoteStartFailedEx("fork() failed");
 }

 return result;
 } catch (IOException ex) {
 zygoteState.close();
 Log.e(LOG_TAG, "IO Exception while communicating with Zygote - "
 + ex.toString());
 throw new ZygoteStartFailedEx(ex);
 }
}

从上面的代码我们可以看到,这里其实很简单,就是通过socket向Zytgote发送了我们启动进程需要的参数,然后再通过socket从Zygote读出创建的进程的pid。

Zygote进程创建子进程

这个时候我们需要来看ZygoteInit的main方法,具体zygote进程是如何在系统启动的时候创建的就不去关注了,这里来关注zygote进程如何去创建应用进程的,这里摘抄了一些它的main函数的代码:

1
2
3
4
5
6
7
preload(bootTimingsTraceLog); //zygote启动之后,预加载代码资源等
zygoteServer = new ZygoteServer(isPrimaryZygote); //创建Zygote 的socket server
caller = zygoteServer.runSelectLoop(abiList); // socket server进入监听状态

if (caller != null) {
 caller.run(); //子进程中的时候caller不为空,会执行,此处会执行我们的ActivityThread的main方法,先分析上面的runSelectLoop,其中会有caller的创建
}

runSelectLoop内我们比较关注的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Runnable runSelectLoop(String abiList) {
 while (true) {
 pollReturnValue = Os.poll(pollFDs, pollTimeoutMs);
 if (pollReturnValue == 0) {
 ...
 } else {
 while (--pollIndex >= 0) {
 if (pollIndex == 0) {
 //如果pollIndex为0,则说明没有socket连接,需要创建socket连接
 ZygoteConnection newPeer = acceptCommandPeer(abiList);
 peers.add(newPeer);
 socketFDs.add(newPeer.getFileDescriptor());
 } else if (pollIndex < usapPoolEventFDIndex) { //读取Primary socket
 ZygoteConnection connection = peers.get(pollIndex);
 boolean multipleForksOK = !isUsapPoolEnabled() && ZygoteHooks.isIndefiniteThreadSuspensionSafe();
 final Runnable command = connection.processCommand(this, multipleForksOK);
 if (mIsForkChild) {
 return command; //子进程,返回command
 } else {
 //父进程的一些处理
 }
 ...
 }
 ....
 }
 ...
 }

 }
}

上面的代码省略了一些如果是Usap进程的代码,代码里面有两层的循环,在内层循环中,以pollIndex作为循环的条件,如果pollIndex为0,在acceptCommandPeer中会建立新的Socket Connet,代码里面就是一个ZygoteConnection。如果存在Connect的情况下会,会通过判断当前pollIndex是否小于usapPollEventFDIndex来判断是否是普通的进程创建,之后会调用connection.processCommand来读取socket数据做后续的处理,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
ZygoteArguments parsedArgs;
try (ZygoteCommandBuffer argBuffer = new ZygoteCommandBuffer(mSocket)) {
 while (true) {
 parsedArgs = ZygoteArguments.getInstance(argBuffer);
 ...
 if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote
 || !multipleOK || peer.getUid() != Process.SYSTEM_UID) {
 pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid,
 parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits,
 parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName,
 fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
 parsedArgs.mInstructionSet, parsedArgs.mAppDataDir,
 parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList,
 parsedArgs.mAllowlistedDataInfoList, parsedArgs.mBindMountAppDataDirs,
 parsedArgs.mBindMountAppStorageDirs); //fork子进程的操作

 try {
 if (pid == 0) { //子进程处理分支
 zygoteServer.setForkChild();

 zygoteServer.closeServerSocket();
 IoUtils.closeQuietly(serverPipeFd);
 serverPipeFd = null;

 return handleChildProc(parsedArgs, childPipeFd,
 parsedArgs.mStartChildZygote);
 } else { //父进程处理分支
 IoUtils.closeQuietly(childPipeFd);
 childPipeFd = null;
 handleParentProc(pid, serverPipeFd);
 return null;
 }
 } finally {
 IoUtils.closeQuietly(childPipeFd);
 IoUtils.closeQuietly(serverPipeFd);
 }
 } else {
 ...
 }
 ...
 }
}

上面代码是处理socket数据的代码,我这里省略了除了创建进程之外的处理其他操作的代码。其中我们可以看到系统是使用了ZygoteArguments来解析我们之前从system_server进程传过来的参数,之后调用Zygote.forkAndSpecialize来创建进程,在linux中,fork完进程之后,是通过pid来判断当前是在父进程还是子进程中的,当前为子进程则pid为0。forkAndSpecialize方法中主要是调用了nativeForkAndSpecialize,这个是native方法,代码在com_android_internal_os_Zygote.cpp中,在native中的方法为com_android_internal_os_Zygote_nativeForkAndSpecialize我们去看看它的代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
...
pid_t pid = zygote::ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore, true);
if (pid == 0) {
 SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities,
 mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE,
 instruction_set, app_data_dir, is_top_app == JNI_TRUE, pkg_data_info_list,
 allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
 mount_storage_dirs == JNI_TRUE);
}
return pid;

上面的代码可以看到,第一行是去fork子进程,后面会判断是否为子进程,如果为子进程则会为子进程做一些处理。其中我省略了前面一部分fds_to_close 和fds_to_ignore赋值的代码,那些为需要关闭或者忽略的文件描述符,会传到这个ForkCommon方法中,我们具体看看这个方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
SetSignalHandlers(); //设置错误信号监听
BlockSignal(SIGCHLD, fail_fn); //暂时关闭SIGCHLD信号,方便后面关闭fd
__android_log_close(); //关闭log相关的FD
AStatsSocket_close();
...
pid_t pid = fork(); //调用系统调用执行fork进程

if (pid == 0) {
 ...
 PreApplicationInit(); //子进程的初始化,主要是设置当前进程不是zygote进程
 DetachDescriptors(env, fds_to_close, fail_fn); //把传进来的要关闭的fd关掉
 ...
} else {
 ...
}

UnblockSignal(SIGCHLD, fail_fn); //重新打开之前关闭的SIGCHLD信号
return pid;

可以看到上面的代码主要是去调用fork系统调用去从zygote进程fork一个新进程作为应用使用的进程,而SpecializeCommon,我们根据传入的参数和代码可以知道,其中主要是设置子进程的用户组,以及挂载应用目录,一些其他相关的初始化,就不分析其代码了。然后我们就可以继续会到java代码。

创建完子进程后的操作

在前面processCommand方法中,我们知道fork成功之后如果是子进程会执行handleChildProc方法,如果是父进程会执行handleParentProc方法,先来看一下父进程执行的代码:

1
2
3
4
5
6
if (pid > 0) {
 setChildPgid(pid);
}
...
mSocketOutStream.writeInt(pid);
mSocketOutStream.writeBoolean(usingWrapper);

这个方法中我们需要关注的就上面这一部分代码,首先是把这个子进程的pid放到进程的当前进程的孩子进程组中去。后面的就是把子进程的pid和是否使用了wrapper写入到socket中,这样我们之前请求创建进程那个地方就能拿到子进程的id了。

再来看子进程所执行的handleChildProc方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
closeSocket();
Zygote.setAppProcessName(parsedArgs, TAG);

if (parsedArgs.mInvokeWith != null) {
 WrapperInit.execApplication(parsedArgs.mInvokeWith,
 parsedArgs.mNiceName, parsedArgs.mTargetSdkVersion,
 VMRuntime.getCurrentInstructionSet(),
 pipeFd, parsedArgs.mRemainingArgs);

 // Should not get here. 
 throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
} else {
 if (!isZygote) {
 return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
 parsedArgs.mDisabledCompatChanges,
 parsedArgs.mRemainingArgs, null /* classLoader */);
 } else {
 return ZygoteInit.childZygoteInit(
 parsedArgs.mRemainingArgs /* classLoader */);
 }
}

首先第一行是关闭socket,前面的native代码其实已经关闭过了socket,但是在java层还是有LocalSocket,也需要关闭。 第二行就是给我们这个进程设置名称。 后面的第一个判断是看我们是否使用wrapper,正常流程不会走到这里,else分支中我们这里也不是fork一个新的zygote进程,因此也只需要看ZygoteInit.zygoteInit这个方法即可。

1
2
3
4
5
6
RuntimeInit.redirectLogStreams(); //关闭默认的log,设置使用android的print来输出system.out和system.error的log

RuntimeInit.commonInit();
ZygoteInit.nativeZygoteInit();
return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,
 classLoader);

App进程的初始化

RuntimeInit.commonInit中是一些初始化,包括错误处理,时区,网络的userAgent等,不看代码了。nativeZygoteInit的代码在AndroidRuntime.cpp

1
2
3
4
static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
{
 gCurRuntime->onZygoteInit();
}

此处调用了gCurRuntimeonZygoteInit()方法,而这个方法是AndroidRuntime中的一个虚方法,在app_main.cpp中我们看到实际上对于应用我们有一个子类AppRuntime中实现了这个方法,代码如下:

1
2
3
4
5
6
virtual void onZygoteInit()
{
 sp<ProcessState> proc = ProcessState::self();
 ALOGV("App process: starting thread pool.\n");
 proc->startThreadPool();
}

我们之前分析binder的时候,知道ProcessState这个类binder是有使用的,调用self方法会打开binder驱动,这个代码里面是为binder创建应用进程的线程池,具体这里就不分析了。继续看RuntimeInit.applicationInit代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges,
 String[] argv, ClassLoader classLoader) {
 nativeSetExitWithoutCleanup(true);

 VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
 VMRuntime.getRuntime().setDisabledCompatChanges(disabledCompatChanges);

 final Arguments args = new Arguments(argv);

 return findStaticMain(args.startClass, args.startArgs, classLoader);
}

上面的方面,前面的代码主要是设置targetSdkversion和其他的一些设置,我们主要来看后面的findStaticMain方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
protected static Runnable findStaticMain(String className, String[] argv,
 ClassLoader classLoader) {
 Class<?> cl;

 try {
 cl = Class.forName(className, true, classLoader);
 } catch (ClassNotFoundException ex) {
 throw new RuntimeException(
 "Missing class when invoking static main " + className,
 ex);
 }

 Method m;
 try {
 m = cl.getMethod("main", new Class[] { String[].class });
 } catch (NoSuchMethodException ex) {
 throw new RuntimeException(
 "Missing static main on " + className, ex);
 } catch (SecurityException ex) {
 throw new RuntimeException(
 "Problem getting static main on " + className, ex);
 }

 int modifiers = m.getModifiers();
 if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
 throw new RuntimeException(
 "Main method is not public and static on " + className);
 }
 return new MethodAndArgsCaller(m, argv);
}

可以看到我们通过反射拿到应用的之前设置的应用入口,也就是ActivityThread类,之后再获取到它的main方法,最后组装成一个MethodAndArgsCaller对象,最后返回。从前面的代码我们知道它会在ZygoteInitmain方法中执行。然后我们就可以来分析ActivityThread代码了。

ActivityThread代码执行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public static void main(String[] args) {
 AndroidOs.install();
 Environment.initForCurrentUser();
 final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
 TrustedCertificateStore.setDefaultUserDirectory(configDir);
 initializeMainlineModules();

 Looper.prepareMainLooper();
 ...
 ActivityThread thread = new ActivityThread();
 thread.attach(false, startSeq);
 Looper.loop();
 throw new RuntimeException("Main thread loop unexpectedly exited");

}

上面的代码可以看到是为应用进程做一些初始化,首先是为sys call使用android的一些定制,其次是指定CA证书的位置,之后安装Mainline的模块,后面是初始化looper进入Looper循环,这样应用的主线程也就完成了初始化。在启动loop之前有一个attach方法,对于应用进程我们传进来的第一个参数为false, 也就是非系统进程,我们来看代码,只看应用进程的分支。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManager.getService();
try {
 mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
 throw ex.rethrowFromSystemServer();
}

BinderInternal.addGcWatcher(new Runnable() {
 public void run() {
 //监听gc,当可用内存比较小的时候尝试回收一些Activity
 }
}

ViewRootImpl.ConfigChangedCallback configChangedCallback = (Configuration globalConfig) -> {
 //config 变化的回调,用来更新app得而configuration
}
ViewRootImpl.addConfigCallback(configChangedCallback);

上面的代码主要做了四件事情,其中两个是注册gc的回调和view configration 变化的回调,我们最关注的是调用ActivityManager的atttachApplication,和RuntimeInit的setApplicationObject,它们都用到了mAppThread,这个对象为ApplicationThread,而它是IApplicationThread.aidl的客户端实现。这里首先是把它的IBinder对象传到RuntimeInit中,这样发生一些事情的时候系统可以通知到应用。

回到AMS

另外我们再来看一下ActivityManager的attchApplication方法,它实际调用的是ActivityManagerServiceattachApplication方法,在它内部又调用了attachApplicationLocked方法,这里只看一下我们比较关心的一部分代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
List<ProviderInfo> providers = normalMode
 ? mCpHelper.generateApplicationProvidersLocked(app)
 : null;
...
final ProviderInfoList providerList = ProviderInfoList.fromList(providers);
...
thread.bindApplication(processName, appInfo,
 app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
 providerList, null, profilerInfo, null, null, null, testMode,
 mBinderTransactionTrackingEnabled, enableTrackAllocation,
 isRestrictedBackupMode || !normalMode, app.isPersistent(),
 new Configuration(app.getWindowProcessController().getConfiguration()),
 app.getCompat(), getCommonServicesLocked(app.isolated),
 mCoreSettingsObserver.getCoreSettingsLocked(),
 buildSerial, autofillOptions, contentCaptureOptions,
 app.getDisabledCompatChanges(), serializedSystemFontMap,
 app.getStartElapsedTime(), app.getStartUptime());
...
if (normalMode) {
 try {
 didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
 } catch (Exception e) {
 Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
 badApp = true;
 }
}
if (!badApp) {
 try {
 didSomething |= mServices.attachApplicationLocked(app, processName);
 checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked"); //检查是否有Service需要在当前进程启动
 } catch (Exception e) {
 Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
 badApp = true;
 }
}

if (!badApp && isPendingBroadcastProcessLocked(pid)) {
 try {
 didSomething |= sendPendingBroadcastsLocked(app); //发送pending的广播
 checkTime(startTime, "attachApplicationLocked: after sendPendingBroadcastsLocked");
 } catch (Exception e) {
 // If the app died trying to launch the receiver we declare it 'bad' 
 Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
 badApp = true;
 }
}

上面省略了一些代码,不过我们application需要做的一些核心代码都还在。除了列出的代码外,这里其实还有一些pending service启动,pending 广播的执行,以及ContentProvider的安装等,这些我们先略过。 首先这个normalMode的判断,我们假设当前已经是使用中而不是刚启动手机,而mProcessesReady是在system_server启动之后就赋值为true了,所以对于app启动的状况来说,这里normalMode为true。这里我们需要重点关注的就两个地方,一个是thread.bindApplication,另一处是mAtmInternal.attachApplication。bindApplication会通过binder调用到应用进程的bindApplication方法。

1
2
3
4
AppBindData data = new AppBindData();
data.processName = processName;
....
sendMessage(H.BIND_APPLICATION, data);

这里主要就去构建了AppBindData,使用ActivityThread内部的H来发送消息,消息回调处会调用ActivityThread的handleBindApplication方法。这个方法的代码非常多,前面的一些是设置包名,进程名称等等信息,以及configration信息以及调试器相关的东西,这些我们都不关注,这里跳过。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21

data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo, isSdkSandbox); //构建apk信息,创建LoadApk对象。

//创建AppContent,并且把confirmration绑定到Context上
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
mConfigurationController.updateLocaleListFromAppContext(appContext);

//创建Instrumentation
mInstrumentation = new Instrumentation();
mInstrumentation.basicInit(this);

if (!data.restrictedBackupMode) { //执行安装ContentProvider
 if (!ArrayUtils.isEmpty(data.providers)) {
 installContentProviders(app, data.providers);
 }
}

//创建程序的Application
app = data.info.makeApplicationInner(data.restrictedBackupMode, null);
//调用Application的onCreate方法
mInstrumentation.callApplicationOnCreate(app);

上面的逻辑我们需要关注makeApplicationInner, 后面的callApplicationOnCreate内部就是调用Application的onCreate方法,不再分析了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
final Application cached = sApplications.get(mPackageName);
if (cached != null) {
 if (!allowDuplicateInstances) {
 mApplication = cached;
 return cached;
 }
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
 cl, appClass, appContext);
appContext.setOuterContext(app);

上面的代码主要是从缓存里面取Application,如果没有则通过Instrumentaion去创建新的Applicaion,我们继续去看newApplication的代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public Application newApplication(ClassLoader cl, String className, Context context)
 throws InstantiationException, IllegalAccessException,
 ClassNotFoundException {
 String appClass = mApplicationInfo.getCustomApplicationClassNameForProcess(
 myProcessName);
 Application app = getFactory(context.getPackageName())
 .instantiateApplication(cl, className);
 app.attach(context);
 return app;
}

这里的代码比较简单,就是拿到AppComponentFactory然后通过反射创建App的Application对象,之后调用app的attach方法,attach方法内部会调用attachBaseContext方法。就不往里去看代码了。

对于需要启动Activity的情况,我们需要看ActivityManagerServiceattachApplication,我们需要再看一下mAtmInternal.attachApplication。它会调用ActivityTaskManagerService的内部类LocalService的方法,内部会调用mRootWindowContainer.attachApplication(wpc);,它的内部又会调用mAttachApplicationHelper.process(app),内部又会调用ensureActivitiesVisible方法,一路看进去最终会调用EnsureActivitiesVisibleHelperprocess方法,它的内部会调用setActivityVisibilityState

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
if (!r.attachedToProcess()) {
 makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges, isTop,
 resumeTopActivity && isTop, r);
} else if (r.isVisibleRequested()) {
 // If this activity is already visible, then there is nothing to do here. 
 if (DEBUG_VISIBILITY) {
 Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r);
 }

 if (r.mClientVisibilityDeferred && mNotifyClients) {
 r.makeActiveIfNeeded(r.mClientVisibilityDeferred ? null : starting);
 r.mClientVisibilityDeferred = false;
 }

 r.handleAlreadyVisible();
 if (mNotifyClients) {
 r.makeActiveIfNeeded(mStarting);
 }
} else {
 r.makeVisibleIfNeeded(mStarting, mNotifyClients);
}

这些就是去执行启动Activity相关的逻辑,这里也先略过。

以下是AMS发起创建新进程的时序图:

sequenceDiagram
AMS->>+ProcessList: startProcessLocked
ProcessList->>ProcessList: newProcessRecordLocked
ProcessList->>ProcessList: startProcess
ProcessList->>+ZygoteProcess: startViaZygote
ZygoteProcess->>ZygoteProcess: openZygoteSocketIfNeeded
ZygoteProcess->>ZygoteProcess: zygoteSendArgsAndGetResult
ZygoteProcess->>ZygoteServer: send args and get Result
ZygoteProcess-->>-ProcessList: return result with pid
ProcessList-->>-AMS: return ProcessRecord

以下是Zygote侧处理fork进程请求的时序图:

sequenceDiagram
ZygoteInit->>ZygoteServer: runSelectLoop
loop 无限循环
ZygoteServer->>ZygoteConnection: processCommand
ZygoteConnection->>Zygote: forkAndSpecialize
ZygoteConnection-->>ZygoteConnection: (Parent): notify child pid by socket
ZygoteServer-->>ZygoteInit: return command(child process)
end
rect rgb(191, 223, 255)
note right of ZygoteInit: fork完子进程执行的内容
ZygoteConnection->>ZygoteInit: zygoteInit
ZygoteInit->>ZygoteInit: nativeZygoteInit
ZygoteInit->>AppRuntime: onZygoteInit
ZygoteInit->>RuntimeInit: findStaticMain
RuntimeInit-->>ZygoteInit: return ActivityThread Main function caller
ZygoteInit->>ActivityThread: main
ActivityThread->>AMS: attachApplication
AMS->>ActivityThread: bindApplication
end

以上就是应用进程启动的完整流程,为了使得流程更加简洁,其中不太重要的步骤有作省略。如果你也对于Android系统源码感兴趣,欢迎与我交流。博文因为个人局限,也难免会出现差错,欢迎大家指正。

看完评论一下吧

Android Binder源码分析:AIDL及匿名服务传输

前面介绍的通过ServiceManager添加服务和获取服务,这些服务都是有名称的,我们可以通过ServiceManager来获取它。除此之外Android系统中还有一类Binder服务是匿名它,它们如何让客户端获得代理对象,并且使用呢,本文就一探究竟。

AIDL 介绍

AIDL全称为Android接口定义语言,是Android系统提供的一款可供用户用来抽象IPC的工具,它提供了语法让我们来定义跨进程通讯的服务接口,也就是.aidl文件,它也提供了工具,帮助我们把定义文件专程目标语言的代码。

我们自己使用AIDL创建的服务,或者一部分系统内的服务,比如IWindowSessionIApplicationThread等,这些多是运行在App进程,一般都不是系统服务,因此都是匿名的。我们这里以IApplicationThread来分析AIDL创建的匿名服务是怎么传递Binder给使用端的。

IApplicationThread来分析,则服务端是我们的App进程,而客户端则是system_server进程。作为AIDL创建的Binder,首先会有一个AIDL文件,这里是IApplicationThread.aidl,其中定义了一些跨进程调用的方法,部分内容如下:

package android.app;
...
oneway interface IApplicationThread {
void scheduleReceiver(in Intent intent, in ActivityInfo info,
in CompatibilityInfo compatInfo,
int resultCode, in String data, in Bundle extras, boolean sync,
int sendingUser, int processState);
@UnsupportedAppUsage
void scheduleStopService(IBinder token);
...
}

当前Android AIDL已经支持生成Java、C++、Rust的代码,对于IApplicationThread这里我们只需关注生成Java版本的代码即可。生成的代码在IApplicationThread当中,大概如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
public interface IApplicationThread extends android.os.IInterface {
public static class Default implements android.app.IApplicationThread {
 @Override
 public void scheduleReceiver(android.content.Intent
 ...) throws android.os.RemoteException {}

 @Override
 public android.os.IBinder asBinder() {
 return null;
 }
}

public abstract static class Stub extends android.os.Binder
 implements android.app.IApplicationThread {
 public Stub() {
 this.attachInterface(this, DESCRIPTOR);
 }

 public static android.app.IApplicationThread asInterface(android.os.IBinder obj) {
 if ((obj == null)) {
 return null;
 }
 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
 if (((iin != null) && (iin instanceof android.app.IApplicationThread))) {
 return ((android.app.IApplicationThread) iin);
 }
 return new android.app.IApplicationThread.Stub.Proxy(obj);
 }

 @Override
 public android.os.IBinder asBinder() {
 return this;
 }

 public static java.lang.String getDefaultTransactionName(int transactionCode) {
 switch (transactionCode) {
 case TRANSACTION_scheduleReceiver:{
 return "scheduleReceiver";
 }
 case TRANSACTION_scheduleCreateService: {
 return "scheduleCreateService";
 }
 default: {
 return null;
 }
 }
 }

 public java.lang.String getTransactionName(int transactionCode) {
 return this.getDefaultTransactionName(transactionCode);
 }

 @Override
 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
 throws android.os.RemoteException {
 java.lang.String descriptor = DESCRIPTOR;
 if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION
 && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
 data.enforceInterface(descriptor);
 }
 switch (code) {
 case INTERFACE_TRANSACTION:{
 reply.writeString(descriptor);
 return true;
 }
 }

 switch (code) {
 case TRANSACTION_scheduleReceiver:{
 android.content.Intent _arg0;
 _arg0 = data.readTypedObject(android.content.Intent.CREATOR);
 ...
 int _arg8;
 _arg8 = data.readInt();
 data.enforceNoDataAvail();
 this.scheduleReceiver(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5, _arg6, _arg7, _arg8);
 break;
 }
 ...
 default:{
 return super.onTransact(code, data, reply, flags);
 }
 }
 return true;
 }

 private static class Proxy implements android.app.IApplicationThread {
 private android.os.IBinder mRemote;

 Proxy(android.os.IBinder remote) {
 mRemote = remote;
 }

 @Override
 public android.os.IBinder asBinder() {
 return mRemote;
 }

 public java.lang.String getInterfaceDescriptor() {
 return DESCRIPTOR;
 }

 @Override
 public void scheduleReceiver(
 android.content.Intent intent,
 ...
 int processState) throws android.os.RemoteException {
 android.os.Parcel _data = android.os.Parcel.obtain();
 try {
 _data.writeInterfaceToken(DESCRIPTOR);
 ...
 _data.writeTypedObject(intent, 0);
 boolean _status = mRemote.transact(
 Stub.TRANSACTION_scheduleReceiver, _data, null, android.os.IBinder.FLAG_ONEWAY);
 } finally {
 _data.recycle();
 }
 }
 ...
 }

 public static final java.lang.String DESCRIPTOR = "android.app.IApplicationThread";
 static final int TRANSACTION_scheduleReceiver = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
 static final int TRANSACTION_scheduleCreateService = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
...
 public int getMaxTransactionId() {
 return 57;
 }
}
...
public void scheduleReceiver(
android.content.Intent intent,
...
int processState)
throws android.os.RemoteException;

}

对于Java代码,AIDL会生成一个跟AIDL同名的接口,同时继承自IInterface,同时还会创建内部类,分别为Default和Stub。Default为默认实现,大多数情况下是没有的,因为我们自己会实现。而Stub为抽象类,我们自己实现的时候会使用它,它以及继承自Binder,AIDL工具帮助我们把Parcel读写相关的代码已经生成,我们只需要去继承它,实现业务逻辑即可。而Stub中还有一个内部类Proxy,这个类用于Binder服务的客户端使用。对于IApplicationThread的实现,在ActivityThread当中。

匿名服务的传输

那么ActivityServiceManager(之后简称AMS)是怎么拿到ApplicationThread的呢,ActivityThread的attach方法则是这一切的入口:

1
2
3
4
5
6
final IActivityManager mgr = ActivityManager.getService();
try {
 mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
 throw ex.rethrowFromSystemServer();
}

代码中,首先拿到AMS的binder客户端类,这里也就是IActivityManager$Stub$Proxy,因为它也用了AIDL,所以跟我们ApplicationThread的类是类似的,具体如何拿到的,这个之前ServiceManager分析getService的时候已经分析过了,这里不看了。我们可以看一下它的attachApplication方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Override public void attachApplication(android.app.IApplicationThread app, long startSeq) throws android.os.RemoteException
{
 android.os.Parcel _data = android.os.Parcel.obtain();
 android.os.Parcel _reply = android.os.Parcel.obtain();
 try {
 _data.writeInterfaceToken(DESCRIPTOR);
 _data.writeStrongInterface(app);
 _data.writeLong(startSeq);
 boolean _status = mRemote.transact(Stub.TRANSACTION_attachApplication, _data, _reply, 0);
 _reply.readException();
 }
 finally {
 _reply.recycle();
 _data.recycle();
 }
}

这里也是跟我们之前addService类似,把binder写入到Parcel中去,因为App进程这里相当于是ApplicationThread它的服务端,因此这里写入的type为BINDER_TYPE_BINDER,而调用mRemote.transact则为调用BinderProxy的同名方法,我们知道最终会调用到IPCThreadStatetransact方法,从而调用binder驱动。

类似于getService的方法,AMS所在的system_server进程会收到BR_TRANSACTION命令,在其中解析数据知道调用的是TRANSACTION_attachApplication这个业务命令,进而使用readStrongInterface来获取到binder的代理对象BinderProxy。具体代码在IActivityManager.StubonTransact中,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
case TRANSACTION_attachApplication:
{
 android.app.IApplicationThread _arg0;
 _arg0 = android.app.IApplicationThread.Stub.asInterface(data.readStrongBinder());
 long _arg1;
 _arg1 = data.readLong();
 data.enforceNoDataAvail();
 this.attachApplication(_arg0, _arg1);
 reply.writeNoException();
 break;
}

到这样调用AMS服务端的attachApplication的时候就能使用IApplicationThread所提供的方法了。

Binder驱动中binder节点的处理

但是看到这里,有个问题,就是我们是匿名的binder,驱动怎么是处理的呢。这个需要去看一下binder驱动的源码,不过就不具体看调用流程了,直接看生成创建node和handle部分的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static int binder_translate_binder(struct flat_binder_object *fp,
 struct binder_transaction *t,
 struct binder_thread *thread)
{
 struct binder_node *node;
 struct binder_proc *proc = thread->proc;
 struct binder_proc *target_proc = t->to_proc;
 struct binder_ref_data rdata;
 int ret = 0;

 node = binder_get_node(proc, fp->binder);
 if (!node) {
 node = binder_new_node(proc, fp);
 if (!node)
 return -ENOMEM;
 }
 ...
 ret = binder_inc_ref_for_node(target_proc, node,
 fp->hdr.type == BINDER_TYPE_BINDER,
 &thread->todo, &rdata);
 ...
 if (fp->hdr.type == BINDER_TYPE_BINDER)
 fp->hdr.type = BINDER_TYPE_HANDLE;
 else
 fp->hdr.type = BINDER_TYPE_WEAK_HANDLE;
 fp->binder = 0;
 fp->handle = rdata.desc;
 fp->cookie = 0;

 ...
 done:
 binder_put_node(node);
 return ret;
}

可以看到对于通过Parcel调用经过binder驱动的binder对象,binder驱动都会给他们创建一个binder_node,并且为其设置handle,在传输到客户端的时候还会把type设置为BINDER_TYPE_HANDLE。 这样我们就对整给流程有所了解了,如果读者还想窥探更多的细节,则需要自行去阅读binder驱动的源码了。

开发者如何使用匿名服务

这上面介绍的部分是我们使用系统的服务来获取AIDL创建的服务,对于应用开发者来说有什么办法呢。我们可以通过AIDL+Service来实现,Android的四大组件之一的Service,它提供了通过bindService的方式来启动服务。在它的onBind中就可以返回IBinder,Android Framework会帮助我们调用操作代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void handleBindService(BindServiceData data) {
 CreateServiceData createData = mServicesData.get(data.token);
 Service s = mServices.get(data.token);
 if (s != null) {
 try {
 ....
 try {
 if (!data.rebind) {
 IBinder binder = s.onBind(data.intent);
 ActivityManager.getService().publishService(
 data.token, data.intent, binder);
 } else {
 ...
 }
 } catch (RemoteException ex) {
 throw ex.rethrowFromSystemServer();
 }
 } catch (Exception e) {
 ....
 }
 }
}

可以看到在ActivityThreadhandleBindService方法 中,我们在拿到Service所提供的IBinder之后,AMS会调用publishService,我们可以在ServiceConnection回调中拿到Binder的代理对象,之后就可以进行跨进程通讯了。

另外Android Framework还为我们提供了Messenger,其实现为Service+AIDL+Handler,让我们不用自己写AIDL,我们自己定义Service的时候使用Messenger和Handler就可以实现跨进程通信了。

总结

到此为止,我们已经分析了Binder服务管家ServiceManager的启动、使用ServiceManger添加服务和查找服务以及匿名服务的传递,在此过程中我们了解了进程是如何与Binder驱动交互的,以及binder调用过程中的会执行的方法等,我们对于Binder就有了一个全面的了解。在本文还简单介绍 了应用开发者如何使用Binder,有了这些基础,我们后面分析Android系统其他部分的代码就会更加容易了。当然关于Binder驱动的代码,BInder线程池的管理这两块还没有分析,读者感性确可以自行阅读,也可查看其他博主的文章。

如果你也对于Android系统源码感兴趣,欢迎与我交流。博文因为个人局限,也难免会出现差错,欢迎大家指正。

看完评论一下吧

Android Binder源码分析:添加服务和获取服务解析

通过ServiceManager添加服务和获取服务分别为addServicegetService,两者流程上其实是有一些类似的,其中我们可以看到binder通讯的全过程。为了让内容更有意义,添加服务选择从Java层的代码触发,获取服务则选择从Native层触发。

添加服务

我们以添加ActivityManagerService为例分析添加一个Service。首先先画个简单的流程图,介绍涉及到的类和调用的方法,不过步骤进行了省略,详细看后面的代码解析。

ServiceManagerProxy请求添加Service

其代码在ActivityManagerServicesetSystemProcess方法当中,具体调用如下:

1
2
ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true,
 DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);

具体实现如下:

1
2
3
4
5
6
7
8
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static void addService(String name, IBinder service, boolean allowIsolated,
 int dumpPriority) {
 try {
 getIServiceManager().addService(name, service, allowIsolated, dumpPriority);
 } catch (RemoteException e) {
 }
}

其中getIServiceManager的代码我们已经分析过了,我们之前拿到的IServiceManager的实例为ServiceManagerProxy,这里可以直接去看它的这个方法:

1
2
3
4
public void addService(String name, IBinder service, boolean allowIsolated, int dumpPriority)
 throws RemoteException {
 mServiceManager.addService(name, service, allowIsolated, dumpPriority);
}

这里调用了mServiceManager的同名方法,而这个成员变量的初始化如下:

1
mServiceManager = IServiceManager.Stub.asInterface(remote);

这里的remote就是我们之前构造函数传入的BinderProxy,而上面的函数后我们发获取到的对象则为IServiceMaanager.Stub.Proxy,我们可以看一下它的同名方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Override public void addService(java.lang.String name, android.os.IBinder service, boolean allowIsolated, int dumpPriority) throws android.os.RemoteException
{
 android.os.Parcel _data = android.os.Parcel.obtain();
 android.os.Parcel _reply = android.os.Parcel.obtain();
 try {
 _data.writeInterfaceToken(DESCRIPTOR);
 _data.writeString(name);
 _data.writeStrongBinder(service);
 _data.writeBoolean(allowIsolated);
 _data.writeInt(dumpPriority);
 boolean _status = mRemote.transact(Stub.TRANSACTION_addService, _data, _reply, 0);
 _reply.readException();
 }
 finally {
 _reply.recycle();
 _data.recycle();
 }
}

这里我们看到就是把设置的数据和binder写入到Parcel之后 调用transact。这里我们可以看一下Parcel首先是写入了InterfaceToken,也就是IServiceManager的描述符,其次才是其他内容。我们主要关注一下如何写入Binder的。其最终调用的方法在android_os_Parcel.cpp中,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)
{
 Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
 if (parcel != NULL) {
 const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object));
 if (err != NO_ERROR) {
 signalExceptionForError(env, clazz, err);
 }
 }
}

我们主要关注第5行,这里有一个ibinderForJavaObject,用于从javaobject中拿到binder的native对象,我们可以看一下其源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
sp<IBinder> ibinderForJavaObject(JNIEnv* env, jobject obj) {
 if (obj == NULL) return NULL;

 // 如果是Binder实例
 if (env->IsInstanceOf(obj, gBinderOffsets.mClass)) {
 JavaBBinderHolder* jbh = (JavaBBinderHolder*)
 env->GetLongField(obj, gBinderOffsets.mObject);
 return jbh->get(env, obj);
 }
 // 如果是BinderProxy实例
 if (env->IsInstanceOf(obj, gBinderProxyOffsets.mClass)) {
 return getBPNativeData(env, obj)->mObject;
 }
 return NULL;
}

BinderProxyNativeData* getBPNativeData(JNIEnv* env, jobject obj) {
 return (BinderProxyNativeData *) env->GetLongField(obj, gBinderProxyOffsets.mNativeData);
}

这个地方会判断我们的传过来的javaobject是Binder的实例还是BinderProxy的实例,前者对应Binder的服务端,后者对应的是客户端,我们刚刚传过来的AMS则是服务端。这里是从javaobject拿到mObject成员变量,对应native的类JavaBBinderHolder,最后调用它的get方法拿到JavaBinder对象。此处算是完成了我们Java层的Binder在Native层的对应对象的获取。现在就可以看看ParcelwriteStrongBinder方法了:

1
2
3
status_t Parcel::writeStrongBinder(const sp<IBinder>& val) {
 return flattenBinder(val);
}

其中又调用了flattenBinder,这个方法比较长,我们先一点点的贴代码:

1
2
3
4
5
6
7
BBinder* local = nullptr;
if (binder) local = binder->localBinder();
if (local) local->setParceled();

if (isForRpc()) {
...
}

这里binder是我们刚刚的拿到的JavaBBinder,它的localBinder()实现如下:

1
2
3
BBinder* BBinder::localBinder() {
 return this;
}

也就是说返回了自己。另外这里我们不是RPC,所以其中的代码我们不需要关注,继续看后面的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
flat_binder_object obj;
if (binder != nullptr) {
 if (!local) {
 ...//如果我们传入的不是BBinder,而是BpBinder执行这里的逻辑,省略
 } else {
 int policy = local->getMinSchedulerPolicy();
 int priority = local->getMinSchedulerPriority();

 if (policy != 0 || priority != 0) {
 // override value, since it is set explicitly
 schedBits = schedPolicyMask(policy, priority);
 }
 obj.flags = FLAT_BINDER_FLAG_ACCEPTS_FDS;
 ....
 obj.hdr.type = BINDER_TYPE_BINDER;
 obj.binder = reinterpret_cast<uintptr_t>(local->getWeakRefs());
 obj.cookie = reinterpret_cast<uintptr_t>(local);
 }
} else {
 ...
}
obj.flags |= schedBits;

上面的代码主要是将binder的一些参数拍平放到flat_binder_object当中。其中binder是放置到cookie字段,binder的弱引用放到了binder字段。

1
2
3
4
status_t status = writeObject(obj, false);
if (status != OK) return status;

return finishFlattenBinder(binder);

这里才开始真正的把数据写入 ,可以先看看这个writeObject方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData)
{
 const bool enoughData = (mDataPos+sizeof(val)) <= mDataCapacity;
 const bool enoughObjects = mObjectsSize < mObjectsCapacity;
 if (enoughData && enoughObjects) {
restart_write:
 *reinterpret_cast<flat_binder_object*>(mData+mDataPos) = val; //把数据写如内存
 ...
 // Need to write meta-data?
 if (nullMetaData || val.binder != 0) {
 mObjects[mObjectsSize] = mDataPos;
 acquire_object(ProcessState::self(), val, this);
 mObjectsSize++;
 }
 return finishWrite(sizeof(flat_binder_object)); //调整dataPos和当前DateSize
 }
 ...
 goto restart_write;
}

上面主要就是把binder写入内存当中,其他的则是处理内存不足的情况,有申请内存的代码,这里我们无须关注。可以在看一下前面的finishFlattenBinder方法:

1
2
3
4
5
status_t Parcel::finishFlattenBinder(const sp<IBinder>& binder) {
 internal::Stability::tryMarkCompilationUnit(binder.get());
 int16_t rep = internal::Stability::getRepr(binder.get());
 return writeInt32(rep);
}

这个方法主要为binder设置Repr,并且把值也写入到Parcel当中去,默认值为Level::SYSTEM,我们不再深入看其代码。

到这里大概就看完了Parcel写数据的代码了。可以看看transact方法,这里的mRemoteBinderProxy,它的transact方法中主要调用了一下代码:

1
2
3
4
5
final boolean result = transactNative(code, data, reply, flags);
if (reply != null && !warnOnBlocking) {
 reply.addFlags(Parcel.FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT);
}
return result;

它的native实现在android_util_Binder.cpp中,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
 jint code, jobject dataObj, jobject replyObj, jint flags)
{
 Parcel* data = parcelForJavaObject(env, dataObj); //从java层的Parcel对象获取native层的Parcel对象
 ...
 Parcel* reply = parcelForJavaObject(env, replyObj);
 ...
 IBinder* target = getBPNativeData(env, obj)->mObject.get(); //获取native层的BinderProxy对象
 if (target == NULL) {
 ...
 return JNI_FALSE;
 }

 status_t err = target->transact(code, *data, reply, flags);
 ...
 return JNI_FALSE;
}

上面主要就 是获取native层的Parcel对象和Binder对象,并且调用binder的transact方法。这里的Binder对象是什么呢,回顾之前分析的javaObjectForIBinder方法,可知此处拿到的应该是BpBinder对象。我们就可以看它的代码了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
status_t BpBinder::transact(
 uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
 // 只有binder或者的时候才能执行
 if (mAlive) {
 bool privateVendor = flags & FLAG_PRIVATE_VENDOR;
 // 用户层的flag移除
 flags = flags & ~FLAG_PRIVATE_VENDOR;

 if (code >= FIRST_CALL_TRANSACTION && code <= LAST_CALL_TRANSACTION) {
 ... //Stability 相等判断,此处略过
 }

 status_t status;
 if (CC_UNLIKELY(isRpcBinder())) {
 ...
 } else {
 status = IPCThreadState::self()->transact(binderHandle(), code, data, reply, flags);
 }
 ....
 if (status == DEAD_OBJECT) mAlive = 0;

 return status;
 }

 return DEAD_OBJECT;
}

因为ServiceManager的id为0,此处binderHandle()拿到的值应为0。此处主要也是调用了IPCThreadStatetransact方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
status_t IPCThreadState::transact(int32_t handle,
 uint32_t code, const Parcel& data,
 Parcel* reply, uint32_t flags)
{
 status_t err;

 flags |= TF_ACCEPT_FDS;
 err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, nullptr);

 if (err != NO_ERROR) {
 if (reply) reply->setError(err);
 return (mLastError = err);
 }

 if ((flags & TF_ONE_WAY) == 0) {
 if (reply) {
 err = waitForResponse(reply);
 } else {
 Parcel fakeReply;
 err = waitForResponse(&fakeReply);
 }
 } else {
 err = waitForResponse(nullptr, nullptr);
 }

 return err;
}

这里主要调用了两个方法,分别是writeTransactionDatawaitForResponse,我们分别看一下。首先是writeTransactionData,它的第一个参数为BC_TRANSACTION,这是用于与Binder驱动交互的命令,除了这个之外还有其他一些,可以在binder.h当中找到。现在可以看writeTransactionData的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
 int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
 binder_transaction_data tr;

 tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */
 tr.target.handle = handle;
 tr.code = code;
 tr.flags = binderFlags;
 tr.cookie = 0;
 tr.sender_pid = 0;
 tr.sender_euid = 0;

 const status_t err = data.errorCheck();
 if (err == NO_ERROR) {
 tr.data_size = data.ipcDataSize();
 tr.data.ptr.buffer = data.ipcData();
 tr.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
 tr.data.ptr.offsets = data.ipcObjects();
 } else if (statusBuffer) {
 ...
 } else {
 return (mLastError = err);
 }

 mOut.writeInt32(cmd);
 mOut.write(&tr, sizeof(tr));

 return NO_ERROR;
}

这个方法中所做的事情为,把我们传入的data和code以及要调用的binder的id等都放到binder_transaction_data中去,同时又把这个tr和调用binder驱动的命令BC_TRANSACTION一起写入到mOut当中去,这个mOut也是一个Parcel对象。到这里,数据都写完了,但是binder驱动在那里读取处理这个数据呢,我们继续看waitForResponse,前面我们因为有传reply过来,因此会调用到第17行的waitForResponse方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
 uint32_t cmd;
 int32_t err;

 while (1) {
 if ((err=talkWithDriver()) < NO_ERROR) break;
 err = mIn.errorCheck();
 if (err < NO_ERROR) break;
 if (mIn.dataAvail() == 0) continue;

 cmd = (uint32_t)mIn.readInt32();

 switch (cmd) {
 case BR_ONEWAY_SPAM_SUSPECT:
 [[fallthrough]];
 ......
 case BR_ACQUIRE_RESULT:
 {
 const int32_t result = mIn.readInt32();
 if (!acquireResult) continue;
 *acquireResult = result ? NO_ERROR : INVALID_OPERATION;
 }
 goto finish;

 case BR_REPLY:
 {
 binder_transaction_data tr;
 err = mIn.read(&tr, sizeof(tr));
 if (err != NO_ERROR) goto finish;

 if (reply) {
 if ((tr.flags & TF_STATUS_CODE) == 0) {
 reply->ipcSetDataReference(
 reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
 tr.data_size,
 reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
 tr.offsets_size/sizeof(binder_size_t),
 freeBuffer);
 } else {
 ....
 } else {
 ...
 continue;
 }
 }
 goto finish;

 default:
 err = executeCommand(cmd);
 if (err != NO_ERROR) goto finish;
 break;
 }
 }

finish:
 if (err != NO_ERROR) {
 if (acquireResult) *acquireResult = err;
 if (reply) reply->setError(err);
 mLastError = err;
 }

 return err;
}

talkWithDriver分析

这里开启了一个while的无限循环,首先调用talkWithDriver,看名字就知道是与Binder驱动进行交互,这里首先会看看这个方法有没有报错,没有报错又会检查mIn是否有报错。我们前面看到过mOut,这里又有mIn,它们是用来做什么的呢,我们看一下talkWithDriver,可以发现一些东西:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
 if (mProcess->mDriverFD < 0) {
 return -EBADF;
 }

 binder_write_read bwr;

 const bool needRead = mIn.dataPosition() >= mIn.dataSize();
 const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;

 bwr.write_size = outAvail;
 bwr.write_buffer = (uintptr_t)mOut.data();

 // This is what we'll read.
 if (doReceive && needRead) {
 bwr.read_size = mIn.dataCapacity();
 bwr.read_buffer = (uintptr_t)mIn.data();
 } else {
 bwr.read_size = 0;
 bwr.read_buffer = 0;
 }

 // 无数据需要读写,直接返回
 if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;

 bwr.write_consumed = 0;
 bwr.read_consumed = 0;
 status_t err;
 do {

#if defined(__ANDROID__)
 if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
 err = NO_ERROR;
 else
 err = -errno;
#else
 err = INVALID_OPERATION;
#endif
 if (mProcess->mDriverFD < 0) {
 err = -EBADF;
 }
 } while (err == -EINTR);


 if (err >= NO_ERROR) {
 if (bwr.write_consumed > 0) {
 if (bwr.write_consumed < mOut.dataSize())
 ...
 else {
 mOut.setDataSize(0);
 processPostWriteDerefs();
 }
 }
 if (bwr.read_consumed > 0) {
 mIn.setDataSize(bwr.read_consumed);
 mIn.setDataPosition(0);
 }
 ...
 return NO_ERROR;
 }

 return err;
}

这个方法传参的默认值为true,也就是需要接受结果。在这里我们看到有一个新的数据结构binder_write_read,此处会把mOut中的数据指针写入到它的write_buffer当中,同时把mIn的数据指针写入到read_buffer中,此处的写指的是向binder驱动中写。随后我们看到是在一个循环当中调用系统调用ioctl来与binder驱动进行交互,这里使用循环的原因是,当我们调用这个系统调用的时候可能会遇到遇到中断,我们之前的调用未能执行,因此需要一直等待到执行为止。

到这里我们就分析完了添加Service调用端的所有代码,此时我们需要看一下ServiceManager服务端与Binder进行交互的代码。

ServiceManager服务端处理添加Service

我们之前分析ServiceManager启动的时候,知道最后会注册Looper的监听,当Binder驱动有消息的时候,BinderCallbak的handleEvent就会执行去处理,那么当我们在客户端请求添加Binder服务的时候,这里也会执行。这个方法中执行了如下代码:

1
IPCThreadState::self()->handlePolledCommands();

这里我们可以看一下详细的源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
status_t IPCThreadState::handlePolledCommands()
{
 status_t result;
 do {
 //读取缓存数据知道处理完成
 result = getAndExecuteCommand();
 } while (mIn.dataPosition() < mIn.dataSize());
 //减少binder的引用数量,此处也会和驱动交互
 processPendingDerefs();
 //若有为执行的命令,全部执行
 flushCommands();
 return result;
}

此处我们主要关注getAndExecuteCommand方法,后面都已经加了注释,此处不需要详细关注。getAndExecuteCommand方法当中也是首先调用talkWithDriver方法,这个方法前面分析过了,不再分析,这样执行完之后,mIn当中就会拿到客户端请求传输过来的数据了,之后就从数据中拿取命令和数据进行执行,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
size_t IN = mIn.dataAvail();
if (IN < sizeof(int32_t)) return result;
cmd = mIn.readInt32(); //读取命令
pthread_mutex_lock(&mProcess->mThreadCountLock); //为了增加线程计数上锁
mProcess->mExecutingThreadsCount++;
if (mProcess->mExecutingThreadsCount >= mProcess->mMaxThreads &&
 mProcess->mStarvationStartTimeMs == 0) {
 mProcess->mStarvationStartTimeMs = uptimeMillis();
}
pthread_mutex_unlock(&mProcess->mThreadCountLock);

result = executeCommand(cmd); //执行命令

pthread_mutex_lock(&mProcess->mThreadCountLock);
mProcess->mExecutingThreadsCount--;
if (mProcess->mWaitingForThreads > 0) {
 pthread_cond_broadcast(&mProcess->mThreadCountDecrement);
}
pthread_mutex_unlock(&mProcess->mThreadCountLock);

代码很多,但是大多都是为了给binder线程计数增减的。我们主要去看一下executeCommand中的代码,该方法中代码很多,而我们在客户端执行的是BC_TRANSACTION,因此这里应该收到的是BR_TRANSACTION命令,因此只需要看该分支的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
BBinder* obj;
RefBase::weakref_type* refs;
status_t result = NO_ERROR;

switch ((uint32_t)cmd) {
 ...
 case BR_TRANSACTION:
 binder_transaction_data_secctx tr_secctx;
 binder_transaction_data& tr = tr_secctx.transaction_data;
 result = mIn.read(&tr, sizeof(tr)); //读取binder携带过来的数据到tr中

 Parcel buffer;
 //将数据的引用放入Parcel当中
 buffer.ipcSetDataReference(
 reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
 tr.data_size,
 reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
 tr.offsets_size/sizeof(binder_size_t), freeBuffer);
 //设置调用这的uid,pid,flag等信息
 mCallingPid = tr.sender_pid;
 mCallingSid = reinterpret_cast<const char*>(tr_secctx.secctx);
 mCallingUid = tr.sender_euid;
 mLastTransactionBinderFlags = tr.flags;
 if (tr.target.ptr) { //ServiceManager的binder无ptr
 //非serviceManager的binder,tr.cookie为本地的BBinder对象指针
 if (reinterpret_cast<RefBase::weakref_type*>(
 tr.target.ptr)->attemptIncStrong(this)) {
 error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,
 &reply, tr.flags);
 reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);
 } else {
 error = UNKNOWN_TRANSACTION;
 }

 } else {
 //ServiceManager使用the_context_object这个BBinder对象。
 error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);
 }
 if ((tr.flags & TF_ONE_WAY) == 0) {
 if (error < NO_ERROR) reply.setError(error);

 constexpr uint32_t kForwardReplyFlags = TF_CLEAR_BUF;
 sendReply(reply, (tr.flags & kForwardReplyFlags)); //写入回复
 } else {
 ...
 }
 ...
}

return result;

上面的代码已经做了省略,逻辑就是首先从mIn这块内存中拿到数据,并且放Parcel中,随后把uid,pid相关的属性设置到当前进程。之后是获取BBinder对象去执行transact方法,对于普通的binder,对于普通的binder,会ptr这个字段,并且tr.cookie就是本地的BBinder对象指针,而对于ServiceManager,这里就会使用在启动ServiceManager时候调用setTheContextObject所设置的BBinder对象,也就是服务端的ServiceManager。这里transact执行完成之后会调用sendReply将执行结果通过binder驱动传递回binder调用端,从而完成整个流程。这里先看transact,分析完再来分析sendReply

transact方法在BBinder类当中,在其中会调用onTransact方法,而到ServiceManager,它的onTransact的实现在BnServiceManager当中,这个类则是通过AIDL工具生成的。因为没有源码,根据经验我们这边可以知道它会调用ServiceManageraddService方法,而其中最重要的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
mNameToService[name] = Service {
 .binder = binder,
 .allowIsolated = allowIsolated,
 .dumpPriority = dumpPriority,
 .debugPid = ctx.debugPid,
};

auto it = mNameToRegistrationCallback.find(name);
if (it != mNameToRegistrationCallback.end()) {
 for (const sp<IServiceCallback>& cb : it->second) {
 mNameToService[name].guaranteeClient = true;
 // permission checked in registerForNotifications
 cb->onRegistration(name, binder);
 }
}

看代码可知道,这里把Binder放到Service结构体当中,随后放入mNameToService当中,mNameToService是一个map。而mNameToRegistrationCallback中为服务注册的回调,当注册完成之后会调用它的onRegistration方法。

前面我们还有一个sendReply方法我们还未分析,这里再看一下:

1
2
3
4
5
6
7
8
9
status_t IPCThreadState::sendReply(const Parcel& reply, uint32_t flags)
{
 status_t err;
 status_t statusBuffer;
 err = writeTransactionData(BC_REPLY, flags, -1, 0, reply, &statusBuffer);
 if (err < NO_ERROR) return err;

 return waitForResponse(nullptr, nullptr);
}

writeTransactionData当中就是把我们的reply打包成为binder_transaction_data写入mOut当中,这里的命令为BC_REPLY,执行完之后调用waitForResponse,其中会调用talkWithDriver来回应,之后便结束了服务端的相应。客户端随后可以读取客户端的mIn数据可以获取reply的数据。到这里就分析完了Service注册的流程。

获取服务(getService)分析

之前分析getIServiceManageraddService我们都是从java层的代码出发去往后走分析代码,而getService其实有一些地方跟他们是类似的,为了减少重复流程的分析,这里从Native层的使用场景出发。这里以获取ICameraService为例。

获取defaultServiceManager

我们的起点在frameworks/av/camera/ndk/impl/ACameraManager.cpp当中,调用代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const char* kCameraServiceName = "media.camera";
....

sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder;
do {
 binder = sm->getService(String16(kCameraServiceName));
 if (binder != nullptr) {
 break;
 }
 usleep(kCameraServicePollDelay);
} while(true);

这里使用defaultServiceManager来拿到ServiceManager,其源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//frameworks/native/libs/binder/IServiceManager.cpp
sp<IServiceManager> defaultServiceManager()
{
 std::call_once(gSmOnce, []() {
 sp<AidlServiceManager> sm = nullptr;
 while (sm == nullptr) {
 sm = interface_cast<AidlServiceManager>(ProcessState::self()->getContextObject(nullptr));
 if (sm == nullptr) {
 sleep(1);
 }
 }

 gDefaultServiceManager = sp<ServiceManagerShim>::make(sm);
 });

 return gDefaultServiceManager;
}

这个代码跟我们之前java层的代码比较类似,也是先拿ContentObject,而ServiceManagerShim相当于是native层的ServiceManager的代理。native层的代码因为不需要把对象转成Java的消耗,代码其实更加简单一点。这里我们拿到了ServiceManagerShim,就可以继续去看它的getService方法了。

请求getService

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
sp<IBinder> ServiceManagerShim::getService(const String16& name) const
{
 static bool gSystemBootCompleted = false;

 sp<IBinder> svc = checkService(name);
 if (svc != nullptr) return svc;

 const bool isVendorService =
 strcmp(ProcessState::self()->getDriverName().c_str(), "/dev/vndbinder") == 0;
 constexpr int64_t timeout = 5000;
 int64_t startTime = uptimeMillis();
 // 如果是Vendor的服务,不能够访问系统的属性
 if (!gSystemBootCompleted && !isVendorService) {
#ifdef __ANDROID__
 char bootCompleted[PROPERTY_VALUE_MAX];
 property_get("sys.boot_completed", bootCompleted, "0");
 gSystemBootCompleted = strcmp(bootCompleted, "1") == 0 ? true : false;
#else
 gSystemBootCompleted = true;
#endif
 }
 // 如果拿不到binder service就等待,系统服务和vendor时间有区分,直到超时才停止
 const useconds_t sleepTime = gSystemBootCompleted ? 1000 : 100;
 int n = 0;
 while (uptimeMillis() - startTime < timeout) {
 n++;
 usleep(1000*sleepTime);

 sp<IBinder> svc = checkService(name);
 if (svc != nullptr) {
 return svc;
 }
 }
 return nullptr;
}

这里就是调用checkService去获取Service,源码如下:

1
2
3
4
5
6
7
8
sp<IBinder> ServiceManagerShim::checkService(const String16& name) const
{
 sp<IBinder> ret;
 if (!mTheRealServiceManager->checkService(String8(name).c_str(), &ret).isOk()) {
 return nullptr;
 }
 return ret;
}

这里我们调用了mTheRealServiceManagercheckService方法,这个变量的实例为ServiceManager的BpBinder子类,也是由AIDL生成,其代码如下:

1
2
3
4
Parcel data, reply;
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor()); data.writeString16(name);
remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply);
return reply.readStrongBinder();

这里跟之前分析addService的部分类似,只有最后多了一个readStrongBinder,addServicewriteStrongBinder到data,这里是读取binder调用返回的数据。后续流程也跟addService类似,这里就不再分析了。我们更关注这个binder我们是怎么拿到的,因此需要看三个地方的代码,一个是ServiceManger拿到binder并且写入到驱动给我们的过程,第二个地方是IPCThreadState当中接收数据的处理,最后就是通过readStrongBinder拿到binder的处理了。

客户端请求获取Binder服务的流程大概如下图所示:

sequenceDiagram
ServiceManagerShim->>ServiceManagerShim: defaultServiceManager()
ServiceManagerShim->>ServiceManagerShim:getService()
ServiceManagerShim->>ServiceManagerShim:checkService()
ServiceManagerShim->>+BpServiceManager:checkService()
BpServiceManager->>+BpBinder: transact()
BpBinder->>+IPCThreadState: transact()
IPCThreadState->>IPCThreadState: writeTransactionData()
IPCThreadState->>IPCThreadState: waitForResponse()
IPCThreadState->>IPCThreadState: talkWithDriver()
IPCThreadState->>+BinderDriver: ioctl(BC_TRANSACTION)
BinderDriver-->>-IPCThreadState: reply:BR_REPLY
IPCThreadState-->>-BpBinder: return resut
BpBinder-->>-BpServiceManager: return result
BpServiceManager->>BpServiceManager: readStrongBinder()
BpServiceManager-->>-ServiceManagerShim: return binder

ServiceManager服务端getService

前面分析addService我们已经知道服务端调用路径是BBinder.transcat–>BnServiceManager.onTransact–>ServiceManger.addService,这里的服务端也是类似,具体可以看下面的流程图。

sequenceDiagram
BinderDriver-->IPCThreadState:handlePolledCommands
loop mIn.dataPosition < mIn.dataSize (当输入数据未处理完)
IPCThreadState->>+IPCThreadState: getAndExecuteCommand
IPCThreadState->>IPCThreadState: executeCommand: BR_TRANSACTION
IPCThreadState->>BBinder: transact()
BBinder->>+BnServiceManager: onTransact()
BnServiceManager->>+ServiceManager: getService()
ServiceManager-->>-BnServiceManager: return Binder
BnServiceManager->>BnServiceManager: writeStrongBinder()
BnServiceManager-->-BBinder: return reply Parcel
BBinder-->>IPCThreadState: return reply
IPCThreadState->>+IPCThreadState: sendReply
IPCThreadState->>IPCThreadState:writeTransactionData
IPCThreadState->>+IPCThreadState:waitForResponse(null, null)
IPCThreadState->>IPCThreadState: talkWithDriver
IPCThreadState->>BinderDriver: ioctl:BC_REPLY
IPCThreadState-->>-IPCThreadState::
IPCThreadState-->-IPCThreadState: finishSendReply
end

我们就省略与Binder交互的许多代码,可以直接去看getService的代码了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Status ServiceManager::getService(const std::string& name, sp<IBinder>* outBinder) {
 *outBinder = tryGetService(name, true);
 return Status::ok();
}

sp<IBinder> ServiceManager::tryGetService(const std::string& name, bool startIfNotFound) {
 auto ctx = mAccess->getCallingContext();

 sp<IBinder> out;
 Service* service = nullptr;
 if (auto it = mNameToService.find(name); it != mNameToService.end()) {
 service = &(it->second);

 if (!service->allowIsolated) { //是否允许多用户环境运行
 uid_t appid = multiuser_get_app_id(ctx.uid);
 bool isIsolated = appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END;

 if (isIsolated) {
 return nullptr;
 }
 }
 out = service->binder;
 }

 if (!mAccess->canFind(ctx, name)) { //SELinux 权限检查
 return nullptr;
 }

 if (!out && startIfNotFound) {
 tryStartService(name);
 }

 return out;
}

ServiceManger中获取Binder就是从我们之前添加Service的时候的那个ServiceMap中查找,当查找后做一些权限检查,当找不到的情况下,因为我们传如的startIfNotFound,因此会调用tryStartService去启动对应的Service,其代码如下:

1
2
3
4
5
6
void ServiceManager::tryStartService(const std::string& name) {
 std::thread([=] {
 if (!base::SetProperty("ctl.interface_start", "aidl/" + name)) {
 ...
 }).detach();
}

代码很简单,就是启动了一个线程,其中设置系统的properties,系统便会尝试启动这个服务,具体我们这里就不分析了。

IPCThreadState接收数据处理

在服务端发送数据时候会调用binder执行BC_REPLY,而客户端后收到BR_REPLY命令,也就是会执行waitForResponse中的如下部分:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
....
case BR_REPLY:
 {
 binder_transaction_data tr;
 err = mIn.read(&tr, sizeof(tr));
 ALOG_ASSERT(err == NO_ERROR, "Not enough command data for brREPLY");
 if (err != NO_ERROR) goto finish;

 if (reply) {
 if ((tr.flags & TF_STATUS_CODE) == 0) {
 reply->ipcSetDataReference(
 reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
 tr.data_size,
 reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
 tr.offsets_size/sizeof(binder_size_t),
 freeBuffer);
 } else {
 ...
 }
 } else {
 ...
 continue;
 }
 }
 goto finish;
.....
}

也就是执行上面的ipcSetDataReference,可以看一下其源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize,
 const binder_size_t* objects, size_t objectsCount, release_func relFunc)
{

 freeData(); //初始化Parcel状态

 mData = const_cast<uint8_t*>(data);
 mDataSize = mDataCapacity = dataSize;
 mObjects = const_cast<binder_size_t*>(objects);
 mObjectsSize = mObjectsCapacity = objectsCount;
 mOwner = relFunc;

 binder_size_t minOffset = 0;
 for (size_t i = 0; i < mObjectsSize; i++) {
 binder_size_t offset = mObjects[i];
 if (offset < minOffset) {

 mObjectsSize = 0;
 break;
 }
 const flat_binder_object* flat
 = reinterpret_cast<const flat_binder_object*>(mData + offset);
 uint32_t type = flat->hdr.type;
 if (!(type == BINDER_TYPE_BINDER || type == BINDER_TYPE_HANDLE ||
 type == BINDER_TYPE_FD)) {
 ....
 break;
 }
 minOffset = offset + sizeof(flat_binder_object);
 }
 scanForFds();
}

代码比较简单,主要就是把data传入Parcel中,但是除此之外我们需要关注一下传入的relFunc,传入的方法为freeBuffer,此方法的执行会在下一次调用freeData的时候执行,它的实现如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
void IPCThreadState::freeBuffer(Parcel* parcel, const uint8_t* data,
 size_t /*dataSize*/,
 const binder_size_t* /*objects*/,
 size_t /*objectsSize*/)
{
 ALOG_ASSERT(data != NULL, "Called with NULL data");
 if (parcel != nullptr) parcel->closeFileDescriptors();
 IPCThreadState* state = self();
 state->mOut.writeInt32(BC_FREE_BUFFER);
 state->mOut.writePointer((uintptr_t)data);
 state->flushIfNeeded();
}

看到这里,我们知道会调用binder发送这个BC_FREE_BUFFER命令,这样驱动内部会清理内存,这样就完成了Parcel和内存缓冲区的空间清理。

readStrongBinder

readStrongBinder和我们之前看过的writeStrongBinder应该是一个相反的过程,直接看代码:

1
2
3
4
5
status_t Parcel::readStrongBinder(sp<IBinder>* val) const
{
 status_t status = readNullableStrongBinder(val);
 return status;
}

上面的代码会调用readNullableStrongBinder,而其内部又会调用unflattenBinder,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
status_t Parcel::unflattenBinder(sp<IBinder>* out) const
{
 if (isForRpc()) {
 ...
 return finishUnflattenBinder(binder, out);
 }

 const flat_binder_object* flat = readObject(false);

 if (flat) {
 switch (flat->hdr.type) {
 case BINDER_TYPE_BINDER: {
 sp<IBinder> binder =
 sp<IBinder>::fromExisting(reinterpret_cast<IBinder*>(flat->cookie));
 return finishUnflattenBinder(binder, out);
 }
 case BINDER_TYPE_HANDLE: {
 sp<IBinder> binder =
 ProcessState::self()->getStrongProxyForHandle(flat->handle);
 return finishUnflattenBinder(binder, out);
 }
 }
 }
 return BAD_TYPE;
}

其中readObject为从Parcel中读取flat_binder_object对象,当请求的进程和服务在同一个进程时候,这里的type就是BINDER_TYPE_BINDER,当请求的进程和服务不在同一个进程则为BINDER_TYPE_HANDLE,因此我们这里是BINDER_TYPE_HANDLEgetStrongProxyForHandle我们之前在分析获取ServiceManager的时候已经分析过了,只不过那个地方handle为固定的0,而这里则是从驱动中传过来的值,最后我们会拿到一个BpBinder,也就完成了查找的过程。

分析完添加服务,查找服务,一直之前介绍的启动ServiceManager和获取ServiceManager基本上就把Binder除了驱动部分的东西都覆盖了。还剩下应用层应该如何使用Binder以及,我们的匿名binder是怎么查找的,这个留待下次在写。

如果你也对于Android系统源码感兴趣,欢迎与我交流。

看完评论一下吧

Android源码分析:ServiceManager启动代码解析

之前已经分析过获取ServiceManager了,不过那是在使用端,在分析使用ServiceManager去获取服务或者添加服务的时候发现,我使用的Android Studio for Platform默认没有把ServiceManager的源码导入。并且同时我们不知道ServiceManager的服务端是怎么启动,怎么响应的,因此决定还是需要分析一下这块的代码。

首先简单画了一下启动的流程,如下图所示(Entry表示我们的调用入口,也就是后面所说的main函数):

sequenceDiagram
autonumber
Entry->>+ProcessState: initWithDriver
ProcessState->>ProcessState: init
ProcessState-->>-Entry: return ProcessState
Entry->>ServiceManager: new
Entry->>ServiceManager: addService(this)
Entry ->>IPCThreadState: setTheContentObject
Entry->>ProcessState: becomeContextManager
Entry->>Looper:prepare
Entry ->>Looper:addFd
loop 永不退出
Entry ->>Looper:pollAll
end

ServiceManager的启动是在系统启动的时候进行启动的,它的启动是使用Linux系统的服务启动方式进行配置,配置文件在servicemanager.rc当中,配置如下:

service servicemanager /system/bin/servicemanager
class core animation
user system
group system readproc
critical
onrestart restart apexd
...
task_profiles ServiceCapacityLow
shutdown critical

具体系统如何调用这个Service的我们这里不必关心,我们可以直接来看ServiceManager启动相关的代码,代码在frameworks/native/cmds/servicemanager目录下面。启动的逻辑在main.cppmain方法中,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main(int argc, char** argv) {
 ...
 const char* driver = argc == 2 ? argv[1] : "/dev/binder";
 sp<ProcessState> ps = ProcessState::initWithDriver(driver);
 ps->setThreadPoolMaxThreadCount(0); //设置最大线程数,因为serviceManager不使用`startThreadPool`启动线程池,因此设置为0
 ps->setCallRestriction(ProcessState::CallRestriction::FATAL_IF_NOT_ONEWAY);
 sp<ServiceManager> manager = sp<ServiceManager>::make(std::make_unique<Access>());
 if (!manager->addService("manager", manager, false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()) {
 LOG(ERROR) << "Could not self register servicemanager";
 }

 IPCThreadState::self()->setTheContextObject(manager);
 ps->becomeContextManager();
 sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/);
 BinderCallback::setupTo(looper);
 ClientCallbackCallback::setupTo(looper, manager);
 while(true) {
 looper->pollAll(-1);
 }
 // 应该不会走到,除非发生了错误
 return EXIT_FAILURE;
}

代码如上,数量不多,并且其中看到了不少熟悉的类名,首先是使用ProcessState类去调用它的initWithDriver,这里最终也会走到init方法,做的事情也是创建ProcessState实例,并且打开Binder驱动,获取驱动的FD,可以参考前面的文章。

第7行代码,我们可以看到,创建了一个ServiceManager对象,这个对象与我们之前分析的ServiceManager是不同的,它是ServiceManager的Binder服务端,这个代码也跟我们的初始化代码在同一个目录。拿到这个manager后面做的第一件事情就是调用addService把自己也加进去,addService的代码后面再来分析。

再看12行代码,setTheContextObject处传入了manager,这里IPCThreadState初始化代码之前也已经分析过,此处略过,这个方法也只是把manager作为它的成员放入,暂时略过,直接看后面的becomeContextManager源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
bool ProcessState::becomeContextManager()
{
 AutoMutex _l(mLock);
 flat_binder_object obj {
 .flags = FLAT_BINDER_FLAG_TXN_SECURITY_CTX,
 };
 //与Binder交互,发送命令BINDER_SET_CONTEXT_MGR_EXT,让当前进程成为Binder的上下文管理者
 int result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR_EXT, &obj);

 if (result != 0) {
 android_errorWriteLog(0x534e4554, "121035042");

 int unused = 0;
 // 执行失败,则重置该参数
 result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR, &unused);
 }
 ...
 return result == 0;
}

上面的代码就是告诉binder驱动,当前的进程要成为binder的上下文管理者,驱动内部作何处理我们便不再深究。

继续看我们前面的代码,就出现了熟悉的Looper,这个Java层的Looper用法和功能都一样,preapre之后又在死循环中调用pollAll,从而我们的代码就会永远在这里执行而不会退出。但是Looper在这里有什么用处,我们需要看看前面的代码。首先是15行的调用,其源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
static sp<BinderCallback> setupTo(const sp<Looper>& looper) {
 sp<BinderCallback> cb = sp<BinderCallback>::make();

 int binder_fd = -1;
 IPCThreadState::self()->setupPolling(&binder_fd);

 int ret = looper->addFd(binder_fd,
 Looper::POLL_CALLBACK,
 Looper::EVENT_INPUT,
 cb,
 nullptr /*data*/);
 return cb;
}

第5行代码调用的setupPolling代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
status_t IPCThreadState::setupPolling(int* fd)
{
 if (mProcess->mDriverFD < 0) { //当前驱动没有成功打开时候,直接返回报错
 return -EBADF;
 }

 mOut.writeInt32(BC_ENTER_LOOPER); //写入Binder调用命令
 flushCommands();
 *fd = mProcess->mDriverFD; //保存Binder驱动的文件描述符到fd当中
 return 0;
}

void IPCThreadState::flushCommands()
{
 if (mProcess->mDriverFD < 0)
 return;
 talkWithDriver(false); //与驱动进行交互
 if (mOut.dataSize() > 0) { //二次确认,未成功则继续交互
 talkWithDriver(false);
 }
}

可以看到上面的代码主要是与Binder驱动交互,并且执行命令进入Binder的循环,且拿到binder驱动的文件描述符,其中talkWithDriver为与binder交互的具体流程,后续在介绍其代码。

拿到binder驱动的文件描述符后执行Looper的addFd方法,最终执行的方法代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//Looper.cpp
int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
 ...

 { // 此代码段上锁
 AutoMutex _l(mLock);

 if (mNextRequestSeq == WAKE_EVENT_FD_SEQ) mNextRequestSeq++;
 const SequenceNumber seq = mNextRequestSeq++;

 Request request;
 request.fd = fd;
 request.ident = ident;
 request.events = events;
 request.callback = callback;
 request.data = data;

 epoll_event eventItem = createEpollEvent(request.getEpollEvents(), seq);
 auto seq_it = mSequenceNumberByFd.find(fd);
 if (seq_it == mSequenceNumberByFd.end()) { //列表中不存在该fd
 int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, fd, &eventItem);
 if (epollResult < 0) {
 ...
 return -1;
 }
 mRequests.emplace(seq, request);
 mSequenceNumberByFd.emplace(fd, seq);
 } else {
 int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_MOD, fd, &eventItem);
 if (epollResult < 0) {
 ...
 return -1;
 }
 const SequenceNumber oldSeq = seq_it->second;
 mRequests.erase(oldSeq);
 mRequests.emplace(seq, request);
 seq_it->second = seq;
 }
 } // release lock
 return 1;
}

上面的代码使用我们的fd创建了epoll_event,并且调用系统调用epoll_ctl来进行注册,只是代码中判断了fd在不再mSequenceNumberByFd当中,在的话使用的是EPOLL_CTL_ADD,不在则使用EPOLL_CTL_MOD。 这里我们需要了解一下epoll,它是linux中的一种高效、可扩展的I/O时间通知机制,而我们这里做的就是监听Binder驱动的FD,当Binder驱动中有变化通知到epoll的文件描述符,也就是我们这里的Looper的回调就可以收到,具体监听的事件为Looper::EVENT_INPUT,从而也就会执行BinderCallbackhandlePolledCommands方法,这个我们留到后面再分析。

看到这,我们可以再看一下main方法的第16行代码,调用的ClientCallbackCallback.setupTo方法,这个类也同样是一个LooperCallback的子类,其代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
static sp<ClientCallbackCallback> setupTo(const sp<Looper>& looper, const sp<ServiceManager>& manager) {
 sp<ClientCallbackCallback> cb = sp<ClientCallbackCallback>::make(manager);

 int fdTimer = timerfd_create(CLOCK_MONOTONIC, 0 /*flags*/);

 itimerspec timespec {
 .it_interval = {
 .tv_sec = 5,
 .tv_nsec = 0,
 },
 .it_value = {
 .tv_sec = 5,
 .tv_nsec = 0,
 },
 };

 int timeRes = timerfd_settime(fdTimer, 0 /*flags*/, &timespec, nullptr);

 int addRes = looper->addFd(fdTimer,
 Looper::POLL_CALLBACK,
 Looper::EVENT_INPUT,
 cb,
 nullptr);

 return cb;
}

这里addFd的代码和之前的一样,但是前面这个timerfd令人感到疑惑,查询一番之后才知道,这里是linux中的定时机制,timerfd为一个基于文件描述符的定时接口,我们这里的代码则是每个5秒钟触发一次,也就是说每隔5秒就会执行一次这个对象的handleEvent方法。

当有客户端请求添加Service或者查询Service等操作的时候,BinderCallbak的handlePolledCommands就会执行去处理,内部会调用如下代码:

1
IPCThreadState::self()->handlePolledCommands();

上面的代码就是会去读取binder传过来的数据,进行处理,具体内容留到后面再分析。

看完评论一下吧

Android源码分析:Binder概述与ServiceManager获取

阅读Android系统源码,Binder是绕不过去的东西,前面看ContentProvider,Activity都有Binder的身影,因此决定还是先把Binder的部分看一看。本文主要简单介绍一下Binder的历史和它的基本架构,介绍Binder的ServiceManager我们在使用的时候如何去拿到它,同时推荐一些Binder的学习资料。

Binder简介

对于普通的Android的应用开发来说,进程的概念是被弱化的。这得益于系统已经帮助我们把Activity,ContentProvider,Broadcast,Service等涉及到跨进程的组件做了很好的封装。我们知道Android也是基于Linux进行开发的,那比如存在跨进程,也就必然存在跨进程通讯。Linux当中跨进程通讯常常使用共享内存、信号量、管道等方式,不过Android中为了安全和使用的便利性,则大部分地方都是使用了Binder。

Binder并不是新提出来的一套跨进程通信机制,它是基于OpenBinder实现的。Binder最早是Be公司开发的, George Hoffman需要一种机制让Be的互联网设备的Javascript UI层与地秤系统服务发生交互,边开发了Binder。后来Be公司的工程师加入了PalmSource开发Palm OS,再后来加入Android,Binder也一直被他们采用,并且也在一直演化,对这段历史感兴趣的话,可以看看《安卓传奇:Android缔造团队回忆录》这本书。开源的OpenBinder是可以工作在Linux内核中的,在2015年已经被合并到Linux 内核3.19版本当中,不仅仅Android,华为的鸿蒙系统当中也在使用Binder。

Binder基本架构

Android中的Binder包括Binder驱动,ServiceManager,Binder服务,他们的关系大概如上图所示。Binder驱动位于Linux内核层,它主要用于实现多个进程之间的通信。ServiceManager位于Android Framework层,用于Binder服务的注册和查找,相当于网络服务中的DNS。而Binder服务的Server端和Client端就是典型的C/S架构,它们通过Binder驱动来进行交互。Android中有两种Binder服务,一种是类似于AMS,PMS这种的系统服务,它们是有名称的服务,注册在ServiceManager当中,Client可以通过名称查找到他们进行使用。还存在另一种匿名Binder服务,比如我们自己通过AIDL创建的,这种我们会直接通过其他的Binder服务把Binder引用传递到客户端,从而双方可以进行通讯。

Binder驱动的源码是在Linux当中的,暂时先不关注了。我这里主要会去看Android Framework层当中Binder相关的代码。ServiceManager本身也是一个Binder,它的ID为0,因此可以很简单的拿到,它的初始化我就不关注了。首先会去关注我们在应用层如何去拿到ServiceManager,因为只有拿到它才能够使用它去注册Binder和获取Binder。其实我们再去看看ServiceManger的addService如何注册一个Binder服务,以及getService 如何获取一个Binder服务。这些看完之后,我们也就知道了Binder的完整运行过程,因为addServicegetService本身也是binder调用,其中我们也会分析Framwork调用kernel相关的代码。最后我们再看看匿名Binder,AIDL,这样差不多就可以对于Framework层的Binder有了全面的了解。

获取ServiceManager

ServiceManager本身是存在一个单独的进程的,并且是在系统启动的时候就启动了它。而我们在其他进程想要通过它来注册服务或者获取服务,就需要首先拿到它的Ibinder对象。通常会用如下的方式获取:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private static IServiceManager getIServiceManager() {
 if (sServiceManager != null) {
 return sServiceManager;
 }

 // Find the service manager 
 sServiceManager = ServiceManagerNative
 .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
 return sServiceManager;
}

上面的代码首先会拿本地的缓存,拿不到才会真正调用获取ServiceManager的步骤,我们先看看这个BinderInternal.getContextObject()方法,它是一个native方法,它的实现在base/core/jni/android_util_Binder.cpp当中,代码如下:

1
2
3
4
5
static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
 sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
 return javaObjectForIBinder(env, b);
}

首先调用ProcessState::self()来拿到ProcessState实例,它内部会执行如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
sp<ProcessState> ProcessState::init(const char *driver, bool requireDefault)
{

 if (driver == nullptr) {
 std::lock_guard<std::mutex> l(gProcessMutex);
 if (gProcess) {
 verifyNotForked(gProcess->mForked);
 }
 return gProcess;
 }

 [[clang::no_destroy]] static std::once_flag gProcessOnce;
 std::call_once(gProcessOnce, [&](){
 if (access(driver, R_OK) == -1) {
 driver = "/dev/binder";
 }

 int ret = pthread_atfork(ProcessState::onFork, ProcessState::parentPostFork,
 ProcessState::childPostFork); //注册fork进程的监听
 LOG_ALWAYS_FATAL_IF(ret != 0, "pthread_atfork error %s", strerror(ret));

 std::lock_guard<std::mutex> l(gProcessMutex);
 gProcess = sp<ProcessState>::make(driver); //智能指针初始化
 });

 if (requireDefault) {
 ...
 }

 verifyNotForked(gProcess->mForked);
 return gProcess;
}

上面的代码传入的参数driver值为/dev/binder也就是binder驱动的地址,requireDefault为false。上面的代码中的std:call_once方法为android的libc所提供,就是保证下面的代码段只会执行一次,这个实现也就是为了实现单例,和Java代码中的其实是差不多的。代码中的make方法内部实际会调用ProcessState的构造方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ProcessState::ProcessState(const char* driver)
 : mDriverName(String8(driver)),
 mDriverFD(-1),
 mVMStart(MAP_FAILED),
 mThreadCountLock(PTHREAD_MUTEX_INITIALIZER),
 mThreadCountDecrement(PTHREAD_COND_INITIALIZER),
 mExecutingThreadsCount(0),
 mWaitingForThreads(0),
 mMaxThreads(DEFAULT_MAX_BINDER_THREADS),
 mStarvationStartTimeMs(0),
 mForked(false),
 mThreadPoolStarted(false),
 mThreadPoolSeq(1),
 mCallRestriction(CallRestriction::NONE) {
 base::Result<int> opened = open_driver(driver);

 if (opened.ok()) {
 mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, opened.value(), 0);
 if (mVMStart == MAP_FAILED) {
 close(opened.value()); //mmap失败,关闭binder文件描述符
 opened = base::Error()
 << "Using " << driver << " failed: unable to mmap transaction memory.";
 mDriverName.clear();
 }
 }
 verifyNotForked(gProcess->mForked); //检查当前的实例不是fork之后的只进程的实例否则报错
 if (opened.ok()) {
 mDriverFD = opened.value(); //记录binder的文件描述符
 }
}

open_driver内部就是调用linux的系统调用open打开binder驱动,并通过ioctl获取驱动打开状态以及进行驱动的一些设置如最大线程数等,这里就查看相关代码了。

打开驱动后又会调用mmap把进行内存映射并保存内存指针到mVMStart上,其中内存映射的大小为BINDER_VM_SIZE,定义如下:

1
2
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
#define _SC_PAGE_SIZE 0x0028

其中sysconf(_SC_PAGE_SIZE)之前的值为4k,最新的Android 15改成了16K,那我们这里仍然以来版本计算,可以得到值为1016Kb,这也就是我们使用Binder交互时候数据传输的限制。

这里我们拿到了binder的文件描述符,也完成了内存映射,也就完成了ProcessState的初始化。ProcessState这个对象如它的名字,在每个进程当中只会有一个实例。

有了实例我们又可以继续看getContextObject,主要代码如下:

1
2
3
4
5
6
7
8
9
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
 sp<IBinder> context = getStrongProxyForHandle(0);

 if (context) {
 internal::Stability::markCompilationUnit(context.get()); //更新Binder的Stability,展示可以跳过
 }
 return context;
}

上面主要关注getStrongProxyForHandle(0),这里传入的id为0,也就是专属于ServiceManager的,此方法代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
ProcessState::handle_entry* ProcessState::lookupHandleLocked(int32_t handle)
{
 const size_t N=mHandleToObject.size();
 if (N <= (size_t)handle) {
 handle_entry e;
 e.binder = nullptr;
 e.refs = nullptr;
 status_t err = mHandleToObject.insertAt(e, N, handle+1-N);
 if (err < NO_ERROR) return nullptr;
 }
 return &mHandleToObject.editItemAt(handle);
}


sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
 sp<IBinder> result;

 AutoMutex _l(mLock);

 handle_entry* e = lookupHandleLocked(handle);

 if (e != nullptr) {
 IBinder* b = e->binder;
 if (b == nullptr || !e->refs->attemptIncWeak(this)) {
 if (handle == 0) {
 IPCThreadState* ipc = IPCThreadState::self();
 CallRestriction originalCallRestriction = ipc->getCallRestriction(); //获取当前的调用限制
 ipc->setCallRestriction(CallRestriction::NONE); //设置限制为空

 Parcel data;
 status_t status = ipc->transact(
 0, IBinder::PING_TRANSACTION, data, nullptr, 0); //调用ping,获取当前Binder的状态

 ipc->setCallRestriction(originalCallRestriction); //恢复原先的限制

 if (status == DEAD_OBJECT)
 return nullptr;
 }

 sp<BpBinder> b = BpBinder::PrivateAccessor::create(handle);
 e->binder = b.get();
 if (b) e->refs = b->getWeakRefs();
 result = b;
 } else {
 result.force_set(b);
 e->refs->decWeak(this);
 }
 }

 return result;
}


struct handle_entry {
 IBinder* binder;
 RefBase::weakref_type* refs;
};

Vector<handle_entry> mHandleToObject;

handle_entry为结构提,其中存放了IBinderrefs,refs为一个弱引用,用于记录Binder的使用数量,这些entry有存放在动态数组mHandleToObject当中。 查找过程很简单,就是数组中有则返回,无则插入一条。对于ServiceManager,此处会调用IPCThreadState的相关方法,首先看看它的self方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
IPCThreadState* IPCThreadState::self()
{
 if (gHaveTLS.load(std::memory_order_acquire)) {
restart:
 const pthread_key_t k = gTLS;
 IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);
 if (st) return st;
 return new IPCThreadState;
 }

 // Racey, heuristic test for simultaneous shutdown.
 if (gShutdown.load(std::memory_order_relaxed)) {
 ALOGW("Calling IPCThreadState::self() during shutdown is dangerous, expect a crash.\n");
 return nullptr;
 }

 pthread_mutex_lock(&gTLSMutex);
 if (!gHaveTLS.load(std::memory_order_relaxed)) {
 int key_create_value = pthread_key_create(&gTLS, threadDestructor);
 if (key_create_value != 0) {
 pthread_mutex_unlock(&gTLSMutex);
 ALOGW("IPCThreadState::self() unable to create TLS key, expect a crash: %s\n",
 strerror(key_create_value));
 return nullptr;
 }
 gHaveTLS.store(true, std::memory_order_release);
 }
 pthread_mutex_unlock(&gTLSMutex);
 goto restart;
}

这里的gHaveTLS类型为atomic<bool>和java中的AtomicBoolean一样都是原子类型安全的Boolean,这里的TLS不是https中我们说的那个TLS而是表示Thread Local Storage,这里我们就可以明白,此处我们是把IPCThreadState存放在Thread Local中,从而保证每一个线程拥有一个IPCThreadState对象,这个类的构造函数如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
IPCThreadState::IPCThreadState()
 : mProcess(ProcessState::self()),
 mServingStackPointer(nullptr),
 mServingStackPointerGuard(nullptr),
 mWorkSource(kUnsetWorkSource),
 mPropagateWorkSource(false),
 mIsLooper(false),
 mIsFlushing(false),
 mStrictModePolicy(0),
 mLastTransactionBinderFlags(0),
 mCallRestriction(mProcess->mCallRestriction) {
 pthread_setspecific(gTLS, this); //key 为gTLS, value为IPCThreadState,存到ThreadLocal中。
 clearCaller();
 mIn.setDataCapacity(256);
 mOut.setDataCapacity(256);
}

void IPCThreadState::clearCaller()
{
 mCallingPid = getpid();
 mCallingSid = nullptr; // expensive to lookup
 mCallingUid = getuid();
}

构造方法中除了设置mInmOut这两个Parcel外,就是设置Callinguidpid为当前调用进程的值。

回到getStrongProxyForHandle方法,检查binder状态的代码看我的注释就好,可以继续看第41行,它内部调用了如下代码:

1
static sp<BpBinder> create(int32_t handle) { return BpBinder::create(handle); }

而这个create方法内部也主要调用了如下代码:

1
return sp<BpBinder>::make(BinderHandle{handle}, trackedUid);

这里使用了强引用指针,我们解析以下实际上是调用了如下代码:

1
BpBinder(BinderHandle{handle}, trackedUid);

也就是创建了一个BpBinder,在它的构造方法中会调用如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
IPCThreadState::self()->incWeakHandle(this->binderHandle(), this);



void IPCThreadState::incWeakHandle(int32_t handle, BpBinder *proxy)
{
 LOG_REMOTEREFS("IPCThreadState::incWeakHandle(%d)\n", handle);
 mOut.writeInt32(BC_INCREFS);
 mOut.writeInt32(handle);
 if (!flushIfNeeded()) {
 // Create a temp reference until the driver has handled this command.
 proxy->getWeakRefs()->incWeak(mProcess.get());
 mPostWriteWeakDerefs.push(proxy->getWeakRefs());
 }
}

上面写入mOut的数据,在将来调用flushCommands的时候会与Binder驱动交互,这个后面再分析。到这里我们就拿到ServiceManager的BpBinder对象了,但是我们现在还是在native层,因此还需要把对象返回到java层,我们这个时候可以看javaObjectForIBinder方法的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
{
 if (val->checkSubclass(&gBinderOffsets)) {
 // It's a JavaBBinder created by ibinderForJavaObject. Already has Java object.
 jobject object = static_cast<JavaBBinder*>(val.get())->object();
 return object;
 }

 BinderProxyNativeData* nativeData = new BinderProxyNativeData();
 nativeData->mOrgue = new DeathRecipientList;
 nativeData->mObject = val;

 jobject object = env->CallStaticObjectMethod(gBinderProxyOffsets.mClass,
 gBinderProxyOffsets.mGetInstance, (jlong) nativeData, (jlong) val.get());
 if (env->ExceptionCheck()) {
 // In the exception case, getInstance still took ownership of nativeData.
 return NULL;
 }
 BinderProxyNativeData* actualNativeData = getBPNativeData(env, object);
 if (actualNativeData == nativeData) {
 // Created a new Proxy
 uint32_t numProxies = gNumProxies.fetch_add(1, std::memory_order_relaxed);
 uint32_t numLastWarned = gProxiesWarned.load(std::memory_order_relaxed);
 ....
 } else {
 delete nativeData;
 }

 return object;
}

上面的代码首先去看看我们现在的指针中的类是否为Java层Binder类的子类,这种情况在binder由ibinderForJavaObject创建,我们这里不是。因此会使用下面的代码,这里gBinderProxyOffsets的相关值如下:

1
2
3
4
5
6
7
8
9
const char* const kBinderProxyPathName = "android/os/BinderProxy";
jclass clazz = FindClassOrDie(env, kBinderProxyPathName);
gBinderProxyOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
gBinderProxyOffsets.mGetInstance = GetStaticMethodIDOrDie(env, clazz, "getInstance",
 "(JJ)Landroid/os/BinderProxy;");
gBinderProxyOffsets.mSendDeathNotice =
 GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
 "(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V");
gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J");

也就是说这里会调用BinderProxygetInstance方法来创建BinderProxy实例,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
private static BinderProxy getInstance(long nativeData, long iBinder) {
 BinderProxy result;
 synchronized (sProxyMap) {
 try {
 result = sProxyMap.get(iBinder);
 if (result != null) {
 return result;
 }
 result = new BinderProxy(nativeData);
 } catch (Throwable e) {
NativeAllocationRegistry.applyFreeFunction(NoImagePreloadHolder.sNativeFinalizer, nativeData);
 throw e;
 }
 NoImagePreloadHolder.sRegistry.registerNativeAllocation(result, nativeData);
 sProxyMap.set(iBinder, result);
 }
 return result;
}

主要就是把nativeData放到BinderProxy对象当中,并且用iBinder做为key放到缓存map当中去。到这里native层的代码就全部分析完了。可以继续回到java层的代码。接下来就是调用ServiceManagerNative.asInterface方法,代码如下:

1
2
3
4
5
6
public static IServiceManager asInterface(IBinder obj) {
 if (obj == null) {
 return null;
 }
 return new ServiceManagerProxy(obj);
}

其中就是用ServiceManagerProxy对我们刚刚拿到的BinderProxy进行代理。这样便完成了获取ServiceManager的整个流程。

获取ServiceManager的时序图如下所示:

sequenceDiagram
ServiceManager->>BinderInternal: getContextObject()
BinderInternal->>ProcessState: self()
ProcessState->>ProcessState: getContextObject()
ProcessState->>ProcessState: getStrongProxyForHandle()
ProcessState->>IPCThreadState: self()
IPCThreadState->>IPCThreadState: transact
note over IPCThreadState: PING_TRANSACTION
ProcessState-->>BinderInternal: createBinder
note right of BinderInternal: BpBinder
BinderInternal-->>ServiceManager: javaObjectForIBinder
note right of ServiceManager: BinderProxy
ServiceManager-->>ServiceManager: asInterface
note right of ServiceManager: ServiceManagerProxy

我们现在所分析的流程,本质上还是客户端去获取一个Binder的流程,当然这个binder比较特殊,它直接写死了id为0。可以再回顾一下刚刚涉及到的类。 首先是ProcessState,每个进程都会有一个它的实例,它用于维护打开binder驱动的文件描述符、维护binder线程池以及创建IPCThreadState等。IPCThreadState则用于具体的Binder连接,它会通过ThreadLocal依附于线程,与Binder驱动交互的相关代码都在它的内部。客户端在native端的binder对象为BpBinder,在java端的对象则为BinderProxy。

Binder学习资料

我的文章只会介绍Android Framework层Binder相关的知识,Binder驱动是在Kernel当中的,我不会涉及。另外ServiceManager也是一个系统的守护进程,系统启动的时候也就会启动,我可能也不会分析了。因此推荐以下资料,方便大家在学习Binder,同时对于我没有涉及到的部分也可以参考。

  1. Binder学习指南 这个介绍的还比较通俗易懂
  2. Gityuan Binder系列详解基于Android 6.0,内容详细,从驱动到应用层全部都有讲解
  3. Android深入浅出之Binder机制 邓平凡老师的讲解,可以大概弄清楚binder的机制。

到这里分析完这个流程,我们后面就可以分析addService流程了,待到下次文章继续分享。相互交流才能更好的提高,欢迎读者朋友评论交流。

看完评论一下吧

Android源码分析: ContentProvider查询以及数据变化监听分析

之前已经分析了启动应用安装ContentProvider,使用时获取ContentProvider,我们这里再分析一下使用ContentProvider查询数据已经监听ContentProvider数据变化的情况。

查询数据

上次的文章已经介绍了使用query的方法,并且已经介绍完了通过acquireProvider获取到ContentProvider,如果是是本地应用的话拿到的是Transport对象,如果是查询其他应用(不严谨的说法,其他应用也要排查userId不同,且不共享签名),则拿到的是ContentProviderProxy,这里我们要分析的查询是其他应用的情况,因此我们需要关注ContentProviderProxyquery方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Override
public Cursor query(@NonNull AttributionSource attributionSource, Uri url,
 @Nullable String[] projection, @Nullable Bundle queryArgs,
 @Nullable ICancellationSignal cancellationSignal)
 throws RemoteException {
 BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
 Parcel data = Parcel.obtain();
 Parcel reply = Parcel.obtain();
 try {
 data.writeInterfaceToken(IContentProvider.descriptor);

 attributionSource.writeToParcel(data, 0);
 url.writeToParcel(data, 0);
 int length = 0;
 if (projection != null) {
 length = projection.length;
 }
 data.writeInt(length);
 for (int i = 0; i < length; i++) {
 data.writeString(projection[i]);
 }
 data.writeBundle(queryArgs);
 data.writeStrongBinder(adaptor.getObserver().asBinder());
 data.writeStrongBinder(
 cancellationSignal != null ? cancellationSignal.asBinder() : null);

 mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0);

 DatabaseUtils.readExceptionFromParcel(reply);

 if (reply.readInt() != 0) {
 BulkCursorDescriptor d = BulkCursorDescriptor.CREATOR.createFromParcel(reply);
 Binder.copyAllowBlocking(mRemote, (d.cursor != null) ? d.cursor.asBinder() : null);
 adaptor.initialize(d);
 } else {
 adaptor.close();
 adaptor = null;
 }
 return adaptor;
 } catch (RemoteException ex) {
 adaptor.close();
 throw ex;
 } catch (RuntimeException ex) {
 adaptor.close();
 throw ex;
 } finally {
 data.recycle();
 reply.recycle();
 }
}

这个代码比较简单,把需要查询的条件写入到Parcel中,然后通过mRemote进行binder调用,在reply中拿到远端执行的结果。如果执行成功了,则通过BulkCursorDescriptor来读取reply中的数据,主要是拿到了其中IBulkCursor的Binder对象和CursorWindow这个对象。在查询的流程中会涉及到很多的类,我这里画了使用端和服务端会使用到的Cursor所涉及到相关类和接口。

其中BulkCursorToCursorAdapter为客户端使用,用于读取服务端通过binder传过来的数据,其中的封装和使用,我们后面还会继续看到。关于服务端的我们先继续往后看代码,随后会涉及到相关的类。

可以看看Provider服务端是如何把这些东西放到reply中的。我们这个时候可以看一下ContentProviderNativeonTransactQUERY_TRANSACTION的这一分支:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
data.enforceInterface(IContentProvider.descriptor);

AttributionSource attributionSource = AttributionSource.CREATOR
 .createFromParcel(data);
Uri url = Uri.CREATOR.createFromParcel(data);

// String[] projection 
int num = data.readInt();
String[] projection = null;
if (num > 0) {
 projection = new String[num];
 for (int i = 0; i < num; i++) {
 projection[i] = data.readString();
 }
}

Bundle queryArgs = data.readBundle();
IContentObserver observer = IContentObserver.Stub.asInterface(
 data.readStrongBinder());
ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface(
 data.readStrongBinder());

这是其中的第一部分代码,就是把binder传过来的查询需要的数据进行反序列化。

1
2
Cursor cursor = query(attributionSource, url, projection, queryArgs,
 cancellationSignal);

第二部分为调用query进行查询,我们知道在数据提供端,其实是Transport,可以看看它的query方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Override
public Cursor query(@NonNull AttributionSource attributionSource, Uri uri,
 @Nullable String[] projection, @Nullable Bundle queryArgs,
 @Nullable ICancellationSignal cancellationSignal) {
 uri = validateIncomingUri(uri);
 uri = maybeGetUriWithoutUserId(uri);
 if (enforceReadPermission(attributionSource, uri)
 != PermissionChecker.PERMISSION_GRANTED) {
 if (projection != null) {
 return new MatrixCursor(projection, 0);
 }

 Cursor cursor;
 final AttributionSource original = setCallingAttributionSource(
 attributionSource);
 try {
 cursor = mInterface.query(
 uri, projection, queryArgs,
 CancellationSignal.fromTransport(cancellationSignal));
 } catch (RemoteException e) {
 throw e.rethrowAsRuntimeException();
 } finally {
 setCallingAttributionSource(original);
 }
 if (cursor == null) {
 return null;
 }

 // Return an empty cursor for all columns. 
 return new MatrixCursor(cursor.getColumnNames(), 0);
 }
 traceBegin(TRACE_TAG_DATABASE, "query: ", uri.getAuthority());
 final AttributionSource original = setCallingAttributionSource(
 attributionSource);
 try {
 return mInterface.query(
 uri, projection, queryArgs,
 CancellationSignal.fromTransport(cancellationSignal));
 } catch (RemoteException e) {
 throw e.rethrowAsRuntimeException();
 } finally {
 setCallingAttributionSource(original);
 Trace.traceEnd(TRACE_TAG_DATABASE);
 }
}

其中的代码也是比较简单的,首先调用enforceReadPermission检查是否有使用这个ContentProvider的权限,如果有权限则调用mInterface.query,我们看源码就知道,这个mInterface也就是一个ContentProvider,也就是我们开发过程实现的那个ContentProvider,query方法也就是我们自己的实现。

我们先不着急分析后面的代码,我们前面说过如果是本进程的ContentProvider查询会直接调用Transportquery方法,那么就不存在binder调用,而是直接调用了我们所实现的query。这个实现还是很妙的,值得我们学习。对于我们跨进程的调用,还需要看ContentProviderNativeonTransact后面的代码,也就是我们要说的第三部分:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
if (cursor != null) {
 CursorToBulkCursorAdaptor adaptor = null;

 try {
 adaptor = new CursorToBulkCursorAdaptor(cursor, observer,
 getProviderName());
 cursor = null;

 BulkCursorDescriptor d = adaptor.getBulkCursorDescriptor();
 adaptor = null;

 reply.writeNoException();
 reply.writeInt(1);
 d.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
 } finally {
 // Close cursor if an exception was thrown while constructing the adaptor. 
 if (adaptor != null) {
 adaptor.close();
 }
 if (cursor != null) {
 cursor.close();
 }
 }
} else {
 reply.writeNoException();
 reply.writeInt(0);
}

这里在拿到数据的时候,通过CursorToBulkCursorAdapter把刚刚查询到Cursor进行了包装,并且通过BulkCursorDescriptor写入到reply中。这样我们刚刚调用端就可以拿到了。 我们看CursorToBulkCursorAdapter可以看到,它的内部又用CrossProcessCursorWrapper来对Cursor进行了封装。

我们已经完成查询,并且获取到Cursor的封装,接下来我们就可以看一下数据的读取了。通过我们前面的Cursor的各个相关类的关系图,我们知道在客户端我们所拿到的是BulkCursorToCursorAdapter,它的初始化代码如下:

1
2
3
4
5
6
7
8
9
public void initialize(BulkCursorDescriptor d) {
 mBulkCursor = d.cursor;
 mColumns = d.columnNames;
 mWantsAllOnMoveCalls = d.wantsAllOnMoveCalls;
 mCount = d.count;
 if (d.window != null) {
 setWindow(d.window);
 }
}

以下为服务端和客户端交互的流程时序图,通过这个图我们可以具体看到查询过程服务端和客户端交互,以及两边封装的类:

sequenceDiagram
App->>ContentProviderProxy: query
ContentProviderProxy->>+Transport(Server): Binder(QUERY_TRANSACTION)
Transport(Server)->>+ContentProvider: query
ContentProvider -->>- Transport(Server): return query result
note right of Transport(Server): Cursor
Transport(Server) -->>- ContentProviderProxy: Binder(write reply)
note left of Transport(Server): CursorToBulkCursorAdaptor
ContentProviderProxy --> App: return query result
note left of ContentProviderProxy: BulkCursorToCursorAdaptor

服务端传输数据到调用端

可以知道我们主要从服务端拿到两个东西,一个是BulkCursor它是后面我们的数据移动的Binder操作类,CursorWindow则用来存放当前位置的数据。当我们调用CursormoveToNext的时候,就会调用BulkCursorToCursorAdapteronMove方法,进而又会通过binder调用CursorToBulkCursorAdapteronMove方法,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public boolean onMove(int oldPosition, int newPosition) {
 throwIfCursorIsClosed();

 try {
 if (mWindow == null
 || newPosition < mWindow.getStartPosition()
 || newPosition >= mWindow.getStartPosition() + mWindow.getNumRows()) {
 setWindow(mBulkCursor.getWindow(newPosition));
 } else if (mWantsAllOnMoveCalls) {
 mBulkCursor.onMove(newPosition);
 }
 } catch (RemoteException ex) {
 return false;
 }

 if (mWindow == null) {
 return false;
 }

 return true;
}

当window还没有初始化的时候,会调用setWindowsetWindow很简单,但是mBulkCursor.getWindow却不简单:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Override
public CursorWindow getWindow(int position) {
 synchronized (mLock) {
 throwIfCursorIsClosed();

 if (!mCursor.moveToPosition(position)) {
 closeFilledWindowLocked();
 return null;
 }

 CursorWindow window = mCursor.getWindow();
 if (window != null) {
 closeFilledWindowLocked();
 } else {
 window = mFilledWindow;
 if (window == null) {
 mFilledWindow = new CursorWindow(mProviderName);
 window = mFilledWindow;
 } else if (position < window.getStartPosition()
 || position >= window.getStartPosition() + window.getNumRows()) {
 window.clear();
 }
 mCursor.fillWindow(position, window);
 }

 if (window != null) {
 window.acquireReference();
 }
 return window;
 }
}

我们可以看到此处会调用fillWindow,而此处的mCursorCrossProcessCursorWrapper的实例,fillWindow则会调用如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Override
public void fillWindow(int position, CursorWindow window) {
 if (mCursor instanceof CrossProcessCursor) {
 final CrossProcessCursor crossProcessCursor = (CrossProcessCursor)mCursor;
 crossProcessCursor.fillWindow(position, window);
 return;
 }

 DatabaseUtils.cursorFillWindow(mCursor, position, window);
}

这样就会把每一个postion的数据填充到CursorWindow当中,但是这样会有个问题,为什么客户端能直接拿到呢。我们可以看看CursorWindow的内部。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public CursorWindow(String name, @BytesLong long windowSizeBytes) {
 if (windowSizeBytes < 0) {
 throw new IllegalArgumentException("Window size cannot be less than 0");
 }
 mStartPos = 0;
 mName = name != null && name.length() != 0 ? name : "<unnamed>";
 mWindowPtr = nativeCreate(mName, (int) windowSizeBytes);
 if (mWindowPtr == 0) {
 throw new AssertionError();
 }
 mCloseGuard.open("CursorWindow.close");
}

我们从这里可以看到,CursorWindow保存数据并没有直接放在java中的,而是在natvie中实现的。我们可以在CursorWindow.cpp中找到nativeCreate的实现,我们在其中会发现如下的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//CursorWindow::create
status_t CursorWindow::create(const String8 &name, size_t inflatedSize, CursorWindow **outWindow) {
 *outWindow = nullptr;

 CursorWindow* window = new CursorWindow();
 if (!window) goto fail;

 window->mName = name;
 window->mSize = std::min(kInlineSize, inflatedSize);
 window->mInflatedSize = inflatedSize;
 window->mData = malloc(window->mSize);
 if (!window->mData) goto fail;
 window->mReadOnly = false;

 window->clear();
 window->updateSlotsData();

 *outWindow = window;
 return OK;

fail:
 LOG(ERROR) << "Failed create";
fail_silent:
 delete window;
 return UNKNOWN_ERROR;
}

但是直接用内存的话,如果我们改变CursorWindow内的数据的时候,在使用端是没办法直接拿到更新的数据的。其实在给插入数据的时候,调用maybeInflate

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
status_t CursorWindow::maybeInflate() {
 int ashmemFd = 0;
 void* newData = nullptr;

 // Bail early when we can't expand any further
 if (mReadOnly || mSize == mInflatedSize) {
 return INVALID_OPERATION;
 }

 String8 ashmemName("CursorWindow: ");
 ashmemName.append(mName);

 ashmemFd = ashmem_create_region(ashmemName.string(), mInflatedSize);
 ...

 newData = ::mmap(nullptr, mInflatedSize, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0);
 ...

 {
 // Migrate existing contents into new ashmem region
 uint32_t slotsSize = sizeOfSlots();
 uint32_t newSlotsOffset = mInflatedSize - slotsSize;
 memcpy(static_cast<uint8_t*>(newData),
 static_cast<uint8_t*>(mData), mAllocOffset);
 memcpy(static_cast<uint8_t*>(newData) + newSlotsOffset,
 static_cast<uint8_t*>(mData) + mSlotsOffset, slotsSize);

 free(mData);
 mAshmemFd = ashmemFd;
 mData = newData;
 mSize = mInflatedSize;
 mSlotsOffset = newSlotsOffset;

 updateSlotsData();
 }

 return OK;
...
}

从代码可以看到,当我们不是只读模式,且size不是和inflateSize相同的时候,会去创建匿名内存,把原来的数据复制的新的匿名内存中去。而会把匿名内存的FD保存到mAshmemFd当中。这样在客户端就可以拿到这个fd,从而可以读取到数据。因为这样做,也只是把fd和CursorWindow的一些基本信息从服务端传到了Client,这样服务端往匿名内存中写数据,客户端也就可以拿到其中的数据了。这样做既可以减少Binder调用的数据量,也可以解决掉Binder传输有1MB的限制。为了验证我们的想法,可以看看代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
status_t CursorWindow::writeToParcel(Parcel* parcel) {
 LOG(DEBUG) << "Writing to parcel: " << this->toString();

 if (parcel->writeString8(mName)) goto fail;
 if (parcel->writeUint32(mNumRows)) goto fail;
 if (parcel->writeUint32(mNumColumns)) goto fail;
 if (mAshmemFd != -1) {
 if (parcel->writeUint32(mSize)) goto fail;
 if (parcel->writeBool(true)) goto fail;
 if (parcel->writeDupFileDescriptor(mAshmemFd)) goto fail;
 } else {
 // Since we know we're going to be read-only on the remote side,
 // we can compact ourselves on the wire. size_t slotsSize = sizeOfSlots();
 size_t compactedSize = sizeInUse();
 if (parcel->writeUint32(compactedSize)) goto fail;
 if (parcel->writeBool(false)) goto fail;
 void* dest = parcel->writeInplace(compactedSize);
 if (!dest) goto fail;
 memcpy(static_cast<uint8_t*>(dest),
 static_cast<uint8_t*>(mData), mAllocOffset);
 memcpy(static_cast<uint8_t*>(dest) + compactedSize - slotsSize,
 static_cast<uint8_t*>(mData) + mSlotsOffset, slotsSize);
 }
 return OK;

fail:
 LOG(ERROR) << "Failed writeToParcel";
fail_silent:
 return UNKNOWN_ERROR;
}

可以看到在服务端转成Parcel的时候,是写入了name,numRow,numColumn, size, ashMemFd这些,同样,在客户端也会读取这些东西。代码就不贴了。

下面再理一下执行onMoveToNext时候的流程。在客户端的调用如下:

sequenceDiagram
box LightYellow
participant App
participant CursorWrapperInner
participant BulkCursorToCursorAdaptor
participant BulkCursorProxy
end
App->>+CursorWrapperInner: moveToNext
CursorWrapperInner->>BulkCursorToCursorAdaptor: onMove
BulkCursorToCursorAdaptor->>BulkCursorProxy: onMove
BulkCursorProxy->>Remote(CursorToBulkCursorAdapter): binder(ON_MOVE_TRANSACTION)
CursorWrapperInner-->>-App: finishMoveToNext

服务端的调用如下,前面的onMove调用在使用SQLiteCursor的时候会有一些不同,这里以读取SQLite数据库为例,内容有简化:

sequenceDiagram
(Client)BulkCursorProxy->>CursorToBulkCursorAdapter: binder(ON_MOVE_TRANSACTION)
CursorToBulkCursorAdapter->>CrossProcessCursorWrapper: onMove
CrossProcessCursorWrapper->>SQLiteCursor: onMove
SQLiteCursor->>SQLiteCursor: fillWindow
SQLiteCursor->>SQLiteConnection: executeForCursorWindow
SQLiteConnection->>CursorWindow: putXX
note right of SQLiteConnection: native 写入数据到CursorWindow
box LightGreen
participant CursorToBulkCursorAdapter
participant CrossProcessCursorWrapper
participant SQLiteCursor
participant SQLiteConnection
participant CursorWindow
end

ContentObserver监听的注册

当我们想要监听一个ContentProvider的变化时,可以按照如下的方法创建一个ContentObserver,并调用registerContentObserver来注册监听,通过传入的Uri来设置指定的数据源,通过Uri的path可以设置监听指定数据源中的某一部分数据的变化。

1
2
3
4
5
6
7
val contentObserver = object: ContentObserver(Handler.getMain()) {
 override fun onChange(selfChange: Boolean) {
 super.onChange(selfChange)
 //do something while receive onChange
 }
}
contentResolver.registerContentObserver(Uri.parse("content://sms"), true, contentObserver)

我们继承的这个ContentObserver有一个内部类Transport,它实现了 IContentObserver.Stub, 这个和ContentProvider的内部类实现类似,也是实现了binder的数据交互。registerContentObserver方法也在ContentResolver中,代码如下:

1
2
3
4
5
6
7
8
9
public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
 ContentObserver observer, @UserIdInt int userHandle) {
 try {
 getContentService().registerContentObserver(uri, notifyForDescendents,
 observer.getContentObserver(), userHandle, mTargetSdkVersion);
 } catch (RemoteException e) {
 throw e.rethrowFromSystemServer();
 }
}

可以看到上面的代码调用了ContentServiceregisterContentObserver方法,这里拿到的是一个binder接口的实现IContentService的代理类,在binder另一端真正执行这个方法的是在ContentService中,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@Override
public void registerContentObserver(Uri uri, boolean notifyForDescendants,
 IContentObserver observer, int userHandle, int targetSdkVersion) {
 final int uid = Binder.getCallingUid();
 final int pid = Binder.getCallingPid();
 userHandle = handleIncomingUser(uri, pid, uid,
 Intent.FLAG_GRANT_READ_URI_PERMISSION, true, userHandle);

 final String msg = LocalServices.getService(ActivityManagerInternal.class)
 .checkContentProviderAccess(uri.getAuthority(), userHandle);
 ...
 synchronized (mRootNode) {
 mRootNode.addObserverLocked(uri, observer, notifyForDescendants, mRootNode,
 uid, pid, userHandle);
 }
}

服务端的主要代码如上,其中传过来的observer为IContentObserver,它就是我们刚刚说到的和Transport相同的接口。其中主要就是调用了mRootNode.addObserverLockedmRootNode是内部类ObserverNode的实例。继续看代码之前先介绍一下这个类,这个类内部又有ObserverEntry内部类,我们的Observer会存放到这个Entry内部。ObserverNode有两个成员mChildrenmObservers,分别表示子一级的ObserverNode和当前路径级的ObserverEntry,从而组成了如下的树状结构。

addObserverLocked这个方法的代码我们也能知晓该树状结构的构成,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void addObserverLocked(Uri uri, int index, IContentObserver observer,
 boolean notifyForDescendants, Object observersLock,
 int uid, int pid, int userHandle) {
 // If this is the leaf node add the observer 
 if (index == countUriSegments(uri)) {
 mObservers.add(new ObserverEntry(observer, notifyForDescendants, observersLock,
 uid, pid, userHandle, uri));
 return;
 }

 // Look to see if the proper child already exists 
 String segment = getUriSegment(uri, index);
 if (segment == null) {
 throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer");
 }
 int N = mChildren.size();
 for (int i = 0; i < N; i++) {
 ObserverNode node = mChildren.get(i);
 if (node.mName.equals(segment)) {
 node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
 observersLock, uid, pid, userHandle);
 return;
 }
 }

 // No child found, create one 
 ObserverNode node = new ObserverNode(segment);
 mChildren.add(node);
 node.addObserverLocked(uri, index + 1, observer, notifyForDescendants,
 observersLock, uid, pid, userHandle);
}

这样操作完,也就完成了添加Observer的操作。

数据更新的发布与分发

那数据更新的部分呢,当我们执行了数据整删改之后,需要调用如下代码通知数据变化:

1
getContext().getContentResolver().notifyChange(uri, null);

这个notifyChange方法有几个实现,最终会调用到如下这个:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public void notifyChange(@NonNull Uri[] uris, ContentObserver observer, @NotifyFlags int flags,
 @UserIdInt int userHandle) {
 try {
 getContentService().notifyChange(
 uris, observer == null ? null : observer.getContentObserver(),
 observer != null && observer.deliverSelfNotifications(), flags,
 userHandle, mTargetSdkVersion, mContext.getPackageName());
 } catch (RemoteException e) {
 throw e.rethrowFromSystemServer();
 }
}

其中getContentService会获取到IContentService在本地的代理,而最终会通过Binder调用到system_server中的ContentService中的notifyChange方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
@Override
public void notifyChange(Uri[] uris, IContentObserver observer,
 boolean observerWantsSelfNotifications, int flags, int userId,
 int targetSdkVersion, String callingPackage) {

 final int callingUid = Binder.getCallingUid();
 final int callingPid = Binder.getCallingPid();
 final int callingUserId = UserHandle.getCallingUserId();


 final ObserverCollector collector = new ObserverCollector();



 for (Uri uri : uris) {
 final int resolvedUserId = handleIncomingUser(uri, callingPid, callingUid,
 Intent.FLAG_GRANT_WRITE_URI_PERMISSION, true, userId);
 final Pair<String, Integer> provider = Pair.create(uri.getAuthority(), resolvedUserId);
 if (!validatedProviders.containsKey(provider)) {
 final String msg = LocalServices.getService(ActivityManagerInternal.class)
 .checkContentProviderAccess(uri.getAuthority(), resolvedUserId);
 if (msg != null) {
 if (targetSdkVersion >= Build.VERSION_CODES.O) {
 throw new SecurityException(msg);
 } else {
 if (msg.startsWith("Failed to find provider")) {
 // Sigh, we need to quietly let apps targeting older API 
 } else {
 Log.w(TAG, "Ignoring notify for " + uri + " from "
 + callingUid + ": " + msg);
 continue;
 }
 }
 }

 // Remember that we've validated this access 
 final String packageName = getProviderPackageName(uri, resolvedUserId);
 validatedProviders.put(provider, packageName);
 }

 synchronized (mRootNode) {
 final int segmentCount = ObserverNode.countUriSegments(uri);
 mRootNode.collectObserversLocked(uri, segmentCount, 0, observer,
 observerWantsSelfNotifications, flags, resolvedUserId, collector);
 }
 }

 final long token = clearCallingIdentity();
 try {
 // Actually dispatch all the notifications we collected 
 collector.dispatch();
 .....
 }
 } finally {
 Binder.restoreCallingIdentity(token);
 }
}

以上代码略有简化,只保留了和ContentObserver通知相关的代码,SyncManager相关的代码未放在这里。这段代码最开始是先遍历传入的Uri列表,对检查对应Uri的ContentProvider是否有权限,如果有权限则会调用collectObserversLocked把满足条件的Observer放到ObserverCollector中去,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//ObserverNode.collectObserversLocked
public void collectObserversLocked(Uri uri, int segmentCount, int index,
 IContentObserver observer, boolean observerWantsSelfNotifications, int flags,
 int targetUserHandle, ObserverCollector collector) {
 String segment = null;
 if (index >= segmentCount) {
 // This is the leaf node, notify all observers 
 collectMyObserversLocked(uri, true, observer, observerWantsSelfNotifications,
 flags, targetUserHandle, collector);
 } else if (index < segmentCount){
 segment = getUriSegment(uri, index);
 // Notify any observers at this level who are interested in descendants 
 collectMyObserversLocked(uri, false, observer, observerWantsSelfNotifications,
 flags, targetUserHandle, collector);
 }

 int N = mChildren.size();
 for (int i = 0; i < N; i++) {
 ObserverNode node = mChildren.get(i);
 if (segment == null || node.mName.equals(segment)) {
 // We found the child, 
 node.collectObserversLocked(uri, segmentCount, index + 1, observer,
 observerWantsSelfNotifications, flags, targetUserHandle, collector);
 if (segment != null) {
 break;
 }
 }
 }
}

传入的segmentCount为Uri的path的数量加上authority的数量,比如content://sms/inbox这个uri它的segmentCount就是2,而从外面传如的index为0。它会先调用collectMyObserversLocked方法来遍历当前Node层级的ObserverEntry,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void collectMyObserversLocked(Uri uri, boolean leaf, IContentObserver observer, boolean observerWantsSelfNotifications, int flags, int targetUserHandle, ObserverCollector collector) {
 int N = mObservers.size();
 IBinder observerBinder = observer == null ? null : observer.asBinder();
 for (int i = 0; i < N; i++) {
 ObserverEntry entry = mObservers.get(i);
 boolean selfChange = (entry.observer.asBinder() == observerBinder);
 if (selfChange && !observerWantsSelfNotifications) {
 continue;
 }

 // Does this observer match the target user? 
 if (targetUserHandle == UserHandle.USER_ALL
 || entry.userHandle == UserHandle.USER_ALL
 || targetUserHandle == entry.userHandle) {
 // Make sure the observer is interested in the notification 
 if (leaf) {
 if ((flags&ContentResolver.NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS) != 0 && entry.notifyForDescendants) {
 continue;
 }
 } else {
 if (!entry.notifyForDescendants) {
 continue;
 }
 }
 collector.collect(entry.observer, entry.uid, selfChange, uri, flags, targetUserHandle);
 }
 }
}

可以看到以上代码就是遍历我们之前注册observer时候的mObservers列表,分别检查了用户id是否相等,是否满足notifyForDescendants和flag等参数后调用collector.collect方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public void collect(IContentObserver observer, int uid, boolean selfChange, Uri uri,
 int flags, int userId) {
 final Key key = new Key(observer, uid, selfChange, flags, userId);
 List<Uri> value = collected.get(key);
 if (value == null) {
 value = new ArrayList<>();
 collected.put(key, value);
 }
 value.add(uri);
}

Collector中则是以observer,uid,selfChange,flag,userId组合成key,Uri作为value放入collectedmap中。而这些只是完成了一个层级的Observer收集,collectObserversLocked方法中还会遍历mChildren,找到其中的name与segment相同的子Node,再进行收集。收集完成之后,则是调用collector.dispatch(),看名字就知道是去通知对应的Observer,具体实现逻辑如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void dispatch() {
 for (int i = 0; i < collected.size(); i++) {
 final Key key = collected.keyAt(i);
 final List<Uri> value = collected.valueAt(i);

 final Runnable task = () -> {
 try {
 key.observer.onChangeEtc(key.selfChange,
 value.toArray(new Uri[value.size()]), key.flags, key.userId);
 } catch (RemoteException ignored) {
 }
 };
 final boolean noDelay = (key.flags & ContentResolver.NOTIFY_NO_DELAY) != 0;
 final int procState = LocalServices.getService(ActivityManagerInternal.class)
 .getUidProcessState(key.uid);
 if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND || noDelay) {
 task.run();
 } else {
 BackgroundThread.getHandler().postDelayed(task, BACKGROUND_OBSERVER_DELAY);
 }
 }
}

这个方法则是遍历collected这个ArrayMap,从每一个key当中取出observer,并使用Runnable封装,在根据flags和当前的进程状态决定是立即通知变化还是延迟通知变化。而这里所调用的onChangeEtc则会通过Binder调用,从而调用到客户端的Observer。

这便完成了ContentProvider内容变化的通知。

总结

本文介绍了使用ContentProvider进行数据的查询、查处来的数据进行窗口移动、注册数据变化监听以及数据变化接受这几块的代码分析,加上前面两篇关于ContentProvider的文章,基本上可以对于ContentProvider整个体系有详细的了解。整删改查这四种操作中,查是比较复杂的,把它看完,增删改这三种流程,想要看明白就会简单很多,因此这里也便不再分析了。

从Android ContentProvider的设计和代码实现中我们可以学到很多东西。其中之一是前面介绍到IContentProvider在自己调用和其他App调用的区别,以及对于代码的巧妙封装,使得代码的实现比较优雅,同时代码量比较少,对于同UID应用来说性能又比较优。另外就是通过CursoWindow的实现,突破Binder数据传输的限制。ObserverNode中使用树来实现了Observer监听。

当然这只是我的一人之言,因为关注点不同,在看代码的过程中还是会有一些细节被我忽略,但是可能对于其他人来说又比较重要的。如果你对ContentProvider也有自己的见解,又或是我有错误的解读,欢迎留言交流。

看完评论一下吧

Android源码分析: 使用场景获取ContentProvider分析

之前已经分析过在应用启动的时候安装ContentProvider的流程了,现在我们再从使用者的角度看看是怎样去拿到ContentProvider的。

在使用ContentProvider的时候,我们通常会使用Context拿到ContentResolver,然后在执行CURD的操作,比如我们要查询手机中的联系人,通常会这样做:

1
2
3
4
5
6
7
8
String[] projection = new String[]    {
 Profile._ID,       
 Profile.DISPLAY_NAME_PRIMARY,       
 Profile.LOOKUP_KEY,       
 Profile.PHOTO_THUMBNAIL_URI    };
Cursor profileCursor = getContentResolver().query(
Profile.CONTENT_URI,
projection , null, null,null);

这里先重点分析一下拿到ContentProvider的过程。首先来看看这个ContentResolver是什么东西。通过源码我们可以看到它是一个抽象类,实现了ContentInterface接口,ContentInterface中则定义了CRUD的相关方法。我们可以在ContextImpl中找到getContentResolver(),通过源码我们知道,实际上我们拿到的是ApplicationContentResolver对象。

这里看完,我们可以继续看query方法,实现在ContentResolver类当中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
IContentProvider unstableProvider = acquireUnstableProvider(uri);
IContentProvider stableProvider = null;
Cursor qCursor = null;
try {
 try {
 qCursor = unstableProvider.query(mContext.getAttributionSource(), uri, projection,
 queryArgs, remoteCancellationSignal);
 } catch (DeadObjectException e) {
 unstableProviderDied(unstableProvider);
 stableProvider = acquireProvider(uri);
 if (stableProvider == null) {
 return null;
 }
 qCursor = stableProvider.query(mContext.getAttributionSource(), uri, projection,
 queryArgs, remoteCancellationSignal);
 }
 qCursor.getCount();
 final IContentProvider provider = (stableProvider != null) ? stableProvider
 : acquireProvider(uri);
 final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
 stableProvider = null;
 qCursor = null;
 return wrapper;
} catch (RemoteException e) {
 return null;
} finally {
 if (qCursor != null) {
 qCursor.close();
 }
 if (unstableProvider != null) {
 releaseUnstableProvider(unstableProvider);
 }
 if (stableProvider != null) {
 releaseProvider(stableProvider);
 }
}

上面的代码看起来还是比较简单的,首先是是去调用acquireUnstableProvider拿到unstableProvider,通过它去取数据,如果拿不到再去调用acquireProviderstableProvider,最后把stableProvider和数据使用CursorWrappInner包装返回给调用者,在finally中把cursor关掉,把provider给释放掉。

我们先来看看拿stableProvider的逻辑:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public final IContentProvider acquireUnstableProvider(Uri uri) {
 if (!SCHEME_CONTENT.equals(uri.getScheme())) {
 return null;
 }
 String auth = uri.getAuthority();
 if (auth != null) {
 return acquireUnstableProvider(mContext, uri.getAuthority());
 }
 return null;
}

简单说一下,上面首先会判断我们的URL是否为content:开头,因为这是ContentProvider的scheme。之后会到url中拿到authority, autority包括这几个部分:[userinfo@]host[:port] 。最后通过authority去调用ApplicationContentResolver中的同名方法。

1
2
3
4
5
protected IContentProvider acquireUnstableProvider(Context c, String auth) {
 return mMainThread.acquireProvider(c,
 ContentProvider.getAuthorityWithoutUserId(auth),
 resolveUserIdFromAuthority(auth), false);
}

上面的方法会从我们的authority分别拿出userId和host,当然userId有可能是不传的,就会默认使用当前用户。我们继续去看ActivityThread.acquireProvider()代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public final IContentProvider acquireProvider(
 Context c, String auth, int userId, boolean stable) {
 final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
 if (provider != null) {
 return provider;
 }
 ContentProviderHolder holder = null;
 final ProviderKey key = getGetProviderKey(auth, userId);
 try {
 synchronized (key) {
 holder = ActivityManager.getService().getContentProvider( getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
 if (holder != null && holder.provider == null && !holder.mLocal) {
 synchronized (key.mLock) {
 if(key.mHolder != null) {
 } else {
 key.mLock.wait(ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS)
 }
 holder = key.mHolder;
 }
 }
 }
 } finally {
 synchronized (key.mLock) {
 key.mHolder = null;
 }
 }
 holder = installProvider(c, holder, holder.info, true, holder.noReleaseNeeded, stable);
 return holder.provider;
}

这里我们有一个参数stable,因此我们前面获取stableProviderunstableProvider都会走到这个方法里面来。 第3行代码,我们首先会到已存在的Provider列表中去拿,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public final IContentProvider acquireExistingProvider(
 Context c, String auth, int userId, boolean stable) {
 synchronized (mProviderMap) {
 final ProviderKey key = new ProviderKey(auth, userId);
 final ProviderClientRecord pr = mProviderMap.get(key);
 if (pr == null) {
 return null;
 }

 IContentProvider provider = pr.mProvider;
 IBinder jBinder = provider.asBinder();
 if (!jBinder.isBinderAlive()) {
 //处理Binder不存活的情况
 handleUnstableProviderDiedLocked(jBinder, true);
 return null;
 }

 ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
 if (prc != null) {
 incProviderRefLocked(prc, stable); //增加引用计数
 }
 return provider;
 }
}

可以看到此处为通过auth构建出来的key到mProviderMap中查找ProviderClientRecord,而这个就是我们之前分析安装Provider时候所创建并且放置到这个map中去的。后面会检查Binder是否仍然存活,并返回。 在这里我们需要注意一点,如果安装我们之前分析安装的流程,我们在自己的app里面拿自己的ContentProvider这里是肯定可以拿到的,但是如果是其他的应用提供的ContentProvider这里很显然是拿不到的。因此我们需要继续回到acquireProvider方法去看其他部分的代码。

在第11行中,我们会到AMS中去获取ContentProviderHolder,如果拿到了远端的holder,但是我们本地的ProviderKey中的holder为空,说明我们本地还没有安装这个ContentProvider,需要等待,也就是执行第16行代码进入等待状态。而这个地方的解除等待在ContentProviderHelper类的publishContentProviders方法中,可以去之前分析安装过程的文章最后一部分查看。

而拿到holder之后,最后又去执行了一次installProvider方法,这里的安装跟我们之前的启动App安装是有一些不同的,我们放到后面再来分析。

然而前面的去AMS拿ContentProviderHolder代码我们还没有看,具体代码也仍然在ContentProviderHelper中,现在去看一下它的getContentProviderImpl()方法,内容比较长,先一点一点的贴代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//ContentProviderHelper.java getContentProviderImpl
synchronized (mService) {
 ProcessRecord r = null;
 if (caller != null) {
 r = mService.getRecordForAppLOSP(caller);
 }

 UserManagerService userManagerService = UserManagerService.getInstance();
 if (!isAuthorityRedirectedForCloneProfile(name)
 || !userManagerService.isMediaSharedWithParent(userId)) { //。mediastore需要特殊判断,这里会把那些情况给过滤掉
 cpr = mProviderMap.getProviderByName(name, userId);
 }
 ...

 ProcessRecord dyingProc = null;
 if (cpr != null && cpr.proc != null) {
 providerRunning = !cpr.proc.isKilled(); //检查ContentProvider目标进程是否被杀掉
 if (cpr.proc.isKilled() && cpr.proc.isKilledByAm()) {
 dyingProc = cpr.proc; //如果被杀了或者正在被杀就记录
 }
 }

 if (providerRunning) {
 cpi = cpr.info;
 if (r != null && cpr.canRunHere(r)) {
 checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, checkCrossUser,
 cpr.name.flattenToShortString(), startTime);
 ContentProviderHolder holder = cpr.newHolder(null, true);
 holder.provider = null;
 return holder;
 }
 //PLACEHOLDER1
 }
 //PLACEHOLDER2
}

以上的代码是我们会遇到的第一种情况,首先去拿到进程ProcessRecord,之后根据Provider的authority name和userId到ProviderMap中拿已有的ContentProviderRecord。拿到之后首先检查ContentProvider提供方的进程是否正在运行中,如果在运行中,并且canRunHere检查为true, 就会检查是否有权限来执行,有权限就会创建一个ContentProviderHolder传递出去。 canRunHere所做的判断代码如下:

1
2
3
4
public boolean canRunHere(ProcessRecord app) {
 return (info.multiprocess || info.processName.equals(app.processName))
 && uid == app.info.uid;
}

解释下就是首先判断Provider是否支持多个进程中运行,也就是在Manifest为provider配置了multiprocess=true,另外检查Provider所在进程和当前调用是否为同一个进程,这两者条件满足一个就可以。同时还要满足当前进程的UID和Provider的进程UID相同,这个在两者为同一个应用,或者两者共享签名,或共享UID的情况下满足。这种情况下就可以直接使用ContentProvider。这种情况会创建新的ContentProviderHolder传递到App进程,其中会携带ContentProviderRecord过去。此时我们看到的传到App进程的ContentProviderConnection也是为空,至于这个对象的用处是什么我们后面会分析。同时还会把Holder的成员provider设置为空,这个有什么用呢,可以后面再看installProvider方法。

在这里还有一个检查权限和是否可以联合运行的方法checkAssociationAndPermissionLocked,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if ((msg = checkContentProviderAssociation(callingApp, callingUid, cpi)) != null) {
 throw new SecurityException("Content provider lookup " + cprName
 + " failed: association not allowed with package " + msg);
}

if ((msg = checkContentProviderPermission(
 cpi, Binder.getCallingPid(), Binder.getCallingUid(), userId, checkUser,
 callingApp != null ? callingApp.toString() : null))
 != null) {
 throw new SecurityException(msg);
}

里面又分别调用了两个方法,第一个用于检查两个进程是否可以联合使用,默认是允许的,除非是系统内置应用或者预装应用会有比较严格的检查,我们这里不必关注。可以去看一下权限检查,这个比较重要:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private String checkContentProviderPermission(ProviderInfo cpi, int callingPid, int callingUid,
 int userId, boolean checkUser, String appName) {
 boolean checkedGrants = false;
 if (checkUser) { //对于普通应用这个值传过来的为true
 int tmpTargetUserId = mService.mUserController.unsafeConvertIncomingUser(userId);
 if (tmpTargetUserId != UserHandle.getUserId(callingUid)) {
 //检查是否有临时授权,这个一般是在Manifest中添加<grant-uri-permission>或者android:grantUriPermissions
 if (mService.mUgmInternal.checkAuthorityGrants(
 callingUid, cpi, tmpTargetUserId, checkUser)) {
 return null; //检查通过直接返回成功
 }
 checkedGrants = true;
 }
 userId = mService.mUserController.handleIncomingUser(callingPid, callingUid, userId,
 false, ActivityManagerInternal.ALLOW_NON_FULL,
 "checkContentProviderPermissionLocked " + cpi.authority, null);
 if (userId != tmpTargetuserId) {
 checkGrants = false;
 }
 }
 if (ActivityManagerService.checkComponentPermission(cpi.readPermission,
 callingPid, callingUid, cpi.applicationInfo.uid, cpi.exported)
 == PackageManager.PERMISSION_GRANTED) { //检查读权限,授权过返回
 return null;
 }
 if (ActivityManagerService.checkComponentPermission(cpi.writePermission,
 callingPid, callingUid, cpi.applicationInfo.uid, cpi.exported)
 == PackageManager.PERMISSION_GRANTED) { //写权限检查,授权过则返回成功
 return null;
 }
 PathPermission[] pps = cpi.pathPermissions;
if (pps != null) {
 int i = pps.length;
 while (i > 0) {
 i--;
 PathPermission pp = pps[i];
 String pprperm = pp.getReadPermission();
 if (pprperm != null && ActivityManagerService.checkComponentPermission(pprperm,
 callingPid, callingUid, cpi.applicationInfo.uid, cpi.exported)
 == PackageManager.PERMISSION_GRANTED) {
 return null;
 }
 String ppwperm = pp.getWritePermission();
 if (ppwperm != null && ActivityManagerService.checkComponentPermission(ppwperm,
 callingPid, callingUid, cpi.applicationInfo.uid, cpi.exported)
 == PackageManager.PERMISSION_GRANTED) {
 return null;
 }
 }
}

}

关于权限,前面的代码我已经加了相关的注释,我们可以对比官方文档,其中共检查了四种权限,分别是临时授权,路径授权,单独的读写授权和单一读写程序级别的授权。关于权限检查的更多内容,这里我们也先略过。此时我们可以继续回来继续分析getContentProviderImpl方法。我们继续看上面留的PLACEHOLDER 1处的代码:

1
2
3
4
5
6
checkAssociationAndPermissionLocked(r, cpi, callingUid, userId, checkCrossUser,
 cpr.name.flattenToShortString(), startTime);
conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage,
 callingTag, stable, true, startTime, mService.mProcessList,
 expectedUserId);

其中还有一些关于OOM设置的代码这里先跳过了,上面主要的代码也是检查权限以及这个incProviderCountLocked方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private ContentProviderConnection incProviderCountLocked(ProcessRecord r,
 final ContentProviderRecord cpr, IBinder externalProcessToken, int callingUid,
 String callingPackage, String callingTag, boolean stable, boolean updateLru,
 long startTime, ProcessList processList, @UserIdInt int expectedUserId) {
 final ProcessProviderRecord pr = r.mProviders;
 for (int i = 0, size = pr.numberOfProviderConnections(); i < size; i++) {
 ContentProviderConnection conn = pr.getProviderConnectionAt(i);
 if (conn.provider == cpr) {
 conn.incrementCount(stable);
 return conn;
 }
 }

 ContentProviderConnection conn = new ContentProviderConnection(cpr, r, callingPackage,
 expectedUserId);
 conn.startAssociationIfNeeded();
 conn.initializeCount(stable);
 cpr.connections.add(conn);
 if (cpr.proc != null) {
 cpr.proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_PROVIDER);
 }
 pr.addProviderConnection(conn);
 mService.startAssociationLocked(r.uid, r.processName, r.mState.getCurProcState(),
 cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
 if (updateLru && cpr.proc != null
 && r.mState.getSetAdj() <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
 processList.updateLruProcessLocked(cpr.proc, false, null);
 }
 return conn;
}

这里有不少关于Association相关的代码,而我们的应用一般不会走到这里。我们只需要关注其中创建Connection以及为他创建引用计数。关于它的计数,我们放到最好再看一下。

PLACEHOLDER 2处,首先处理的就是provider为运行的情况,这种情况就会回到Provider的进程去安装ContentProvider,这部分代码我们之前已经分析过了,这里略过。而我们是在使用者进程调用的此处的caller也不为空,再往后,则应该是如下的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
mService.grantImplicitAccess(userId, null, callingUid,
 UserHandle.getAppId(cpi.applicationInfo.uid));

if (caller != null) {
 synchronized (cpr) {
 if (cpr.provider == null) {
 if (cpr.launchingApp == null) {
 return null;
 }

 if (conn != null) {
 conn.waiting = true;
 }
 }
 }
 return cpr.newHolder(conn, false);
}

这里可以看到,就是先给调用的uid授权,设置wait 为true,创建一个ContentProviderHolder返回。这里是带着ContentProviderConnectionIContentProvider的。

代码讲解的部分只介绍了我们认为caller不为空的情况,实际上是更加复杂的,这里就把其中的完整流程流程图放在这里,如有需要可参考流程图以及之前的App启动时候的ContentProvider安装一起看。

---
title: getContentProviderImpl流程
---
flowchart TD
A(getContentProviderImpl) --> B(mProviderMap.getProviderByName)
B --> C(providerRunning = !cpr.proc.isKilled)
C --> D{Check providerRunning}
D --> |providerRunning == true|E{cpr.canRunHere}
E --> |No| I{CheckPermission}
E --> |Yes|F{ChecPermission}
F --> |Pass|G((Return local Holder))
F --> |Not Pass|H(Throw Exception)
I --> |Pass|J(incProviderCountLocked)
I --> |Not Pass|H
D --> |No|K(PMS.resolveContentProvider)
K --> L{CheckPermission}
L --> |Not Pass|H
L --> |Pass|M(Generate CPRecord)
M --> A1{cpr.canRunHere}
A1 --> |true|A2((Return local Holder))
A1 --> |False| A3{Process Live}
A3 --> |Process is live|A4(Install Provider)
A3 --> |Not Start Or Die| A5(Start Process)
A4 --> A6(incProviderCountLocked)
A5 --> A6
A6 --> B1(AMS.grantImplictAccess)
J --> B1
B1 --> B2{From customer Call}
B2 --> |Yes|B3((Return Remote Holder))
B2 --> |No|B4{cpr.provider==null}
B4 --> |Yes|B5((cpr.wait))
B5 --> B4
B4 --> |No|B6((Return Remote Holder))

看了这么多,我们就可以继续回去看App进程的代码了。在App进程就是执行我们前面说的installProvider过程。 我们可以继续分析query的过程,看代码我们知道调用的是IContentProvider的query方法,对于同UID的进程,IContentProvider为我们在instalProvider创建的本地的ContentProvider中的mTransport而其他的则是AMS调用带过来的IcontentProvider远端接口,我们这里以非本进程的情况来分析,它的获取是如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//android.content.ContentProviderNative.java
static public IContentProvider asInterface(IBinder obj)
{
 if (obj == null) {
 return null;
 }
 IContentProvider in =
 (IContentProvider)obj.queryLocalInterface(descriptor);
 if (in != null) {
 return in;
 }

 return new ContentProviderProxy(obj);
}

也就是说,如果是相同的UID的进程拿到的为Transport对象,如果是其他的则拿到的是ContentProviderProxy对象。

前面我们还有关于ContentProviderConnection还有很多东西没有介绍,这里继续看一下。首先是incProviderCountLocked方法中所调用的conn.incrementCount(stable)。在我看代码的过程中stable这个变量唯有这里使用了,我们继续看它的源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public int incrementCount(boolean stable) {
 synchronized (mLock) {
 if (stable) {
 mStableCount++;
 mNumStableIncs++;
 } else {
 mUnstableCount++;
 mNumUnstableIncs++;
 }
 return mStableCount + mUnstableCount;
 }
}

可以看到这个类主要记录了Stable和UnStable的调用次数,实际上AMS这一端stable和unstable似乎除了计数之外没有什么区别。但是在客户端installProvider的时候却是有区别的。我们之前分析的启动时候安装的情况stable都是为true,我们可以看看ActivityThread.installProvider如下的代码:

1
2
3
4
5
6
7
8
if (noReleaseNeeded) {
 prc = new ProviderRefCount(holder, client, 1000, 1000);
} else {
 prc = stable
 ? new ProviderRefCount(holder, client, 1, 0)
 : new ProviderRefCount(holder, client, 0, 1);
}
mProviderRefCountMap.put(jBinder, prc);

ProviderRefCount用于记录Provider的引用计数,其中用stableCount和unstableCount来计数,当我们不需要释放Provider的时候,两个数字都设置为了1000,当我们是stable的时候只设置stable数为1,unstable数量为0,当为unstable的时候也同理。之前我们是有看到对于已经存在的provider是通过incProviderRefLocked来增加起计数的。那我们有了增加计数,那么使用完之后也应该需要减少计数。在query的finally代码块中有如下代码:

1
2
3
4
5
6
if (unstableProvider != null) {
 releaseUnstableProvider(unstableProvider);
}
if (stableProvider != null) {
 releaseProvider(stableProvider);
}

他们最终调用的为ActivityThread.releaseProvider方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public final boolean releaseProvider(IContentProvider provider, boolean stable) {
 if (provider == null) {
 return false;
 }

 IBinder jBinder = provider.asBinder();
 synchronized (mProviderMap) {
 ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
 if (prc == null) {
 // The provider has no ref count, no release is needed. 
 return false;
 }

 boolean lastRef = false;
 if (stable) {
 if (prc.stableCount == 0) {
 return false;
 }
 prc.stableCount -= 1;
 if (prc.stableCount == 0) {
 lastRef = prc.unstableCount == 0;
 try {

 ActivityManager.getService().refContentProvider(
 prc.holder.connection, -1, lastRef ? 1 : 0);
 } catch (RemoteException e) {
 //do nothing content provider object is dead any way 
 }
 }
 } else {
 if (prc.unstableCount == 0) {
 return false;
 }
 prc.unstableCount -= 1;
 if (prc.unstableCount == 0) {
 lastRef = prc.stableCount == 0;
 if (!lastRef) {
 try {

 ActivityManager.getService().refContentProvider(
 prc.holder.connection, 0, -1);
 } catch (RemoteException e) {
 //do nothing content provider object is dead any way 
 }
 }
 }
 }

 return true;
 }
}

代码主要分了两个分支,分别对stable和unstable的情况进行处理,他们都是先把本地对应的ProviderRefCount中的数字减一,但是调用AMS.refContentProvider却不一样,stable count减为0的时候会直接调用,而unstable为0的时候要stableCount不为0才会调用。传递的参数也有区别,代码很简单就不详解了。直接去看ContentProviderHelperrefContentProvider方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
boolean refContentProvider(IBinder connection, int stable, int unstable) {
 ContentProviderConnection conn;
 try {
 conn = (ContentProviderConnection) connection;
 } catch (ClassCastException e) {

 }
 if (conn == null) {
 throw new NullPointerException("connection is null");
 }

 try {
 conn.adjustCounts(stable, unstable);
 return !conn.dead;
 } finally {

 }
}

这里的代码其实比较简单,就是调用ContentProviderConnectionadjustCounts,这个方法的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void adjustCounts(int stableIncrement, int unstableIncrement) {
 synchronized (mLock) {
 if (stableIncrement > 0) {
 mNumStableIncs += stableIncrement;
 }
 final int stable = mStableCount + stableIncrement;
 if (stable < 0) {
 throw new IllegalStateException("stableCount < 0: " + stable);
 }
 if (unstableIncrement > 0) {
 mNumUnstableIncs += unstableIncrement;
 }
 final int unstable = mUnstableCount + unstableIncrement;
 if (unstable < 0) {
 throw new IllegalStateException("unstableCount < 0: " + unstable);
 }
 if ((stable + unstable) <= 0) {
 throw new IllegalStateException("ref counts can't go to zero here: stable="
 + stable + " unstable=" + unstable);
 }
 mStableCount = stable;
 mUnstableCount = unstable;
 }
}

这里就是来根据传过来的参数来调整stableCountunstableCount,也就完成了这几个count的变化。也就是完成了AMS端的减少计数。

到此位置,我们也就拿到了IContentProvider,也就可以使用它提供的CRUD方法,进行数据的增删改查了。至于具体是如何查询数据,如何做到数据的跨进程共享,如何绕过Binder传输限制1MB实现跨进程传输数据,限于篇幅下次再来分析。

看完评论一下吧

Android源码分析: 应用启动安装ContentProvider分析

ContentProvider是Android应用开发的四大组件之一,并且源码相对于其他几个也是比较简单的。因此我们先来看看它的源码。ContentProvider的使用我们会涉及到外部程序调用应用的ContentProvider来查询数据,也有监听数据的变化,以及ContentProvider的安装。我们先来看安装部分的源码。

ContentProvider根据它的名字就知道,他是一个内容提供者,它提供了整删改查的接口,方便Android应用跨应用跨进程的数据共享,在Android系统中,相册,通讯录等等都是通过ContentProvider来共享数据让其他应用可以使用。看源码,我们需要关注两个点,一个是ContentProvider如何安装的,另一个就是当我们发起一个查询的时候,是怎样和内容提供的那个进程进行交互的。

ContentProvider的安装触发通常有两个场景,一是外部程序需要使用ContentProvider的时候,另一个是在应用Application启动的时候,这个一般触发的场景有,启动Service,启动Activity。这所有场景的共同点都是拉起进程,初始化Application。这两大场景来安装ContentProvider除了开始的路径会有差别,后面的部分大致都相等。因此我们这里以启动App同时安装Provider作为分析路径。

ContentProvider相关的类有如下这些:

classDiagram
class IContentProvider
<<interface>> IContentProvider
namespace SYSTEM_SERVER进程 {
class ContentProviderRecord
class ProviderMap
class ProcessProviderRecord
}
namespace 应用进程 {
class ContentProvider
class ContentProviderNative
class Transport
class ProviderClientRecord
class Binder
}
<<abstract>> ContentProvider
class ProviderInfo
<<abstract>> ContentProviderNative
ContentProviderRecord *-- IContentProvider
ContentProviderRecord *-- ProviderInfo
IContentProvider <|.. ContentProviderNative
Binder <|-- ContentProviderNative
ContentProviderNative <|-- Transport
ContentProvider *-- Transport
class ContentProviderHolder
ContentProviderHolder *-- IContentProvider
ProviderClientRecord *-- IContentProvider
ProviderClientRecord *-- ContentProvider
ProviderClientRecord *-- ContentProviderHolder
ContentProviderHolder *-- ProviderInfo
ProviderMap o-- ContentProviderRecord
ProcessProviderRecord o-- ContentProviderRecord

PackageManager解析出来的Provider信息通过ProviderInfo来保存,我们平时创建的ContentProvider它有一个内部类Transport,在它里面实现了Binder的客户端和服务端。通过它的Binder代理,AMS进程能够执行ContentProvider的增删改查,这不是本文的重点,下次再说。在App进程内,除了我们的ContentProvider对象,还会构建ProviderClientRecord对象,ContentProvider和它的binder对象和ProviderInfo信息会存在这个对象中。在服务端也有一个对应的ContentProviderRecord对象,里面存储了IcontentProvider binder对象和ProviderInfo。并且客户端和服务端的这些record都会存到Map中去,这样方便后续调用的时候查找。而构建所有这些record和把他们放入到Map中的过程,其实就是Provider的安装过程。

我们开始看代码,在应用进程创建完成之后,AMS会执行attachApplicationLocked从而来创建App的Application对象,因此我们从这里开始看,而进程的启动可以挖个坑以后再写。最初起点应该在ActivityManagerServiceattachApplicationLocked方法中:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
List<ProviderInfo> providers = normalMode
 ? mCpHelper.generateApplicationProvidersLocked(app)
 : null;
...
final ProviderInfoList providerList = ProviderInfoList.fromList(providers);
thread.bindApplication(processName, appInfo,
 app.sdkSandboxClientAppVolumeUuid, app.sdkSandboxClientAppPackage,
 providerList, null, profilerInfo, null, null, null, testMode,
 mBinderTransactionTrackingEnabled, enableTrackAllocation,
 isRestrictedBackupMode || !normalMode, app.isPersistent(),
 new Configuration(app.getWindowProcessController().getConfiguration()),
 app.getCompat(), getCommonServicesLocked(app.isolated),
 mCoreSettingsObserver.getCoreSettingsLocked(),
 buildSerial, autofillOptions, contentCaptureOptions,
 app.getDisabledCompatChanges(), serializedSystemFontMap,
 app.getStartElapsedTime(), app.getStartUptime());

可以看到AMS中首先是通过mCpHelper去生成当前应用的Provider列表,之后调用应用进程的bindApplication的时候再带过去。mCpHelper是一个ContentProviderHelper对象,我们先来看看它的generateApplicationProvidersLocked方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
List<ProviderInfo> generateApplicationProvidersLocked(ProcessRecord app) {
 final List<ProviderInfo> providers;
 providers = AppGlobals.getPackageManager().queryContentProviders(
 app.processName, app.uid, ActivityManagerService.STOCK_PM_FLAGS
 | PackageManager.GET_URI_PERMISSION_PATTERNS
 | PackageManager.MATCH_DIRECT_BOOT_AUTO, /*metaDataKey=*/ null)
 .getList();
 int numProviders = providers.size();
 final ProcessProviderRecord pr = app.mProviders;
 pr.ensureProviderCapacity(numProviders + pr.numberOfProviders());
 for (int i = 0; i < numProviders; i++) {
 // NOTE: keep logic in sync with installEncryptionUnawareProviders 
 ProviderInfo cpi = providers.get(i);
 boolean singleton = mService.isSingleton(cpi.processName, cpi.applicationInfo,
 cpi.name, cpi.flags);
 if (singleton && app.userId != UserHandle.USER_SYSTEM) {
 // This is a singleton provider, but a user besides the 
 // default user is asking to initialize a process it runs 
 // in... well, no, it doesn't actually run in this process, // it runs in the process of the default user. Get rid of it. 
 providers.remove(i);
 numProviders--;
 i--;
 continue;
 }
 final boolean isInstantApp = cpi.applicationInfo.isInstantApp();
 final boolean splitInstalled = cpi.splitName == null || ArrayUtils.contains(
 cpi.applicationInfo.splitNames, cpi.splitName);
 if (isInstantApp && !splitInstalled) {
 // For instant app, allow provider that is defined in the provided split apk. 
 // Skipping it if the split apk is not installed. 
 providers.remove(i);
 numProviders--;
 i--;
 continue;
 }

 ComponentName comp = new ComponentName(cpi.packageName, cpi.name);
 ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, app.userId);
 if (cpr == null) {
 cpr = new ContentProviderRecord(mService, cpi, app.info, comp, singleton);
 mProviderMap.putProviderByClass(comp, cpr);
 }
 pr.installProvider(cpi.name, cpr);
 if (!cpi.multiprocess || !"android".equals(cpi.packageName)) {
 app.addPackage(cpi.applicationInfo.packageName, cpi.applicationInfo.longVersionCode,
 mService.mProcessStats);
 }
 mService.notifyPackageUse(cpi.applicationInfo.packageName,
 PackageManager.NOTIFY_PACKAGE_USE_CONTENT_PROVIDER);
 }
 return providers.isEmpty() ? null : providers;
}

上面第三行代码为调用PackageManagerService去读取当前应用所有的ContentProvider信息并存储到ProviderInfo列表中,具体代码在ComputerEngine中,这里不分析了。ProviderInfo中存储了每一个ContentProvider的信息,包括它的组件名称,查询的authority,运行的进程,读写的权限等等。这里我们需要注意一下,我们在Manifest文件中声明ContentProvider的时候,是可以指定它所运行的进程的,在这个地方,我们传进来而的app也是一个ProcessRecord进程,它对应的是我们的一个进程的记录而不是app的记录,因此,我们拿到的ProviderInfo也是当前进程需要启动的所有进程。

随后会开启一个循环对每一个Provider做处理,在37行,通过packagename和name组合出ComponentName,这个和其他的构造Activity,Service等的类似。随后会尝试从ProviderMap中获取已经存在的记录,正常情况下这里都是空,如果一个App有多个进程,并且provider可以在多个进程运行,那么这里可能是可以拿到缓存的。

如果没有拿到缓存,我们会开始创建ContentProviderRecord,这是ContentProvider在AMS当中的记录,而它也会放到ProviderMap中,这样下次使用的时候就不需要再次创建了。

43行会调用ProcessProviderRecord的installProvider,这里只是把这条record存放到ProcessRecord的mProviders中去。

我们再回到AMS的代码中去,AMS当中会把我们的List<ProviderInfo>包装成一个ProviderInfoList对象,最后调用到ApplicationThread的bindApplication方法,从而把这些东西传递到App进程。

来到ActivityThread的源码,bindApplication会把AMS带过来的数据封装成AppBindData,通过sendMessage把传递ActivityThread类,并且调用它的handleBindApplication方法,其中我们会看到如下代码:

1
2
3
4
5
6
7
8
9
Application app;
app = data.info.makeApplicationInner(data.restrictedBackupMode, null);
if (!data.restrictedBackupMode) {
 if (!ArrayUtils.isEmpty(data.providers)) {
 installContentProviders(app, data.providers);
 }
}

mInstrumentation.callApplicationOnCreate(app);

上面第5行就是去安装ContentProviders。第二行是去创建我们的Application,其中会调用Application的attach方法,第9行是调用Application的onCreate方法,可见ContentProvider的一些方法是在他们两之间执行的,这也是为什么很多SDK通过使用ContentProvider来初始化他们的代码。继续看installContentProviders的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
final ArrayList<ContentProviderHolder> results = new ArrayList<>();
for (ProviderInfo cpi : providers) {
 ContentProviderHolder cph = installProvider(context, null, cpi,
 false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
 if (cph != null) {
 cph.noReleaseNeeded = true;
 results.add(cph);
}
}
ActivityManager.getService().publishContentProviders(
 getApplicationThread(), results);

上面的代码就是去遍历每一个ContentProvider去安装,我们继续看installProvider的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
ContentProvider localProvider = null;
IContentProvider provider;
if (holder == null || holder.provider == null) {
 Context c = null;
 ApplicationInfo ai = info.applicationInfo;
 if (context.getPackageName().equals(ai.packageName)) {
 c = context;
 }
 ...
 final java.lang.ClassLoader cl = c.getClassLoader();
 LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
 if (packageInfo == null) {
 // System startup case. 
 packageInfo = getSystemContext().mPackageInfo;
 }
 localProvider = packageInfo.getAppFactory()
 .instantiateProvider(cl, info.name);
 provider = localProvider.getIContentProvider();

 localProvider.attachInfo(c, info);
}
...
synchronized (mProviderMap) {
 IBinder jBinder = provider.asBinder();
 if (localProvider != null) {
 ComponentName cname = new ComponentName(info.packageName, info.name);

 holder = new ContentProviderHolder(info);
 holder.provider = provider;
 holder.noReleaseNeeded = true;
 pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
 mLocalProviders.put(jBinder, pr);
 mLocalProvidersByName.put(cname, pr);

 }

 ...
}

以上代码简化很多,仅保留启动App安装Provider的代码。第10行到第17行的代码,为通过LoadedApk通过反射去创建ContentProvider这个对象,随后通过它拿到IContentProvider对象,也就是它的Binder对象。随后调用attachInfo方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
private void attachInfo(Context context, ProviderInfo info, boolean testing) {
 mCallingAttributionSource = new ThreadLocal<>();
 mContext = context;
 if (context != null && mTransport != null) {
 mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
 Context.APP_OPS_SERVICE);
 }
 mMyUid = Process.myUid();
 if (info != null) {
 setReadPermission(info.readPermission);
 setWritePermission(info.writePermission);
 setPathPermissions(info.pathPermissions);
 mExported = info.exported;
 mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0;
 setAuthorities(info.authority);
 }

 ContentProvider.this.onCreate();
}

可以看到其中是为ContentProvider设置一些信息,包括它的Context,以及把Manifest上面设置的一些属性,权限之类的保存到当前这个对象中,最后会调用onCreate方法,这会执行我们重写时写的代码。

再回到installProvider方法,在28行,会创建ContentProviderHolder,随后调用installProviderAuthoritiesLocked把Provider和它所对应的authority对应,并创建ProviderClientRecord,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
 ContentProvider localProvider, ContentProviderHolder holder) {
 final String auths[] = holder.info.authority.split(";");
 final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
 ...
 final ProviderClientRecord pcr = new ProviderClientRecord(
 auths, provider, localProvider, holder);
 for (String auth : auths) {
 final ProviderKey key = new ProviderKey(auth, userId);
 final ProviderClientRecord existing = mProviderMap.get(key);
 if (existing != null) {
 } else {
 mProviderMap.put(key, pcr);
 }
 }
 return pcr;
}

上面的代码很简单,就是创建了ProviderClientRecord,其中保存了auths,我们创建的ContentProvider,以及IContentProvider,ContentProviderHolder,最后把每个authority作为key, ProviderClientRecord作为value,存放到了mProviderMap中。

上面的代码执行完之后,在installProvider中,又分别以binder对象和ComponentName对象为key,ProviderClientRecord对象为value存放到map中。

这一切做完之后,我们还需要回到installContentProviders方法的最后,看看第10行的代码,看代码名称是发布我们的Provider,那我们继续到AMS中去看代码,其中主要调用了如下代码:

1
mCpHelper.publishContentProviders(caller, providers);

继续去ContentHelper中看代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void publishContentProviders(IApplicationThread caller, List<ContentProviderHolder> providers) {
 synchronized (mService) {
 final ProcessRecord r = mService.getRecordForAppLOSP(caller);
 for (int i = 0, size = providers.size(); i < size; i++) {
 ContentProviderHolder src = providers.get(i);
 ContentProviderRecord dst = r.mProviders.getProvider(src.info.name);
 if (dst == null) {
 continue;
 }
 ComponentName comp = new ComponentName(dst.info.packageName, dst.info.name);
 mProviderMap.putProviderByClass(comp, dst);
 String[] names = dst.info.authority.split(";");
 for (int j = 0; j < names.length; j++) {
 mProviderMap.putProviderByName(names[j], dst);
 }
 r.addPackage(dst.info.applicationInfo.packageName,
 dst.info.applicationInfo.longVersionCode, mService.mProcessStats);
 synchronized (dst) {
 dst.provider = src.provider;
 dst.setProcess(r);
 dst.notifyAll();
 dst.onProviderPublishStatusLocked(true);
 }
 }
 }
}

上面的代码第3行,通过我们传过来的IApplicationThread来获取到我们的进程在AMS当中对应的ProcessRecord。随后会遍历每一个ContentProviderHolder,检查ProcessRecord当中的Record是否都有,随后会把ProcessRecord当中所存储的的CotentProviderRecord按照类名和authority分别存储到mProviderMap当中,ContentProviderHolder会存储到ContentProviderRecord当中。

最后也放一下整个流程的流程图方便看代码:

sequenceDiagram
autonumber
box LIGHTYELLOW SYSTEM_SERVER进程
participant AMS
participant ContentProviderHelper
participant PMS
participant ProcessProviderRecord
end
box LIGHTGREEN 应用进程
participant ApplicationThread
participant ActivityThread
participant ContentProvider
end
rect rgb(191, 223, 255)
note right of AMS: attachApplicationLocked
AMS->>+ContentProviderHelper: generateApplicationProvidersLocked
ContentProviderHelper->>+PMS: queryContentProviders
PMS-->>-ContentProviderHelper: List<ProviderInfo>
ContentProviderHelper->>ProcessProviderRecord: installProvider
ContentProviderHelper-->>-AMS: List<ProviderInfo>
AMS->>ApplicationThread: bindApplication(binder call)
end
ApplicationThread->>ActivityThread: handleBindApplication
rect rgb(191, 223, 255)
note right of ActivityThread: installContentProviders
ActivityThread->>ActivityThread: installProvider
ActivityThread->>ContentProvider: attachInfo
ActivityThread->>ActivityThread: installProviderAuthoritiesLocked
end
ActivityThread->>AMS: publishContentProviders
AMS->>ContentProviderHelper: publishContentProviders

至此,就执行完了所有的ContentProvider安装的工作。至于使用ContentProvider的场景,我们之后在继续分析。本文以Android13的代码分析,如果读者对照最好也是以同样版本的代码看。以上是本人关于Android代码阅读的一点分享,由于个人可能存在一些误区,难免会有理解错误,或者笔误,如有发现,欢迎指正,也欢迎读者与我交流Android技术。

(文中类图,时序图使用mermaid绘制,如果使用rss无法渲染,请点击原文查看)

看完评论一下吧

Android源码分析:ClientTransaction分析

分析Android Activity的启动过程,发现Android 在Android9.0中引入了ClientTransaction这一系列的对象,来简化system_server与App进程中处理Activity启动相关的任务。这里就来分析一下。

在服务端(system_server进程)主要有上面这些类,我们首先需要关注的就是ClientTransaction类,这个类在使用的时候主要是有以下几个成员:

1
2
3
4
private List<ClientTransactionItem> mActivityCallbacks;
private ActivityLifecycleItem mLifecycleStateRequest;
private IApplicationThread mClient;
private IBinder mActivityToken;

mClient是对应的app的ApplicationThread,他是一个Binder对象,mActivityToken则是Activity的Binder Token,这两个在很多地方都会看到。而mActivityCallbacks为ClientTransactionItem对象,比如说LaunchActivityItemNewIntentItem这些都是它的子类,同一个ClientTransactionItem中是可以有多个的。mLifecycleStateRequestActivityLifecycleItem,它的子类是PauseActivityItemStopActivityItem这些,每一个是希望Activity执行到的一个状态。相关的类的类图如下:

classDiagram
class Parcelable
<<interface>> Parcelable
class BaseClientRequest
<<abstract>> BaseClientRequest
class ClientTransactionItem
<<abstract>> ClientTransactionItem
BaseClientRequest <|-- ClientTransactionItem
Parcelable <.. ClientTransactionItem
BaseClientRequest: void preExecute(ClientTransactionHandler client, IBinder token)
BaseClientRequest: void execute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions)
BaseClientRequest: void postExecute(ClientTransactionHandler client, IBinder token, PendingTransactionActions pendingActions)
ClientTransactionItem: public int getPostExecutionState()
ClientTransactionItem: boolean shouldHaveDefinedPreExecutionState()
class ActivityTransactionItem
<<abstract>> ActivityTransactionItem
ClientTransactionItem <|-- ActivityTransactionItem
ActivityTransactionItem: ActivityClientRecord getActivityClientRecord(ClientTransactionHandler client, IBinder token)
ActivityTransactionItem <|-- LaunchActivityItem
ActivityTransactionItem <|-- NewIntentItem
ActivityTransactionItem <|-- ActivityResultItem
ActivityTransactionItem <|-- `..`
class ActivityLifecycleItem
ActivityTransactionItem <|-- ActivityLifecycleItem
ActivityLifecycleItem: public abstract int getTargetState()
ActivityLifecycleItem <|-- ResumeActivityItem
ActivityLifecycleItem <|-- PauseActivityItem
ActivityLifecycleItem <|-- StartActivityItem
ActivityLifecycleItem <|-- StopActivityItem
ActivityLifecycleItem <|-- DestroyActivityItem

启动Activity时候的代码调用在ActivityTaskSupervisor这个类的realStartActivityLocked方法中,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
final ClientTransaction clientTransaction = ClientTransaction.obtain(
 proc.getThread(), r.token);
clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
 System.identityHashCode(r), r.info,
 // TODO: Have this take the merged configuration instead of separate global 
 // and override configs. 
 mergedConfiguration.getGlobalConfiguration(),
 mergedConfiguration.getOverrideConfiguration(), r.compat,
 r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
 proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
 results, newIntents, r.takeOptions(), isTransitionForward,
 proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
 r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken));
final ActivityLifecycleItem lifecycleItem;
if (andResume) {
 lifecycleItem = ResumeActivityItem.obtain(isTransitionForward,
 r.shouldSendCompatFakeFocus());
} else {
 lifecycleItem = PauseActivityItem.obtain();
}
clientTransaction.setLifecycleStateRequest(lifecycleItem);
mService.getLifecycleManager().scheduleTransaction(clientTransaction);

从上面的代码我们看到ClientTransaction以及其他的一些使用到的比较多的对象,Android系统中都做了对象池,内部基本上都是数组维护,我们这里不分析了。

启动Activity就是创建了一个LaunchActivityItem并且设置了对应的LifecycleStateRequest,最后是通过LifecycleManager调用scheduleTransaction来执行。这里的mServiceActivityTaskManagerService的实例,lifecycleManagerClientLifecycleManager的实例,方法代码如下:

1
2
3
4
5
6
7
void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
 final IApplicationThread client = transaction.getClient();
 transaction.schedule();
 if (!(client instanceof Binder)) {
 transaction.recycle();
 }
}

这里ClientTransaction会获取Client,也就是IApplicationThread,system_server这一端是ApplicationThread的客户端,因此schedule和recycle方法都会执行。主要看一下schedule方法:

1
2
3
public void schedule() throws RemoteException {
 mClient.scheduleTransaction(this);
}

又调用了Client的scheduleTransaction方法,参数为我们的ClientTransaction方法,这是在客户端调用,那我们需要去服务端看这个方法的执行,实现也就是在ApplicationThread类当中。

1
2
3
public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
 ActivityThread.this.scheduleTransaction(transaction);
}

ActivityThreadClientTransactionHandler的子类,这个scheduleTransaction就在ClientTransaction当中,代码如下:

1
2
3
4
5

void scheduleTransaction(ClientTransaction transaction) {
 transaction.preExecute(this);
 sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
}

其中的preExecute方法就是直接调用了,内部实现就是分别调用所有的activityCallbackpreExecute方法,以及mLifecycleStateRequestpreExecute方法,而execute没有直接调用,而是通过消息发出去了,实现就是ActivityThread的sendMessage,我们也都知道是用它的H这个Handler,可以在它的handleMessage中找到如下代码:

1
2
3
4
5
6
case EXECUTE_TRANSACTION:
 final ClientTransaction transaction = (ClientTransaction) msg.obj;
 mTransactionExecutor.execute(transaction);
 if (isSystem()) {
 transaction.recycle();
 }

可以看到这里是通过TransactionExecutor来调用execute,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public void execute(ClientTransaction transaction) {
 final IBinder token = transaction.getActivityToken();
 if (token != null) {
 final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed =
 mTransactionHandler.getActivitiesToBeDestroyed();
 final ClientTransactionItem destroyItem = activitiesToBeDestroyed.get(token);
 if (destroyItem != null) {
 if (transaction.getLifecycleStateRequest() == destroyItem) {
 activitiesToBeDestroyed.remove(token);
 }
 if (mTransactionHandler.getActivityClient(token) == null) {
 return;
 }
 }
 }

 executeCallbacks(transaction);
 executeLifecycleState(transaction);
 mPendingActions.clear();
}

public void executeCallbacks(ClientTransaction transaction) {
 final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
 if (callbacks == null || callbacks.isEmpty()) {
 return;
 }

 final IBinder token = transaction.getActivityToken();
 ActivityClientRecord r = mTransactionHandler.getActivityClient(token);

 final ActivityLifecycleItem finalStateRequest = transaction.getLifecycleStateRequest();
 final int finalState = finalStateRequest != null ? finalStateRequest.getTargetState()
 : UNDEFINED;
 final int lastCallbackRequestingState = lastCallbackRequestingState(transaction);

 final int size = callbacks.size();
 for (int i = 0; i < size; ++i) {
 final ClientTransactionItem item = callbacks.get(i);
 final int postExecutionState = item.getPostExecutionState();

 if (item.shouldHaveDefinedPreExecutionState()) {
 final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
 item.getPostExecutionState());
 if (closestPreExecutionState != UNDEFINED) {
 cycleToPath(r, closestPreExecutionState, transaction);
 }
 }

 item.execute(mTransactionHandler, token, mPendingActions);
 item.postExecute(mTransactionHandler, token, mPendingActions);
 if (r == null) {
 r = mTransactionHandler.getActivityClient(token);
 }

 if (postExecutionState != UNDEFINED && r != null) {
 // Skip the very last transition and perform it by explicit state request instead. 
 final boolean shouldExcludeLastTransition =
 i == lastCallbackRequestingState && finalState == postExecutionState;
 cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction);
 }
 }
}

execute中调用了executeCallbacks和executeLifecycleState这两个方法,我们上面贴出了前一个方法的代码,这里先来分析。 首先来介绍一下TransactionExecutor的一个成员变量mTransactionHandler我们可以发现TransactionExecutor的初始化是在ActivityThread中,这个的mTransactionHandler就是ActivityThread。 这里首先会先判断Activity是否需要执行到某一个状态,也就是通过getPostExecutionState来设置,比如NewItentItem中是有设置的,而LaunchActivityItem则不需要。如果Activity需要进入指定的状态,则会调用cycleToPath来执行到对应的状态,我们后面再分析。 随后就会调用item的execute方法和postExecute方法。我们就来看一下LaunchActivityItem的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public void execute(ClientTransactionHandler client, IBinder token,
 PendingTransactionActions pendingActions) {
 Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
 ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
 mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
 mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
 client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
 mTaskFragmentToken);
 client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
 Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}

我们知道这里的client就是我们的ActivityThread,而这个handleLaunchActivity的实现也是在ActivityThread中实现,而启动Activity的参数都是在这个LaunchActivityItem里面的,他们通过Binder跨进程从system_server传到了app进程。这样一来原来放在ApplicationThread当中的handleLaunchActivity方法就抽离到LaunchActivity中了,ActivityThread这个文件中的很多代码就抽出去了。Activity启动的流程这里就不分析了,继续去看看后面的代码。

这里执行完了还是会检查是否需要把Activity推进到某一个状态,如果是LaunchActivityItem还是不需要的。我们继续去看executeLifecycleState的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void executeLifecycleState(ClientTransaction transaction) {
 final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest();
 if (lifecycleItem == null) {
 // No lifecycle request, return early. 
 return;
 }

 final IBinder token = transaction.getActivityToken();
 final ActivityClientRecord r = mTransactionHandler.getActivityClient(token);

 if (r == null) {
 // Ignore requests for non-existent client records for now. 
 return;
 }

 // Cycle to the state right before the final requested state. 
 cycleToPath(r, lifecycleItem.getTargetState(), true /* excludeLastState */, transaction);

 // Execute the final transition with proper parameters. 
 lifecycleItem.execute(mTransactionHandler, token, mPendingActions);
 lifecycleItem.postExecute(mTransactionHandler, token, mPendingActions);
}

可以看到其中还是首先调用了cycleToPath这个方法,但是我们需要注意这里这个方法的调用,excludeLastState这个值传的是true,也就是说如果我们设置的LifecycleStateResumeActivityItem那么它会把状态设置为ON_START而不是ON_RESUME。为什么这样做呢,因为后面还会调用lifecycleItem.execute,在其中我们会自行把状态推进到我们需要的状态,ResumeActivityItem的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public void execute(ClientTransactionHandler client, ActivityClientRecord r,
 PendingTransactionActions pendingActions) {
 Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
 client.handleResumeActivity(r, true /* finalStateRequest */, mIsForward,
 mShouldSendCompatFakeFocus, "RESUME_ACTIVITY");
 Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}

@Override
public void postExecute(ClientTransactionHandler client, IBinder token,
 PendingTransactionActions pendingActions) {
 ActivityClient.getInstance().activityResumed(token, client.isHandleSplashScreenExit(token));
}

上面的代码中可以看到execute中是调用的ActivityThreadhandleResumeActivity方法,从而让Activity执行resume并且进入ON_RESUME状态。 我们再来看一下cycleToPath方法:

1
2
3
4
5
6
private void cycleToPath(ActivityClientRecord r, int finish, boolean excludeLastState,
 ClientTransaction transaction) {
 final int start = r.getLifecycleState();
 final IntArray path = mHelper.getLifecyclePath(start, finish, excludeLastState);
 performLifecycleSequence(r, path, transaction);
}

其中首先是拿到Activity当前的状态,再通过mHelper拿到我们需要执行的所有状态,代码如下(异常判断代码移除了,这里关注重点):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public IntArray getLifecyclePath(int start, int finish, boolean excludeLastState) {


 mLifecycleSequence.clear();
 if (finish >= start) {
 if (start == ON_START && finish == ON_STOP) {
 mLifecycleSequence.add(ON_STOP);
 } else {
 // just go there 
 for (int i = start + 1; i <= finish; i++) {
 mLifecycleSequence.add(i);
 }
 }
 } else { // finish < start, can't just cycle down 
 if (start == ON_PAUSE && finish == ON_RESUME) {
 // Special case when we can just directly go to resumed state. 
 mLifecycleSequence.add(ON_RESUME);
 } else if (start <= ON_STOP && finish >= ON_START) {
 // Restart and go to required state. 

 // Go to stopped state first. for (int i = start + 1; i <= ON_STOP; i++) { 
 mLifecycleSequence.add(i);
 }
 // Restart 
 mLifecycleSequence.add(ON_RESTART);
 // Go to required state 
 for (int i = ON_START; i <= finish; i++) {
 mLifecycleSequence.add(i);
 }
 } else {
 // Relaunch and go to required state 

 // Go to destroyed state first. for (int i = start + 1; i <= ON_DESTROY; i++) { 
 mLifecycleSequence.add(i);
 }
 // Go to required state 
 for (int i = ON_CREATE; i <= finish; i++) {
 mLifecycleSequence.add(i);
 }
 }
 }

 // Remove last transition in case we want to perform it with some specific params. 
 if (excludeLastState && mLifecycleSequence.size() != 0) {
 mLifecycleSequence.remove(mLifecycleSequence.size() - 1);
 }

 return mLifecycleSequence;
}

可以看到以上代码还是比较简单的,就是按照顺序把Android Activity生命周期状态,按照当前的状态和需要执行结束的状态,把需要执行的放到数组中,最后再看看最后的那个状态要不要移除掉。我们继续看performLifecycleSequence的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private void performLifecycleSequence(ActivityClientRecord r, IntArray path,
 ClientTransaction transaction) {
 final int size = path.size();
 for (int i = 0, state; i < size; i++) {
 state = path.get(i);
 switch (state) {
 case ON_CREATE:
 mTransactionHandler.handleLaunchActivity(r, mPendingActions,
 null /* customIntent */);
 break;
 case ON_START:
 mTransactionHandler.handleStartActivity(r, mPendingActions,
 null /* activityOptions */);
 break;
 case ON_RESUME:
 mTransactionHandler.handleResumeActivity(r, false /* finalStateRequest */,
 r.isForward, false /* shouldSendCompatFakeFocus */,
 "LIFECYCLER_RESUME_ACTIVITY");
 break;
 case ON_PAUSE:
 mTransactionHandler.handlePauseActivity(r, false /* finished */,
 false /* userLeaving */, 0 /* configChanges */,
 false /* autoEnteringPip */, mPendingActions,
 "LIFECYCLER_PAUSE_ACTIVITY");
 break;
 case ON_STOP:
 mTransactionHandler.handleStopActivity(r, 0 /* configChanges */,
 mPendingActions, false /* finalStateRequest */,
 "LIFECYCLER_STOP_ACTIVITY");
 break;
 case ON_DESTROY:
 mTransactionHandler.handleDestroyActivity(r, false /* finishing */,
 0 /* configChanges */, false /* getNonConfigInstance */,
 "performLifecycleSequence. cycling to:" + path.get(size - 1));
 break;
 case ON_RESTART:
 mTransactionHandler.performRestartActivity(r, false /* start */);
 break;
 default:
 throw new IllegalArgumentException("Unexpected lifecycle state: " + state);
 }
 }
}

这里可以看到代码其实很简单,就是按照顺序调用mTransactionHandler也就是ActivityThread的各个生命周期需要执行的方法。

ActivityThread和TransactionExecutor的关系如下图:

classDiagram
class ActivityThread
class TransactionExecutor
class ClientTransactionHandler
<<abstract>> ClientTransactionHandler
ClientTransactionHandler <|-- ActivityThread
TransactionExecutor .. ClientTransactionHandler
ActivityThread *-- TransactionExecutor
ActivityThread: TransactionExecutor mTransactionExecutor
TransactionExecutor: ClientTransactionHandler mTransactionHandler
TransactionExecutor: execute(ClientTransaction transaction)

这里也画一下时序图方便看代码,发送端的代码比较简单,这里只画一下接收和执行端的图

sequenceDiagram
autonumber
ApplicationThread->>ActivityThread: scheduleTransaction
ActivityThread->>ClientTransaction: preExecute
ActivityThread->>ActivityThread: sendMessage:EXECUTE_TRANSACTION
ActivityThread->>TransactionExecutor: execute
TransactionExecutor->>TransactionExecutor: executeCallbacks
TransactionExecutor->>ClientTransaction: execute
TransactionExecutor->>ClientTransaction:postExecute
TransactionExecutor->>TransactionExecutor: executeLifecycleState
TransactionExecutor->>TransactionExecutor: cycleToPath
TransactionExecutor->>ActivityThread: handleToTargetStatus

上面的分析是system_server调用,当然App进程也是可以自己调用的,比如下面的代码:

1
2
3
4
5
6
private void scheduleResume(ActivityClientRecord r) {
 final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token);
 transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false,
 /* shouldSendCompatFakeFocus */ false));
 executeTransaction(transaction);
}

可以看到,在app进程中就直接调用TransactionExecutor的execut去执行了。 以上就是ClientTransaction的全部分析了。我也是在分析Android Activity启动的过程发现这些代码的,Activity的启动代码量巨大,在Android 9的时候把一部分代码抽离到ClientTransaction中去,在Android 10以后启动的相当一部分代码抽离到ActivityTaskManagerService中去了,让代码逻辑更清晰一点,不过绕来绕去的看代码也是很麻烦。

因此,这里先试试水,把其中的一小部分也就是ClientTransaction相关的先拿出来写一写。从我的角度看,为什么要抽出来ClientTransaction这个机制,原先所有的代码都需要在ApplicationThread中定义binder接口,而android随着各种屏幕的出现,小窗模式等等,这样的化每次有新功能都需要增加新的binder方法,而用了ClientTransaction则都可以通过scheduleTransaction来调用,同时ActivityThread内的代码也会有所减少,代码功能更加独立。

Android系统因为功能的代码,代码也更加复杂,很多地方因为许多的新功能多了很多逻辑判断,为我们看代码增加了难度。但是只要我们记住我们看的主线,关注我们一路传过来的值,关注我们自己会执行的那个分支,这样一路下来就可以把整个逻辑理清楚。

本文以Android13的代码分析,如果读者对照最好也是以同样版本的代码看。以上是本人关于Android代码阅读的一点分享,由于个人可能存在一些误区,难免会有理解错误,或者笔误,如有发现,欢迎指正,也欢迎读者与我交流Android技术。

(文中类图,时序图使用mermaid绘制,如果使用rss无法渲染,请点击原文查看)

看完评论一下吧

记录再次编译Android系统的坑

之前已经多次编译过Android系统的代码,但是一直没有静下来去阅读Android源代码。最近不太忙,决定开始好好读读系统源码。这篇文章作为开篇,先记录把Android系统编译出来,并且把源码阅读的环境准备好。

首先介绍一下,这次使用的是Ubuntu22.0.4 LTS版本,起初准备使用虚拟机安装,但是映射虚拟硬盘老是出问题,还是直接搞上了Windows & Ubuntu双系统,Ubuntu安装到移动硬盘里面,插上移动硬盘就能用Ubuntu,拔掉还能使用Windows系统。为什么要用移动硬盘,因为官方说了至少要留400G空间给源码和编译所需,不过我最后测下来300G也是够的。

为什么选择Ubuntu 22.0.4而不是最新的Ubuntu 24.0.4,是因为22.0.4后面的版本移除了libncurses.so.5,在22.0.4版本的时候我们还可以通过以下的命令安装,而后面的版本我们可能就只能使用android源码提供的相关库,并且自己去做文件的映射处理,反正我试过之后发现还是有问题,就重新安装了Ubuntu 22.0.4。

1
sudo apt install libncurses5

除此之外我们按照官方教程来做就行了。

首先是安装必备的软件工具包:

1
sudo apt-get install git-core gnupg flex bison build-essential zip curl zlib1g-dev libc6-dev-i386 x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils xsltproc unzip fontconfig

安装repo,可以自己下载最新版本,也只直接使用ubuntu的软件包安装:

1
sudo apt-get install repo

设置git的用户信息:

1
2
git config --global user.name "user name"
git config --global user.email "user@email.com"

创建一个目录,然后在这个目录下面初始化Android系统 Repo

1
repo init -u https://android.googlesource.com/platform/manifest -b master

-u后面是Android仓库的清单文件地址, -b是我们要拉出来的代码分支,可以是分支名称,也可以是tag名称,我选择的是Android 13的源码,用了这个tag: android-13.0.0_r83

然后就可以调用repo sync来下载代码了,这个过程可能需要等待几个小时,看你的网速,可以在后面加上 -c -j线程数来加快速度,-c表示拉当前分支,-j开启多个线程下载,如:

1
repo sync -c -j16

下载过程中如果中断了,重新执行这个命令可以继续下载,如果有遇到错误说有.lock文件,去子文件夹的.git文件夹下面找到相关的lock文件删除再重试就行了。

下载完我们在工作目录,首先执行以下代码,初始化工作环境和一些命令:

1
source ./build/envsetup.sh

执行以下命令,初始化我们要构建的目标:

1
lunch sdk_phone_x86_64-eng

以上这两句,我们需要每次启动一个终端或者重启电脑后都需要运行,不然m和emulator等命令都用不了。

然后后面的目标也可以不写,这样会进入一个选择列表让你选要构建的目标。 之后就可以输入m来构建系统了。

构建完系统在命令行执行emulator理论上就可以在模拟器中运行我们的系统了。但是我这里模拟器确黑屏了,只有一个小窗口,运行失败,命令行日志只看到libvulkan.so移动失败,但是看了以下模拟器的目录下面,是有这个文件的,然后在这台电脑上安装了android sdk,在其中创建了AVD,并启动它,发现是可以的。 这个时候想到一个妙招,就是把sdk的模拟器拷贝到我们源码的模拟器目录,把这边的模拟器给替换掉。然后神奇的事情发生了,我们编译出来的系统运行成功了。如果你遇到类似的情况也可以这样试试,把android sdk目录中的模拟器复制到./prebuilts/android-emulator/linux-x86_64目录下面。

关于阅读源码,之前大家都是使用idegen来生成适用于Android Studio的工程文件,但是默认会把所有文件都导入,打开速度极慢,我想这可能也是我之前无法把代码阅读下去的一个理由。在去年,android官方推出了Android Studio For Platform,可以这里下载: https://developer.android.com/studio/platform ,UI跟Android Stuido一样,不过它可以帮我们自动执行source 和launch命令,以及对于platform的module的设置,导入的时候我们选择自己要构建的target就行了,使用几天下来是很好用的。

除了使用Android Studio platform 在Android 10之后google还为我们提供了AIDEGen这个工具,我就没花时间去用了,感兴趣的可以看看这个博主的文章:https://juejin.cn/post/7276812358663733263

以上就是我这次的编译过程,期待对大家有用。在此立个Flag,后续把阅读Android源码的内容也写出来。

一些参考的资料:

  1. 下载 Android 源代码-官方资料
  2. 针对 AOSP 开发进行设置(9.0 或更高版本)-官方资料
  3. 构建Android-官方资料
  4. AndroidStudio导入Android系统源码

看完评论一下吧

一个Android开发者的Google IO 2024信息汇总

AI和大模型很火,今年的google io上面感觉各个方向都和AI有关,Android平台相关的东西倒是感觉不太多了。我这里整理一下Android相关的信息。Android主要就是Android 15的发布,以及jetpack compose的更新和google play的一些更新。

AI与Android

Android 14开始手机会内置Gemini Nano,开发者可以通过AI Core来调用AI的能力,目前是google pixel 8 pro和三星的s24已经内置了。除了端上,开发这还可以使用Firebase新提供的一些能力使用AI模型。

对于开发者来说,除了开发应用提供AI能力给用户,开发过程中也能体验到google提供的AI能力,下载Android Studio Koala即可使用,提供了类似github copilot的功能,有代码补全,自动代码生成,对话生成代码,UI设计图生成代码,代码重构等功能。

Android 15

每年一个大版本现在是Android系统的惯例了。google io开始的这天发布了Android 15 Beta2。

摄像头方面引入了低光增强功能,这个对于国产手机拍照功能基本都是已有功能,不过对于开发者可以通过Camera2的接口来使用相机提供的这个功能。同时还提供了新的闪光等强度控制API,在支持的设备上更好的控制相机硬件和算法。

文字方面,中日韩语的字体,android上的是NotoSansCJK,新的版本将是可变的了,这代表可以中文可以有更多的样式和变化了。Android 15开始,可使用 JUSTIFICATION_MODE_INTER_CHARACTER 利用字母间距将文本两端对齐,Android 8.0引入了字词间对其,这对于中文这种单个字符的语言是不友好的,新的字符间对其可以大大改善中文排版的美观度。Android 15中还提供了标记来控制换行,即 <nobreak>来避免换行,<nohyphen>避免断字。

1
2
3
<resources>
 <string name="pixel8pro">The power and brains behind <nobreak>Pixel 8 Pro.</nobreak></string>
</resources>

未使用nobreak 使用nobreak

对于GPU的使用,Android在7.0的时候引入了Vulkan,在今年他们有引入了ANGLE,这个对于普通开发者不需要关注,对于游戏开发者或者需要使用Open GL进行图形处理的需要注意,毕竟Google计划未来只支持通过ANGLE使用Open GL。

预测性返回动画之前在android14中就有了,但是需要在开发者模式中手动打开,普通用户还用不了,现在默认打开了。

最小target sdk version提高到24,低于此版本的将无法在Android15的手机上安装。

另外关于前台服务,隐私空间,系统UI等方面也有一些提升和新功能。更多内容可以看Android 15的文档: https://developer.android.com/about/versions/15/summary

Kotlin和Jetpack compose

Kotlin 主要介绍了jetpack的更多库适配了kmp,让开发者可以在更多平台共用代码。具体可查看文档:https://android-developers.googleblog.com/2024/05/android-support-for-kotlin-multiplatform-to-share-business-logic-across-mobile-web-server-desktop.html

Jetpack compose支持了share elements 动画,列表动画,Text支持链接和html富文本而不必使用ClickableText,以及一些性能提升。当然,最大的变化应该属于在kotlin 2.0的时候,compose的编译器将从谷歌的代码库移到kotlin的代码库,对于我们来说这意味着compose的编译器插件版本将和kotlin一样,减少我们升级版本的很多烦恼。更多详见:https://android-developers.googleblog.com/2024/05/whats-new-in-jetpack-compose-at-io-24.html

同时Compse对于更多尺寸的屏幕,以及手表,电视等等有了更好的支持。compose adaptive 库提供了一些api来让我们更多的适配各种屏幕尺寸,主要的是NavigationSuiteScaffold, ListDetailPaneScaffold, SupportingPaneScaffold。这次大会更新了Compose for wearos 的库,让更多功能稳定下来。正式发布了Compose for TV的1.0。这样下来所有的Android 平台都可以使用Compose进行开发了。

我们的桌面小组件,也发布了Jetpack Glance 1.1,来让我们支持使用Compose来编写桌面小组件。当然其中的一些widget和普通compose拥有一样的名称,但是却来自不同的package,因为最后还是会编译成remoteview,因此不能混用。

由此可见Android的原生开发以后将是Compose的天下,加油学吧。

Flutter

Dart支持了宏,从而对于Json的序列化和反序列化会更加简单。具体的使用方法: https://dart.dev/go/macros

Flutter 通过WebAssembly在浏览器上面运行性能更好,因此官方后面在Web的方向应该是WebAssembly了, 对于Flutter转js这个方案应该会被放弃。

Google Play

谷歌正式放出了隐私合规检查工具 Checks,可以帮助我们检查app的隐私合规的问题,检查app收集的用户数据,使用的权限,sdk等等,在当前各个国家对于隐私政策越来越严的当下,这个东西挺不错的。访问官网了解:https://checks.google.com/

谷歌在2021年发布了Google Play SDK Console给一些大的SDK开发者,现在这个Consle开放给了所有SDK开发者,借助这个平台SDK开发者可以在sdk有漏洞或者安全问题时,让使用到sdk的用户在谷歌得到通知来升级sdk。同时还可以查看sdk的用户数据,以及让应用开发者共享sdk的崩溃和卡顿日志给sdk的开发者,从而更快的解决问题。

谷歌还发布了Engage SDK, 帮助开发者在google play 内展示app的内容,吸引用户使用我们的应用,但是这个SDK需要开发者在媒体体验计划中或者应用用至少100K的DAU才能申请。当然除了这个sdk,新的google play还支持我们使用搜索关键定制商店详情页,这样可以对不同的来源做定制,提高用户下载app的转化率。应用详情页之前只能展示针对当前设备的屏幕截图和介绍,新版本将支持展示所有支持的设备的信息。

Play Integrity API也有更新,新增加了应用访问风险检查,服务端接口也可以使用play protect的验证。

新版本的Google play后台中还支持对于Deeplink 的管理,通过Google play甚至可以变更deeplink, 而不用更新app。

更多的内容还是请参考官方内容:

Android系统的更新:https://developer.android.com/about/versions/15/summary

Android Studio 的更新: https://android-developers.googleblog.com/2024/05/google-io-2024-whats-new-in-android-development-tools.html

Google Play的更新: https://android-developers.googleblog.com/2024/05/io-24-whats-new-in-google-play.html

Compose的更新: https://android-developers.googleblog.com/2024/05/whats-new-in-jetpack-compose-at-io-24.html

Flutter 更新: https://docs.flutter.dev/release/whats-new

看完评论一下吧

记国产手机无法在Chrome使用Passkey问题解决

众所周知,在国产Android系统上面,Google play service是被阉割掉的。部分厂商提供了打开google play service的选项,让我们可以登录google 账号,使用Google play store,以及部分的Google 云服务,但是Google password manager以及Credential Api确是没有的。在Android手机上面,Passkey是依赖于GMS和 Credential Manager API的,因此,国行手机上面也就没法使用passkeykey。不过使用Chrome浏览器的话,还是能够使用Passkey的,这是因为Chrome提供了相关的实现。然而,前几日Chrome升级后,我的Passkey突然就不能使用了。

首先尝试了重新卸载重装Chrome,手机的google账号管理里面测试passkey等等,结果还是不行。最后只得尝试在Google搜索,找了很多,发现了这样一个页面How do I use Passkeys in Google Chrome on Android?, 其中介绍的是如何在android手机上使用1password。其中关于chrome的flag部分引起来我的注意,因为Android 14后开始支持使用除了google password外其他的应用提供的passkey功能,所以我猜想可能是因为这个,google 最近改动了chrome关于passkey的逻辑,默认会使用手机系统的Credential Management Api而不是Chrome自己内置的Api。尝试了一下把这个Flag改为Disabled, 重启一下Chrome,Passkey又工作正常了。

操作方法为Chome地址栏中输入:

chrome://flags

然后搜索Passkey,出现这个条目之后,修改其设置。

另外还要说一下,虽然Chrome中的passkey使用问题解决了,但是因为手机内没有Credential Manager Api,应用还是没法使用passkey的。除此之外,通过手机扫码,让电脑使用passkey的时候,也会一直处在连接中,最后也会失败,目前这个也无解。因此,如果想要顺畅的使用Passkey只要两个解决办法,一个方式是换iPhone,另一个方法是买一个海外版的Android手机,比如Google 的Pixel,三星,或者尝试一下海外版本的ROM。

看完评论一下吧

Passkey在Android端的应用实践

Passkey,中文名通行密钥,他是WebAuthn的一部分,由FIDO联盟开发,可以做到让用户不使用用户名和密码,来直接验证身份。在2022年的WWDC上,Apple正式宣布了对Passkey的支持,当前10月份,google也宣布了对于passkey的支持。目前已经有一些应用支持了passkey,包括谷歌,微软,github,whatsapp等。最近在我们的Android应用上集成Passkey踩了很多的坑,简单记录一下。 Passkey

Passkey原理简介

简单来说,Passkey就是通过密钥对验证替代密码验证,原理和SSL/TLS验证类似,密钥对即公钥和私钥。用户的设备上存储的是私钥,创建的时候会将公钥发送到服务器端进行存储。验证的时候,服务端发送一段通过公钥加密的options信息,设备端使用私钥解密后回传给服务器,则能够验证成功。设备上的私钥需要验证用户的指纹、faceid或者yubikey才能使用。 Android系统需要Android 9.0以后,iOS系统需要iOS116以后才能支持,除此之外,Android设备需要登录google 账号,并且手机上有google play service才行,iOS需要开启iCloud 钥匙串。Appple 和 Google 还会通过他们的Cloud来帮助我们在多台设备之间同步同一个用户的身份验证,从而可以让我们实现同一个passkey在同一个用户的多台设备使用。 具体到Android系统,首先需要用户的手机系统在Android 9.0以上,我猜测这是因为在Android 9.0之后要求手机要有安全硬件,放到StrongBox的密钥必须是放到安全硬件中的。web用户可以在手机的chrome浏览器中使用passkey。对于Android应用则需要用户手机上安装的最新的(至少是2023年版本的)Google play service,且手机上的play services不能是中国特色的阉割版本,否则google password manager不能使用,应用也不能够使用Passkey。对于Android 14以后的系统,应用是可以使用第三方密码管理器的,不过我还没有实践,这里不做讨论。

Android应用接入

Passkey验证流程 在Android中接入Passkey其实是比较简单的,具体是有两个场景,分别是创建Passkey和验证Passkey。

准备工作

为了让Android系统能够识别我们的应用支持Passkey,需要在我们的后端服务器中配置我们的Digital Asset Links JSON文件,这个文件如果我们配置过Android的App Links支持,应该是已经有的,这个文件的路径应该为https://example.com/.well-known/assetlinks.json,配置的内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[ 
 {   
 "relation" : ["delegate_permission/common.handle_all_urls",      "delegate_permission/common.get_login_creds" ],
 "target" : {     
 "namespace" : "android_app", 
 "package_name" : "com.example.android",
 "sha256_cert_fingerprints" : [
 SHA_HEX_VALUE
 ]
 }
 }
]

其中的relation用来指定声明的关系,handle_all_urls就是指可以让app处理所有的app links, get_login_creds指的是处理登录验证。 target是表示该声明应用到的目标,namespace指定为android应用,package_name为我们应用的包名,sha256_cert_fingerprints为应用的签名SHA256。 这个文件放到我们的服务器,需要保证访问路径,跟我们前面说到的一样。并且请求返回的Content-Type为application/json。如果我们的服务端有robots.txt文件要配置允许google去访问该文件:

User-agent : *
Allow: /.well-known/

Google为我们提供了Credential Manager 来使用Passkey,需要添加如下依赖:

1
2
3
4
dependencies {
 implementation("androidx.credentials:credentials:1.3.0-alpha01")
 implementation("androidx.credentials:credentials-play-services-auth:1.3.0-alpha01")
}

上面第二个依赖,如果我们是只支持Android 14以上,且不用谷歌的密码管理器,可以不用。 Proguard文件中需要添加如下内容:

-if class androidx.credentials.CredentialManager
-keep class androidx.credentials.playservices.** {
*;
}

创建和验证Passkey都需要CredentialManager,创建方式如下:

1
val credentialManager = CredentialManager.create(context)

创建Passkey

创建Passkey 创建Passkey的流程如上图所示,首先需要从服务端的接口拿到一些数据,把这个作为requestJson创建CreatePublicKeyCredentialRequest去调用创建Credential,代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest(
requestJson = requestJson,
preferImmediatelyAvailableCredentials = true/false
)
coroutineScope.launch {
 try {
 val result = credentialManager.createCredential(
 context = activityContext,
 request = createPublicKeyCredentialRequest
 )
 } catch (e: CreateCredentialException) {
 handleFailure(e)
 }
}

上面的requestJson是我们从服务端拿到的,他应该是符合WebAuthn标准的json内容,prefImmediatelyAvailableCredentials, 如何设置为true,手机上没有可用的passkey注册提供者会直接报错,而不是看有没有混合可用的passkey。requestJson的demo如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
 "challenge": "abc123", //服务端随机生成的字符串,用于后续判断客户端回传,用于避免被攻击
 "rp": { //Replay party信赖方试题,用来表示应用信息,id为域名,需要和wellknown用的域名相同
 "name": "Credential Manager example",
 "id": "credential-manager-test.example.com"
 },
 "user": { //用户信息,id和name不能缺少,displayName是可选的
 "id": "def456",
 "name": "helloandroid@gmail.com",
 "displayName": "helloandroid@gmail.com"
 },
 "pubKeyCredParams": [ //公钥凭据支持的算法类型和密钥类型,这个在webAuthn网站上可以找到相同的文档
 {
 "type": "public-key",
 "alg": -7
 },
 {
 "type": "public-key",
 "alg": -257
 }
 ],
 "timeout": 1800000, //验证超时时间,毫秒
 "attestation": "none",
 "excludeCredentials": [ //可选项,排除的凭据,可通过这个来限制不让同一台设备设置多个passkey
 {"id": "ghi789", "type": "public-key"},
 {"id": "jkl012", "type": "public-key"}
 ],
 "authenticatorSelection": { //设置支持的类型
 "authenticatorAttachment": "platform", //platform就只支持手机内置的,若为cross-platform就可支持usb的验证,yubikey等
 "requireResidentKey": true, //设置为true,则可检测到的凭据会将用户信息存到passkey中,并可以让用户在进行身份验证时选择账号。
 "userVerification": "required" //用于设置使用设备屏幕锁定功能进行用户验证,默认是preferred,用户可以跳过,建议设置为required。
 }
}

以上json更多的解释可以看webauthn的网站: Web Authentication: An API for accessing Public Key Credentials - Level 3 (w3c.github.io)

我们客户端调用createCredential方法后拿到的结果,类似如下:

1
2
3
4
5
6
7
8
9
{
 "id": "KEDetxZcUfinhVi6Za5nZQ", //创建的passkey的base64网址编码id,需要后端存储
 "type": "public-key", //此值始终为public-key,不过ios手机上可能为passkey
 "rawId": "KEDetxZcUfinhVi6Za5nZQ",
 "response": {
 "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoibmhrUVhmRTU5SmI5N1Z5eU5Ka3ZEaVh1Y01Fdmx0ZHV2Y3JEbUdyT0RIWSIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9", //ArrayBuffer编码的客户端数据
 "attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUj5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGRdAAAAAAAAAAAAAAAAAAAAAAAAAAAAEChA3rcWXFH4p4VYumWuZ2WlAQIDJiABIVgg4RqZaJyaC24Pf4tT-8ONIZ5_Elddf3dNotGOx81jj3siWCAWXS6Lz70hvC2g8hwoLllOwlsbYatNkO2uYFO-eJID6A" //arraybuffer编码的证明对象,包含rpid,标志,公钥等
 }
}

我们需要将这个json回传给服务器端,服务器会从其中拿到公钥,并检查其中的数据跟服务端之前给客户端的challenge是否相同,相同后会将公钥,id,与用户id对应保存起来。

验证Passkey

验证Passkey 使用Passkey进行身份验证,首先也是需要从服务端拿一些信息,如下:

1
2
3
4
5
6
7
{
 "challenge": "T1xCsnxM2DNL2KdK5CLa6fMhD7OBqho6syzInk_n-Uo", //服务端生成防止被重现攻击,跟创建流程中的一样
 "allowCredentials": [], //允许的凭证,比如只允许当前设备之前创建的凭证
 "timeout": 1800000,
 "userVerification": "required",
 "rpId": "credential-manager-app-test.glitch.me" //信任实体Id
}

客户端使用这个json来进行验证:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
val getPublicKeyCredentialOption = GetPublicKeyCredentialOption(requestJson = requestJson)
val getCredRequest = GetCredentialRequest(listOf(getPublicKeyCredentialOption))
coroutineScope.launch {
 try {
 val result = credentialManager.getCredential(context = activityContext, request = getCredRequest)
 handleSignIn(result)
 } catch (e: GetCredentialException) {
 handleFailure(e)
 }
}

google play service的凭据提供者会找到与rpid匹配的凭据,并且弹窗让用户选择,如果我们设置了allowCredentials并且只有一条会直接弹出指纹或生物验证,成功后会返回类似如下信息给我们:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
 "id": "KEDetxZcUfinhVi6Za5nZQ",
 "type": "public-key",
 "rawId": "KEDetxZcUfinhVi6Za5nZQ",
 "response": {
 "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiVDF4Q3NueE0yRE5MMktkSzVDTGE2Zk1oRDdPQnFobzZzeXpJbmtfbi1VbyIsIm9yaWdpbiI6ImFuZHJvaWQ6YXBrLWtleS1oYXNoOk1MTHpEdll4UTRFS1R3QzZVNlpWVnJGUXRIOEdjVi0xZDQ0NEZLOUh2YUkiLCJhbmRyb2lkUGFja2FnZU5hbWUiOiJjb20uZ29vZ2xlLmNyZWRlbnRpYWxtYW5hZ2VyLnNhbXBsZSJ9",
 "authenticatorData": "j5r_fLFhV-qdmGEwiukwD5E_5ama9g0hzXgN8thcFGQdAAAAAA",
 "signature": "MEUCIQCO1Cm4SA2xiG5FdKDHCJorueiS04wCsqHhiRDbbgITYAIgMKMFirgC2SSFmxrh7z9PzUqr0bK1HZ6Zn8vZVhETnyQ",
 "userHandle": "2HzoHm_hY0CjuEESY9tY6-3SdjmNHOoNqaPDcZGzsr0"
 }
}

回传以上信息给服务端,服务端会通过存储的公钥来验证signature,能验证则说明是匹配的用户,验证通过。

踩坑分享与总结

从上面的接入代码可以看到,google 的credentials libary已经帮我们把大部分的工作做掉了,我们更多的主要是去除了requestJson中的一些参数,客户端的代码还是比较简单的。当然也遇到很多的坑。 首先就是一定要保证服务端的返回json是要符合协议的,比如authenticatorSelection要按照格式来写,pubKeyCredParams最好把常见的支持的都加上,rp信息中的的name和id一定要有,id一定要用域名。

如果存在测试版本和线上版本包名不同,签名不同的情况,一定要分别设置好digital asset设置,如果用的是同一个域名,那么是可以在一个asset文件中设置多个app的。

因为google play services是该功能的基础,所以开发测试阶段使用的网络需要能够流畅的访问谷歌的服务。使用的测试机最好也是能够使用完整的google play services的。如果测试中出现类似下面的错误,可以去尝试升级google play services解决。

During create public key credential, fido registration failure: advy: Algorithm with COSE value -8 not supported

总体来说,如果是一个出海应用,并且对于应用的安全性有很高的要求,passkey是一个很好的解决方案。但是对于一个中国的应用来说,目前passkey还是不可用的,如果使用类似的公钥-私钥验证机制,那可以使用FIDO来实现。当然因为不是像google 和apple这样对于passkey支持这么好,实现起来会更加复杂,以后有机会可以再写一写。

参考资料

看完评论一下吧

记解决MaterialButton背景颜色与设置值不同

最近的开发过程中,因为设计的风格采用了Android的Material风格,因此我们在项目开发过程中也使用了Android 的Material组件和主题,但是开发过程中法使用MaterialButton的时候,我们给按钮设置的背景颜色和实际展示的背景颜色不一样。网上搜索了一番也没找到原因,于是便开始查阅MateriButton的代码。

期望的背景 实际的背景

经过一番研读终于找到原因,最终通过在style文件中添加如下设置解决。

1
<item name="elevationOverlayEnabled">false</item>

MaterialButton介绍

Google在Material库中给我们提供了MaterialButton组件,我们可以通过设置很多属性来设置它的样式,仅仅背景就可以设置它的边框,背景的圆角,背景的颜色,甚至可以自己设置背景的形状,除此之外还能设置文字样式,按钮上的图标等等。因为我们今天的主题是关于背景的问题的,这里我们仅仅介绍背景设置相关的东西。

正常情况下,对于一个Android的View我们可以通过设置setBackground() setBackgroundColor() setBackgroundResource()等方法来设置View的背景,而MaterialButton为了让我们能够直接修改颜色,设置圆角,则重写了setBackgroundColor()方法,实现如下:

1
2
3
4
5
6
7
8
public void setBackgroundColor(@ColorInt int color) {
 if (isUsingOriginalBackground()) {
 materialButtonHelper.setBackgroundColor(color);
 } else {
 // If default MaterialButton background has been overwritten, we will let View handle 
 // setting the background color. super.setBackgroundColor(color); 
 }
}

通过查阅代码,我们可以看到MaterialButton内部通过MaterialButtonHelper这个类来管理它的背景,如果我们没有通过setBackground给这个Button设置一个背景Drawable,MaterialButtonHelper会帮我们创建一个Drawable,当我们调用 setBackgroundColor的时候,实际上也会在MaterialButtonHelper内部处理,至于MaterialButtonHelper如何创建BackgroundDrawabale的流程,可以自行去看源码。

修改背景颜色的具体过程

上面说到的MaterialButtonHelper创建的背景Drawable就是一个MaterialShapeDrawable,前面的setBackgroundColor调用后实际上会调用到MaterialShapeDrawable的setTintList()方法来修改背景的颜色,实际上就是使用了Tint来修改我们的背景,在draw方法中我们可以看到如下的代码:

1
fillPaint.setColorFilter(tintFilter);

以上代码实现来背景颜色的修改。

在setTintList()的代码中和调用的函数中,我们发现了这样一行代码它修改了tintFilter这个变量。

1
2
3
4
5
6
tintFilter =
 calculateTintFilter(
 drawableState.tintList,
 drawableState.tintMode,
 fillPaint,
 /* requiresElevationOverlay= */ true);

此处的tintList为我们刚刚设置的颜色,tintMode默认值是SRC_IN均不为空,该方法内部又调用了calculateTintColorTintFilter()方法。

到这里我们总结一下,setBackgroud是通过tint来修改了背景的颜色,tint的实现其实就是使用了Android画笔的颜色混合滤镜(PorterDuffColorFilter)来实现的。

背景颜色为什么与设置的不同?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
private PorterDuffColorFilter calculateTintColorTintFilter(
 @NonNull ColorStateList tintList,
 @NonNull PorterDuff.Mode tintMode,
 boolean requiresElevationOverlay) {
 int tintColor = tintList.getColorForState(getState(), Color.TRANSPARENT);
 if (requiresElevationOverlay) {
 tintColor = compositeElevationOverlayIfNeeded(tintColor);
 }
 resolvedTintColor = tintColor;
 return new PorterDuffColorFilter(tintColor, tintMode);
}

以上是calculateTintColorTintFilter方法的代码,我们知道requiresElevationOverlay总是true,那就一定会执行到compositeElevationOverlayIfNeeded,那就说明这个方法内部把我们的颜色修改了,查看其实现,果然如此,代码如下:

1
2
3
4
5
6
7
protected int compositeElevationOverlayIfNeeded(@ColorInt int backgroundColor) {
 float elevation = getZ() + getParentAbsoluteElevation();
 return drawableState.elevationOverlayProvider != null
 ? drawableState.elevationOverlayProvider.compositeOverlayIfNeeded(
 backgroundColor, elevation)
 : backgroundColor;
}

翻阅代码我们可以看到drawableState是在Drawable创建的时候就创建了,而elevationOverlayProvider则是在MaterialButtonHelper中调用drawable的initializeElevationOverlay方法来初始化的,为ElevationOverlayProvider,而正是它的compsiteOverlayIfNeeded方法来变化了颜色。

修改颜色为我们设置的颜色

看到这里,首先想到是吧这个elevationOverlayProvider设置为null,那我们不就不会调用这个方法,颜色也就是我们最初设置的颜色了吗。然而,我们没法在MaterialButton中或者它的子类中去拿到Drawable的DrawableState,因此只能作罢。

再来继续看,ElevationOverlayProvider的compositeOverlayIfNeeded方法,它既然有个IfNeeded,那看来也不是一定会改变颜色了,继续看它的实现。

1
2
3
4
5
6
7
public int compositeOverlayIfNeeded(@ColorInt int backgroundColor, float elevation) {
 if (elevationOverlayEnabled && isThemeSurfaceColor(backgroundColor)) {
 return compositeOverlay(backgroundColor, elevation);
 } else {
 return backgroundColor;
 }
}

可以看到满足elevationOverlayEnabled且 backgroudColor和themSurfaceColor相同的情况下才会改变颜色,原来我设置的按钮颜色和主题中设置的colorSurface相同,此处我不可能去修改按钮颜色,我们只能去看看elevationOverlayEnabled能否修改,查看ElevationOverlayProvider的源码可以看到初始化的时候通过如下代码初始化了该值。

MaterialAttributes.resolveBoolean(context, R.attr.elevationOverlayEnabled, false)

看到这里,我们也就知道该如何解决我们的问题了,也就是在AppTheme或者当前Activity的Theme中修改elevationOverlayEnabled 为false。

除了Button之外,Material的其他一些组件也有同样使用这个属性来设置是否修改颜色的,遇到的时候也可以同样的方式解决。

看完评论一下吧

新版Android Studio Logcat view使用简明教程

logcat-window.png

从Android Studio Dophin开始,Android Studio中的默认展示了新版的logcat。新版的logcat色彩上是更加的好看了,不同的tag会有不同的颜色,不同level等级的log默认也有不同的颜色。log过滤修改的更简洁了,当然使用起来也更加复杂了。原先的log视图只需要勾选就可以选择不同level的log了,只需要选择只展示当前应用的log就可以过滤掉其他应用的log了,但是新版只提供了一个输入框去过滤。在经过几个月的适应和对于官方文档的学习后,终于使用了,这里简单记录和分享一下。

定义自己专属的log view

log view 默认提供了两种视图,Standard View 和Compat View。Stand View会展示每一条log的日期,时间,进程线程id,tag,包名,log level以及message。Compat View只展示时间,log level和详细的message。可以通过log view左边的Configure Logcat Formatting Options按钮来修改,同时这个按钮中还有一个Modify Views选项可以来修改standard和 Compat视图的具体展示内容,可以定制自己的logview样式,如下图所示。

logcat-view-setting.jpg

个性化的logcat 视图不仅仅是可以自定义展示的内容,还可以修改log和filter的配色方案。前往Settings(Windows)/Preferences(Mac) ->Editor -> Color Scheme,选择Android Logcat即可修改log 的颜色,选择Logcat Filter即可修改filter的颜色。

以上修改的是logcat view的外表,我们还可以修改它的内核,一个是logcat循环滚动区的大小,以及新logcat window的默认filter,可以通过前往Settings(Windows)/Preferences(Mac) -> Tools -> Logcat 设置。

一些操作技巧

在标准布局下,或者我们的log太长的时候,一屏通常展示不下,我们需要不停的向右滑动,滚动才能看到log的信息,我们可以用log view左侧的Soft-Wrap logcat-soft-wrap.png 按钮来让log换行。

左侧的Clear Logcat按钮可以清空logcat。左侧的Pause按钮可以暂停logcat的输出,方便看错误日志,可以避免关心的日志被新的日志冲掉。

新版本中,可以通过点击logcat tab右侧的New tab logcat-new-tab.png 按钮来同时创建多个logcat view窗口。这种方式创建的不能同时展示,而利用logcat view左侧的split Panels 按钮则可以创建多个窗口,并且同时展示。每一个窗口都可以设置自己要展示的连接设备,展示样式,以及过滤选项。这样就可以很方便的同时观察多种log。

logcat-multi-window.jpg

通过键值对来过滤Log

logcat-query-suggestions.png

新的过滤器,看起来简单,实际上更加复杂且强大了。通过Ctrl+Space按键可以查看系统建议的一些查询列表。这里介绍一下查询中会用到的键:

  • tag: 匹配日志的tag字段
  • package:匹配记录日志的软件包名,其中特殊值mine匹配当前打开项目对应的应用log。
  • process:匹配记录日志的进程名
  • message:匹配日志中我们自己填写的message的部分。
  • level:与指定或者更高级别的日志匹配,比如debug或者error,输入level后as会自动提示可以选择。
  • age:让窗口中只保留最近一段时间的log,值为数字加单位,s表示秒,m表示分钟,h表示小时,d表示天。如age:10s就只保留最近10s的日志。
  • is: 这个键有两个固定的value取值,crash匹配应用崩溃日志,stacktrace匹配任意类似java堆栈轨迹的日志,这两个对于看crash查问题是非常好用的。

这么多的键匹配,是可以逻辑组合的。我们可以使用&|以及圆括号,系统会强制执行常规的运算符优先级。level:ERROR | tag:foo & package:mine 会被强转为level:ERROR | (tag:foo & package:mine ) 。如果我们没有填写逻辑运算符,查询语言会将多个具有相同键的非否定过滤视为OR,其他过滤视为AND。 如: tag:fa tag:ba package:mine 计算逻辑是 (tag:fa | tag:ba) & package:minetag:fa -tag:ba package:mine 计算逻辑是 tag:fa & -tag:ba & package:mine。这里的-用来表示否定,既tag不包含ba的情况。

新版的logcat view当然也是支持正则的,tag、message、package、process这几项是支持正则的。使用正则需要在键后面加一个~,例如: tag~:My.*Report。 除了正则这个选项之外,这几个键还有完全匹配和包含字符串即可的选项。不加修饰符号就是包含指定的字符串即可匹配。如果后面加=则要完全匹配才可以,例如process=:system_serverprocess:system_ser可以匹配到system_server的log,但是process=:system_ser则无法匹配到。

同时如上几个匹配选项都支持和前面说的否定符号连用如:-process=:system_server

既然新版支持了这么复杂和强大过滤功能,如果每次都现想现写,那肯定是头皮发麻。as也为我们提供了收藏和历史记录功能。点击右侧的的星星按钮即可收藏当前的过滤条件,点击左侧的漏斗即可查看历史和收藏,并且可以删除不想要的记录。

切换回旧版log view

最后的最后,如果你觉得新版本适应不了,还是想要切换回旧版本的log view,还想要保留新版的android studio,也还是可以通过修改设置进行切换的。 前往Settings(Windows)/Preferences(Mac) -> Experimental, 反选Enable new logcat tool window 即可,如下图所示。

disable_new_logview.jpg

学习工具的目的,是为了让工具更好的为我们服务。希望大家都能够通过使用as提供的新功能来提高效率,从而有更多的时间去风花雪月。

参考:https://developer.android.com/studio/debug/logcat

看完评论一下吧

2022年在MacOs上编译AOSP

之前苦于电脑磁盘空间比较小,而android系统的源码越来越大,一直没有机会自己编译Android系统。这次换了电脑,磁盘足够大,可以尝试一下了。 而android源码的网站红色的字写着 Platform development on MacOS isn’t supported as of June 22, 2021. ,我就知道不会那么容易了。

我的电脑环境:

2021款 M1芯片 Macbook Pro 16GB运行内存 MacOS Monterey(12.1)

环境准备

安装xcode和相关工具

  • 从appstore安装Xcode
  • 安装xcode command line tools:

$ xcode-select –install

  • 安装Rosetta(M1芯片需要,x86芯片不需要)

$ softwareupdate –install-rosetta

创建大小写敏感的磁盘映像

我们先创建个350GB的大小

$ hdiutil create -type SPARSE -fs ‘Case-sensitive Journaled HFS+’ -size 350g ~/forest.dmg

设置环境变量

我本地用的zsh,直接在.zsh_env中写,并配置挂载映像的方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 设置最大打开文件数量,防止编译过程中因为打开文件数量太多失败
ulimit -S -n 2048
# 编译缓存
export USE_CCACHE=1
# 挂载映像
function mountForest { hdiutil attach ~/forest.dmg.sparseimage -mountpoint /Volumes/Forest; }
#卸下挂载
function umountForest() { hdiutil detach /Volumes/Forest; }
export PATH="/opt/local/bin:/opt/local/sbin:$PATH"
export PATH=~/.bin:$PATH

编辑完保存之后,执行一下如下语句使配置当前就能生效 source ~/.zshenv

下载源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ mkdir ~/.bin
$ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo #下载repo
$ chmod a+x ~/.bin/repo #设置repo权限
$ mountForest #挂载映像
$ cd /Volumes/Forest
$ mkdir aosp_mata
$ cd aosp_mata
$ git config --global user.name "Your Name"
$ git config --global user.email "you@example.com"

$ repo init -u https://android.googlesource.com/platform/manifest -b android-11.0.0_r48 #可自选版本,我这用的是11.0.0的最后一个tag
$ repo sync

之后便是无尽的等待去下载源码,国内的网络下载不了,自己想办法爬墙吧。

开始编译

如果没有问题,在源码目录直接执行以下命令就可以编译了

1
2
3
$ source build/envsetup.sh
$ lunch aosp_arm-eng
$ make -j4

lunch后面的参数也可以不填,则会显示出来所有可以的选项,自己选一个进行设置就行。 make就开始编译, -jN用于设置任务数, N应该介于计算机上的CPU线程数的1-2倍之间为宜,我的M1 MAC 是10核,就先设置了24。

理论上这样就可以慢慢的等就能编译成功了,然而,如果可以这么简单就不需要我写一篇文章了,直接看android官方文档就行了。为了节省时间,先把下面的问题改改再编译。

问题解决

问题一:Could not find a supported mac sdk

大概是这样的log

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[ 94% 171/181] test android/soong/cc
FAILED: out/soong/.bootstrap/soong-cc/test/test.passed
out/soong/.bootstrap/bin/gotestrunner -p ./build/soong/cc -f out/soong/.bootstrap/soong-cc/test/test.passed
-- out/soong/.bootstrap/soong-cc/test/test -test.short
--- FAIL: TestDefaults (10.86s)
 cc_test.go:3075: "Could not find a supported mac sdk: [\"10.10\" \"10.11\" \"10.12\" \"10.13\" \"10.14\" \"10.15\"]"
 cc_test.go:3075: "Could not find a supported mac sdk: [\"10.10\" \"10.11\" \"10.12\" \"10.13\" \"10.14\" \"10.15\"]"
 cc_test.go:3075: "Could not find a supported mac sdk: [\"10.10\" \"10.11\" \"10.12\" \"10.13\" \"10.14\" \"10.15\"]"
 cc_test.go:3075: "Could not find a supported mac sdk: [\"10.10\" \"10.11\" \"10.12\" \"10.13\" \"10.14\" \"10.15\"]"
 cc_test.go:3075: "Could not find a supported mac sdk: [\"10.10\" \"10.11\" \"10.12\" \"10.13\" \"10.14\" \"10.15\"]"
 cc_test.go:3075: "Could not find a supported mac sdk: [\"10.10\" \"10.11\" \"10.12\" \"10.13\" \"10.14\" \"10.15\"]"
--- FAIL: TestDoubleLoadbleDep (0.05s)
 cc_test.go:733: "Could not find a supported mac sdk: [\"10.10\" \"10.11\" \"10.12\" \"10.13\" \"10.14\" \"10.15\"]"
..... ....

原因是指不到指定的mac sdk 可以看一下/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs文件下有没有 MacOSX12.3.sdk , 然后在然后在 /build/soong/cc/config/x86_darwin_host.go 文件中找到 “darwinSupportedSdkVersions“ 添加 MacOSX12.3.sdk 对应的版本号——12.3,如果你的macosx的sdk是别的就填别的。

darwinSupportedSdkVersions = []string{
"10.10",
"10.11",
"10.12",
"10.13",
"10.14",
"10.15",
"12.3",
}

另外你也可以到https://github.com/phracker/MacOSX-SDKs/releases 去下载10.15的sdk放到上面的文件夹里面。

问题二: v8引擎无法编译,一些文件找不到

由于2021年后,官方不维护mac上的开发环境了,所以external/v8下面有很多编译错误,这里直接采用回滚代码的方式,我是回滚到了 Upgrade V8 to 8.8.278.14 提交的前一个Commit

1
2
cd external/v8
git checkout 9304fbb

问题三: undeclared identifier ‘PAGE_SIZE’

1
2
3
4
5
6
system/core/base/cmsg.cpp:36:21: error: use of undeclared identifier 'PAGE_SIZE'
 if (cmsg_space >= PAGE_SIZE) {
 ^
system/core/base/cmsg.cpp:78:21: error: use of undeclared identifier 'PAGE_SIZE'
 if (cmsg_space >= PAGE_SIZE) {
 ^

看起来是PAGE_SIZE这个常量没定义,那就去补上呗。去 system/core/base/cmsg.cpp 文件开头添加 PAGE_SIZE 的声明

1
2
3
#ifndef PAGE_SIZE
#define PAGE_SIZE (size_t)(sysconf(_SC_PAGESIZE))
#endif

问题四: incompatible pointer types passing ‘unsigned long *’ to parameter of type ‘uint32_t *‘

external/python/cpython2/Modules/getpath.c:414:50: error: incompatible pointer types passing 'unsigned long *' to parameter of type 'uint32_t *' (aka 'unsigned int *') [-Werror,-Wincompatible-pointer-types]
else if(0 == _NSGetExecutablePath(progpath, &nsexeclength) && progpath[0] == SEP)

external/python/cpython2/Modules/getpath.c 中:

1
2
3
4
5
6
7
#ifdef __APPLE__
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
 uint32_t nsexeclength = MAXPATHLEN;
#else
 unsigned long nsexeclength = MAXPATHLEN;
#endif
#endif

改成:

1
2
3
#ifdef __APPLE__
 uint32_t nsexeclength = MAXPATHLEN;
#endif

external/python/cpython3/Modules/getpath.c 中的:

1
2
3
4
5
6
7
8
#ifdef __APPLE__
char execpath[MAXPATHLEN + 1];
#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4
 uint32_t nsexeclength = Py_ARRAY_LENGTH(execpath) - 1;
#else
 unsigned long nsexeclength = Py_ARRAY_LENGTH(execpath) - 1;
#endif
#endif

改成:

1
2
3
4
#ifdef __APPLE__
char execpath[MAXPATHLEN + 1];
uint32_t nsexeclength = Py_ARRAY_LENGTH(execpath) - 1;
#endif

以上问题改完应该就可以编译成功了,如果是在后面java编译阶段失败,可以先试试重新执行make试试,如果还是不行的话就上网找解决方案吧。

编译idegen模块导入Android Studio

使用如下命令编译idegen模块:

1
mmm development/tools/idegen/

完成之后,执行如下命令:

1
development/tools/idegen/idegen.sh

之后就会在根目录生成对应的 android.iprandroid.iml IEDA工程配置文件,之后在IDEA或者Android Studio中打开android.ipr就能浏览源码了。

参考

参考了以下资料和网友的分享,非常感谢:

看完评论一下吧

简单聊聊Android Architecture Componets

Google IO大会进行中,本次大会Android最大的新闻当属Android O以及Kotlin被官方认可。我发现了原来还有发布官方的架构库,以及推荐使用指南,分享给大家。

架构原则

  • 关注分离
  • 模型驱动UI,优先持久化模型

新架构

架构图

如上图所示,为新的架构模式:

Activity/Fragment

UI层,通常是Activity/Fragment等

监听ViewModel,当VIewModel数据更新时刷新UI

监听用户事件反馈到ViewModel。

ViewModel

持有保存,或者想Repository来获取UI层需要的数据

响应UI层的事件,执行响应的操作

响应变化,并且通知到UI层

Repository

App的完全的数据模型,ViewModel交互的对象

提供简单的数据修改和获取的接口

配合好网络层数据的更新与本地持久化数据的更新,同步等

Data Source

包含本地的数据库等,网络api等

这些基本上和现有的一些MVVM,以及Clean架构的组合比较相似,不过谷歌提供了一些新的类库来帮助我们实现这个架构。

谷歌的新玩具

本地IO大会谷歌提供了新的类库来实现这个功能,小标题我写新玩具是因为这个库目前还在alpha1版本,官方只建议在个人小项目中使用。

这个类库包含如下一些东西:

  • Lifecycle

Android声明周期的回调,帮助我们将原先需要在onStart()等生命周期回调的代码可以分离到Activity或者Fragment之外。

  • LiveData

一个数据持有类,持有数据并且这个数据可以被观察被监听,和其他Observer不同的是,它和Lifecycle是绑定的。

  • ViewModel

用于实现架构中的ViewModel,同时是与Lifecycle绑定的,使用者无需担心生命周期。方便在多个Fragment之前分享数据,比如旋转屏幕后Activity会重新create,这时候使用ViewModel可以方便使用之前的数据,不需要再次请求网络数据。

  • Room

谷歌推出的一个Sqlite ORM库,不过使用起来还不错,使用注解,极大简化数据库的操作。

框架补充

​ 工具库帮助我们进行开发,如果不满足官方的库其实可以自己实现。比如LiveData在某些情况下可使用RxJava代替。

​ 数据层官方推荐使用Room或者Realm或者其他Sqlite ORM等都可以,同时从某些方面看Room风格很像Retrofit。网络请求也被推荐使用Retrofit。

​ 各层之间的耦合推荐使用服务发现(Service Locator)或者依赖注入(DI),会上推荐了Dagger。

测试

​ 各层之间的合理分层,为测试提供极大的方便。

  • UI层测试

​ 使用Android Instrumentation Test,借助Espresso库进行,借助Mock的ViewModel,可以专注于测试UI

  • ViewModel 测试

​ 使用Mock的Repository来提供数据,使用JUnit测试,因为不涉及UI,运行速度会快很多。

  • Repository测试

​ 数据层Mock一些数据返回给Repository,使用JUnit测试即可

  • 数据层测试

​ 使用JUnit测试

​ 数据库,使用Room的话官方提供了测试支持,在测试时候创建内存数据库即可。

​ 网络请求,使用MockWebServer来提供假的服务端即可。

示例

再补一个会议时的项目结构图,以一个用户信息页面为例。

最后的话

​ 目前这个库还不完善,api可能随时会变,公司项目不建议使用,个人项目可以尝鲜。另外对于已经有的项目,也不建议更换到现在的架构。不过这个项目的好的思想可以借鉴到我们自己的项目中来,同时这个库的方式我们其实可以借助其他的开源库来实现。

本文不再贴相关代码,具体各个库的使用请查看官方文档https://developer.android.com/topic/libraries/architecture/guide.html

附上官方的DEMO项目:https://github.com/googlesamples/android-architecture-components

这次的视频:https://www.youtube.com/watch?v=FrteWKKVyzI

文中如果错误,欢迎指正.

看完评论一下吧

聊聊Android N开始支持的Lambda

Android N 正式版已经发布了。对于开发者来说一个重大的更新是对于Java支持到了Java8,其中一点就是支持Lambda。我们就来聊聊什么是lambda,怎么在Android中使用。

什么是lambda

Lambda 可以理解为匿名函数,帮助我们写出更加简洁的代码。

给view设置一个clicklistener,原本你需要写出这样的代码:

1
2
3
4
5
6
v.setOnClickListener(new View.OnClickListener(View v) {
 @Override
 public void onClick(View v) {
 Toast.makeText(getActivity(), "clicked", Toast.LENGTH_LONG).show()
 }
});

使用lambda之后:

1
.setOnClickListener(v -> Toast.makeText(getActivity(), "clicked", Toast.LENGTH_LONG).show());

是不是代码量爆减。这里再看下怎么写lambda。

在JavaScript,python等语言中函数是一等公民,但是Java中类才是。使用lambda时候,lambda其实应该是一个对象,依附于函数式接口(只包含一个抽象方法声明的接口,例如刚刚我们举例的OnClickListener就是,在Java 8 需要使用@FunctionalInterface这样保证在编译的时候一个接口只有一个抽象注解)。

写法的基本规则是这样:

1
(arguments) -> {body}

arguments 是参数列表,0~n个, 参数为一个时候,可以不要括号
body 为具体代码部分,如果代码只有一句的话可以不要大括号
返回值会自动推导出类型

一些写法实例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(int a, int b) -> { return a + b; }

() -> System.out.println("Hello World");

(String s) -> System.out.println(s);

() -> 42

() -> { return 3.1415 };
(a, b) -> {return a+b;}

另外一点需要注意的是,在我们的lambda表达式中this关键指的是外部对象,而不是我们以为的lambda这个对象。在语法糖的实现过程中,lambda表达式最后会被变为类的私有方法,因此可以放心的使用this。

使用retrolambda

目前有个比较成熟的解决方案,使用retrolambd,接入的配置如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apply plugin: 'com.android.application'
apply plugin:'me.tatarka.retrolambda'

buildscript {
 repositories {
 mavenCentral()
 }

 dependencies {
 classpath 'me.tatarka:gradle-retrolambda:3.2.5'
 }
}
 // Required because retrolambda is on maven central
repositories {
 mavenCentral()
}
 android {
 compileOptions {
 sourceCompatibility JavaVersion.VERSION_1_8
 targetCompatibility JavaVersion.VERSION_1_8
 }
}
//指定将源码编译的级别,以下会将代码编译到1.7的自己码格式
retrolambda {
 javaVersion JavaVersion.VERSION_1_7
}

当前,retrolambda对于android gradle插件是有依赖的,需要使用1.5+的插件才可以。

retrolambda的原理是在编译的过程中,给class文件增加包裹,转成java 1.7支持的格式。

使用jack

在Android N,支持使用Java 8, google给我们提供了新的编译工具jack,因此可以直接支持lambda,为了支持低版本的Android也可以用lambda,我们需要将targetSdkVersioncompileSdkVersion设置为23或者更小。启用jack,修改build.gradle如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
android {
 ...
 defaultConfig {
 ...
 jackOptions {
 enabled true
 }
 }
 compileOptions {
 sourceCompatibility JavaVersion.VERSION_1_8
 targetCompatibility JavaVersion.VERSION_1_8
 }
}

jack工具链会的编译步骤如下:

Jack (.java --> .jack --> .dex)

和之前相比,将中间转换为class文件的步骤省略了,不需要多个工具链。 在低版本兼容lambda,也同样是使用的语法糖来实现。

后记

如上两种工具都可以让我们在进行Android开发的时候来使用lambda,retrolambda出来的时间更早,经过很多次的迭代,目前也有一些app在使用,相比较来说更加成熟。jack则是google开发,减少了对javac的依赖,更多谷歌的自主性,相信后面谷歌大力推广的,但是出于刚刚开发出来因此还不够成熟,对于lint,proguard,instant run还有很多地方支持不好的地方,我们相信以后jack会是趋势。

出于尝鲜,还是可以来使用的,但是在大的项目里面还是不建议使用的,毕竟万一出了问题还是很难排查的。

另外,如果想要在android开发更爽快的使用lambda,也可以去试试kotlin这个语言。

参考资料:

  1. http://viralpatel.net/blogs/Lambda-expressions-java-tutorial/
  2. https://developer.android.com/guide/platform/j8-jack.html
  3. https://github.com/evant/gradle-retrolambda

看完评论一下吧

小红书Android客户端演进之路

小红书Android客户端第一个版本于2014年8月8日发布,转眼到了2016年8月8日,小红书Android版本发版两周年。趁机回顾一下小红书的Android版本,两年中我们踩过很多坑,收获很多经验,分享出来与大家共勉。 小红书从最初1.0到现在目前4.7版本,历经两年,安装包从原先的5M发展到现在的17M,产品模块也从原先的只有社区模块发展到了具有社区和电商两个大模块。App包含社区、电商、支付、推送、直播、统计等各种功能和模块,那么开始吧。

功能演进

两年的时间,30多个版本的迭代,许多功能都有了翻天覆地的变化。我们的新人欢迎页也是从最初的比较炫的效果发展到目前比较稳定的简洁版本。当初钟大侠花了无数个日日夜夜,苦心做出来了多个欢迎页动画,虽然现在已经不再使用,但是我们也学习到了一些新技术。后来,钟大侠还是将其贡献到了github开源社区中。 欢迎页第一版

下载地址:https://github.com/w446108264/XhsParallaxWelcome

欢迎页第二版

下载地址: https://github.com/w446108264/XhsWelcomeAnim

社区是小红书的核心价值之一,笔记是小红书社区的核心体现,毋庸置疑,笔记发布是小红书App的核心功能之一,我们一直在产品和技术上,优化我们的笔记发布流程和功能,包括我们将只支持分享单张图片,扩展到现在支持多张图片同时发布。同时支持更丰富的图片编辑效果,更加便捷的发布笔记。

小红书的笔记展现形式和大多数其他的图片社交App类似,我们也支持图上标签功能。最初小红书图上标签是同其他App类似的黑色的标签。不过在3.0之后,小红书创造了独特的树状标签,给用户带来焕然一新的体验,同时也被其他App竞相模仿。新的标签给技术也带了很多的挑战,我们重新定义了标签的结构,以及标签的生成和展示。可以查看我以前的博客,来看看我是怎样做标签的动画的。(http://blog.isming.me/2016/06/07/path-property-animation/

UI的改版,功能上的改动还有很多,这里不再一一提起。小红书Android整体上的风格和iOS保持一致,不过我们在15年初开始,对于App内的细节进行Material Design 适配,包括一些按钮风格、点击效果、字体规范、对话框等等,希望为Android用户带来更好的使用体验。

技术选型进化

在技术选型上,这里主要讲一下网络层的框架选型升级和图片加载库的升级。

网络框架的演进

App的最初框架是由钟大侠一人花了10来天完成,包括基本的网络请求框架、App大体的架构以及一些主要的功能。最初时候选择框架的原则就是选择自己最熟悉的,因此我们采用了async-http这套框架作为我们底层的网络请求框架,框架完成了网络的异步请求与回调,能够满足当时的需求。

然而仅仅不到半年之后,我们就决定了使用Volley来替换。替换以后,底层的网络请求代码更加清晰,在Volley返回的结果即直接返回了我们需要的Object,同时将统一的错误处理、公共的参数处理和一些公共的返回使用的参数,全部放在我们自定义的Request当中,这样外部请求所需要传入的参数更少,对于错误的处理更加简单,只需要考虑业务需要的Response,其他全局的返回内容则无需进行干扰。通过Volley的引入,帮助我们在业务的开发上变得更加便捷。引入Volley之初,Volley的底层使用的是HttpClient+HttpURLConnection,后期通过网上的资料发现OkHttp使用NIO更加高效,并且被Android 引入作为系统底层的网络请求,我们也将Volley的底层也替换为OkHttp。

与此同时,小红书的api请求也在不断进行RESTful,我们遇到一个问题就是经常找一个api的定义比较麻烦。大约在15年11月份,我们引入了Retrofit,通过二次改造,使其支持了公共参数的构建,以及对于GsonConvert的改进支持直接返回我们需要的Object,而且对于RESTful风格的良好支持给我们提供了极大的便利。配合RxJava,我们可以方便的进行多个api的同时请求、api返回的多个线程的切换。

图片加载框架的演进

小红书的笔记是以图片加文字为主体的内容,因此会有大量的图片显示需求。和网络框架选型类似,早期选择了比较熟悉的UIL来做图片加载,可以同时支持本地图片和网络图片的加载,在当时可以满足我们的基本需求。

15年初,我们开始使用更加高清的图片,随之加载速度变慢,占用更多的内存,而且这个时候UIL的作者基本很少维护。我们开始调研使用新的图片加载框架。此时Fresco刚刚出来,还不太稳定,当时没敢用。给我们的可选项有Picasso和Glide两个可选项,Picasso比较轻量,但是相比于UIL在性能上没有太好的提高。Glide代码量较大,不过它会在本地保存多份缓存(原始图片和实际显示尺寸的图片),这样加载本地缓存的时候,可以直接显示大小刚好的尺寸,减少解码的时间,因此会比UIL要快很多。

15年下半年,我们需要支持gif的动画显示,而Glide对动画的兼容性又不是特别好,这个时候我们直接切到了Fresco。同时Fresco对webp的良好支持,使得我们在后期切换到webp格式的时候,减少了很多工作量。Fresco在4.4及以下版本使用匿名内存来作为内存缓存,为我们减少OOM做了巨大的贡献。

我们使用的这几个图片加载框架,每个框架的使用都有非常大的区别,这就导致迁移的时候工作量巨大。为了降低迁移成本,我们封装了自己的ImageLoader,在ImageLoader中来实现具体的图片加载,这样保证在迁移的时候,最大程度的降低代码的改动(不过在迁移到Fresco的时候还是改动巨大,因为我们不能直接使用ImageView了o(︶︿︶)o。

推送的升级

推送,我觉得也有必要说一说。最初我们快速选用了百度云推送,在当时看来百度的推送比较稳定,同时接入比较简单。实际使用了一年之后,发现送达率不是特别高,并且数据统计做的不太好,无法比较好的统计推送效果。在调研之后,我们决定迁移到小米推送+友盟推送的模式,针对小米用户开启小米推送,其他用户采用友盟推送,为了平滑过渡,在切换期间同时向未升级的老用户继续使用百度云推送进行推送。

架构升级

由于一直以来在业务开发占用的时间比较多,目前App的整体架构没有做过太大的改变。

在Adapter的使用方面,我们将ListView或RecyclerView的Item放到单独的ItemHander,这样可以在不同的页面可以通过将不同的Item组装到一起,从而满足不同地方的需求。这样可以在ListView或RecyclerView来复用相同的代码,提高代码的可维护性。

前面网络层说到我们的错误处理,这个也是做过比较大的升级。最初时候,网络错误、http请求错误、后台和客户端的错误,都分别在不同的层级进行处理。目前我们在发生错误的时候将错误全部以Exception的方式抛出,最后在上层进行错误的处理。

App中的状态同步,早期使用使用数据库缓存部分数据,或者使用LocalBroadcast进行广播通讯,前者有很多的限制,后者使用起来较为复杂。近期我们改用EventBus进行状态同步,同时这样也使得各个页面之间的耦合也低。

App中占比很大的部分是从网络请求数据,获得数据后进行展示,还是以MVC为主。在一些模块的部分地方,做一些databinding,MVP等的测试。后面有机会会更多大范围的重构。

其他周边进化

我们的开发最初是使用Eclipse进行开发的,但是Eclipse仅仅存在了不到一个月。在我苦口婆心的劝说下,钟大侠和我一起切换到了Android Studio。而这导致我们的项目目录一直都是使用Eclipse时代的目录格式,直到今年年初才切换到Android Studio推荐的目录格式,切换完目录为我们做debug和release差异化提供了极大的便利。

APK最初大约只有5M,历史最高峰达到了23M,在App减肥上我们也做了一些努力,主要是使用tinypng压缩图片,so只保留arm的支持。项目的复杂也使得每次编译都变得很慢,关于这个可以看下我以前的gradle加速http://blog.isming.me/2015/03/18/android-build-speed-up/

现在持续集成还是蛮火的,自然我们也在用。最初的时候,我们每天需要手动打包,打完包之后打开fir的网站,将apk传上去,然后在公司的微信群吼一声,告诉大家我们发包了。经历一段时间后,我们编写了一个Gradle插件帮助我们自动上传到fir,在之后我们搭建了Jenkins自动完成这一系列步骤,并通过邮件告知大家,然后就可以愉快的玩耍了。

Jenkins

未完待续

本文介绍了我们两年来的一些大的变化,通过一篇文章可能很多东西还是说不清楚,暂时就写这么多。目前项目的组织架构还没有特别大的变化,我们目前已经在做一些小范围的测试,后面将对继续不断的进化和演进。

看完评论一下吧

Path和Property Animation配合让线条动起来

之前做过一个图上标签但是动画样式不太好看,经过查找资料发现了一种全新的思路来实现动画,流畅的让标签的线显示和隐藏,示例如下,就在这里说一说。本文会涉及到Path,Property Animation, PathEffect, PathMeasure。我们开始一一道来。

示例

使用Path绘制曲线

当我们需要画曲线的时候,可能会直接使用drawLine来画,不太复杂的话还比较好实现,如果需要画曲线,或者拐弯的线的时候使用drawLine就比较复杂了。这时候,我们可以借助Path来drawPath。

1
2
3
4
5
6
7
8
9
Paint paint = new Paint();
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE); //一定要设置为画线条
Path path = new Path();
path.moveTo(100, 100); //定位path的起点
path.lineTo(100, 200);
path.lineTo(200, 150);
path.close();
canvas.drawPath(path, paint);

通过以上的方法代码我们就可以画出三角形了。

测量Path的长度

实现动画的前提是首先得到Path的长度,然后根据长度计算出每个时间节点应该显示的长度。因为系统给我们提供了测量长度的方法,就不需要我们去进行复杂的计算了。直接使用PathMeasure就可以了。

1
2
PathMeasure measure = new PathMeasure(path, false);
float length = measure.getLength();

只绘制Path的一部分

为了让Path能够逐步显示出来,或者逐步隐藏。我们需要做到能够显示path的一部分,并且改变显示的长度。我们知道可以通过DashPathEffect来显示虚线效果。同时我们可以借助DashPathEffect让我们的实线和虚线的部分的长度分别为我们的Path的长度,然后来改变偏移量,实现只显示path的一部分。

1
2
3
PathEffect effect = new DashPathEffect(new float[]{pathLength, pathLength}, pathLength/2);
paint.setPathEffect(effect);
canvas.drawPath(path, paint)

让Path动起来

通过上面说的,我们改变PathEffect的偏移量就可以改变path显示的长度,因此我们可以给我们的View或者对象定义个属性,通过Property Animation来改变这个属性的值,即可实现动画。

PathEffect 属性值变化

1
2
float percentage = 0.0f;
PathEffect effect = new DashPathEffect(new float[]{pathLength, pathLength}, pathLength - pathLength*percentage);

动画定义:

1
Animator animatorLine = ObjectAnimator.ofFloat(view, percentage, 0.0f, 1.0f);

其他

就这样就实现了。思路甚至代码都是参考一篇国外的博客。思路很重要,一年前做这个动画的时候百思不得姐,花了好多时间,后面实现的效果还是比较僵硬。这次发现了其他人的思路之后,很容易就解决了。

思路很重要,以及要了解更加全面的知识,不然很多东西都不知道,自己的思路还是会被限制。

最后就是多google,百毒上除了广告,别的东西都挺难找到的。

没有Demo了,可以参考我参考的那个github的库吧。同时作者已经实现svg的动画显示了,原理相同,只是把svg加载为path,使用同样的动画。代码:https://github.com/matthewrkula/AnimatedPathView

看完评论一下吧

Android系统更改状态栏字体颜色

随着时代的发展,Android的状态栏都不是乌黑一片了,在Android4.4之后我们可以修改状态栏的颜色或者让我们自己的View延伸到状态栏下面。我们可以进行更多的定制化了,然而有的时候我们使用的是淡色的颜色比如白色,由于状态栏上面的文字为白色,这样的话状态栏上面的文字就无法看清了。因此本文提供一些解决方案,可以是MIUI6+,Flyme4+,Android6.0+支持切换状态栏的文字颜色为暗色。

修改MIUI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 public static boolean setMiuiStatusBarDarkMode(Activity activity, boolean darkmode) {
 Class<? extends Window> clazz = activity.getWindow().getClass();
 try {
 int darkModeFlag = 0;
 Class<?> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
 Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
 darkModeFlag = field.getInt(layoutParams);
 Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
 extraFlagField.invoke(activity.getWindow(), darkmode ? darkModeFlag : 0, darkModeFlag);
 return true;
 } catch (Exception e) {
 e.printStackTrace();
 }
 return false;
 }

上面为小米官方提供的解决方案,主要为MIUI内置了可以修改状态栏的模式,支持Dark和Light两种模式。

修改Flyme

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 public static boolean setMeizuStatusBarDarkIcon(Activity activity, boolean dark) {
 boolean result = false;
 if (activity != null) {
 try {
 WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
 Field darkFlag = WindowManager.LayoutParams.class
 .getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON");
 Field meizuFlags = WindowManager.LayoutParams.class
 .getDeclaredField("meizuFlags");
 darkFlag.setAccessible(true);
 meizuFlags.setAccessible(true);
 int bit = darkFlag.getInt(null);
 int value = meizuFlags.getInt(lp);
 if (dark) {
 value |= bit;
 } else {
 value &= ~bit;
 }
 meizuFlags.setInt(lp, value);
 activity.getWindow().setAttributes(lp);
 result = true;
 } catch (Exception e) {
 }
 }
 return result;
 }

同理使用跟miui类似的方式

修改Android6.0+

Android 6.0开始,谷歌官方提供了支持,在style属性中配置android:windowLightStatusBar 即可, 设置为true时,当statusbar的背景颜色为淡色时,statusbar的文字颜色会变成灰色,为false时同理。

1
2
3
4
<style name="statusBarStyle" parent="@android:style/Theme.DeviceDefault.Light">
 <item name="android:statusBarColor">@color/status_bar_color</item>
 <item name="android:windowLightStatusBar">false</item>
</style>

目前为止,android6.0的市场占有率还很少,而MIUI和flyme在国内占有率还算可以,因此,我们可以尽自己所能,适配更多。如果你还有其他的奇淫技巧,也欢迎分享补充。

看完评论一下吧

Android WebView 上传文件支持全解析

默认情况下情况下,使用Android的WebView是不能够支持上传文件的。而这个,也是在我们的前端工程师告知之后才了解的。因为Android的每个版本WebView的实现有差异,因此需要对不同版本去适配。花了一点时间,参考别人的代码,这个问题已经解决,这里把我踩过的坑分享出来。

主要思路是重写WebChromeClient,然后在WebViewActivity中接收选择到的文件Uri,传给页面去上传就可以了。

创建一个WebViewActivity的内部类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class XHSWebChromeClient extends WebChromeClient {

 // For Android 3.0+
 public void openFileChooser(ValueCallback<Uri> uploadMsg) {
 CLog.i("UPFILE", "in openFile Uri Callback");
 if (mUploadMessage != null) {
 mUploadMessage.onReceiveValue(null);
 }
 mUploadMessage = uploadMsg;
 Intent i = new Intent(Intent.ACTION_GET_CONTENT);
 i.addCategory(Intent.CATEGORY_OPENABLE);
 i.setType("*/*");
 startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
 }

 // For Android 3.0+
 public void openFileChooser(ValueCallback uploadMsg, String acceptType) {
 CLog.i("UPFILE", "in openFile Uri Callback has accept Type" + acceptType);
 if (mUploadMessage != null) {
 mUploadMessage.onReceiveValue(null);
 }
 mUploadMessage = uploadMsg;
 Intent i = new Intent(Intent.ACTION_GET_CONTENT);
 i.addCategory(Intent.CATEGORY_OPENABLE);
 String type = TextUtils.isEmpty(acceptType) ? "*/*" : acceptType;
 i.setType(type);
 startActivityForResult(Intent.createChooser(i, "File Chooser"),
 FILECHOOSER_RESULTCODE);
 }

 // For Android 4.1
 public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
 CLog.i("UPFILE", "in openFile Uri Callback has accept Type" + acceptType + "has capture" + capture);
 if (mUploadMessage != null) {
 mUploadMessage.onReceiveValue(null);
 }
 mUploadMessage = uploadMsg;
 Intent i = new Intent(Intent.ACTION_GET_CONTENT);
 i.addCategory(Intent.CATEGORY_OPENABLE);
 String type = TextUtils.isEmpty(acceptType) ? "*/*" : acceptType;
 i.setType(type);
 startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
 }


//Android 5.0+
 @Override
 @SuppressLint("NewApi")
 public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
 if (mUploadMessage != null) {
 mUploadMessage.onReceiveValue(null);
 }
 CLog.i("UPFILE", "file chooser params:" + fileChooserParams.toString());
 mUploadMessage = filePathCallback;
 Intent i = new Intent(Intent.ACTION_GET_CONTENT);
 i.addCategory(Intent.CATEGORY_OPENABLE);
 if (fileChooserParams != null && fileChooserParams.getAcceptTypes() != null
 && fileChooserParams.getAcceptTypes().length > 0) {
 i.setType(fileChooserParams.getAcceptTypes()[0]);
 } else {
 i.setType("*/*");
 }
 startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE);
 return true;
 }
}

上面openFileChooser是系统未暴露的接口,因此不需要加Override的注解,同时不同版本有不同的参数,其中的参数,第一个ValueCallback用于我们在选择完文件后,接收文件回调到网页内处理,acceptType为接受的文件mime type。在Android 5.0之后,系统提供了onShowFileChooser来让我们实现选择文件的方法,仍然有ValueCallback,在FileChooserParams参数中,同样包括acceptType。我们可以根据acceptType,来打开系统的或者我们自己创建文件选择器。当然如果需要打开相机拍照,也可以自己去使用打开相机拍照的Intent去打开即可。

处理选择的文件

以上是打开响应的选择文件的界面,我们还需要处理接收到文件之后,传给网页来响应。因为我们前面是使用startActivityForResult来打开的选择页面,我们会在onActivityResult中接收到选择的结果。Show code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
 super.onActivityResult(requestCode, resultCode, data);
 if (requestCode == FILECHOOSER_RESULTCODE) {
 if (null == mUploadMessage) return;
 Uri result = data == null || resultCode != RESULT_OK ? null : data.getData();
 if (result == null) {
 mUploadMessage.onReceiveValue(null);
 mUploadMessage = null;
 return;
 }
 CLog.i("UPFILE", "onActivityResult" + result.toString());
 String path = FileUtils.getPath(this, result);
 if (TextUtils.isEmpty(path)) {
 mUploadMessage.onReceiveValue(null);
 mUploadMessage = null;
 return;
 }
 Uri uri = Uri.fromFile(new File(path));
 CLog.i("UPFILE", "onActivityResult after parser uri:" + uri.toString());
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 mUploadMessage.onReceiveValue(new Uri[]{uri});
 } else {
 mUploadMessage.onReceiveValue(uri);
 }

 mUploadMessage = null;
 }
}

以上代码主要就是调用ValueCallback的onReceiveValue方法,将结果传回web。

注意,其他要说的,重要

由于不同版本的差别,Android 5.0以下的版本,ValueCallback 的onReceiveValue接收的参数类型是Uri, 5.0及以上版本接收的是Uri数组,在传值的时候需要注意。

选择文件会使用系统提供的组件或者其他支持的app,返回的uri有的直接是文件的url,有的是contentprovider的uri,因此我们需要统一处理一下,转成文件的uri,可参考以下代码(获取文件的路径)。

调用getPath可以将Uri转成真实文件的Path,然后可以自己生成文件的Uri

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
public class FileUtils {
 /**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is ExternalStorageProvider.
 */
 public static boolean isExternalStorageDocument(Uri uri) {
 return "com.android.externalstorage.documents".equals(uri.getAuthority());
 }

 /**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is DownloadsProvider.
 */
 public static boolean isDownloadsDocument(Uri uri) {
 return "com.android.providers.downloads.documents".equals(uri.getAuthority());
 }

 /**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is MediaProvider.
 */
 public static boolean isMediaDocument(Uri uri) {
 return "com.android.providers.media.documents".equals(uri.getAuthority());
 }

 /**
 * Get the value of the data column for this Uri. This is useful for
 * MediaStore Uris, and other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @param selection (Optional) Filter used in the query.
 * @param selectionArgs (Optional) Selection arguments used in the query.
 * @return The value of the _data column, which is typically a file path.
 */
 public static String getDataColumn(Context context, Uri uri, String selection,
 String[] selectionArgs) {

 Cursor cursor = null;
 final String column = "_data";
 final String[] projection = {
 column
 };

 try {
 cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
 null);
 if (cursor != null && cursor.moveToFirst()) {
 final int column_index = cursor.getColumnIndexOrThrow(column);
 return cursor.getString(column_index);
 }
 } finally {
 if (cursor != null)
 cursor.close();
 }
 return null;
 }

 /**
 * Get a file path from a Uri. This will get the the path for Storage Access
 * Framework Documents, as well as the _data field for the MediaStore and
 * other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @author paulburke
 */
 @SuppressLint("NewApi")
 public static String getPath(final Context context, final Uri uri) {

 final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

 // DocumentProvider
 if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
 // ExternalStorageProvider
 if (isExternalStorageDocument(uri)) {
 final String docId = DocumentsContract.getDocumentId(uri);
 final String[] split = docId.split(":");
 final String type = split[0];

 if ("primary".equalsIgnoreCase(type)) {
 return Environment.getExternalStorageDirectory() + "/" + split[1];
 }

 // TODO handle non-primary volumes
 }
 // DownloadsProvider
 else if (isDownloadsDocument(uri)) {

 final String id = DocumentsContract.getDocumentId(uri);
 final Uri contentUri = ContentUris.withAppendedId(
 Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

 return getDataColumn(context, contentUri, null, null);
 }
 // MediaProvider
 else if (isMediaDocument(uri)) {
 final String docId = DocumentsContract.getDocumentId(uri);
 final String[] split = docId.split(":");
 final String type = split[0];

 Uri contentUri = null;
 if ("image".equals(type)) {
 contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
 } else if ("video".equals(type)) {
 contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
 } else if ("audio".equals(type)) {
 contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
 }

 final String selection = "_id=?";
 final String[] selectionArgs = new String[] {
 split[1]
 };

 return getDataColumn(context, contentUri, selection, selectionArgs);
 }
 }
 // MediaStore (and general)
 else if ("content".equalsIgnoreCase(uri.getScheme())) {
 return getDataColumn(context, uri, null, null);
 }
 // File
 else if ("file".equalsIgnoreCase(uri.getScheme())) {
 return uri.getPath();
 }

 return null;
 }

}

再有,即使获取的结果为null,也要传给web,即直接调用mUploadMessage.onReceiveValue(null),否则网页会阻塞。

最后,在打release包的时候,因为我们会混淆,要特别设置不要混淆WebChromeClient子类里面的openFileChooser方法,由于不是继承的方法,所以默认会被混淆,然后就无法选择文件了。

就这样吧。

看完评论一下吧

Android WebView使用的技巧与一些坑

随着手机性能的提高,以及iOS和Android两个平台的普及,更多的App都会选择两个平台的App都进行开发,在有些时候,为了更加快速的开发,我们会采用hybird方式开发,这个时候我们需要使用webview并且自己进行一些配置。Android的webview在低版本和高版本采用了不同的webkit版本内核,4.4后直接使用了chrome,因此问题很多,这里分享一些我使用过程的一些技巧和遇到的坑。

webview配置

1
2
3
4
mWebview.getSettings().setJavaScriptEnabled(true); //设置允许运行javascript
// HTML5 API flags
mWebview.getSettings().setAppCacheEnabled(true); //设置允许缓存
mWebview.getSettings().setDatabaseEnabled(true); //设置允许使用localstore

上面webview.getSettings()会获得WebSettings对象,在这个对象中会保存Webview的一些设置,比如上面所设置的这些,更多的设置请查看WebSettings的api文档。

通常我们还会使用WebViewClient和WebChromeClient这两个组件来辅助WebView。WebViewClient主要帮助处理各种通知请求事件等,比如页面开始加载,加载完成等。WebChromeClient主要辅助WebView处理javascript对话框,网站图标,网站标题,加载进度等等。 实际应该根据实际情况使用这两个组件,重写响应的方法,在其中执行自己的一些操作。

Javascript的使用

开启javascript的方法上面已经提到了。

客户端调用网页中的js代码,或者执行相应的代码。

1
2
3
4
5
6
7
private void evaluateJavascript(String js) {
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
 mWebview.evaluateJavascript(js, null);
 } else {
 mWebview.loadUrl(js);
 }
}

在android4.4开始系统提供了evaluateJavascript方法来执行js方法,并且可以进行回调。但是在低于4.4的版本并没有这个方法,我们需要只要直接通过loadUrl的方式来执行js,此时需要在js代码前加”javascript:”。

另外可以在客户端定义一些javascript给网页中调用。 比如这样:

首先定义一个给js执行的类:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class WebAppInterface {
 Context mContext;

 /** Instantiate the interface and set the context */
 WebAppInterface(Context c) {
 mContext = c;
 }

 /** Show a toast from the web page */
 @JavascriptInterface
 public void showToast(String toast) {
 Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show();
 }
}

webView.addJavascriptInterface(new WebAppInterface(this), "Android");

之后用*addJavascriptInterface&设置到webview上,在js中就可以用Android.showToast(“fdf")调用了。

需要注意的是,在我们给js的接口方法需要是public的,使用到了JavascriptInterface的注解,这个注解在Android4.2的时候添加,更新的android如果不加这个注解是不可以使用的。

硬件加速

硬件加速是个大坑,请勿打开。 在android4.4后使用的chrome,系统会自行开启。

其他

以及使用WebView,给忘了给应用申请网络访问的权限。

还有一些知识点没整理到,请参考webview的文档,更多的坑以后踩到再更新。

另外JeremyHe总结的知识也不错,可以参考:http://zlv.me/posts/2015/01/14/08_Android-Webview%E4%BD%BF%E7%94%A8%E5%B0%8F%E7%BB%93/

看完评论一下吧

改变support中AlertDialog的样式

android最近的support库提供了AlertDialog,可以让我们在低于5.0的系统使用到跟5.0系统一样的Material Design风格的对话框,但是使用了一段时间想到一些办法去改变对话框按钮字体的颜色,都不生效。

最近在网上找到了改变的方法,首先来说一下。

改变AlertDialog的样式

在xml中定义一个主题:

1
2
3
4
5
6
7
8
<style name="MyAlertDialogStyle" parent="Theme.AppCompat.Light.Dialog.Alert">
 <!-- Used for the buttons -->
 <item name="colorAccent">#FFC107</item>
 <!-- Used for the title and text -->
 <item name="android:textColorPrimary">#FFFFFF</item>
 <!-- Used for the background -->
 <item name="android:background">#4CAF50</item>
</style>

样式如下图所示:

在创建的对话框的时候,这样创建就可以了。

1
2
3
4
5
6
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.MyAlertDialogStyle);
builder.setTitle("AppCompatDialog");
builder.setMessage("Lorem ipsum dolor...");
builder.setPositiveButton("OK", null);
builder.setNegativeButton("Cancel", null);
builder.show();

这样的方法是每个地方使用的时候,都要在构造函数传我们的这个Dialog的Theme,我们也可以全局的定义对话框的样式。

1
2
3
4
<style name="MyTheme" parent="Base.Theme.AppCompat.Light">
 <item name="alertDialogTheme">@style/MyAlertDialogStyle</item>
 <item name="colorAccent">@color/accent</item>
</style>

在我们的AndroidManifest.xml文件中声明application或者activity的时候设置theme为MyTheme即可,不过需要注意的一点是,我们的Activity需要继承自AppCompatActivity。

其他

从上面改变对话框的样式,可以想到用同样的思路来实现应用的换肤,应用主题之类的功能。

看完评论一下吧

一个上传apk到fir的gradle插件

声明,这不是广告,没有任何利益瓜葛。

App内测需要把安装把安装包放在一个地方进行托管,方便内测人员下载。国内有蒲公英,fir,等等这些网站可以用。

最近fir上了新版本了,上了新的api,新界面,本以为它们会提供gradle的上传工具,结果没有,而且它们新版本还不好用,原本的下载统计浏览统计都没有了,结果上传很慢,甚至上传不了,我便写了一个gradle的上传工具。

先介绍使用方法吧

使用方法

插件目前只有唯一一个task

uploadFir –上传apk到fir

集成插件本插件,你要按照如下方法使用

编辑build.gradle

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
buildscript {
 repositories {
 jcenter()
 }

 dependencies {
 classpath 'com.squareup.okhttp:okhttp:2.2.0'
 classpath 'com.squareup.okhttp:okhttp-urlconnection:2.2.0'
 classpath 'org.json:json:20090211'
 classpath 'me.isming:firup:0.4.1'
 }
}

apply plugin: 'me.isming.fir'

fir {
 appId = "" //app的appid,在fir中可以找到
 userToken = "" //fir用户的token,也在在fir中找到

 apks {
 release {
 // 要上传的apk的路径,类似下面
 sourceFile file("/project/main/build/outputs/apk/xxx.apk")
 name "" //app的名称
 version "3.3.0" //app的版本version
 build "330" //app的版本号
 changelog "" //更新日志
 icon file("....../res/drawable-xxhdpi/icon_logo.png") //app的icon的路径
 }
 }
}

####运行

$ ./gradlew uploadFir

你也可以在本任务的基础上,在你的build脚本中增加以下内容:

1
uploadFir.dependsOn assembleRelease //后面为你生成apk的任务

这样就可以在执行上传到fir之前首先会生成一个最新的安装包了

本插件基于fir.im官方提供的api文档进行编写,时间匆忙,可能还有一些地方不够完善,还有许多地方可以优化,欢迎star,fork,共同完善。

也可以给我提意见,我来优化。

还有一些代优化的点没有做,后面有空会做,version,build,icon通过程序自动做,而不用手工填写。

项目托管在github上面,生成的jar放在jcenter上面。

github地址:https://github.com/sangmingming/gradle-fir-plugin

原文地址:http://blog.isming.me/2015/08/01/gradle-fir-plugin/,转载请注明出处。

看完评论一下吧

Android应用使用自定义字体的一些探究

最近团队里面在做程序界面统一的工作,因此希望统一字体,接到一个研究怎么自定义字体的任务。因为我们的开发模式,所以需要研究在界面内的字体自定义,以及webview的显示中的字体自定义。

android系统内置字体

android 系统本身内置了一些字体,可以在程序中使用,并且支持在xml配置textView的时候进行修改字体的样式。支持字段为android:textStyle ,android:typeface, android:fontFamily,系统内置了normal|bold|italic三种style, 内置了normalsans,serif,monospace,几种字体(实测这几种字体仅英文有效),typace和fontFamily功能一样。

使用自定义的字体

以上的方式可以改变字体的样式,还不是真正的自定义。

android系统支持TypeFace,即ttf的字体文件。

我们可以在程序中放入ttf字体文件,在程序中使用Typeface设置字 体。

第一步,在assets目录下新建fonts目录,把ttf字体文件放到这。 第二步,程序中调用:

1
2
3
AssetManager mgr=getAssets();//得到AssetManager
Typeface tf=Typeface.createFromAsset(mgr, "fonts/ttf.ttf");//根据路径得到Typeface
tv.setTypeface(tf);//设置字体

注意ttf文件命名不能使用中文,否则可能无法加载。

对于需要使用比较多的地方,可以写一个TextView的子类来统一处理。

在webview中使用自定义地体

对于本地的网页,在asset目录放字体文件,并在css中添加以下内容,自定义一个字体face,并且在需要的地方使用这个字体face即可。

1
2
3
4
5
6
@font-face {
 font-family: "MyFont";
 src: url('file:///android_asset/fonts/ttf.ttf');
}

h3 { font-family:"MyFont"}

对于在线的网页,则需要把字体文件放到服务器,使用同样的方式定义字体face,应用到每个地方。

为了减少网页或者说服务器端的工作,可以使用本地注入的方式注入font-face的css,并对整个网页进行样式替换。

给webview自定义webViewClient,重写onPageFinish,在其中添加如下内容:

1
2
3
4
5
view.loadUrl("javascript:!function(){" +
 "s=document.createElement('style');s.innerHTML="
 + "\"@font-face{font-family:myhyqh;src:url('**injection**/hyqh.ttf');}*{font-family:myhyqh !important;}\";"
 + "document.getElementsByTagName('head')[0].appendChild(s);" +
 "document.getElementsByTagName('body')[0].style.fontFamily = \"myhyqh\";}()");

由于网页上是没有权限访问本地的asset文件夹的,因此我们需要拦截请求来加载本地的文件,我这里替换了file:///android_assets/**injection**/了,我们还需要重写 shouldInterceptRequest 在请求为我们这个字体文件的时候,加载本地文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
@Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
 WebResourceResponse response = super.shouldInterceptRequest(view, url);
 CLog.i("load intercept request:" + url);
 if (url != null && url.contains("**injection**/")) {

 //String assertPath = url.replace("**injection**/", "");
 String assertPath = url.substring(url.indexOf("**injection**/") + "**injection**/".length(), url.length());
 try {

 response = new WebResourceResponse("application/x-font-ttf",
 "UTF8", getAssets().open(assertPath)
 );
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 return response;
}

问题

使用字体统一界面,但是也遇到了一些问题,如下:

  1. 运行速度变慢(毫秒级,用户觉查不到),由于需要读取自定义的字体文件,以及需要渲染,比使用系统字体要慢。
  2. emoji在5.0以下的系统会有问题。
  3. 在网页,如果采用服务器文件的方法,会消耗用户的流量
  4. 在网页,采用本地注入方式,因为是在onpagefinish后才开始加载字体,因此页面会重新渲染,影响效果。这样还会造成网页可能会出现样式错误。

因为我们的程序中大量使用到emoji,以及考虑到性能的问题,决定还是使用系统自带的字体了。

如果你在这方面有更好的方案,欢迎交流!

看完评论一下吧

图片贴纸旋转缩放功能的实现

我们的项目包含图片编辑功能,特别是包含图片添加水印贴纸的功能,从最初的简单版可以添加一个图片并且移动位置,到现在添加的图片可以进行移动,以及缩放,旋转,已经是和其他的图片处理可以达到一样的很好的效果了。一直想要整理一下,分享一下实现的改进过程,一直没空,也由于我过于懒,没有动笔。今天正好有时间,分享一下。

原始阶段:直接添加ImageView,并且设置其在父view中的位置

父视图为RelativeLayout,贴纸view就是一个ImageView,通过设置topMargin和leftMargin来设置在父视图中显示的位置,不支持缩放和旋转。功能快速实现,代码比较冗余。再有了新的需求不方便扩展。

新阶段:自定义View,通过matrix变换实现各种功能

主要是定义一个View,在使用的时候放到需要用到的地方,大小设置和目标图片相同大小。通过matrix对平移,旋转,缩放的操作进行映射,最终改变贴纸图片的绘制结果,因此实现目标功能。下面具体分析各个功能。

首先创建的视图在设置完贴纸图片之后,要创建一个浮点型数组,用于保存默认未进行任何变换的时候贴纸图片的关键点,以及一个原始矩形用于保存一个默认绘制区域的矩形,用代码表示就是:

1
2
3
4
float imgWidth = mBitmap.getWidth();
float imgHeight = mBitmap.getHeight();
float[] originPoints = new float[]{0, 0, imgWidth,0, imgWidth, imgHeight, 0, imgHeight, imgWidth/2, imgHeight/2}; //分别为矩形的四个点,与中心点
RectF mOriginRect = new RectF(0, 0, imgWidth, imgHeight);

变换后的点通过Matrix.mapPoints(newPoints, originPoints)进行映射,变换后的矩形通过Matrix.mapRect(newRect, originRect)进行映射,可以通过这些新的点画一些附加元素。至于贴纸图,可以通过获取后的rect进行定位画,也可以直接使用canvas.drawBitmap(bitmap, matrix, paint)方法绘制。

至于如何进行变换操作,如何进行变换,则是在onTouch中处理各种触摸事件,或者在dispatchTouchEvent。

平移

通过判断ACTION_DOWN,ACTION_UP,判断触摸是否在我们的贴纸图片上面,然后计算手指滑动的距离,可以获取到x轴和y轴的平移距离,调用mMatrix.postTranslate(x,y),然后重新映射绘图即可。

旋转

以贴纸图片的一个边缘点为旋转触摸点,以贴纸图片的中心(非贴纸view的中心),计算旋转的角度,调用mMatrix.postRotate(rotation, px, py), px,py为贴纸图片的中心点(为上面映射后的点,而不是原始点)。

缩放

同样通过触摸位置计算两次滑动过程中的缩放比例,来通过Matrix.postScale(scale, scale, px, py)进行缩放。

其他

开始的时候没有想到使用Matrix,进行了很多的尝试,没有很好的结果。最后使用了Matrix之后,则简单很多,只是在计算缩放和旋转的时候,因为数学没有学好,花了很久才把数学问题搞定。

这里分享我自己的一个完整的贴纸View,开箱即用,https://github.com/sangmingming/StickerView 。如果在这方面,你又更好的实现方式,也欢迎留言,与我进行交流。

看完评论一下吧

打破Android应用65K方法数魔咒

近日,我们的应用,在编译的时候不幸的遇到这个错误

Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536

这才让我意识到原来我们的程序中,方法数已经超过了65536。在之前,已经知道了android系统的java虚拟机dalvik在执行java程序时,使用原生类型short来索引dex文件中的方法,因此方法数就呗限制在65536了。之前我一直以为,这个数量已经很大了,不会达到上限,结果今天就达到了。

不过这个东西呢,我们也是很容易的进行解决的,因为,就在去年不久前,google官方提供了多dex的支持库,因此,我们可以很简单的解决这个问题。

开发工具升级

将android sdks build tools 和android support library要升级到最新的,这个使用android sdks manager很容易就完成了。

配置build.gradle

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
android {
compileSdkVersion 21
buildToolsVersion "21.1.0"
defaultConfig {
...
minSdkVersion 14
targetSdkVersion 21
...
// Enabling multidex support.
multiDexEnabled true
}
...
}
dependencies{
compile 'com.android.support:multidex:1.0.0 //dependencies
}

让应用支持多dex

androidManifest.xml中application中声明android.support.multidex.MultiDexApplication;

或自己定义一个Application类,继承自MultiDexApplication;

或者自己定义的Application类,在attachBaseContext()方法中,添加MultiDex.install(this);

其他

通过上面的方法即可轻松完成多dex,不过在低版本的android系统(低于android4.0)可能会有bug出现,还要多进行测试。

究其原因,其实我们的app,自己写的代码现在其实不是很多,代码中使用了大量的第三方sdk,以及其他的一些功能集成。

下面,就要想办法,减少第三方的功能库了。这里跟大家分享一下解决方案。

参考资料: http://developer.android.com/tools/building/multidex.html

看完评论一下吧

加速Android Studio/Gradle构建

已经使用Android Studio进行开发超过一年,随着项目的增大,依赖库的增多,构建速度越来越慢,现在最慢要6分钟才能build一个release的安装包,在网上查找资料,发现可以通过一些配置可以加快速度,这里跟大家分享一下。

开启gradle单独的守护进程

在下面的目录下面创建gradle.properties文件:

  • /home/<username>/.gradle/ (Linux)
  • /Users/<username>/.gradle/ (Mac)
  • C:\Users\<username>\.gradle (Windows)

并在文件中增加:

1
org.gradle.daemon=true

同时修改项目下的gradle.properties文件也可以优化:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Project-wide Gradle settings.

# IDE (e.g. Android Studio) users:
# Settings specified in this file will override any Gradle settings
# configured through the IDE.

# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html

# The Gradle daemon aims to improve the startup and execution time of Gradle.
# When set to true the Gradle daemon is to run the build.
# TODO: disable daemon on CI, since builds should be clean and reliable on servers
org.gradle.daemon=true

# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
org.gradle.parallel=true

# Enables new incubating mode that makes Gradle selective when configuring projects.
# Only relevant projects are configured which results in faster builds for large multi-projects.
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:configuration_on_demand
org.gradle.configureondemand=true

同时上面的这些参数也可以配置到前面的用户目录下的gradle.properties文件里,那样就不是针对一个项目生效,而是针对所有项目生效。

上面的配置文件主要就是做, 增大gradle运行的java虚拟机的大小,让gradle在编译的时候使用独立进程,让gradle可以平行的运行。

修改android studio配置

在android studio的配置中,开启offline模式,以及修改配置。实际上的配置和上面的一大段一样,主要是在这个地方配置的只会在ide构建的时候生效,命令行构建不会生效。

开启offline

设置运行模式和VM配置

命令行构建

基于上面的配置,命令行构建时在命令后面加上这个参数即可 --daemon --parallel --offline

引入依赖库时使用aar

使用网上第三方的依赖库时尽量使用aar,可以在mavenhttp://gradleplease.appspot.com/或者githuhttps://github.com/Goddchen/mvn-repo搜索。

自己的库模块也可以打包成aar,关于这个可以参考stormzhang的文章http://www.stormzhang.com/android/2015/03/01/android-reference-local-aar/

后记

经过这样一番折腾,我原来需要4,5分钟才能构建完成的项目,现在只需要30秒左右就可以构建完成了。当然了,如果你这样还是不可以的话,那么,你应该换电脑了,ssd的硬盘,更大的内存,必须的上了。

看完评论一下吧

java注解

从java 5.0开始,为我们提供注解功能,通过注解可以限制代码的重载,过时,以及实现一些其他功能,这里,就来分析一下java的注解。

java 元注解

首先来看java元注解,分别是:

@Target

@Retention

@Documented

@Inherited

这些注解和他们所修饰的类在java.lang.annotation包中,代码都很简单,可以去查看一下。

@Target 描述注解的使用范围,取值:

ElementType.CONSTRUCTOR:描述构造器 ElementType.FIELD:描述成员变量 ElementType.VARIABLE: 描述局部变量 ElementType.METHOD: 描述方法 ElementType.PACKAGE: 描述包 ElementType.PARAMETER:描述方法的参数 ElementType.Type: 描述类,接口(包括注解类型)或enum声明.

@Retention 注解的声明周期,即在什么级别保留,取值:

RetentionPoicy.SOURCE :在源文件中有效(在.java文件中有效)
RetentionPoicy.CLASS: 在class文件中有效 RetentionPoicy.RUNTIME:在运行时有效

@Documented 用于描述其他类型的annotation应该被作为被标注的程序成员的公共API,可以被javdoc的工具文档化,无成员。

@Inherited 用于标注某个标注是被继承的,即父类中使用了一个Annotation,则子类继承父类的这个annotation,annotation需要标记为RUNTIME的才可以。

java内置注解

以上是元标记,再看java内置的标准注解,@Override,@Deprecated, @SuppressWarnings

@Override

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

从前面的元注解介绍可以看到,Override用于标注方法,有效期是在源码期间。用于标注方法重写。

@Deprecated

1
2
3
4
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Deprecated {
}

标注 过时,或者不建议使用,也是会保留到运行时,添加了Documented元标签,这样在生成文档时候,就可以生成过时的标记。

@SuppressWarnings

1
2
3
4
5
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
 String[] value();
}

忽略错误报告,有效时是源码级。

自定义注解

我们再来看看如何自定义注解。自定义的注解就和java内置的注解类似,也需要用到元注解,通过远注解设置那些地方可以使用,设置作用域。比如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
 int value() default;
}


@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface MyNewAnnotation{
 String author();
 int version() default 1;
}

public class MyClass {
 @MyAnnotation(12)
 public boolean isOK() {
 return true;
 }

 @MyNewAnnotation(author=sam, version=2)
 public int getAge() {
 return 19;
 }
}

上面前面的代码是定义注解,后面是使用。可以看到使用@interface来定义注解。

注解配置参数名为注解类的方法名,并且方法没有方法体,没有参数没有修饰符,不可以抛异常。返回值只能是基本类型,String,Class,annotation,enumeration,或者他们的一维数组。只有一个默认属性,可以直接用value()函数,没有属性,则这个注解是标记注解。可以加default表示默认值。

Android内置注解

作为android程序员,我们还是了解一下android中自带的注解,以及用法含义。

@SuppressLint: 指示lint检查时忽略注解元素的警告信息。
@TargetApi:指示lint把当前这个注解元素的target api为指定值,而不是项目设置的target api。
@NonNull:表示一个成员变量,或者参数,或者方法返回值永远不能为NULL。
@Nullable:标识一个成员变量,或者参数,方法返回值,可以为NULL。

android.support.annotation包中还有更多的注解可以使用。

另外,http://codekk.com/open-source-project-analysis/detail/Android/Trinea/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7%82%B9%E4%B9%8BJava%20%E6%B3%A8%E8%A7%A3%20Annotation对于注解的分析很好,推荐一下。

看完评论一下吧

android动画-View Animation

视图动画(View Animation),又称补间动画(Tween Animation),即给出两个关键帧,通过一些算法将给定属性值在给定的时间内在两个关键帧间渐变。本文首先讲解各种基本动画的使用,其实介绍View动画的工作过程。

概述

视图动画只能作用于View对象,是对View的变换,默认支持的类型有:

  • 透明度变化(AlphaAnimation)
  • 缩放(ScaleAnimation)
  • 位移(TranslateAnimation)
  • 旋转(RotateAnimation)

可以使用AnimationSet让多个动画集合在一起运行,使用插值器(Interpolator)设置动画的速度。

上面说到的几种动画,以及AnimationSet都是Animation的之类,因此Animation中有的属性,以及xml的配置属性,他们都有,因此,单独说每个动画的时候只说其特有的方法和属性。对于使用xml配置时需要放到res下面的animation文件夹下。

AlphaAnimation 透明度动画

就是改变视图的透明度,可以实现淡入淡出等动画。这个动画比较简单只需要设置开始透明度和结束透明度即可。

1
Animation animation = new AlphaAnimation(0.1f, 1.0f); //fromAlpha 0.1f toAlpha 1.0f

1
<alpha android:fromAlpha = "0.1f" android:toAlpha="1.0f" />

ScaleAnimation 缩放

缩放动画,支持设置开始x缩放(宽度缩放倍数),开始y缩放, 结束x缩放,结束y缩放,以及缩放基点x坐标,缩放基点y坐标。

x缩放和y缩放都是相对于原始的宽度和高度的,1.0表示不缩放。

坐标基点,同时有参数可以设置坐标基点类型,分别是:

  • Animation.ABSOLUTE(默认值) 相对于控件的0点的坐标值
  • Animation.RELATIVE_TO_SELF 相对于自己宽或者高的百分比(1.0表示100%)
  • Animation.RELATIVE_TO_PARENT 相对于父控件的宽或者高的百分比.

默认基点是视图的0点,默认坐标基点类型是ABSOLUTE。

有如下几种构造函数

1
2
3
4
5
6
7
ScaleAnimation(Context context, AttributeSet attrs)
ScaleAnimation(float fromX, float toX, float fromY, float toY)
new ScaleAnimation(1.0f, 1.5f, 1.0f, 1.5f);
ScaleAnimation(float fromX, float toX, float fromY, float toY, float pivotX, float pivotY)
new ScaleAnimation(1.0f, 1.5f, 1.0f, 1.5f, 10, 10);
ScaleAnimation(float fromX, float toX, float fromY, float toY, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)
new ScaleAnimation(1.0f, 1.5f, 1.0f, 1.5f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); //以中心点为基点

XML配置:

1
2
3
4
5
6
7
<scale
 android:fromXScale="float"
 android:toXScale="float"
 android:fromYScale="float"
 android:toYScale="float"
 android:pivotX="float"
 android:pivotY="float" />

TranslateAnimation 位移

平移支持x轴平移起点和y轴平移起点,以及设置结束点。同时每个点都可以设置type,type和上面缩放动画的基点类型一样,默认类型是ABSOLUTE.

有以下几个构造函数:

1
2
3
TranslateAnimation(Context context, AttributeSet attrs)
TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)
TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue, int fromYType, float fromYValue, int toYType, float toYValue)

XML配置:

1
2
3
4
5
<translate
 android:fromXDelta="float"
 android:toXDelta="float"
 android:fromYDelta="float"
 android:toYDelta="float" />

RoatationAnimation 旋转

旋转支持设置旋转开始角度,和旋转结束角度,以及旋转基点,和旋转基点类型。类型同上面一样,默认旋转基点是(0,0),默认类型同上面一样,也不多说了。

1
2
3
4
RotateAnimation(Context context, AttributeSet attrs)
RotateAnimation(float fromDegrees, float toDegrees)
RotateAnimation(float fromDegrees, float toDegrees, float pivotX, float pivotY)
RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)

XML配置:

1
2
3
4
5
<rotate
 android:fromDegrees="float"
 android:toDegrees="float"
 android:pivotX="float"
 android:pivotY="float" />

AnimationSet 动画集合

动画集合就是可以让多个动画一起运行,或者依次运行。

通过addAnimation(Animation a)向集合中添加动画,使用子动画的setStartOffset(long offset)设置延时,从而实现子动画之间的间隔。可以设置是否共享时间插值器。

xml配置:

1
2
3
4
5
<set>
<!--这里写子动画-->
<rotation ..../>
<alpha ...../>
</set>

属性动画(Property Animation)

Animation

单独把Animation拿出来说,是因为前面几个都是Animation,他们有一些属性都是从父类继承的。包括时常,插值器,是否重复,监听器等。

setFillBefore(boolean)和setFillAfter(boolean)分别是动画开始前和动画结束后是否保持动画状态,默认前者为ture,后者为false;

xml中可以配置的属性(这些在前面几个动画中省略了,也是可以使用的):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
android:detachWallpaper
android:duration
android:fillAfter
android:fillBefore
android:fillEnabled
android:interpolator
android:repeatCount
android:repeatMode INFINTE(无限期),RESTART(重新开始,默认值)
android:startOffset
android:zAdjustment ZORDER_BOTTOM,ZORDER_NORMAL, ZORDER_TOP

启动动画:

1
2
3
4
view.startAnimation(animation);
//或者这样
view.setAnimation(animation);
animation.start();

Interpolator 插值器

通过设置插值器可以改变动画的速度,以及最终效果。 android sdk提供了几种默认插值器,而且这些插值器在新的protery animation上仍然可以使用,这个后面再说。

  • AccelerateDecelerateInterpolator 先加速后减速
  • AccelerateInterpolator 加速
  • AnticipateInterPolator
  • AnticipateOvershootInterpolator
  • BounceInterpolator
  • CycleInterpolator
  • LinearInterpolator
  • OvershootInterpolator
  • PathInterpolator

当然,我们也可以自定义Interpolator,一般开始值为0,结束值为1.0,然后根据算法来改变值。

动画原理解析

动画就是根据间隔时间,不停的去刷新界面,把时间分片,在那个时间片,通过传入插值器的值到Animation.applyTransformation(),来计算当前的值(比如旋转角度值,透明度等).

因此,我们也可以继承Animation,从写applyTransformation()来实现我们的其他的动画。

其他

使用view动画时,如果需要用到类似基点类型和基点设置的,一定要注意设置对点,不然效果恨不如意。

另外,view动画,若动画前view在a点,动画过程以及动画后,view变化了位置,则点击点仍然在原位置,这是个大问题,特别需要注意。

在android apidemo中,有动画的使用,以及自定义动画,各种插值器效果,各位可以查看,我已经将其放到github上面了,地址:https://github.com/sangmingming/Android-ApiDemos

看完评论一下吧

android动画-Frame Animation

动画可以在视觉上增加程序的流畅度,我之前对于动画这一块,是会用,但是不全面,这里写下博客,全面梳理一下Android动画方面的知识。当然,关于动画这块,也有很多前人写了很多内容,大家可以去参考。

3.0以前,android支持两种动画模式,Tween Animation,Frame Animation,在android3.0中又引入了一个新的动画系统:Property Animation,这三种动画模式在SDK中被称为Property Animation,View Animation,Drawable Animation。 可通过NineOldAndroids项目在3.0之前的系统中使用Property Animation。另外呢,还有activity之间的过渡动画,android5.0增加的矢量动画,过渡效果等。

本文首先来说Frame Animation.

帧动画,在android中又称Drawable Animation,就是通过一系列的Drawable依次显示来达到模拟动画的效果。 android中提供了AnimationDrawable类来实现帧动画,我们可以使用AnimationDrawable作为View的背景。我们通常可以使用xml来配置动画。

在项目的res/drawable/目录下面创建一个xml文件。

文件中以<animation-list>作为根节点, 每一张图片作为一个<item>

如:

1
2
3
4
5
6
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
 android:oneshot="true">
 <item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
 <item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
 <item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>

上面代码中,onshot若为true,则动画只播放一次,否则动画会循环播放。item中的duration用于设置当前帧的停留时间。

在代码中获取并,启动动画。

如上面的xml文件为rocket_thrust.xml,则在代码中使用如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
AnimationDrawable rocketAnimation;

public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.main);

 ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
 rocketImage.setBackgroundResource(R.drawable.rocket_thrust);
 rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
}

public boolean onTouchEvent(MotionEvent event) {
 if (event.getAction() == MotionEvent.ACTION_DOWN) {
 rocketAnimation.start();
 return true;
 }
 return super.onTouchEvent(event);
}

上面代码具体就是首先从资源中获取到我们的动画,然后设置为view的背景,之后启动动画。

需要注意的是,动画的启动需要在view和window建立连接后才可以绘制,比如上面代码是在用户触摸后启动。如果我们需要打开界面就启动动画的话,则可以在Activity的onWindowFocusChanged()方法中启动。

上面介绍的是在xml中定义动画,当然也可以在java代码中定义动画。如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
AnimationDrawable rocketAnimation;

public void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.main);

 ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);

 rocketAnimation = new AnimationDrawable();
 rocketAnimation.addFrame(getResources().getDrawable(R.drawable.rocket_thrust1, 200);
 rocketAnimation.addFrame(getResources().getDrawable(R.drawable.rocket_thrust2, 200);
 rocketAnimation.addFrame(getResources().getDrawable(R.drawable.rocket_thrust3, 200);
rocketImage.setBackground(rocketAnimation);
}

public boolean onTouchEvent(MotionEvent event) {
 if (event.getAction() == MotionEvent.ACTION_DOWN) {
 rocketAnimation.start();
 return true;
 }
 return super.onTouchEvent(event);
}

更多使用方法,可以去查看AnimationDrawable的api手册.

另外,看了一下相关代码,AnimationDrawable代码不长,其父类是DrawableContainer,用于保存Drawable list,另外有一个数组保存每一帧的停顿时间。每隔一定时间,替换Drawable,重新刷新,实现动画。

看完评论一下吧

android反编译-如何防止反编译

前言

前面介绍了怎样去反编译别人的代码。哈哈,这里居然又写进行防止反编译。但是,还是先来写写吧。

使用ProGuard

proguard android的sdk中就有提供,使用它可以对代码进行混淆和精简,处理后的代码,虽然仍然可以反编译,但是阅读起来相当困难,降低代码的可读性。操作简单,推荐使用。

proguard使用方法和配置,可以看我之前的博客: https://isming.me/2014-05-31-use-proguard/

另外网上有别人共享的proguard配置模板,也可以参考: https://github.com/krschultz/android-proguard-snippets

如果大家有去proguard的官网,ProGuard的公司提供的DexGuard可以给android程序提供更多的优化和保护,不过这个软件收费的,有需要的也可以去了解以下(我不是广告,(^_^))。

代码转移到native

代码放在native层的话,使用我前面的方法就没办法去反编译了,这时就需要借助反编译c的方法了,这个我没有研究过了。

因此写在native层也是很安全的,但是因为native更难写,只建议偏重于专利,或者机密数据,等一些功能和逻辑写在native层。更加安全,也更快速。

使用第三方加密工具

国内现在也出现了很多apk加固工具,比如爱加密,梆梆加密等等。这些没有去使用过,但是看过网上的介绍,以及他们的自己的介绍,大致了解到,是在我们的apk之外加壳,对我们的dex文件进行加密来做的。

使用这些工具可以来帮助提高软件的安全性,但是使用之前也要确保服务的可靠性,服务商的信誉。

个人之见

以上只是本人想到的几种,比较可行的方案。同时肯定还有其他的方式,比如采用签名验证,插件开发等等机制。

在我看来,软件的一定程度的混淆是有必要的,毕竟这个一个公司的财产(很多公司靠一个app营收),不过一些不是很特有的东西也是可以开源的。毕竟,现在网上的开源项目很多,我们也从中使用,借鉴了很多,也要回馈开源社区才行

文章系本人拙见,如果这方面你有什么好的方法,或者有什么好的建议,也可以评论交流。

看完评论一下吧

android反编译-smali语法

前言

前面我们有说过android反编译的工具,如何进行反编译。反编译后可以得到jar或者得到smali文件。Android采用的是java语言进行开发,但是Android系统有自己的虚拟机Dalvik,代码编译最终不是采用的java的class,而是使用的smali。我们反编译得到的代码,jar的话可能很多地方无法正确的解释出来,如果我们反编译的是smali则可以正确的理解程序的意思。因此,我们有必要熟悉smali语法。

类型的表示

java里面包含两种类型,原始类型和引用类型(包括对象),同时映射到smali也是有这两大类型。

原始类型

V void (只能用于返回值类型) Z boolean B byte S short C char I int J long F float D Double

对象类型

Lpackage/name/ObjectName; 相当于java中的package.name.ObjectName

L 表示这是一个对象类型 package/name 该对象所在的包 ObjectName 对象名称 ; 标识对象名称的结束

数组的表示

[I 表示一个int型的一维数组,相当于int[]; 增加一个维度增加一个 [,如*[[I表示int[][]*

数组每一个维度最多 255个;

对象数组表示也是类似,如String数组的表示是 [Ljava/lang/String

寄存器与变量

java中变量都是存放在内存中的,android为了提高性能,变量都是存放在寄存器中的,寄存器为32位,可以支持任何类型,其中long和double是64为的,需要使用两个寄存器保存。

寄存器采用v和p来命名 v表示本地寄存器,p表示参数寄存器,关系如下

如果一个方法有两个本地变量,有三个参数

v0 第一个本地寄存器 v1 第二个本地寄存器 v2 p0 (this) v3 p1 第一个参数 v4 p2 第二个参数 v5 p3 第三个参数

当然,如果是静态方法的话就只有5个寄存器了,不需要存this了。

.registers 使用这个指令指定方法中寄存器的总数 .locals 使用这个指定表明方法中非参寄存器的总数,放在方法的第一行。

方法和字段的表示

方法签名

methodName(III)Lpackage/name/ObjectName;

如果做过ndk开发的对于这样的签名应该很熟悉的,就是这样来标识一个方法的。 上面methodName标识方法名,III表示三个整形参数,Lpackage/name/ObjectName;表示返回值的类型。

方法的表示

Lpackage/name/ObjectName;——>methodName(III)Z 即 package.name.ObjectName中的 function boolean methondName(int a, int b, int c) 类似这样子

字段的表示

Lpackage/name/ObjectName;——>FieldName:Ljava/lang/String;

即表示: 包名,字段名和各字段类型

方法的定义

比如我下面的一个方法

1
2
3
private static int sum(int a, int b) {
 return a+b;
}

使用编译后是这样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

.method private static sum(II)I
 .locals 4 #表示需要申请4个本地寄存器
 .parameter
 .parameter #这里表示有两个参数

 .prologue
 .line 27
 move v0, p0

 .local v0, a:I
 move v1, p1

 .local v1, b:I
 move v2, v0

 move v3, v1

 add-int/2addr v2, v3

 move v0, v2

 .end local v0 #a:I
 return v0
.end method

从上面可以看到函数声明使用*.method开始 .end method结束,java中的关键词private,static 等都可以使用,同时使用签名来表示唯一的方法,这里是sum(II)I*。

声明成员

.field private name:Lpackage/name/ObjectName; 比如:private TextView mTextView;表示就是 .field private mTextView:Landroid/widget/TextView; private int mCount; .field private mCount:I

指令执行

smali字节码是类似于汇编的,如果你有汇编基础,理解起来是非常容易的。

比如: move v0, v3 #把v3寄存器的值移动到寄存器v0上.

const v0, 0x1 #把值0x1赋值到寄存器v0上。

invoke-static {v4, v5}, Lme/isming/myapplication/MainActivity;->sum(II)I #执行方法sum(),v4,v5的值分别作为sum的参数。

其他

通过前面我们可以看到,smali就是类似汇编,其中很多命令,我们可以去查它的手册来一一对应。学习时,我们可以自己写一个比较简单的java文件,然后转成smali文件来对照学习。

下面,我贴一个我写的一个比较简单的java文件以及其对应的smali,其中包含if判断和for循环。

java文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package me.isming.myapplication;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

public class MainActivity extends ActionBarActivity {

 private TextView mTextView;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);

 mTextView = (TextView) findViewById(R.id.text);

 mTextView.setText("a+b=" + sum(1,2) + "a>b?" + max(1,2) + "5 accumulate:" + accumulate(5));

 }

 private static int sum(int a, int b) {
 return a+b;
 }

 private boolean max(int a, int b) {
 if (a > b) {
 return true;
 } else {
 return false;
 }
 }

 private int accumulate(int a) {
 if (a <= 0) {
 return 0;
 }
 int sum = 0;
 for(int i = 0; i <= a; i++) {
 sum += a;
 }
 return sum;
 }
}

对应的smali:

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
.class public Lme/isming/myapplication/MainActivity;
.super Landroid/support/v7/app/ActionBarActivity;
.source "MainActivity.java"


# instance fields
.field private mTextView:Landroid/widget/TextView;


# direct methods
.method public constructor <init>()V
 .locals 2

 .prologue
 .line 10
 move-object v0, p0

 .local v0, this:Lme/isming/myapplication/MainActivity;
 move-object v1, v0

 invoke-direct {v1}, Landroid/support/v7/app/ActionBarActivity;-><init>()V

 return-void
.end method

.method private accumulate(I)I
 .locals 6
 .parameter

 .prologue
 .line 39
 move-object v0, p0

 .local v0, this:Lme/isming/myapplication/MainActivity;
 move v1, p1

 .local v1, a:I
 move v4, v1

 if-gtz v4, :cond_0

 .line 40
 const/4 v4, 0x0

 move v0, v4

 .line 46
 .end local v0 #this:Lme/isming/myapplication/MainActivity;
 :goto_0
 return v0

 .line 42
 .restart local v0 #this:Lme/isming/myapplication/MainActivity;
 :cond_0
 const/4 v4, 0x0

 move v2, v4

 .line 43
 .local v2, sum:I
 const/4 v4, 0x0

 move v3, v4

 .local v3, i:I
 :goto_1
 move v4, v3

 move v5, v1

 if-gt v4, v5, :cond_1

 .line 44
 move v4, v2

 move v5, v1

 add-int/2addr v4, v5

 move v2, v4

 .line 43
 add-int/lit8 v3, v3, 0x1

 goto :goto_1

 .line 46
 :cond_1
 move v4, v2

 move v0, v4

 goto :goto_0
.end method

.method private max(II)Z
 .locals 5
 .parameter
 .parameter

 .prologue
 .line 31
 move-object v0, p0

 .local v0, this:Lme/isming/myapplication/MainActivity;
 move v1, p1

 .local v1, a:I
 move v2, p2

 .local v2, b:I
 move v3, v1

 move v4, v2

 if-le v3, v4, :cond_0

 .line 32
 const/4 v3, 0x1

 move v0, v3

 .line 34
 .end local v0 #this:Lme/isming/myapplication/MainActivity;
 :goto_0
 return v0

 .restart local v0 #this:Lme/isming/myapplication/MainActivity;
 :cond_0
 const/4 v3, 0x0

 move v0, v3

 goto :goto_0
.end method

.method private static sum(II)I
 .locals 4
 .parameter
 .parameter

 .prologue
 .line 27
 move v0, p0

 .local v0, a:I
 move v1, p1

 .local v1, b:I
 move v2, v0

 move v3, v1

 add-int/2addr v2, v3

 move v0, v2

 .end local v0 #a:I
 return v0
.end method


# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
 .locals 8
 .parameter

 .prologue
 .line 16
 move-object v0, p0

 .local v0, this:Lme/isming/myapplication/MainActivity;
 move-object v1, p1

 .local v1, savedInstanceState:Landroid/os/Bundle;
 move-object v2, v0

 move-object v3, v1

 invoke-super {v2, v3}, Landroid/support/v7/app/ActionBarActivity;->onCreate(Landroid/os/Bundle;)V

 .line 17
 move-object v2, v0

 const v3, 0x7f030017

 invoke-virtual {v2, v3}, Lme/isming/myapplication/MainActivity;->setContentView(I)V

 .line 19
 move-object v2, v0

 move-object v3, v0

 const v4, 0x7f08003f

 invoke-virtual {v3, v4}, Lme/isming/myapplication/MainActivity;->findViewById(I)Landroid/view/View;

 move-result-object v3

 check-cast v3, Landroid/widget/TextView;

 iput-object v3, v2, Lme/isming/myapplication/MainActivity;->mTextView:Landroid/widget/TextView;

 .line 21
 move-object v2, v0

 iget-object v2, v2, Lme/isming/myapplication/MainActivity;->mTextView:Landroid/widget/TextView;

 new-instance v3, Ljava/lang/StringBuilder;

 move-object v7, v3

 move-object v3, v7

 move-object v4, v7

 invoke-direct {v4}, Ljava/lang/StringBuilder;-><init>()V

 const-string v4, "a+b="

 invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

 move-result-object v3

 const/4 v4, 0x1

 const/4 v5, 0x2

 invoke-static {v4, v5}, Lme/isming/myapplication/MainActivity;->sum(II)I

 move-result v4

 invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;

 move-result-object v3

 const-string v4, "a>b?"

 invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

 move-result-object v3

 move-object v4, v0

 const/4 v5, 0x1

 const/4 v6, 0x2

 invoke-direct {v4, v5, v6}, Lme/isming/myapplication/MainActivity;->max(II)Z

 move-result v4

 invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Z)Ljava/lang/StringBuilder;

 move-result-object v3

 const-string v4, "5 accumulate:"

 invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

 move-result-object v3

 move-object v4, v0

 const/4 v5, 0x5

 invoke-direct {v4, v5}, Lme/isming/myapplication/MainActivity;->accumulate(I)I

 move-result v4

 invoke-virtual {v3, v4}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;

 move-result-object v3

 invoke-virtual {v3}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

 move-result-object v3

 invoke-virtual {v2, v3}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V

 .line 23
 return-void
.end method

参考资料

最后附上一些参考资料:

http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html

https://code.google.com/p/smali/w/list

http://www.miui.com/thread-409543-1-1.html

看完评论一下吧

android反编译-反编译工具和方法

前言

开发过程中有些时候会遇到一些功能,自己不知道该怎么做,然而别的软件里面已经有了,这个时候可以采用反编译的方式,解开其他的程序,来了解一些它的做法,同时啊,还可以借鉴别人的软件结构,资源文件,等等,哈哈。那我就来讲解一些关于反编译相关的知识,主要分三篇,第一篇介绍反编译的工具和方法,第二篇,介绍smali的语法,第三篇介绍如何防止反编译,主要通过这几篇文章,了解如何去做反编译和代码加固。

工具

apktools-目前最强大的反编译工具

轻松反编译apk,解析出资源文件,xml文件,生成smali文件,还可以把修改后的文件你想生成apk。

支持windows,linux,mac。

下载地址:https://code.google.com/p/android-apktool/downloads/list 请自备梯子

dex2jar

将apk中的dex文件转换成为jar文件,很多人不会看smali文件,还是看java类文件比较舒服,这个时候可以借助这个工具来转成java,也是支持windows,linux,mac。

下载地址:http://code.google.com/p/dex2jar/downloads/list

jd-gui

查看jar文件,基本可以看到java class文件了,也是支持mac,windows,linux。

下载地址:http://jd.benow.ca/

apktool的命令行综合工具推荐 apktool plus

其实是别人写的一个工具,集合了apktool的功能,另外还支持给apk签名。最新版本是v9update6,只支持windows系统。

下载地址:http://dl.dbank.com/c0jndlkbu4#

进行反编译

使用apktools

在apktools目录下执行以下命令

./apktool d pathtoapk outdir #mac linux apktool.bat d pathtoapk outdir #window

这样就可以反编译成功了,可以查看其中的资源文件,smali文件,当然有的app进行了特殊处理,不是全部可以反编译的。

同时apktool还可以对反编译后的文件逆向成apk文件,格式如下。

./apktool b apppath outpath

逆向后的文件要是无签名的需要先签名才可以安装。

使用dex2jar

apk文件本身其实就是一个zip压缩包,先讲apk改成一个*.zip*文件解压后得到一个classes.dex。到dex2jar的目录,执行以下命令.

./d2j-dex2jar.sh pathtoclasses.dex #mac linux d2j-dex2jar.bat pathtoclasses.dex #wind

之后会生成一个jar文件,用jd-gui打开就可以看到其中的java代码了。

其他

其实我们使用的反编译也就这些足够了,通常很多时候无法获取很多的代码,毕竟人家也有措施应对的。

看完评论一下吧

android异步操作总结

Android中经常会有一些操作比如网络请求,文件读写,数据库操作,比较耗时,我们需要将其放在非UI线程去处理,此时,我们需要处理任务前后UI的变化和交互。我们需要通过类似js中异步请求处理,这里总结我所了解到的,方便自己记忆,也方便别人的浏览。

  1. AsyncTask

new AysncTask().execute();

AsyncTask会按照流程执行在UI线程和一个耗时的任务线程。

1.onPreExecute() 执行预处理,它运行于UI线程,可以为后台任务做一些准备工作,比如绘制一个进度条控件。

2.doInBackground(Params…) 后台进程执行的具体计算在这里实现,doInBackground(Params…)是AsyncTask的关键,此方法必须重载。在这个方法内可以使用publishProgress(Progress…)改变当前的进度值。

3.onProgressUpdate(Progress…) 运行于UI线程。如果在doInBackground(Params…) 中使用了publishProgress(Progress…),就会触发这个方法。在这里可以对进度条控件根据进度值做出具体的响应。

4.onPostExecute(Result) 运行于UI线程,可以对后台任务的结果做出处理,结果就是doInBackground(Params…)的返回值。此方法也要经常重载,如果Result为null表明后台任务没有完成(被取消或者出现异常)。

  1. Handler 创建Handler时需要传Lopper,默认是UI线程的。 通过Handler发送消息(Message)到主线程或者Handler的线程,

  2. Activity.runOnUiThread(Runnable) Runnable即可在UI线程执行

  3. View.post(Runnable) Runnable运行在UI线程 View.post(Runnable)方法。在post(Runnable action)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里。在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。在Handler再次处理该Message时,有一条分支(未解释的那条)就是为它所设,直接调用runnable的run方法。而此时,已经路由到UI线程里,因此,我们可以毫无顾虑的来更新UI。

所有的异步操作原理本质都是通过Handler

基本上就这几种方法,当然也可自己使用消息循环常见类似的任务处理机制。

看完评论一下吧

Android图像开源视图:SmartImageView

项目需要,开发中需要加载图片,自己要写图片从网上下载的方法,还要写缓存,等等。

在网上找到一个开源项目,smartImageVIew,支持从URL和通讯录中获取图像,可以替代Android标准的ImageView。

特征:

根据URL地址装载图像;
支持装载通讯录中的图像;
支持异步装载;
支持缓存;

这个是作者的项目主页,有使用方法。

http://loopj.com/android-smart-image-view/

下载作者的jar包导入项目后,在xml中加入控件

<com.loopj.android.image.SmartImageView android:id="@+id/my_image" />

代码里找到该控件

SmartImageView myImage = (SmartImageView) this.findViewById(R.id.my_image);

使用控件

通过url加载图片

myImage.setImageUrl("http://www.awesomeimages.com/myawesomeimage.jpg");

加载通讯录的图片

myImage.setImageContact(contactAddressBookId);

github上面有源码,需要的可以看看:

https://github.com/loopj/android-smart-image-view

看完评论一下吧

微慕小程序专业版支持Donut多端框架

微慕团队近期发布了微慕专业版5.0版,加入了对微信官方的多端框架 Donut 的支持。Donut 多端框架是支持使用小程序技术和工具开发移动应用的框架,开发者可以一次编码,分别编译为小程序和 Android 以及 iOS 应用。此框架最重要的特性:支持使用原生语法开发的微信小程序和多端应用。 如果已经有开发好的小程序,引入Donut后,可以迅速而简单地构建生成App。

目前使用 Donut 多端框架与工具的开发、编译、调试、预览等基础版功能永久免费;云构建安装包需消耗较多资源,官方提供基础发布与更新所需的免费次数。

微慕专业版加入Donut架构后,通过较小的代码修改,已成功发布Adroid 和 iOS版本,并入选官方推荐案例

1、选择Donut的理由

一个重要的原因是这个是微信官方推出的多端框架,对微信小程序原生代码支持最完整,如果已经有了微信小程序产品,做少量的代码修改,就可以适配生成App,且微信小程序的大量API和sdk都可以兼容并应用到App里,目前这个api和sdk还在不断完善更新中,相信未来会有越来越多的小程序功能可以延展到App里,为App赋予更多的能力。
API兼容性总览参考文档:https://dev.weixin.qq.com/docs/framework/dev/jsapi/total.html

2、多端框架选型

多端的框架很多,微慕团队尝试过多端框架有finclip,uni-app, Donut

finclip是凡泰极客出品,2021年开始构架,已经形成比较完善的生态体系,从开发工具,到与微信小程序兼容框架,到应用市场,到云服务,技术和商务的支持,对小团队来说,比较友好。微慕团队在2022年时候,曾尝试在微慕开源版引入了finclip小程序框架,经过较小的改动,就生成了finclip版本的App,App的兼容性也比较好,此框架适合从微信小程序转App,转换的过程基本没有门槛。用此框架转换的微慕应用还获得了凡泰极客主办的首届FinClip Hackathon二等奖

uni-app是多端业态里时间最长最成熟的框架。这个框架需要采用vue的原生代码来生成小程序和app,如果原来是用微信原生代码的话,需要通过转换的工具把微信小程序原生代码转换成vue的代码,因此一个产品应用在刚刚创建之初,就考虑将来要支持多端的话,用vue代码从零开始构建一个uni-app应用,是非常合适的选择。针对微慕开源版,也搞一个版本进行适配,下载链接是:https://ext.dcloud.net.cn/plugin?id=2214

同时,微慕团队基于uni-app全新开发了微慕增强版,完全放弃了原生版本,真正做到一套代码生成多端。

3、关于App上架应用市场

通过微慕专业版App上架应用市场过程来看,个人觉得上架苹果App Store 应用商店要简单些,对相关资质要求低一些,主要涉及隐私或技术细节的问题,而上架国内市场,著作权、备案和各类资质,都要准备比较充分,过程长,比较折腾。关于上架应用市场常见问题,可以参考这个链接:https://dev.weixin.qq.com/docs/framework/faq/publish.html

访问微慕多端小程序和App可以扫描以下二维码

微信专业版 增强版微信 增强版百度 增强版字节 增强版QQ
微信专业版 微信增强版 百度增强版 字节增强版 QQ增强版
支付宝增强版 快手增强版 专业版APP(ADROID) 专业版APP(IOS) 增强版APP
支付宝增强版 快手增强版 专业版安卓app 专业版苹果app 增强版app

谢谢你的阅读,谢谢你对微慕小程序的支持。

笔记 | 已 Root 安卓手机开启游戏高帧率

红米 K40 作为我的备用机有着 iPhone12 所不具备的 120 Hz 帧率的高刷屏,然而在为手机刷上 PixelExperience 系统后还未曾体验过 120 Hz 玩游戏是什么体验。在一番浅浅的研究后发现,「应用伪装」可以在不影响系统的情况下,只让调整游戏到高帧率。

笔记 | 已 Root 安卓手机开启游戏高帧率最先出现在Jack‘s Space

❌