D

Dorck's Blog

修技术之道,日三省吾身 | App Dev & Freelance Thinker & Hacker Growth | 这里是 @道轲(Dorck) 的个人博客,与你一起探寻世界。

Android上的Deep-Link技术调研

本文是两年前输出的文章,可能与现在的成熟方案存在部分细节上“代沟”,请诸位选择性阅读,适当参考即可。 一、需求背景 现在有很多 APP 支持从浏览器或其他地方唤起自家应用,并跳转至特定页面去提高用户黏性。在提高运营手段的背景下,应用也需要迫切提高自身的曝光入口以及活动场景。如果用户可以通过点击短信中的一个活动链接能够直接跳转到目标 APP 的特定页面,那么带来的好处也是不言而喻的。 二、方案调研 iOS 系统提供的 universial link 来帮助应用实现这个功能,而至于 Android 可能就比较繁琐了,Android 6.0 之后开始才提供了 App Links 来帮助开发者从外部唤起自家应用,但是 6.0 之前一般只能通过 URI Scheme 方式来唤起应用。不同的方案各自都存在对应的兼容性问题,以下是各方案的对比情况: 技术 Universal Link Android App Link URI Scheme Chrome Intent 平台要求 >= iOS 9 >= Android 6 Chrome 1 < 25, iOS Chrome 1 >= 25 未安装表现 打开 Web 页面 打开 Web 页面 发生错误 可以打开 Web 页面 能否不发生跳转 不能 不能 能 能 能否去下载页面 能 能 不能 能 iframe 触发 不支持 不支持 Chrome 1 <= 18, iOS < 9 不支持 链接格式 2 正常的 URL 正常的 URL 自定义协议的 URL intent 协议的 URL 本文只针对移动端浏览器,其中 Chrome 表示 Chrome for Android,以及 Android Browser 的对应版本。 链接的作用方式有 3 种:用户点击这样的 <a> 标签;脚本中进行页面重定向;设置 iframe 的 src。 每种实现方式都有其适用的平台和浏览器,要兼容多数浏览器需要根据 User Agent 应用不同的策略。 这些实现方式的行为也不一致,可能需要配合产品需求才能确定结合哪几种实现方式。 下面来看下各方案的具体实现过程。 三、URI Scheme 方案 URI Scheme 这种方式比较古老,目前也广泛用于从外部应用打开APP,这种协议由开发者自己定义,有比较丰富的定制性,但唤起时会有一个系统级别弹窗来让用户去选择通过匹配到的 APP 或者浏览器打开。URI 协议格式如下: [scheme]://[host]/[path]?[params] 例如:http://www.baidu.com/data?page=home&uid=ut9092 上面的 URI 可以在 Android 应用中通过 intent-filter 去识别和处理: 1 2 3 4 5 6 7 8 9 <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:host="www.baidu.com" android:pathPattern="data" android:scheme="http"/> </intent-filter> 下面,我们来通过 URI Scheme 来实现一个简单的小 demo,要求能够通过系统的记事本应用中的链接进入到 APP 的目标页面。首先,我们先来约束一下协议格式内容,如下所示: 1 http://com.example.deeplink?path=demo_page&text=im_from_system_notebook 可以看到,我们定义的 scheme 为 http,而 host 对应的 com.example.deeplink,这里我设置的是应用的包名,一般不会更改。至于 path 和 text 就很显然是作为参数内容了,这里分别代表着页面路径参数和对应的页面内容参数,方便我们定制不同的页面逻辑,大家可以根据情况制定自己的规则。好的,接下来开始我们的第二步 — 在 demo 项目中额外创建一个页面(EvokeDemoActivity)并添加相应的 intent 匹配逻辑: 1 2 3 4 5 6 7 8 9 10 <activity android:name=".deeplink.EvokeDemoActivity"> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="http" android:host="com.example.deeplink"/> </intent-filter> </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 /** * Deep link 唤起的目标页面 */ public class EvokeDemoActivity extends AppCompatActivity { public static final String EVOKE_DEMO_PAGE = "demo_page"; private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_evoke_demo); textView = findViewById(R.id.textView); obtainDeepLink(); } private void obtainDeepLink() { Intent intent = getIntent(); if (intent != null && intent.getData() != null && intent.getData().getQueryParameter("path").equals(EVOKE_DEMO_PAGE)) { Log.e("DemoPage", "received deep link intent: " + intent.getData().toString()); String paramContent = intent.getData().getQueryParameter("text"); if (!TextUtils.isEmpty(paramContent)) { textView.setText(paramContent); } } } } 接着运行一下 demo,然后在系统记事本中输入测试链接,如: [http://com.example.deeplink?path=demo_page&text=im_from_system_notebook](http://com.example.deeplink?path=demo_page&text=im_from_system_notebook) 保存后点击查看,可以看到如下效果: 可以发现,我们在记事本里点击链接后弹出了系统弹窗让我们选择打开链接的应用,需要选择我们自己的 APP 来打开(示例这边是 Android samples 应用),然后成功进入了目标 app 的制定页面并拿到了携带的参数内容。 四、App links 方案 与 iOS 中的 universial links 方案类似,Android 在 6.0 推出了 App links,用标准的 Web 页面 URL,同时绑定对应的 App。可以支持从白名单配置中的链接直接唤起 App,而不需要额外弹窗提示,对于用户来说会降低困扰,提升用户体验度。 首先,我们需要生成一份配置文件放到服务端,点击 Android studio 的 Tools > App Links Assistant,进入配置页面后第一步点击 Open URL Mapping Editor,添加一个 URL: 然后在 Android manifest 文件中修改一下配置: 1 2 3 4 5 6 7 8 9 10 <activity android:name=".deeplink.EvokeDemoActivity"> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="http" android:host="com.example.deeplink"/> </intent-filter> </activity> 可以看到,这里我们只是添加了一个 autoVerify 属性。接着通过点击 Open Digital Asset Links File Generator **来生成配置的 json 文件: **具体可参考官方文档的配置过程:https://developer.android.com/studio/write/app-link-indexing?utm_source=android-studio#associatesite 最终配置文件上传到服务器并测试通过后,就可以同样打开上面的测试链接,发现直接可以唤起我们的 APP,而不需要经过弹窗拦截。 此外,我们除了可以通过Android studio提供的 tools 来验证 app link 配置的正确性以外,也可以直接通过以下 adb 命令来验证: 1 adb shell dumpsys [package_name] domain-preferred-apps 如果显示如下信息,则校验成功: 1 2 3 Package: com.xxx.example Domains: example.applink.xxx.com Status: always : 200000036 另外,需要注意的是:想要该配置关联生效,APP 在安装时必须联网。 五、测试 App Links 唤起方式 当您 App Links 所有的环节已经配置完成后,可以利用测试设备测试唤起效果,测试步骤如下: 1、创建一条用于测试的 DeepLink ; 2、在测试设备中原生场景下进行测试,如:短信、备忘录等等; (不要直接在第三方软件中进行测试,例如微信。第三方软件通常对 DeepLink 跳转存在限制) 3、点击测试 DeepLink,如果可以直接跳转到 App 中,说明可以直接唤起 App,为最理想流程。 4、如果未直接跳转到 App 中,而是跳入下载引导中间页,并且系统弹窗询问是否要在应用中打开,此时可以通过上面的 adb shell dumpsys [package_name] domain-preferred-apps 命令 中的 Status 查看状态,如果状态显示为“ask”,并且确认 App Links 集成流程正确无误,则可能是当前测试设备机型在 AppLinks 遇到校验问题,对于该情况请参考下方常见问题说明。 对于 Status 状态的说明: Status 状态 描述 ask Applink校验失败, 每次打开连接跳转时会弹出一个对话框, 提示选择打开短链的App always 校验成功,理想状态 never 用户选择不再打开 always-ask 可忽略,尚未发现这一个出现, 跟 never 一样需要手动干预才会出现 undefined 尚未校验完成, 请稍后再试 常见问题: 1、多数客户可能会在验证环节得到的 Status 为 ask,这是为什么呢? AppLinks 的合法性是由系统校验,不同的手机系统使用不同的校验组件,即使是一个厂商的不同型号手机都可能使用不同的校验组件。 如果系统使用 com.android.statementservice 进行 AppLinks 的校验,在网络正常的情况下基本都能顺利通过, 如果系统使用 com.google.android.gms 组件校验,在手机能够科学上网的情况,也就是能够正常访问 Google 时,校验才能通过。常见华为 mate 系列,P 系列使用的都是 gms,也就是 Status 会为 ask。 查看自己手机是使用哪种组件,在命令行中输入以下命令: adb shell dumpsys package i 综上,Applink 不能顺利通过系统检验,原因有以下可能: 可能是国内网络问题,使用 gms 组件校验的手机需要联通 Google 服务 可能是您产品配置问题,GIO 填写的签名和手机上运行的 APP 签名不同 六、在浏览器唤起 APP 的场景 可是,重点来了,我们在实际测试过程中发现了一堆兼容性问题,在说明这些兼容性问题前,我们先解释一个概念:intent:// 协议。 Android Chrome 25+ 后已经不支持自定义 scheme 的方式,只支持 intent:// 协议(Android Intents with Chrome),最终要的是需要用户手动进行 点击 才能跳转,举个例子: 1 2 3 4 5 <a href="intent://scan/#Intent;scheme=zxing;package=com.google.zxing.client.android;S.browser_fallback_url=http%3A%2F%2Fzxing.org;end"> Take a QR code </a> <iframe href="intent://xxx" style="display: none;">></iframe>// 失效 <iframe href="strange://xxx" style="display: none;">></iframe>// 失效 intent:// 协议格式说明: 1 2 3 4 5 6 intent: //scan/ #Intent; package=com.google.zxing.client.android; scheme=zxing; end; 目前市面上大多第三方浏览器都是基于 Chrome 开发,这就带来了兼容性问题(没有条件覆盖所有的系统浏览器,这里只是有限测试的结果): 1、部分浏览器,只支持 intent:// 协议 手动 唤起,如chrome、锤子。 2、部分浏览器只支持 scheme 唤起,如 UC 浏览器。 3、大部分浏览器,同时支持 scheme 私有协议和 intent:// 协议 自动 唤起。但,都没有按标准的 intent:// 协议来实现(除了 360 浏览器,给 360 点个赞): 有的浏览器在 App 没安装时并没有执行 S.browser_fallback_url,而是跳转到应用市场如猎豹浏览器 4.46.3、乐视浏览器 1.2.1.29。 有的浏览器不支持 S.browser_fallback_url 如搜狗浏览器、欧朋浏览器、猎豹浏览器。 有的浏览器无论应用有无安装 S.browser_fallback_url 一直都会执行如 QQ 浏览器。 所以对这部分浏览器,不能使用 intent:// 协议。 4、更奇葩者,二者都不支持,如百度浏览器。 兼容性问题解决方案 针对上述三个兼容性问题,第 4 种情况无解我们直接忽略,第 2 第 3 种情况只能用自定义 scheme 的方式。 问题出在第 1 种情况,因为只能手动唤起,我们需要对浏览器类型进行判断(浏览器没有提供是否支持 自定义 scheme、intent:// 的 API 只能通过 UA 判断),结合我们有限的测试结果,如果是锤子、Chrome 原生浏览器,需在页面中内置一个“下载应用”的按钮引导用户点击。 1 2 3 4 <a href="intent://strange/#Intent;scheme=strange://login;package=com.strange;S.browser_fallback_url=http%3A%2F%2Fstrange.com;end">跳转到活动页/下载</a> 我们来分析一下浏览器的 UA ,举几个例子: 1 2 3 4 5 6 7 8 //小米系统浏览器 User-Agent:Mozilla/5.0 (Linux; U; Android 6.0.1; zh-cn; MI 3W Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.146 Mobile Safari/537.36 XiaoMi/MiuiBrowser/8.9.5 // 小米 Chrome 原生浏览器 User-Agent:Mozilla/5.0 (Linux; Android 6.0.1; MI 3W Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.108 Mobile Safari/537.36 // 锤子系统浏览器 User-Agent:Mozilla/5.0 (Linux; Android 5.1.1; YQ603 Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.11 Mobile Safari/537.36 // 锤子 Chrome 原生浏览器 User-Agent:Mozilla/5.0 (Linux; Android 5.1.1; YQ603 Build/LMY47V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.89 Mobile Safari/537.36 可以看出小米浏览器是在 Chrome 原生浏览器的 UA 上增加了 XiaoMi/MiuiBrowser/8.9.5 这部分特征码。类似的,很多第三方浏览器都是在 Chrome 基础上增加自己的特征码,换句话说 Chrome 原生浏览器 UA 没有自己的特征。 而锤子系统浏览器和 Chrome 原生浏览器 UA 几乎一样,这就使得判断是否锤子系统浏览器、 Chrome 原生浏览器 变得异常困难,要想尽可能完美解决问题只能使用排除法。 排除法由于不可能排除所有非 Chrome 原生浏览器,可能会存在误伤的可能。 经浏览器中转唤起 App 总结 要实现经浏览器中转 自动 唤起 App,Android 和 iOS 都可以通过 自定义 scheme 的方式,但 Android 的情况稍显复杂,因为部分浏览器并不支持,必须换成 intent:// 协议的方式 手动 唤起。 考虑到浏览器判断的难度,结合浏览器市场占有率的情况,我们最终的方案是暂时忽略 锤子系统浏览器、 Chrome 原生浏览器 这部分不支持 自定义 scheme 自动唤起 App 的用户。 App Links兼容性问题 App links在国内的支持还不够,部分安卓浏览器并不支持跳转至App,而是直接在浏览器上打开对应页面(直接将Applink作为链接跳转)。 系统询问是否打开对应App时,假如用户选择“取消”并且选中了“记住此操作”,那么用户以后就无法再跳转 App。 1 2 3 <a href="https://tuyasmart.applink.smart321.com/triple/fromalexa?client_id=xxx&response_type=alexa&state=1&scope=uejdj&redirect_uri=https.baidu.com">通过applink唤起</a> 1 2 3 <a href="intent://applink/deviceshare/#Intent;scheme=tuyasmart;package=com.tuya.smart;S.browser_fallback_url=https://www.zhihu.com/question/270839820;end">通过 chrome Intent 唤起</a> 1 2 3 <a href="tuyasmart://com.tuya.smart/applink/deviceshare?short_code=124141">通过scheme唤起</a> 经过自己简单部署的前端页面发现,在 App link、chrome intent 以及 scheme 三种唤起方式在不同机型和浏览器表现上存在较大差异,以下是几种机型和浏览器简单的对比(并不全):   App link(Android 6.0) chrome intent scheme Pixel 2 XL(chrome) 成功且无弹窗 成功且无弹窗 成功且无弹窗 一加7t(夸克) 失败 成功,唤起前会有确认弹窗 成功,唤起前会有确认弹窗 三星GT-19500(三星浏览器 6.0 以下) 失败 成功且无弹窗 成功且无弹窗 三星GT-19500(chrome浏览器 6.0 以下) 成功,但有选择弹窗 成功且无弹窗 成功且无弹窗 虽然安卓还支持 APP Links 技术,但是这个技术依赖于谷歌服务器的验证,而谷歌服务器是被墙的,所以是在国内基本不能用。所以在安卓端能用的技术只有scheme技术了)可是大部分 App 的 URL Scheme 方式都被微信QQ封锁了,不过微信QQ有维护着一个白名单,如果你的域名在白名单内,那这个域名下所有的页面发起的 URL Scheme 就都会被允许。 七、项目中应用现状 目前内置项目中已经部分模块实现了从外部网页直接唤起进入 APP 内部的功能,主要还是借助于传统 Deep link 的方式实现的: 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 <activity android:name="com.example.activity.ThirdAuthActivity" android:exported="@ref/0x7f05000d" android:launchMode="2" android:screenOrientation="1" android:configChanges="0x4a0"> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="https" android:host="xyz.com" android:path="/action/share" /> </intent-filter> </activity> <activity android:name="com.example.activity.ThirdLinkActivity" android:screenOrientation="1" android:configChanges="0x4a0"> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="https" android:host="xyz123.com" android:path="/action/share" /> </intent-filter> </activity> 经过与产品确认,目前只考虑 Android 6.0及以上的情况统一使用 AppLinks 来从外部网页唤起应用。然而,针对于国内浏览器环境等综合因素及 Android6.0 以下的情况,还是使用传统 DeepLinks 方式兜底。 八、理想方案 综合起来就是: 通过 App Links(iOS 则是Universal Links),可以实现点击短信链接直接唤起 App; 如果系统因为各种原因不支持 App Links,备选方案是 URI Scheme,不过会出弹框让用户选择用哪个 App 打开链接; 如果用户没有选择我们的 App 而是选择了浏览器打开,则通过 URI scheme 尝试唤起 App; 由于技术和成本问题,我们忽略不支持 URI Scheme 的浏览器。 如下图所示: 网页能否判断当前运行的浏览器环境,如果是 chrome 浏览器(或内置chrome内核),则通过 Applink 唤起应用页面,如果其他类型的浏览器能否判断是否支持 intent:// 唤起,否则都通过原生 scheme 来兜底,至于 Android 6.0 以下机型默认执行 scheme 唤起,这也是 App link 在低版本机型上的默认兜底方式。 相关参考 一切为了运营!如何从推广短信链接唤起 App? 知乎讨论:如何用手机短信唤起app Android官方文档:App Links 使用 Android官方:处理Android应用链接 通过 Url 链接打开 APP Deep Linking:从浏览器调起 APP Android 文档:APP links 验证 Growingio 专注用户增长 sdk 文档 说说 Applink 的使用以及原理 https://juejin.cn/post/6844903494760349703 几种在浏览器网页唤起APP的方案 网页唤起APP开源库推荐 https://developer.umeng.com/docs/191212/detail/194585 Web 前端几种常见的本地部署方式

2023/9/16
articleCard.readMore

Launcher进程启动过程剖析

上一篇文章我们对 Android 系统启动过程做了一定了解,本文将继续分析 Launcher 进程的启动流程。 本文基于 Android 13 最新代码来进行源码分析。 Launcher 进程启动 上文中,我们最终谈到了 system_server 进程的创建过程,并且该进程主要负责: 创建系统服务管理器——SystemServiceManager 启动各种服务(AMS、PMS等) 开启 Looper 循环,等待其他线程发送消息并处理 我们看一下 AMS 服务创建及配置这块实现: frameworks/base/services/java/com/android/server/SystemServer.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 // Activity manager runs the show. t.traceBegin("StartActivityManager"); // TODO: Might need to move after migration to WM. ActivityTaskManagerService atm = mSystemServiceManager.startService( ActivityTaskManagerService.Lifecycle.class).getService(); mActivityManagerService = ActivityManagerService.Lifecycle.startService( mSystemServiceManager, atm); mActivityManagerService.setSystemServiceManager(mSystemServiceManager); mActivityManagerService.setInstaller(installer); ...... // We now tell the activity manager it is okay to run third party // code. It will call back into us once it has gotten to the state // where third party code can really run (but before it has actually // started launching the initial applications), for us to complete our // initialization. mActivityManagerService.systemReady(() -> { Slog.i(TAG, "Making services ready"); t.traceBegin("StartActivityManagerReadyPhase"); mSystemServiceManager.startBootPhase(t, SystemService.PHASE_ACTIVITY_MANAGER_READY); t.traceEnd(); t.traceBegin("StartObservingNativeCrashes"); try { mActivityManagerService.startObservingNativeCrashes(); } catch (Throwable e) { reportWtf("observing native crashes", e); } ...... }, t); 下面重点关注一下 ActivityManagerService.systemReady 方法,通过注释可以简单了解到 Launcher 应用貌似就是在这里启动的,来找一下代码实现: frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public ActivityManagerService(Context systemContext, ActivityTaskManagerService atm) { ... mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class); ... } /** * Ready. Set. Go! */ public void systemReady(final Runnable goingCallback, @NonNull TimingsTraceAndSlog t) { t.traceBegin("PhaseActivityManagerReady"); mSystemServiceManager.preSystemReady(); ... // Some systems - like automotive - will explicitly unlock system user then switch // to a secondary user. // TODO(b/242195409): this workaround shouldn't be necessary once we move // the headless-user start logic to UserManager-land. if (isBootingSystemUser && !UserManager.isHeadlessSystemUserMode()) { t.traceBegin("startHomeOnAllDisplays"); mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady"); t.traceEnd(); } } 启动 Launcher 应用是在 ActivityTaskManagerInternal#startHomeOnAllDisplays 方法中,我们需要找一下 ActivityTaskManagerInternal 的实现类,但发现它是通过 LocalServices.getService 方法获取的: frameworks/base/core/java/com/android/server/LocalServices.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 public final class LocalServices { private LocalServices() {} private static final ArrayMap<Class<?>, Object> sLocalServiceObjects = new ArrayMap<Class<?>, Object>(); /** * Returns a local service instance that implements the specified interface. * * @param type The type of service. * @return The service object. */ @SuppressWarnings("unchecked") public static <T> T getService(Class<T> type) { synchronized (sLocalServiceObjects) { return (T) sLocalServiceObjects.get(type); } } /** * Adds a service instance of the specified interface to the global registry of local services. */ public static <T> void addService(Class<T> type, T service) { synchronized (sLocalServiceObjects) { if (sLocalServiceObjects.containsKey(type)) { throw new IllegalStateException("Overriding service registration"); } sLocalServiceObjects.put(type, service); } } /** * Remove a service instance, must be only used in tests. */ @VisibleForTesting public static <T> void removeServiceForTest(Class<T> type) { synchronized (sLocalServiceObjects) { sLocalServiceObjects.remove(type); } } } 不难发现,LocalServices 其实就是个容器类,用于存储各项服务,既然可以通过 getService 获取服务实现,那么添加服务应该就是通过 addService 实现的,我们查找一下调用该方法的地方,并指定 class 为 ActivityTaskManagerInternal,最终发现实现层在 ActivityTaskManagerService#LocalService 中: frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.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 public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public ActivityTaskManagerService(Context context) { mContext = context; ... mInternal = new LocalService(); ... } ... private void start() { LocalServices.addService(ActivityTaskManagerInternal.class, mInternal); } ... final class LocalService extends ActivityTaskManagerInternal { ... @Override public boolean startHomeOnAllDisplays(int userId, String reason) { synchronized (mGlobalLock) { return mRootWindowContainer.startHomeOnAllDisplays(userId, reason); } } ... } ... } 继续追溯到 WindowManagerService#mRoot 并查找实现: frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 boolean startHomeOnDisplay(int userId, String reason, int displayId, boolean allowInstrumenting, boolean fromHomeKey) { // Fallback to top focused display or default display if the displayId is invalid. if (displayId == INVALID_DISPLAY) { final Task rootTask = getTopDisplayFocusedRootTask(); displayId = rootTask != null ? rootTask.getDisplayId() : DEFAULT_DISPLAY; } final DisplayContent display = getDisplayContent(displayId); return display.reduceOnAllTaskDisplayAreas((taskDisplayArea, result) -> result | startHomeOnTaskDisplayArea(userId, reason, taskDisplayArea, allowInstrumenting, fromHomeKey), false /* initValue */); } 继续再跟进到该类的 startHomeOnTaskDisplayArea 中: 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 /** * This starts home activity on display areas that can have system decorations based on * displayId - default display area always uses primary home component. * For secondary display areas, the home activity must have category SECONDARY_HOME and then * resolves according to the priorities listed below. * - If default home is not set, always use the secondary home defined in the config. * - Use currently selected primary home activity. * - Use the activity in the same package as currently selected primary home activity. * If there are multiple activities matched, use first one. * - Use the secondary home defined in the config. */ boolean startHomeOnTaskDisplayArea(int userId, String reason, TaskDisplayArea taskDisplayArea, boolean allowInstrumenting, boolean fromHomeKey) { // Fallback to top focused display area if the provided one is invalid. if (taskDisplayArea == null) { final Task rootTask = getTopDisplayFocusedRootTask(); taskDisplayArea = rootTask != null ? rootTask.getDisplayArea() : getDefaultTaskDisplayArea(); } Intent homeIntent = null; ActivityInfo aInfo = null; if (taskDisplayArea == getDefaultTaskDisplayArea()) { homeIntent = mService.getHomeIntent(); aInfo = resolveHomeActivity(userId, homeIntent); } else if (shouldPlaceSecondaryHomeOnDisplayArea(taskDisplayArea)) { Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, taskDisplayArea); aInfo = info.first; homeIntent = info.second; } if (aInfo == null || homeIntent == null) { return false; } if (!canStartHomeOnDisplayArea(aInfo, taskDisplayArea, allowInstrumenting)) { return false; } // Updates the home component of the intent. homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name)); homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK); // Updates the extra information of the intent. if (fromHomeKey) { homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true); if (mWindowManager.getRecentsAnimationController() != null) { mWindowManager.getRecentsAnimationController().cancelAnimationForHomeStart(); } } homeIntent.putExtra(WindowManagerPolicy.EXTRA_START_REASON, reason); // Update the reason for ANR debugging to verify if the user activity is the one that // actually launched. final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId( aInfo.applicationInfo.uid) + ":" + taskDisplayArea.getDisplayId(); mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason, taskDisplayArea); return true; } 定位到这里就知道我们离最终启动 Launcher 进程的地方不远了,因为我们发现了 Intent 的踪迹,这个方法内构建了一个 homeIntent 用于启动 Launcher 的 Activity。于是继续跟进: frameworks/base/services/core/java/com/android/server/wm/ActivityStartController.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 void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason, TaskDisplayArea taskDisplayArea) { final ActivityOptions options = ActivityOptions.makeBasic(); options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN); if (!ActivityRecord.isResolverActivity(aInfo.name)) { // The resolver activity shouldn't be put in root home task because when the // foreground is standard type activity, the resolver activity should be put on the // top of current foreground instead of bring root home task to front. options.setLaunchActivityType(ACTIVITY_TYPE_HOME); } final int displayId = taskDisplayArea.getDisplayId(); options.setLaunchDisplayId(displayId); options.setLaunchTaskDisplayArea(taskDisplayArea.mRemoteToken .toWindowContainerToken()); // The home activity will be started later, defer resuming to avoid unnecessary operations // (e.g. start home recursively) when creating root home task. mSupervisor.beginDeferResume(); final Task rootHomeTask; try { // Make sure root home task exists on display area. rootHomeTask = taskDisplayArea.getOrCreateRootHomeTask(ON_TOP); } finally { mSupervisor.endDeferResume(); } mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason) .setOutActivity(tmpOutRecord) .setCallingUid(0) .setActivityInfo(aInfo) .setActivityOptions(options.toBundle()) .execute(); mLastHomeActivityStartRecord = tmpOutRecord[0]; if (rootHomeTask.mInResumeTopActivity) { // If we are in resume section already, home activity will be initialized, but not // resumed (to avoid recursive resume) and will stay that way until something pokes it // again. We need to schedule another resume. mSupervisor.scheduleResumeTopActivities(); } } 接着发现启动 Launcher 是通过 obtainStarter 获取 ActivityStarter 并内部调用 execute 方法来实现的,而这里的 mLastHomeActivityStartResult 则是启动结果。我们具体来看下 ActivityStarter 的 execute 是如何实现的: frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.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 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 /** * Resolve necessary information according the request parameters provided earlier, and execute * the request which begin the journey of starting an activity. * @return The starter result. */ int execute() { try { onExecutionStarted(); // Refuse possible leaked file descriptors if (mRequest.intent != null && mRequest.intent.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); } final LaunchingState launchingState; synchronized (mService.mGlobalLock) { final ActivityRecord caller = ActivityRecord.forTokenLocked(mRequest.resultTo); final int callingUid = mRequest.realCallingUid == Request.DEFAULT_REAL_CALLING_UID ? Binder.getCallingUid() : mRequest.realCallingUid; launchingState = mSupervisor.getActivityMetricsLogger().notifyActivityLaunching( mRequest.intent, caller, callingUid); } // If the caller hasn't already resolved the activity, we're willing // to do so here. If the caller is already holding the WM lock here, // and we need to check dynamic Uri permissions, then we're forced // to assume those permissions are denied to avoid deadlocking. if (mRequest.activityInfo == null) { mRequest.resolveActivity(mSupervisor); } // Add checkpoint for this shutdown or reboot attempt, so we can record the original // intent action and package name. if (mRequest.intent != null) { String intentAction = mRequest.intent.getAction(); String callingPackage = mRequest.callingPackage; if (intentAction != null && callingPackage != null && (Intent.ACTION_REQUEST_SHUTDOWN.equals(intentAction) || Intent.ACTION_SHUTDOWN.equals(intentAction) || Intent.ACTION_REBOOT.equals(intentAction))) { ShutdownCheckPoints.recordCheckPoint(intentAction, callingPackage, null); } } int res; synchronized (mService.mGlobalLock) { final boolean globalConfigWillChange = mRequest.globalConfig != null && mService.getGlobalConfiguration().diff(mRequest.globalConfig) != 0; final Task rootTask = mRootWindowContainer.getTopDisplayFocusedRootTask(); if (rootTask != null) { rootTask.mConfigWillChange = globalConfigWillChange; } ProtoLog.v(WM_DEBUG_CONFIGURATION, "Starting activity when config " + "will change = %b", globalConfigWillChange); final long origId = Binder.clearCallingIdentity(); res = resolveToHeavyWeightSwitcherIfNeeded(); if (res != START_SUCCESS) { return res; } res = executeRequest(mRequest); Binder.restoreCallingIdentity(origId); if (globalConfigWillChange) { // If the caller also wants to switch to a new configuration, do so now. // This allows a clean switch, as we are waiting for the current activity // to pause (so we will not destroy it), and have not yet started the // next activity. mService.mAmInternal.enforceCallingPermission( android.Manifest.permission.CHANGE_CONFIGURATION, "updateConfiguration()"); if (rootTask != null) { rootTask.mConfigWillChange = false; } ProtoLog.v(WM_DEBUG_CONFIGURATION, "Updating to new configuration after starting activity."); mService.updateConfigurationLocked(mRequest.globalConfig, null, false); } // The original options may have additional info about metrics. The mOptions is not // used here because it may be cleared in setTargetRootTaskIfNeeded. final ActivityOptions originalOptions = mRequest.activityOptions != null ? mRequest.activityOptions.getOriginalOptions() : null; // If the new record is the one that started, a new activity has created. final boolean newActivityCreated = mStartActivity == mLastStartActivityRecord; // Notify ActivityMetricsLogger that the activity has launched. // ActivityMetricsLogger will then wait for the windows to be drawn and populate // WaitResult. mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(launchingState, res, newActivityCreated, mLastStartActivityRecord, originalOptions); if (mRequest.waitResult != null) { mRequest.waitResult.result = res; res = waitResultIfNeeded(mRequest.waitResult, mLastStartActivityRecord, launchingState); } return getExternalResult(res); } } finally { onExecutionComplete(); } } 这里主要做一些 Activity 启动相关细节参数的校验,验证通过后继续执行 executeRequest 方法: 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 /** * Executing activity start request and starts the journey of starting an activity. Here * begins with performing several preliminary checks. The normally activity launch flow will * go through {@link #startActivityUnchecked} to {@link #startActivityInner}. */ private int executeRequest(Request request) { ... final ActivityRecord r = new ActivityRecord.Builder(mService) .setCaller(callerApp) .setLaunchedFromPid(callingPid) .setLaunchedFromUid(callingUid) .setLaunchedFromPackage(callingPackage) .setLaunchedFromFeature(callingFeatureId) .setIntent(intent) .setResolvedType(resolvedType) .setActivityInfo(aInfo) .setConfiguration(mService.getGlobalConfiguration()) .setResultTo(resultRecord) .setResultWho(resultWho) .setRequestCode(requestCode) .setComponentSpecified(request.componentSpecified) .setRootVoiceInteraction(voiceSession != null) .setActivityOptions(checkedOptions) .setSourceRecord(sourceRecord) .build(); mLastStartActivityRecord = r; ... mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession, request.voiceInteractor, startFlags, true /* doResume */, checkedOptions, inTask, inTaskFragment, restrictedBgActivity, intentGrants); if (request.outActivity != null) { request.outActivity[0] = mLastStartActivityRecord; } return mLastStartActivityResult; } 首先构建了一个 ActivityRecord 用于记录 Activity 相关信息(如包名、进程名、启动模式、task 信息等),接着调用 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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 /** * Start an activity while most of preliminary checks has been done and caller has been * confirmed that holds necessary permissions to do so. * Here also ensures that the starting activity is removed if the start wasn't successful. */ private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, boolean doResume, ActivityOptions options, Task inTask, TaskFragment inTaskFragment, boolean restrictedBgActivity, NeededUriGrants intentGrants) { int result = START_CANCELED; final Task startedActivityRootTask; // Create a transition now to record the original intent of actions taken within // startActivityInner. Otherwise, logic in startActivityInner could start a different // transition based on a sub-action. // Only do the create here (and defer requestStart) since startActivityInner might abort. final TransitionController transitionController = r.mTransitionController; Transition newTransition = (!transitionController.isCollecting() && transitionController.getTransitionPlayer() != null) ? transitionController.createTransition(TRANSIT_OPEN) : null; RemoteTransition remoteTransition = r.takeRemoteTransition(); try { mService.deferWindowLayout(); transitionController.collect(r); try { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner"); result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor, startFlags, doResume, options, inTask, inTaskFragment, restrictedBgActivity, intentGrants); } finally { Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); startedActivityRootTask = handleStartResult(r, options, result, newTransition, remoteTransition); } } finally { mService.continueWindowLayout(); } postStartActivityProcessing(r, result, startedActivityRootTask); return result; } 继续追踪到 startActivityInner 方法中: 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 int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, boolean doResume, ActivityOptions options, Task inTask, TaskFragment inTaskFragment, boolean restrictedBgActivity, NeededUriGrants intentGrants) { ... if (mDoResume) { final ActivityRecord topTaskActivity = startedTask.topRunningActivityLocked(); if (!mTargetRootTask.isTopActivityFocusable() || (topTaskActivity != null && topTaskActivity.isTaskOverlay() && mStartActivity != topTaskActivity)) { mTargetRootTask.ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, !PRESERVE_WINDOWS); mTargetRootTask.mDisplayContent.executeAppTransition(); } else { // If the target root-task was not previously focusable (previous top running // activity on that root-task was not visible) then any prior calls to move the // root-task to the will not update the focused root-task. If starting the new // activity now allows the task root-task to be focusable, then ensure that we // now update the focused root-task accordingly. if (mTargetRootTask.isTopActivityFocusable() && !mRootWindowContainer.isTopDisplayFocusedRootTask(mTargetRootTask)) { mTargetRootTask.moveToFront("startActivityInner"); } mRootWindowContainer.resumeFocusedTasksTopActivities( mTargetRootTask, mStartActivity, mOptions, mTransientLaunch); } } mRootWindowContainer.updateUserRootTask(mStartActivity.mUserId, mTargetRootTask); ... } 发现最终又调用回了 RootWindowContainer: 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 boolean resumeFocusedTasksTopActivities( Task targetRootTask, ActivityRecord target, ActivityOptions targetOptions, boolean deferPause) { if (!mTaskSupervisor.readyToResume()) { return false; } boolean result = false; // 如果是栈顶 Activity 则调用 resumeTopActivityUncheckedLocked if (targetRootTask != null && (targetRootTask.isTopRootTaskInDisplayArea() || getTopDisplayFocusedRootTask() == targetRootTask)) { result = targetRootTask.resumeTopActivityUncheckedLocked(target, targetOptions, deferPause); } for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) { final DisplayContent display = getChildAt(displayNdx); final boolean curResult = result; boolean[] resumedOnDisplay = new boolean[1]; display.forAllRootTasks(rootTask -> { final ActivityRecord topRunningActivity = rootTask.topRunningActivity(); if (!rootTask.isFocusableAndVisible() || topRunningActivity == null) { return; } if (rootTask == targetRootTask) { // Simply update the result for targetRootTask because the targetRootTask // had already resumed in above. We don't want to resume it again, // especially in some cases, it would cause a second launch failure // if app process was dead. resumedOnDisplay[0] |= curResult; return; } if (rootTask.getDisplayArea().isTopRootTask(rootTask) && topRunningActivity.isState(RESUMED)) { // Kick off any lingering app transitions from the MoveTaskToFront // operation, but only consider the top task and root-task on that // display. rootTask.executeAppTransition(targetOptions); } else { resumedOnDisplay[0] |= topRunningActivity.makeActiveIfNeeded(target); } }); result |= resumedOnDisplay[0]; if (!resumedOnDisplay[0]) { // In cases when there are no valid activities (e.g. device just booted or launcher // crashed) it's possible that nothing was resumed on a display. Requesting resume // of top activity in focused root task explicitly will make sure that at least home // activity is started and resumed, and no recursion occurs. final Task focusedRoot = display.getFocusedRootTask(); if (focusedRoot != null) { result |= focusedRoot.resumeTopActivityUncheckedLocked(target, targetOptions); } else if (targetRootTask == null) { result |= resumeHomeActivity(null /* prev */, "no-focusable-task", display.getDefaultTaskDisplayArea()); } } } return result; } 第一次正常启动 Launcher 会执行到 resumeTopActivityUncheckedLocked: frameworks/base/services/core/java/com/android/server/wm/Task.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 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 /** * Ensure that the top activity in the root task is resumed. * * @param prev The previously resumed activity, for when in the process * of pausing; can be null to call from elsewhere. * @param options Activity options. * @param deferPause When {@code true}, this will not pause back tasks. * * @return Returns true if something is being resumed, or false if * nothing happened. * * NOTE: It is not safe to call this method directly as it can cause an activity in a * non-focused root task to be resumed. * Use {@link RootWindowContainer#resumeFocusedTasksTopActivities} to resume the * right activity for the current system state. */ @GuardedBy("mService") boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options, boolean deferPause) { if (mInResumeTopActivity) { // Don't even start recursing. return false; } boolean someActivityResumed = false; try { // Protect against recursion. mInResumeTopActivity = true; 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) { // Skip the task if no running activity and continue resuming next task. continue; } // Otherwise, assuming everything behind this task should also be invisible. break; } someActivityResumed |= child.resumeTopActivityUncheckedLocked(prev, options, deferPause); // Doing so in order to prevent IndexOOB since hierarchy might changes while // resuming activities, for example dismissing split-screen while starting // non-resizeable activity. if (idx >= mChildren.size()) { idx = mChildren.size() - 1; } } } // When resuming the top activity, it may be necessary to pause the top activity (for // example, returning to the lock screen. We suppress the normal pause logic in // {@link #resumeTopActivityUncheckedLocked}, since the top activity is resumed at the // end. We call the {@link ActivityTaskSupervisor#checkReadyForSleepLocked} again here // to ensure any necessary pause logic occurs. In the case where the Activity will be // shown regardless of the lock screen, the call to // {@link ActivityTaskSupervisor#checkReadyForSleepLocked} is skipped. final ActivityRecord next = topRunningActivity(true /* focusableOnly */); if (next == null || !next.canTurnScreenOn()) { checkReadyForSleep(); } } finally { mInResumeTopActivity = false; } return someActivityResumed; } 最终会跟进到 TaskFragment 当中: frameworks/base/services/core/java/com/android/server/wm/TaskFragment.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options, boolean deferPause) { ... if (next.attachedToProcess()) { ... } else { // Whoops, need to restart this activity! if (!next.hasBeenLaunched) { next.hasBeenLaunched = true; } else { if (SHOW_APP_STARTING_PREVIEW) { next.showStartingWindow(false /* taskSwich */); } if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Restarting: " + next); } ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Restarting %s", next); mTaskSupervisor.startSpecificActivity(next, true, true); } } 重点看下 ActivityTaskSupervisor#startSpecificActivity: 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 void startSpecificActivity(ActivityRecord r, boolean andResume, boolean checkConfig) { // Is this activity's application already running? final WindowProcessController wpc = mService.getProcessController(r.processName, r.info.applicationInfo.uid); boolean knownToBeDead = false; if (wpc != null && wpc.hasThread()) { try { realStartActivityLocked(r, wpc, andResume, checkConfig); return; } catch (RemoteException e) { Slog.w(TAG, "Exception when starting activity " + r.intent.getComponent().flattenToShortString(), e); } // If a dead object exception was thrown -- fall through to // restart the application. knownToBeDead = true; // Remove the process record so it won't be considered as alive. mService.mProcessNames.remove(wpc.mName, wpc.mUid); mService.mProcessMap.remove(wpc.getPid()); } r.notifyUnknownVisibilityLaunchedForKeyguardTransition(); final boolean isTop = andResume && r.isTopRunningActivity(); mService.startProcessAsync(r, knownToBeDead, isTop, isTop ? HostingRecord.HOSTING_TYPE_TOP_ACTIVITY : HostingRecord.HOSTING_TYPE_ACTIVITY); } 跟踪了方法调用栈这么久终于找到“罪魁祸首”了,startSpecificActivity 方法做了两件事: 如果 Activity 的进程已经启动,则直接执行 realStartActivityLocked 来启动 Activity(通常是启动一般已运行进程的 Activity); 如果 Activity 的进程尚未启动,那么需要先执行 ActivityTaskManagerService#startProcessAsync 创建进程(通常是进程尚未启动的情况,例如初次打开 APP)。 上面的 Activity 在本场景下指代 Launcher 进程的首个 Activity,由于是第一次启动该 Activity,所以进程肯定还没有创建,我们先去看下 startProcessAsync 中是如何创建 Launcher 进程的: frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void startProcessAsync(ActivityRecord activity, boolean knownToBeDead, boolean isTop, String hostingType) { try { if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "dispatchingStartProcess:" + activity.processName); } // Post message to start process to avoid possible deadlock of calling into AMS with the // ATMS lock held. final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::startProcess, mAmInternal, activity.processName, activity.info.applicationInfo, knownToBeDead, isTop, hostingType, activity.intent.getComponent()); mH.sendMessage(m); } finally { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } 由此可见,这里通过 Message-Handler 来发送创建进程的消息,消息接收处理是通过设置的 Callback,最终回调给 ActivityManagerInternal::startProcess: frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.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 @Override public void startProcess(String processName, ApplicationInfo info, boolean knownToBeDead, boolean isTop, String hostingType, ComponentName hostingName) { try { if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "startProcess:" + processName); } synchronized (ActivityManagerService.this) { // If the process is known as top app, set a hint so when the process is // started, the top priority can be applied immediately to avoid cpu being // preempted by other processes before attaching the process of top app. startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */, new HostingRecord(hostingType, hostingName, isTop), ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE, false /* allowWhileBooting */, false /* isolated */); } } finally { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } } @GuardedBy("this") 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 */); } 接着执行到 ProcessList#startProcessLocked : frameworks/base/services/core/java/com/android/server/am/ProcessList.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 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 ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting, boolean isolated, int isolatedUid, boolean isSdkSandbox, int sdkSandboxUid, String sdkSandboxClientAppPackage, String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) { long startTime = SystemClock.uptimeMillis(); ProcessRecord app; if (!isolated) { app = getProcessRecordLocked(processName, info.uid); checkSlow(startTime, "startProcess: after getProcessRecord"); ... } else { // If this is an isolated process, it can't re-use an existing process. app = null; } ... final boolean success = startProcessLocked(app, hostingRecord, zygotePolicyFlags, abiOverride); checkSlow(startTime, "startProcess: done starting proc!"); return success ? app : null; } /** * @return {@code true} if process start is successful, false otherwise. */ @GuardedBy("mService") boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord, int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks, String abiOverride) { if (app.isPendingStart()) { return true; } ... ApplicationInfo definingAppInfo; if (hostingRecord.getDefiningPackageName() != null) { definingAppInfo = new ApplicationInfo(app.info); definingAppInfo.packageName = hostingRecord.getDefiningPackageName(); definingAppInfo.uid = uid; } else { definingAppInfo = app.info; } runtimeFlags |= Zygote.getMemorySafetyRuntimeFlags( definingAppInfo, app.processInfo, instructionSet, mPlatformCompat); ... // 进程入口函数所在类 final String entryPoint = "android.app.ActivityThread"; return startProcessLocked(hostingRecord, entryPoint, app, uid, gids, runtimeFlags, zygotePolicyFlags, mountExternal, seInfo, requiredAbi, instructionSet, invokeWith, startUptime, startElapsedTime); } catch (RuntimeException e) { Slog.e(ActivityManagerService.TAG, "Failure starting process " + app.processName, e); mService.forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid), false, false, true, false, false, app.userId, "start failure"); return false; } } boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app, int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal, String seInfo, String requiredAbi, String instructionSet, String invokeWith, long startUptime, long startElapsedTime) { ... if (mService.mConstants.FLAG_PROCESS_START_ASYNC) { ... return true; } else { try { 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); } catch (RuntimeException e) { ... } return app.getPid() > 0; } } /** * Main handler routine to start the given process from the ProcStartHandler. * * <p>Note: this function doesn't hold the global AM lock intentionally.</p> */ private void handleProcessStart(final ProcessRecord app, final String entryPoint, final int[] gids, final int runtimeFlags, int zygotePolicyFlags, final int mountExternal, final String requiredAbi, final String instructionSet, final String invokeWith, final long startSeq) { final Runnable startRunnable = () -> { try { final Process.ProcessStartResult startResult = startProcess(app.getHostingRecord(), entryPoint, app, app.getStartUid(), gids, runtimeFlags, zygotePolicyFlags, mountExternal, app.getSeInfo(), requiredAbi, instructionSet, invokeWith, app.getStartTime()); synchronized (mService) { handleProcessStartedLocked(app, startResult, startSeq); } } catch (RuntimeException e) { ... } }; // Use local reference since we are not using locks here final ProcessRecord predecessor = app.mPredecessor; if (predecessor != null && predecessor.getDyingPid() > 0) { handleProcessStartWithPredecessor(predecessor, startRunnable); } else { // Kick off the process start for real. startRunnable.run(); } } private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint, ProcessRecord app, int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal, String seInfo, String requiredAbi, String instructionSet, String invokeWith, long startTime) { try { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " + app.processName); ... final Process.ProcessStartResult startResult; boolean regularZygote = false; if (hostingRecord.usesWebviewZygote()) { ... } else if (hostingRecord.usesAppZygote()) { final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app); // We can't isolate app data and storage data as parent zygote already did that. 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()}); } ... return startResult; } finally { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } } 经过重重方法调用,最终抵达了进程创建的目的地,它的前置方法 startProcessLocked 主要用于包装进程启动相关参数信息及合并状态信息。值得注意的是,参数中的 entryPoint 这里特指 android.app.ActivityThread,表示后续在创建进程时的入口类。我们主要关注一下上面的 startProcess 方法,它内部涉及到两个不同的创建新进程的分支,分别为: appZygote:AppZygote 进程来孵化新进程,与常规 zygote 创建的应用相比受到更多限制; regularZygote:常规的 zygote32/zygote64 进程,是所有 Android 应用的父进程。 我们这里着重关注 regularZygote 的分支,具体看下内部通过 Process.start 创建进程的实现: frameworks/base/core/java/android/os/Process.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 /** * State associated with the zygote process. * @hide */ public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess(); public static ProcessStartResult start(@NonNull final String processClass, @Nullable final String niceName, int uid, int gid, @Nullable int[] gids, int runtimeFlags, int mountExternal, int targetSdkVersion, @Nullable String seInfo, @NonNull String abi, @Nullable String instructionSet, @Nullable String appDataDir, @Nullable String invokeWith, @Nullable String packageName, int zygotePolicyFlags, boolean isTopApp, @Nullable long[] disabledCompatChanges, @Nullable Map<String, Pair<String, Long>> pkgDataInfoMap, @Nullable Map<String, Pair<String, Long>> whitelistedDataInfoMap, boolean bindMountAppsData, boolean bindMountAppStorageDirs, @Nullable String[] zygoteArgs) { return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges, pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData, bindMountAppStorageDirs, zygoteArgs); } 可以看到,Process.start 最终调用的是 ZygoteProcess#start 方法: frameworks/base/core/java/android/os/ZygoteProcess.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 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 public final Process.ProcessStartResult start(@NonNull final String processClass, final String niceName, int uid, int gid, @Nullable int[] gids, int runtimeFlags, int mountExternal, int targetSdkVersion, @Nullable String seInfo, @NonNull String abi, @Nullable String instructionSet, @Nullable String appDataDir, @Nullable String invokeWith, @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[] zygoteArgs) { // TODO (chriswailes): Is there a better place to check this value? if (fetchUsapPoolEnabledPropWithMinInterval()) { informZygotesOfUsapPoolStatus(); } try { return startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false, packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges, pkgDataInfoMap, allowlistedDataInfoList, bindMountAppsData, bindMountAppStorageDirs, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); throw new RuntimeException( "Starting VM process through Zygote failed", ex); } } 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(processClass); if (extraArgs != null) { Collections.addAll(argsForZygote, extraArgs); } synchronized(mLock) { // The USAP pool can not be used if the application will not use the systems graphics // driver. If that driver is requested use the Zygote application start path. return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), zygotePolicyFlags, argsForZygote); } } private Process.ProcessStartResult zygoteSendArgsAndGetResult( ZygoteState zygoteState, int zygotePolicyFlags, @NonNull ArrayList<String> args) throws ZygoteStartFailedEx { ... return attemptZygoteSendArgsAndGetResult(zygoteState, msgStr); } 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(); // Always read the entire result from the input stream to avoid leaving // bytes in the stream for future process starts to accidentally stumble // upon. 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); } } 总的来说,上面就是 zygote 孵化进程的过程。主要分为以下几个步骤: 启动进程的参数封装 打开 Socket 套接字,并与 zygote 进程建立连接(详见 openZygoteSocketIfNeeded) 将进程的参数信息写入 BufferedWriter,并传输给 zygote 进程;通过 DataInputStream 来接收 zygote 进程返回的结果 打开套接字并与 ZygoteServer 建立连接是在 openZygoteSocketIfNeeded 中完成的: 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 /** * Tries to open a session socket to a Zygote process with a compatible ABI if one is not * already open. If a compatible session socket is already open that session socket is returned. * This function may block and may have to try connecting to multiple Zygotes to find the * appropriate one. Requires that mLock be held. */ private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx { try { attemptConnectionToPrimaryZygote(); if (primaryZygoteState.matches(abi)) { return primaryZygoteState; } if (mZygoteSecondarySocketAddress != null) { // The primary zygote didn't match. Try the secondary. attemptConnectionToSecondaryZygote(); if (secondaryZygoteState.matches(abi)) { return secondaryZygoteState; } } } catch (IOException ioe) { throw new ZygoteStartFailedEx("Error connecting to zygote", ioe); } throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi); } private void attemptConnectionToPrimaryZygote() throws IOException { if (primaryZygoteState == null || primaryZygoteState.isClosed()) { primaryZygoteState = ZygoteState.connect(mZygoteSocketAddress, mUsapPoolSocketAddress); maybeSetApiDenylistExemptions(primaryZygoteState, false); maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState); } } 如果没有打开 zygote 进程的套接字,则尝试开启并建立连接。下面看下 ZygoteState 类的静态方法 connect。 frameworks/base/core/java/android/os/ZygoteProcess.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 /** * Create a new ZygoteState object by connecting to the given Zygote socket and saving the * given USAP socket address. * * @param zygoteSocketAddress Zygote socket to connect to * @param usapSocketAddress USAP socket address to save for later * @return A new ZygoteState object containing a session socket for the given Zygote socket * address * @throws IOException */ static ZygoteState connect(@NonNull LocalSocketAddress zygoteSocketAddress, @Nullable LocalSocketAddress usapSocketAddress) throws IOException { DataInputStream zygoteInputStream; BufferedWriter zygoteOutputWriter; final LocalSocket zygoteSessionSocket = new LocalSocket(); if (zygoteSocketAddress == null) { throw new IllegalArgumentException("zygoteSocketAddress can't be null"); } try { zygoteSessionSocket.connect(zygoteSocketAddress); zygoteInputStream = new DataInputStream(zygoteSessionSocket.getInputStream()); zygoteOutputWriter = new BufferedWriter( new OutputStreamWriter(zygoteSessionSocket.getOutputStream()), Zygote.SOCKET_BUFFER_SIZE); } catch (IOException ex) { try { zygoteSessionSocket.close(); } catch (IOException ignore) { } throw ex; } return new ZygoteState(zygoteSocketAddress, usapSocketAddress, zygoteSessionSocket, zygoteInputStream, zygoteOutputWriter, getAbiList(zygoteOutputWriter, zygoteInputStream)); } ZygoteState 类哟用于表示与 Zygote 进程通信的状态。假如传递给 connect 方法的实参是 ZYGOTE_SOCKET(即”zygote”),这代表要连接的远程地址。该方法中首先创建一个 LocalSocket 对象,接着调用其 connect 方法,连接成功之后,就可以得到套接字的输入和输出流,并将输入流其封装成 DataInputStream 对象,然后将输出流封装成 BufferedWriter 对象。接着就可以通过 DataInputStream 对象获得 Zygote 进程发送过来的消息,而通过 BufferedWriter 对象发送消息给 Zygote 进程。 那么问题来了,我们只知道了 socket 通信的一端,另外一端如何找到呢?其实上一篇关于Android系统启动过程的文章中在分析到 zygote 进程启动过程时已经提过一笔了: com.android.internal.os.ZygoteInit 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 static void main(String[] argv) { ZygoteServer zygoteServer = null; Runnable caller; try { String zygoteSocketName = "zygote"; ... Log.i(TAG, "Accepting command socket connections"); // The select loop returns early in the child process after a fork and // loops forever in the zygote. caller = zygoteServer.runSelectLoop(abiList); } catch (Throwable ex) { Log.e(TAG, "System zygote died with fatal exception", ex); throw ex; } finally { if (zygoteServer != null) { zygoteServer.closeServerSocket(); } } // We're in the child process and have exited the select loop. Proceed to execute the // command. if (caller != null) { caller.run(); } } zygote 进程通过 ZygoteServer#runSelectLoop 来循环监听其他进程的 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 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 Runnable runSelectLoop(String abiList) { ArrayList<FileDescriptor> socketFDs = new ArrayList<>(); ArrayList<ZygoteConnection> peers = new ArrayList<>(); // sServerSocket是socket通信中的服务端,即zygote进程,保存到fds[0] socketFDs.add(mZygoteSocket.getFileDescriptor()); peers.add(null); mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP; while (true) { ... StructPollfd[] pollFDs; // Allocate enough space for the poll structs, taking into account // the state of the USAP pool for this Zygote (could be a // regular Zygote, a WebView Zygote, or an AppZygote). if (mUsapPoolEnabled) { usapPipeFDs = Zygote.getUsapPipeFDs(); pollFDs = new StructPollfd[socketFDs.size() + 1 + usapPipeFDs.length]; } else { pollFDs = new StructPollfd[socketFDs.size()]; } /* * For reasons of correctness the USAP pool pipe and event FDs * must be processed before the session and server sockets. This * is to ensure that the USAP pool accounting information is * accurate when handling other requests like API deny list * exemptions. */ int pollIndex = 0; for (FileDescriptor socketFD : socketFDs) { pollFDs[pollIndex] = new StructPollfd(); pollFDs[pollIndex].fd = socketFD; pollFDs[pollIndex].events = (short) POLLIN; ++pollIndex; } final int usapPoolEventFDIndex = pollIndex; if (mUsapPoolEnabled) { pollFDs[pollIndex] = new StructPollfd(); pollFDs[pollIndex].fd = mUsapPoolEventFD; pollFDs[pollIndex].events = (short) POLLIN; ++pollIndex; // The usapPipeFDs array will always be filled in if the USAP Pool is enabled. assert usapPipeFDs != null; for (int usapPipeFD : usapPipeFDs) { FileDescriptor managedFd = new FileDescriptor(); managedFd.setInt$(usapPipeFD); pollFDs[pollIndex] = new StructPollfd(); pollFDs[pollIndex].fd = managedFd; pollFDs[pollIndex].events = (short) POLLIN; ++pollIndex; } } int pollTimeoutMs; ... int pollReturnValue; try { // 处理轮询状态,当pollFds有事件到来则往下执行,否则阻塞在这里 pollReturnValue = Os.poll(pollFDs, pollTimeoutMs); } catch (ErrnoException ex) { throw new RuntimeException("poll failed", ex); } if (pollReturnValue == 0) { // The poll returned zero results either when the timeout value has been exceeded // or when a non-blocking poll is issued and no FDs are ready. In either case it // is time to refill the pool. This will result in a duplicate assignment when // the non-blocking poll returns zero results, but it avoids an additional // conditional in the else branch. mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP; mUsapPoolRefillAction = UsapPoolRefillAction.DELAYED; } else { boolean usapPoolFDRead = false; while (--pollIndex >= 0) { // 采用I/O多路复用机制,当接收到客户端发出连接请求或者数据处理请求到来,则往下执行; // 否则进入continue,跳出本次循环。 if ((pollFDs[pollIndex].revents & POLLIN) == 0) { continue; } if (pollIndex == 0) { // 即fds[0],代表的是sServerSocket,则意味着有客户端连接请求; // 则创建ZygoteConnection对象,并添加到fds。 ZygoteConnection newPeer = acceptCommandPeer(abiList); peers.add(newPeer); socketFDs.add(newPeer.getFileDescriptor()); } else if (pollIndex < usapPoolEventFDIndex) { // 从Zygote服务器套接字接受会话套接字 try { ZygoteConnection connection = peers.get(pollIndex); boolean multipleForksOK = !isUsapPoolEnabled() && ZygoteHooks.isIndefiniteThreadSuspensionSafe(); final Runnable command = connection.processCommand(this, multipleForksOK); ... } catch (Exception e) { ... } finally { // Reset the child flag, in the event that the child process is a child- // zygote. The flag will not be consulted this loop pass after the // Runnable is returned. mIsForkChild = false; } } else { ... } } ... } ... } } mUsapPoolEnabled 标识是否开启 USAP 机制。从Android Q(10)开始,Google 引入了一种新的机制:USAP(Unspecialized App Process)。通过 prefork 的方式提前创建好一批进程,当有应用启动时,直接将已经创建好的进程分配给它,从而省去了 fork 的动作,因此可以提升性能。后面将会有专门篇幅来研究 USAP 机制的运行原理,本文暂不做详细介绍。 其实,最终 ZygoteServer 会接收到来自 system_server 进程 AMS 创建新应用进程的消息,并通过 ZygoteConnection#processCommand 来处理并创建新进程: frameworks/base/core/java/com/android/internal/os/ZygoteConnection.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 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 /** * Reads a command from the command socket. If a child is successfully forked, a * {@code Runnable} that calls the childs main method (or equivalent) is returned in the child * process. {@code null} is always returned in the parent process (the zygote). * If multipleOK is set, we may keep processing additional fork commands before returning. * * If the client closes the socket, an {@code EOF} condition is set, which callers can test * for by calling {@code ZygoteConnection.isClosedByPeer}. */ Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) { ZygoteArguments parsedArgs; try (ZygoteCommandBuffer argBuffer = new ZygoteCommandBuffer(mSocket)) { while (true) { ... int pid; FileDescriptor childPipeFd = null; FileDescriptor serverPipeFd = null; ... if (canPreloadApp() && parsedArgs.mPreloadApp != null) { byte[] rawParcelData = Base64.getDecoder().decode(parsedArgs.mPreloadApp); Parcel appInfoParcel = Parcel.obtain(); appInfoParcel.unmarshall(rawParcelData, 0, rawParcelData.length); appInfoParcel.setDataPosition(0); ApplicationInfo appInfo = ApplicationInfo.CREATOR.createFromParcel(appInfoParcel); appInfoParcel.recycle(); if (appInfo != null) { handlePreloadApp(appInfo); } else { throw new IllegalArgumentException("Failed to deserialize --preload-app"); } return null; } ... if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote || !multipleOK || peer.getUid() != Process.SYSTEM_UID) { // Continue using old code for now. TODO: Handle these cases in the other path. 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); try { if (pid == 0) { // in child zygoteServer.setForkChild(); zygoteServer.closeServerSocket(); IoUtils.closeQuietly(serverPipeFd); serverPipeFd = null; return handleChildProc(parsedArgs, childPipeFd, parsedArgs.mStartChildZygote); } else { // In the parent. A pid < 0 indicates a failure and will be handled in // handleParentProc. IoUtils.closeQuietly(childPipeFd); childPipeFd = null; handleParentProc(pid, serverPipeFd); return null; } } finally { IoUtils.closeQuietly(childPipeFd); IoUtils.closeQuietly(serverPipeFd); } } else { ZygoteHooks.preFork(); Runnable result = Zygote.forkSimpleApps(argBuffer, zygoteServer.getZygoteSocketFileDescriptor(), peer.getUid(), Zygote.minChildUid(peer), parsedArgs.mNiceName); if (result == null) { // parent; we finished some number of forks. Result is Boolean. // We already did the equivalent of handleParentProc(). ZygoteHooks.postForkCommon(); // argBuffer contains a command not understood by forksimpleApps. continue; } else { // child; result is a Runnable. zygoteServer.setForkChild(); Zygote.setAppProcessName(parsedArgs, TAG); // ??? Necessary? return result; } } } } ... } 首先调用 ZygoteArguments 得到客户端发来的启动进程参数,接着进行权限检查,然后调用 forkAndSpecialize 创建新进程(native 层通过 fork() 来克隆出新进程,此时 native 层的进程其实相当于已经生成了),返回 pid 若为 0,则开始调用 handleChildProc 来处理子进程;如果返回 pid > 0,则开始调用 handleParentProc 来处理父进程。注意,这里并不是简单的 if-else 关系,调用 forkAndSpecialize 之后其实会返回两次,一次返回到父进程,另一次返回到新启动的子进程。至于为什么会返回两次,这里先埋下一个彩蛋,后续文章会填坑。接下来我们来继续看下如果返回的 pid 是 0,即在子进程返回的情况下,handleChildProc 做了些什么: 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 private Runnable handleChildProc(ZygoteArguments parsedArgs, FileDescriptor pipeFd, boolean isZygote) { /* * By the time we get here, the native code has closed the two actual Zygote * socket connections, and substituted /dev/null in their place. The LocalSocket * objects still need to be closed properly. */ closeSocket(); Zygote.setAppProcessName(parsedArgs, TAG); // End of the postFork event. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 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 */); } } } 由于我们创建的是应用进程,所以上方代码显然会执行到 ZygoteInit.childZygoteInit: 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 /** * The main function called when starting a child zygote process. This is used as an alternative * to zygoteInit(), which skips calling into initialization routines that start the Binder * threadpool. */ static Runnable childZygoteInit(String[] argv) { RuntimeInit.Arguments args = new RuntimeInit.Arguments(argv); return RuntimeInit.findStaticMain(args.startClass, args.startArgs, /* classLoader= */null); } /** * Invokes a static "main(argv[]) method on class "className". * Converts various failing exceptions into RuntimeExceptions, with * the assumption that they will then cause the VM instance to exit. * * @param className Fully-qualified class name * @param argv Argument vector for main() * @param classLoader the classLoader to load {@className} with */ 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); } /* * This throw gets caught in ZygoteInit.main(), which responds * by invoking the exception's run() method. This arrangement * clears up all the stack frames that were required in setting * up the process. */ return new MethodAndArgsCaller(m, argv); } 由上述代码可知,最终会通过反射的方式来为之前传递进来的 processClass 创建实例对象,根据上面流程的分析,这里的 processClass 显然是 android.app.ActivityThread 这个类,接着调用该类的 main 方法: frameworks/base/core/java/android/app/ActivityThread.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static void main(String[] args) { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain"); // Install selective syscall interception AndroidOs.install(); ... Looper.prepareMainLooper(); ... ActivityThread thread = new ActivityThread(); thread.attach(false, startSeq); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } ActivityThread 的 main 中准备一个主线程的 Looper 并初始化消息队列,并创建一个 ActivityThread 实例,执行 attach 方法创建 Application 和绑定生命周期,最后开启主线程的消息循环。我们重点看下 attach 方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 private void attach(boolean system, long startSeq) { sCurrentActivityThread = this; mConfigurationController = new ConfigurationController(this); mSystemThread = system; // 此处 system 为 false if (!system) { android.ddm.DdmHandleAppName.setAppName("<pre-initialized>", UserHandle.myUserId()); RuntimeInit.setApplicationObject(mAppThread.asBinder()); final IActivityManager mgr = ActivityManager.getService(); try { mgr.attachApplication(mAppThread, startSeq); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } ... } else { ... } ... } attach 中执行了 IActivityManager 的 attachApplication 方法,而 IActivityManager 其实是一个 Binder 接口,这里通过 ActivityManager.getService 获取它的代理服务 AMS,进而调用 attachApplication 来将 ActivityThread 内部类 ApplicationThread 绑定到 AMS,这样就可以通过 Binder IPC 间接控制应用进程了: frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.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 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 @Override public final void attachApplication(IApplicationThread thread, long startSeq) { if (thread == null) { throw new SecurityException("Invalid application interface"); } synchronized (this) { int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); attachApplicationLocked(thread, callingPid, callingUid, startSeq); Binder.restoreCallingIdentity(origId); } } private boolean attachApplicationLocked(@NonNull IApplicationThread thread, int pid, int callingUid, long startSeq) { // Find the application record that is being attached... either via // the pid if we are running in multiple processes, or just pull the // next app record if we are emulating process with anonymous threads. ProcessRecord app; long startTime = SystemClock.uptimeMillis(); long bindApplicationTimeMillis; if (pid != MY_PID && pid >= 0) { synchronized (mPidsSelfLocked) { app = mPidsSelfLocked.get(pid); } if (app != null && (app.getStartUid() != callingUid || app.getStartSeq() != startSeq)) { ... // If there is already an app occupying that pid that hasn't been cleaned up cleanUpApplicationRecordLocked(app, pid, false, false, -1, true /*replacingPid*/, false /* fromBinderDied */); removePidLocked(pid, app); app = null; } } else { app = null; } ... try { ... ApplicationInfo appInfo = instr != null ? instr.mTargetInfo : app.info; app.setCompat(compatibilityInfoForPackage(appInfo)); if (app.getIsolatedEntryPoint() != null) { // This is an isolated process which should just call an entry point instead of // being bound to an application. thread.runIsolatedEntryPoint( app.getIsolatedEntryPoint(), app.getIsolatedEntryPointArgs()); } else if (instr2 != null) { ... } else { 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()); } ... // Make app active after binding application or client may be running requests (e.g // starting activities) before it is ready. synchronized (mProcLock) { app.makeActive(thread, mProcessStats); checkTime(startTime, "attachApplicationLocked: immediately after bindApplication"); } ... } catch (Exception e) { ... } boolean didSomething = false; // See if the top visible activity is waiting to run in this process... if (normalMode) { try { didSomething = mAtmInternal.attachApplication(app.getWindowProcessController()); } catch (Exception e) { Slog.wtf(TAG, "Exception thrown launching activities in " + app, e); badApp = true; } } ... return true; } ApplicationThread 作为 ActivityThread 的内部类,它是 IApplicationThread 这个 Binder 接口的最终实现类,即 ApplicationThread 内部实现了一套 AIDL 接口,这个接口与 Application 的创建、Activity 及 Service 等四大组件启动等息息相关。最终调用 IApplicationThread 的 bindApplication 方法: frameworks/base/core/java/android/app/ActivityThread.java#ApplicationThread 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 @Override public final void bindApplication(String processName, ApplicationInfo appInfo, String sdkSandboxClientAppVolumeUuid, String sdkSandboxClientAppPackage, ProviderInfoList providerList, ComponentName instrumentationName, ProfilerInfo profilerInfo, Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher, IUiAutomationConnection instrumentationUiConnection, int debugMode, boolean enableBinderTracking, boolean trackAllocation, boolean isRestrictedBackupMode, boolean persistent, Configuration config, CompatibilityInfo compatInfo, Map services, Bundle coreSettings, String buildSerial, AutofillOptions autofillOptions, ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges, SharedMemory serializedSystemFontMap, long startRequestedElapsedTime, long startRequestedUptime) { ... AppBindData data = new AppBindData(); data.processName = processName; data.appInfo = appInfo; data.sdkSandboxClientAppVolumeUuid = sdkSandboxClientAppVolumeUuid; data.sdkSandboxClientAppPackage = sdkSandboxClientAppPackage; data.providers = providerList.getList(); data.instrumentationName = instrumentationName; data.instrumentationArgs = instrumentationArgs; data.instrumentationWatcher = instrumentationWatcher; data.instrumentationUiAutomationConnection = instrumentationUiConnection; data.debugMode = debugMode; data.enableBinderTracking = enableBinderTracking; data.trackAllocation = trackAllocation; data.restrictedBackupMode = isRestrictedBackupMode; data.persistent = persistent; data.config = config; data.compatInfo = compatInfo; data.initProfilerInfo = profilerInfo; data.buildSerial = buildSerial; data.autofillOptions = autofillOptions; data.contentCaptureOptions = contentCaptureOptions; data.disabledCompatChanges = disabledCompatChanges; data.mSerializedSystemFontMap = serializedSystemFontMap; data.startRequestedElapsedTime = startRequestedElapsedTime; data.startRequestedUptime = startRequestedUptime; sendMessage(H.BIND_APPLICATION, data); } 上面代码通过一个 Handler 实例发送 H.BIND_APPLICATION 消息,意味着需要构建和绑定 Application 对象。这个 Handler 同样是 ActivityThread 的内部类: frameworks/base/core/java/android/app/ActivityThread.java#H 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 class H extends Handler { public static final int BIND_APPLICATION = 110; ... public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { case BIND_APPLICATION: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication"); AppBindData data = (AppBindData)msg.obj; handleBindApplication(data); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; case EXIT_APPLICATION: if (mInitialApplication != null) { mInitialApplication.onTerminate(); } Looper.myLooper().quit(); break; } Object obj = msg.obj; if (obj instanceof SomeArgs) { ((SomeArgs) obj).recycle(); } if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what)); } } 通过跨进程通信,Application 的创建和绑定最终回到了主线程: 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 void handleBindApplication(AppBindData data) { ... // Instrumentation info affects the class loader, so load it before // setting up the app context. final InstrumentationInfo ii; if (data.instrumentationName != null) { ii = prepareInstrumentation(data); } else { ii = null; } final ContextImpl appContext = ContextImpl.createAppContext(this, data.info); mConfigurationController.updateLocaleListFromAppContext(appContext); ... // Continue loading instrumentation. if (ii != null) { initInstrumentation(ii, data, appContext); } else { mInstrumentation = new Instrumentation(); mInstrumentation.basicInit(this); } // Allow disk access during application and provider setup. This could // block processing ordered broadcasts, but later processing would // probably end up doing the same disk access. Application app; final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy(); try { // If the app is being launched for full backup or restore, bring it up in // a restricted environment with the base application class. app = data.info.makeApplicationInner(data.restrictedBackupMode, null); ... mInitialApplication = app; ... try { mInstrumentation.callApplicationOnCreate(app); } catch (Exception e) { if (!mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to create application " + app.getClass().getName() + ": " + e.toString(), e); } } } finally { ... } ... } 最终通过 LoadedApk 中的 makeApplication 方法来尝试创建 Application 对象并执行它的 onCreate 生命周期方法: frameworks/base/core/java/android/app/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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public Application makeApplicationInner(boolean forceDefaultAppClass, Instrumentation instrumentation) { return makeApplicationInner(forceDefaultAppClass, instrumentation, /* allowDuplicateInstances= */ false); } private Application makeApplicationInner(boolean forceDefaultAppClass, Instrumentation instrumentation, boolean allowDuplicateInstances) { if (mApplication != null) { return mApplication; } Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication"); ... Application app = null; final String myProcessName = Process.myProcessName(); String appClass = mApplicationInfo.getCustomApplicationClassNameForProcess( myProcessName); if (forceDefaultAppClass || (appClass == null)) { appClass = "android.app.Application"; } try { ... ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); // The network security config needs to be aware of multiple // applications in the same process to handle discrepancies NetworkSecurityConfigProvider.handleNewApplication(appContext); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); appContext.setOuterContext(app); } catch (Exception e) { ... } mActivityThread.mAllApplications.add(app); mApplication = app; ... if (instrumentation != null) { try { instrumentation.callApplicationOnCreate(app); } catch (Exception e) { ... } } Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); return app; } 如果 Application 对象依然存在则直接返回当前对象,否则会通过 Instrumentation(ActivityThread中的成员变量 mInstrumentation,而非参数传递进来的) 来新建一个 Application: frameworks/base/core/java/android/app/Instrumentation.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Application app = getFactory(context.getPackageName()) .instantiateApplication(cl, className); app.attach(context); return app; } public @NonNull Application instantiateApplication(@NonNull ClassLoader cl, @NonNull String className) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return (Application) cl.loadClass(className).newInstance(); } 可以看到,最终是通过反射生成一个全新的 Application 对象并返回给上层,并执行了它的 onCreate 方法。至此,Application 创建并启动完毕。 继续启动 Activity 仔细想一下,之前我们分析 Launcher 进程创建过程中的时候,它会提前创建一个 ActivityRecord 对象并置于栈顶,如果当前进程已创建,则直接执行 realStartActivityLocked 来真正启动一个 Activity;如果进程还没有创建,则先创建进程。我们上面分析了一大通不也正是研究了进程还没有创建的情况吗。既然我们后来创建了一个新的 Application,那是不是应该继续之前未完成的使命呢?也就是将先前打断的 Activity 启动的工作继续执行起来。Android 系统不可能连这个问题都考虑不到对吧,所以 Application 创建完毕后应该会继续执行启动 Activity 的逻辑! 我们回到之前的 ActivityManagerService 中查看 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 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 private boolean attachApplicationLocked(@NonNull IApplicationThread thread, int pid, int callingUid, long startSeq) { ... try { ... ApplicationInfo appInfo = instr != null ? instr.mTargetInfo : app.info; app.setCompat(compatibilityInfoForPackage(appInfo)); if (app.getIsolatedEntryPoint() != null) { // This is an isolated process which should just call an entry point instead of // being bound to an application. thread.runIsolatedEntryPoint( app.getIsolatedEntryPoint(), app.getIsolatedEntryPointArgs()); } else if (instr2 != null) { ... } else { 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()); } ... // Make app active after binding application or client may be running requests (e.g // starting activities) before it is ready. synchronized (mProcLock) { app.makeActive(thread, mProcessStats); checkTime(startTime, "attachApplicationLocked: immediately after bindApplication"); } ... } catch (Exception e) { ... } boolean didSomething = false; // See if the top visible activity is waiting to run in this process... if (normalMode) { try { didSomething = mAtmInternal.attachApplication(app.getWindowProcessController()); } catch (Exception e) { Slog.wtf(TAG, "Exception thrown launching activities in " + app, e); badApp = true; } } // Find any services that should be running in this process... if (!badApp) { try { didSomething |= mServices.attachApplicationLocked(app, processName); checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked"); } catch (Exception e) { Slog.wtf(TAG, "Exception thrown starting services in " + app, e); badApp = true; } } ... return true; } 可以看到,在成功创建和绑定 Application 之后,又继续执行了ActivityTaskManagerInternal#attachApplication,它又做了什么事情呢?: frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java 1 2 3 4 5 6 7 8 9 10 11 12 13 public boolean attachApplication(WindowProcessController wpc) throws RemoteException { synchronized (mGlobalLockWithoutBoost) { if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "attachApplication:" + wpc.mName); } try { return mRootWindowContainer.attachApplication(wpc); } finally { Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } } 继续跟进到 RootWindowContainer#attachApplication: frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.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 boolean attachApplication(WindowProcessController app) throws RemoteException { try { return mAttachApplicationHelper.process(app); } finally { mAttachApplicationHelper.reset(); } } boolean process(WindowProcessController app) throws RemoteException { mApp = app; for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) { getChildAt(displayNdx).forAllRootTasks(this); if (mRemoteException != null) { throw mRemoteException; } } if (!mHasActivityStarted) { ensureActivitiesVisible(null /* starting */, 0 /* configChanges */, false /* preserveWindows */); } return mHasActivityStarted; } void ensureActivitiesVisible(ActivityRecord starting, int configChanges, boolean preserveWindows, boolean notifyClients) { if (mTaskSupervisor.inActivityVisibilityUpdate() || mTaskSupervisor.isRootVisibilityUpdateDeferred()) { // Don't do recursive work. return; } try { mTaskSupervisor.beginActivityVisibilityUpdate(); // First the front root tasks. In case any are not fullscreen and are in front of home. for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) { final DisplayContent display = getChildAt(displayNdx); display.ensureActivitiesVisible(starting, configChanges, preserveWindows, notifyClients); } } finally { mTaskSupervisor.endActivityVisibilityUpdate(); } } 这里是查找 Activity 栈是否存在需要被启动的 Activity,继续跟进 DisplayContent#ensureActivitiesVisible: frameworks/base/services/core/java/com/android/server/wm/DisplayContent.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 void ensureActivitiesVisible(ActivityRecord starting, int configChanges, boolean preserveWindows, boolean notifyClients) { if (mInEnsureActivitiesVisible) { // Don't do recursive work. return; } mInEnsureActivitiesVisible = true; mAtmService.mTaskSupervisor.beginActivityVisibilityUpdate(); try { forAllRootTasks(rootTask -> { rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows, notifyClients); }); if (mTransitionController.useShellTransitionsRotation() && mTransitionController.isCollecting() && mWallpaperController.getWallpaperTarget() != null) { // Also update wallpapers so that their requestedVisibility immediately reflects // the changes to activity visibility. // TODO(b/206005136): Move visibleRequested logic up to WindowToken. mWallpaperController.adjustWallpaperWindows(); } } finally { mAtmService.mTaskSupervisor.endActivityVisibilityUpdate(); mInEnsureActivitiesVisible = false; } } 接着跟进 Task#ensureActivitiesVisible: frameworks/base/services/core/java/com/android/server/wm/Task.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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 /** * Make sure that all activities that need to be visible in the root task (that is, they * currently can be seen by the user) actually are and update their configuration. * @param starting The top most activity in the task. * The activity is either starting or resuming. * Caller should ensure starting activity is visible. * @param preserveWindows Flag indicating whether windows should be preserved when updating * configuration in {@link EnsureActivitiesVisibleHelper}. * @param configChanges Parts of the configuration that changed for this activity for evaluating * if the screen should be frozen as part of * {@link EnsureActivitiesVisibleHelper}. * */ void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows) { ensureActivitiesVisible(starting, configChanges, preserveWindows, true /* notifyClients */); } /** * Ensure visibility with an option to also update the configuration of visible activities. * @see #ensureActivitiesVisible(ActivityRecord, int, boolean) * @see RootWindowContainer#ensureActivitiesVisible(ActivityRecord, int, boolean) * @param starting The top most activity in the task. * The activity is either starting or resuming. * Caller should ensure starting activity is visible. * @param notifyClients Flag indicating whether the visibility updates should be sent to the * clients in {@link EnsureActivitiesVisibleHelper}. * @param preserveWindows Flag indicating whether windows should be preserved when updating * configuration in {@link EnsureActivitiesVisibleHelper}. * @param configChanges Parts of the configuration that changed for this activity for evaluating * if the screen should be frozen as part of * {@link EnsureActivitiesVisibleHelper}. */ // TODO: Should be re-worked based on the fact that each task as a root task in most cases. void ensureActivitiesVisible(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows, boolean notifyClients) { mTaskSupervisor.beginActivityVisibilityUpdate(); try { forAllLeafTasks(task -> { task.updateActivityVisibilities(starting, configChanges, preserveWindows, notifyClients); }, true /* traverseTopToBottom */); if (mTranslucentActivityWaiting != null && mUndrawnActivitiesBelowTopTranslucent.isEmpty()) { // Nothing is getting drawn or everything was already visible, don't wait for // timeout. notifyActivityDrawnLocked(null); } } finally { mTaskSupervisor.endActivityVisibilityUpdate(); } } final void updateActivityVisibilities(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows, boolean notifyClients) { mTaskSupervisor.beginActivityVisibilityUpdate(); try { mEnsureActivitiesVisibleHelper.process( starting, configChanges, preserveWindows, notifyClients); } finally { mTaskSupervisor.endActivityVisibilityUpdate(); } } 进入 EnsureActivityVisibilities : frameworks/base/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.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 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 void process(@Nullable ActivityRecord starting, int configChanges, boolean preserveWindows, boolean notifyClients) { reset(starting, configChanges, preserveWindows, notifyClients); ... // We should not resume activities that being launched behind because these // activities are actually behind other fullscreen activities, but still required // to be visible (such as performing Recents animation). final boolean resumeTopActivity = mTopRunningActivity != null && !mTopRunningActivity.mLaunchTaskBehind && mTaskFragment.canBeResumed(starting) && (starting == null || !starting.isDescendantOf(mTaskFragment)); ArrayList<TaskFragment> adjacentTaskFragments = null; for (int i = mTaskFragment.mChildren.size() - 1; i >= 0; --i) { final WindowContainer child = mTaskFragment.mChildren.get(i); final TaskFragment childTaskFragment = child.asTaskFragment(); if (childTaskFragment != null && childTaskFragment.getTopNonFinishingActivity() != null) { childTaskFragment.updateActivityVisibilities(starting, configChanges, preserveWindows, notifyClients); ... } else if (child.asActivityRecord() != null) { setActivityVisibilityState(child.asActivityRecord(), starting, resumeTopActivity); } } } private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting, final boolean resumeTopActivity) { ... if (reallyVisible) { ... if (!r.attachedToProcess()) { // 需要关联到具体的进程并启动activity makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges, isTop, resumeTopActivity && isTop, r); } else if (r.isVisibleRequested()) { // If this activity is already visible, then there is nothing to do here. ... } else { r.makeVisibleIfNeeded(mStarting, mNotifyClients); } // Aggregate current change flags. mConfigChanges |= r.configChangeFlags; } else { ... r.makeInvisible(); } ... } private void makeVisibleAndRestartIfNeeded(ActivityRecord starting, int configChanges, boolean isTop, boolean andResume, ActivityRecord r) { // We need to make sure the app is running if it's the top, or it is just made visible from // invisible. If the app is already visible, it must have died while it was visible. In this // case, we'll show the dead window but will not restart the app. Otherwise we could end up // thrashing. if (!isTop && r.isVisibleRequested() && !r.isState(INITIALIZING)) { return; } // This activity needs to be visible, but isn't even running... // get it started and resume if no other root task in this root task is resumed. ... if (r != starting) { mTaskFragment.mTaskSupervisor.startSpecificActivity(r, andResume, true /* checkConfig */); } } 看到这里,我们可以大胆猜测 Activity 即使已经存在,但尚未显示,也没有关联到具体的进程。如果顶部没有其他的 Activity,我们可以尝试启动这个 Activity: frameworks/base/services/core/java/com/android/server/wm/ActivityTaskSupervisor.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 void startSpecificActivity(ActivityRecord r, boolean andResume, boolean checkConfig) { // Is this activity's application already running? final WindowProcessController wpc = mService.getProcessController(r.processName, r.info.applicationInfo.uid); boolean knownToBeDead = false; if (wpc != null && wpc.hasThread()) { try { realStartActivityLocked(r, wpc, andResume, checkConfig); return; } catch (RemoteException e) { Slog.w(TAG, "Exception when starting activity " + r.intent.getComponent().flattenToShortString(), e); } // If a dead object exception was thrown -- fall through to // restart the application. knownToBeDead = true; // Remove the process record so it won't be considered as alive. mService.mProcessNames.remove(wpc.mName, wpc.mUid); mService.mProcessMap.remove(wpc.getPid()); } r.notifyUnknownVisibilityLaunchedForKeyguardTransition(); final boolean isTop = andResume && r.isTopRunningActivity(); mService.startProcessAsync(r, knownToBeDead, isTop, isTop ? HostingRecord.HOSTING_TYPE_TOP_ACTIVITY : HostingRecord.HOSTING_TYPE_ACTIVITY); } Wonderful!这代码是不是有点熟悉,没错,我们再次回到了这个分叉路口: 如果 Activity 的进程已经启动,则直接执行 realStartActivityLocked 来启动 Activity; 如果 Activity 的进程尚未启动,那么需要先执行 ActivityTaskManagerService#startProcessAsync 创建进程。 只不过这次我们已经拥有了一个正在运行的进程,所以这一次将会执行到 realStartActivityLocked 来启动一个 Activity。 总结 Android 中在 system_server 进程启动 Launcher 进程主要有以下几个步骤: 准备参数信息:AMS 执行 systemReady,接着组装 homeIntent 和 ActivityRecord。 判断是否需要新建进程:如果当前存在进程,则直接启动 Activity,否则先创建和启动进程。 等待创建请求:Zygote 进程会在启动后等待新的应用程序进程的创建请求。 接收创建请求:当有新的应用程序进程的创建请求到达时,Zygote 进程会接收该请求,并根据该请求创建一个新的进程。 复制进程:为了快速创建新的进程,Zygote 进程会通过复制自身进程并清除一些状态来创建一个新的应用程序进程。这个过程是通过使用类似于 Unix 的 fork 系统调用实现的。 初始化应用程序进程:新的应用程序进程会继承 Zygote 进程的一些特性,例如 Java 虚拟机实例、类和资源等。Zygote 进程会对新进程进行一些初始化工作,例如为新进程设置一些系统属性、启动主线程等。 加载应用程序代码:新的应用程序进程会在启动时加载应用程序的代码,并开始执行应用程序的入口函数。 启动 Launcher Activity:当进程和 Application 创建完毕后,如果存在未启动的 Activity,则前往创建执行启动。 关于 Activity 的启动过程将在下一章节来剖析,到时也会附上整个 APP 启动过程关键节点流程图。 相关面试题 应用进程的孵化过程? Android 创建进程时的 USAP 机制是什么? 为什么 Android 进程的 fork() 会返回两次? Android 系统的 Activity Task 管理机制? 相关参考 https://juejin.cn/post/6978743408756162590 https://blog.csdn.net/zhuzp_blog/article/details/120894309 https://blog.csdn.net/qq_34512207/article/details/113725772

2023/5/5
articleCard.readMore

回顾Java中经典的生产/消费者模型

概述 生产者-消费者模型是 Java 并发编程中比较常见的加锁应用场景之一,以下是维基百科的对于该名词的定义: 生产者消费者问题(Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个多进程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个进程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。 问题分析 结合上图及定义我们可以提取出该模型具有以下特点: 生产者和消费者可以同时并发运作 缓冲区满则阻塞等待消费者消费数据 缓冲区空则阻塞等待生产者生产数据 我们知道,生产者和消费者可以同时运作,并且二者执行效率和规模也很有可能出现严重的不对等性。那么为了保证生产和消费操作的原子性和共享数据的可见性,我们需要借助一种同步机制来保障该模型的正常运行。 实现方式 以下来介绍几种常见的生产-消费模型的实现方式。 1. synchorized + wait/notify 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 class GoodsStorageDispatcher { // 仓库容纳上限 private static final int MAX_SIZE = 100; // 仓库容纳的货物集合 private LinkedList<Good> list = new LinkedList(); public void produce(Good good) { synchronized (list) { while (list.size() == MAX_SIZE) { System.out.println("仓库已满 >> 生产暂停,等待消费"); try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } list.add(good); System.out.println("生产了一个新产品,现库存为:" + list.size()); list.notifyAll(); } } public void consume() { synchronized (list) { while (list.size() == 0) { System.out.println("库存已清仓 >> 消费暂停,等待生产"); try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } list.remove(); System.out.println("消费了一个产品,现库存为:" + list.size()); list.notifyAll(); } } } 可以看到,我们采用对象锁以及等待/唤醒的方式来实现生产和消费的同步机制。这里的 wait 和 notifyAll 在示例中含义如下: wait:在缓冲区满/空的情况下先挂起生产/消费线程并释放锁,进入等待状态,让其他线程执行 notify:当生产/消费一个商品时,通知其他等待的线程继续执行并释放锁,进入等待状态 值得注意的是,produce 和 consume 方法中都使用了 while 循环来判断缓冲区的存储状态(空/满),这样处理的原因是什么呢,为何不直接用 if 呢? 其实这里用 while 是为了防止虚假唤醒,我们结合一个例子更好理解一些: 如上图所示,假设我们现在有 1 个生产者,3 个消费者在执行生产和消费任务,而目前缓冲区(Warehouse)仅有一个数据:A。 如果当 Consumer-1 消费了 A 后,缓冲区就为空了,Consumer-2 和 Consumer-3 从缓冲区取数据消费时就会陷入等待状态(因为 Consumer-2 消费时缓冲区为空会执行 wait 方法并释放锁,Consumer-3也会重蹈覆辙)。而此时又有一个生产者 Producer-2 生产了数据 B 并加入缓冲区,通过 notifyAll 去唤醒所有等待的消费者,假设消费者 Consumer-2 优先抢到了使用权将 B 消费后缓冲区又恢复到空空如也的状态。接下来的情况就值得注意了:消费者 Consumer-3 想终于可以消费了,然而由于使用的是 if,所以唤醒后继续执行到 list.remove(),毫无疑问的抛出了 IndexOutOfBoundsException,因为此时 list 为空了,这就是所谓的“虚假唤醒”。其实这里我们不能直接消费数据,而是要继续等待。因此这里使用 while 循环判断,当唤醒继续执行代码时重新进入 while 内判断缓冲区数据是否为空。 2. Lock + Condition 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 class GoodsStorageDispatcher { // 仓库容纳上限 private static final int MAX_SIZE = 100; // 仓库容纳的货物集合 private LinkedList<Good> list = new LinkedList(); // 锁 final Lock lock = new ReentrantLock(); // 用于等待或唤醒线程(仓库满后需要等待被消费,成功消费后需要唤醒线程继续生产) final Condition notFull = lock.newCondition(); // 用于等待或唤醒线程(仓库清仓后需要等待生产,生产成功后需要唤醒线程继续消费) final Condition notEmpty = lock.newCondition(); public void produce(Good good) throws InterruptedException { lock.lock(); try { while (list.size() == MAX_SIZE) { //防止虚假唤醒,Condition的await调用一般会放在一个循环判断中 System.out.println("仓库已满 >> 生产暂停,等待消费"); notFull.await(); } list.add(good); notEmpty.signal(); } finally { lock.unlock(); } } public void consume() throws InterruptedException { lock.lock(); try { while (list.size() == 0) { System.out.println("库存已清仓 >> 消费暂停,等待生产"); notEmpty.await(); } list.remove(); notFull.signal(); } finally { lock.unlock(); } } } 通过 ReentrantLock + Condition 来替代实现上面的等待/唤醒,但无疑它的功能性更加齐全和灵活(有限等待、公平锁、读写锁分离等)。 3. BlockingQueue 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 public class GoodsStorageDispatcher { // 仓库容纳上限 private static final int MAX_SIZE = 100; // 仓库容纳的货物集合 private LinkedBlockingQueue<Good> list = new LinkedBlockingQueue<>(MAX_SIZE); public void produce(Good good) { if (list.size() == MAX_SIZE) { System.out.println("仓库已满 >> 生产暂停,等待消费"); } try { list.put(good); } catch (InterruptedException e) { e.printStackTrace(); } } public void consume() { if (list.size() == 0) { System.out.println("库存已清仓 >> 消费暂停,等待生产"); } try { list.take(); } catch (InterruptedException e) { e.printStackTrace(); } } } BlockingQueue 其实就是阻塞队列,是基于阻塞机制实现的线程安全的队列。而阻塞机制的实现是通过在入队和出队时加锁的方式避免并发操作。我们可以理解它内部其实已经实现了上述的加锁和等待/唤醒这一套流程,只不过帮我们封装好了这一切。 应用场景 我们所熟知的线程池,它的内部其实就是构建了一个生产者-消费者模型用于将任务管理和线程管理两部分工作解耦,很大程度上提高了可扩展性。 平时业务中也会有生产消费模型的用武之地,比如多固件更新过程就符合生产-消费模型,用户通过手动操作选中特定设备的固件包加入升级队列,随后由 UpdateDispatcher 根据具体设备类型进行不同策略的分发消费。 相关参考 wait()方法为什么要放在while循环里面-虚假唤醒 深入理解 BlockingQueue

2023/4/29
articleCard.readMore

Android系统启动流程剖析

Android 手机从长按开机到应用的启动阶段都经历了些什么呢?Android 启动过程涉及一系列操作,首先是启动 ROM,接着是引导加载程序、内核启动、init、Zygote 和 SystemServer 创建等过程。整个过程涉及到虚拟机的启动、Binder 线程池的创建以及各项系统服务启动等过程。熟悉 APP 启动过程还可以帮助我们打破性能优化瓶颈,助力于启动性能的提升。下面是启动过程涉及到的关键进程和服务: 本文基于 Android 13 最新代码来分析 Android 系统及应用启动过程。 Init 进程启动 init 进程在 Android 系统中扮演着非常重要的角色,它不仅仅是系统的第一个用户进程(pid 为 1),还负责整个系统的启动和进程的管理。当 Android 设备启动时,Bootloader 会初始化硬件设备,并在硬件自检之后将控制权交给 Kernel,Kernel 会加载 Android 系统的内核。而 init 进程的启动过程就发生在内核启动后,接着调用 system/core/init/main.cpp 来创建 init 进程,在此期间读取 init.rc 文件,启动 zygote 进程、servicemanager 等,最后完成初始化工作。下面来结合源码具体分析 init 启动后都做了些什么: system/core/init/main.cpp 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 using namespace android::init; int main(int argc, char** argv) { #if __has_feature(address_sanitizer) __asan_set_error_report_callback(AsanReportCallback); #elif __has_feature(hwaddress_sanitizer) __hwasan_set_error_report_callback(AsanReportCallback); #endif // Boost prio which will be restored later setpriority(PRIO_PROCESS, 0, -20); if (!strcmp(basename(argv[0]), "ueventd")) { return ueventd_main(argc, argv); } if (argc > 1) { if (!strcmp(argv[1], "subcontext")) { android::base::InitLogging(argv, &android::base::KernelLogger); const BuiltinFunctionMap& function_map = GetBuiltinFunctionMap(); return SubcontextMain(argc, argv, &function_map); } if (!strcmp(argv[1], "selinux_setup")) { return SetupSelinux(argv); } if (!strcmp(argv[1], "second_stage")) { return SecondStageMain(argc, argv); } } return FirstStageMain(argc, argv); } 内核启动后会调用到这里的 main 函数,主要执行了以下几个操作: ueventd_main:该函数是 ueventd 守护进程的主要函数,通过 netlink scoket 响应内核生成的 uevent 事件。它设置信号处理程序,创建用于侦听传入 uevent 的套接字,然后进入无限循环以处理 uevent 事件。 它其实也算是 init 进程,不过具体细节本文不再深究,后面会单独拎出来讲一讲。 SubcontextMain:subcontext 进程的主要处理函数,负责为 init 进程创建和管理子进程,并通过 socket 与 init 进行通信。 SetupSelinux:该函数会尝试加载在init.rc文件中定义的SELinux策略文件。如果文件存在,则在系统启动时加载所需的SELinux策略,并执行其他相关的SELinux设置。这些设置包括设置SELinux上下文,为设备中的所有进程和授权的服务分配安全策略和权限等。 FirstStageMain:init 执行的第一阶段,主要做一些初始化文件系统、kernel 日志以及为第二阶段设定一些环境变量。 SecondStageMain:init 执行的第二阶段,主要负责加载属性文件、启动属性服务、加载 init.rc 文件并创建和启动子进程。 下面我们着重分析一下 init.rc 加载及子进程启动过程。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int SecondStageMain(int argc, char** argv) { if (REBOOT_BOOTLOADER_ON_PANIC) { InstallRebootSignalHandlers(); } ...... // 加载属性文件 PropertyInit(); ...... // 启动属性服务 StartPropertyService(&property_fd); // Make the time that init stages started available for bootstat to log. RecordStageBoottimes(start_time); ...... ActionManager& am = ActionManager::GetInstance(); ServiceList& sm = ServiceList::GetInstance(); // 加载解析 init.rc LoadBootScripts(am, sm); } init.rc 文件的解析是在 LoadBootScripts() 中完成的,详见下面的实现: 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 static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) { Parser parser = CreateParser(action_manager, service_list); std::string bootscript = GetProperty("ro.boot.init_rc", ""); if (bootscript.empty()) { parser.ParseConfig("/system/etc/init/hw/init.rc"); if (!parser.ParseConfig("/system/etc/init")) { late_import_paths.emplace_back("/system/etc/init"); } // late_import is available only in Q and earlier release. As we don't // have system_ext in those versions, skip late_import for system_ext. parser.ParseConfig("/system_ext/etc/init"); if (!parser.ParseConfig("/vendor/etc/init")) { late_import_paths.emplace_back("/vendor/etc/init"); } if (!parser.ParseConfig("/odm/etc/init")) { late_import_paths.emplace_back("/odm/etc/init"); } if (!parser.ParseConfig("/product/etc/init")) { late_import_paths.emplace_back("/product/etc/init"); } } else { parser.ParseConfig(bootscript); } } Init 进程依次解析不同路径下谷歌内置 system、SoC 厂商 vendor 以及 odm 厂商的 init.rc 文件并执行启动相关进程。init.rc 本质上只是一个语法文件,至于具体的 Service 和 Action 解析逻辑是在 ServiceParser 和 ActionParser 中处理,至于最终执行逻辑则是由解析处理器将所有 Service 和 Action 封装到 ServiceList 和 ActionList 单例对象中集中处理的。这里以 init.zygote64_32.rc 中一段启动 zygote 的 rc 代码为例: Note:system/core/rootdir/init.rc 中通过 import 动态引入了基于不同平台的 zygote 进程启动配置,以下仅以常见的 init.zygote64_32.rc 平台为例。 service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket- name=zygote class main priority -20 user root group root readproc reserved_disk socket zygote stream 660 root system socket usap_pool_primary stream 660 root system onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse onrestart write /sys/power/state on onrestart restart audioserver onrestart restart cameraserver onrestart restart media onrestart restart media.tuner onrestart restart netd onrestart restart wificond task_profiles ProcessCapacityHigh MaxPerformance critical window=${zygote.critical_window.minute:-off} target=zygote-fatal Service 是一个服务类型的程序,以 service 开头,由 init 进程启动,其中每一个 service ,在启动时会通过 fork 方式生成子进程。其中,zygote 是进程的名字,/system/bin/app_process 是执行程序的路径,它是通过编译生成的,其包含了 main() 函数在内的一系列代码。在编译过程中,编译器会将源代码编译成目标文件,并将目标文件链接成可执行文件。在链接过程中,编译器会将不同的目标文件链接到一起,形成一个统一的可执行文件。在这个过程中, app_process 可执行文件会链接到 app_main.cpp 文件中定义的 main() 函数,因此运行 app_process 可执行文件时,系统会自动调用 app_main.cpp 文件中的 main() 函数。 后面几项则是传递给执行程序的参数,其中 --start-system-server 表示在 Zygote 进程启动后需要启动 System Server 进程。Zygote 进程是使用 Socket 来进行跨进程通信的,所以会创建一个名为 zygote 的 socket,660 表示访问权限 rw-rw----,表示文件拥有者和同一群组用户具有读写权限。 了解更多关于 init.rc 语法约定参考:Android启动初始化 init.rc 详解 接着 zygote 进程通过以下代码被启动: system/core/init/service.cpp 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 Result<void> Service::Start() { ... pid_t pid = -1; if (namespaces_.flags) { pid = clone(nullptr, nullptr, namespaces_.flags | SIGCHLD, nullptr); } else { pid = fork(); } if (pid == 0) { umask(077); cgroups_activated.CloseWriteFd(); setsid_finished.CloseReadFd(); RunService(descriptors, std::move(cgroups_activated), std::move(setsid_finished)); _exit(127); } else { cgroups_activated.CloseReadFd(); setsid_finished.CloseWriteFd(); } if (pid < 0) { pid_ = 0; return ErrnoError() << "Failed to fork"; } ... } 可以看到最终是通过 clone 或者 fork 的方式来创建一个进程,并返回 pid,其实最终它们都是通过系统调用基于父进程孵化出一个子进程,并与当前进程共享地址空间和文件描述符等资源。 zygote 进程 通过上面分析我们知道,zygote 进程在启动后会执行到 app_main.cpp 的 main 函数中: frameworks/base/cmds/app_process/app_main.cpp 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 #if defined(__LP64__) static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist64"; static const char ZYGOTE_NICE_NAME[] = "zygote64"; #else static const char ABI_LIST_PROPERTY[] = "ro.product.cpu.abilist32"; static const char ZYGOTE_NICE_NAME[] = "zygote"; #endif int main(int argc, char* const argv[]) { AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv)); ... // Parse runtime arguments. Stop at first unrecognized option. bool zygote = false; bool startSystemServer = false; bool application = false; String8 niceName; String8 className; ++i; // Skip unused "parent dir" argument. while (i < argc) { const char* arg = argv[i++]; // 通过 init.rc 中约定的 `--zygote` 来判断是zygote进程 if (strcmp(arg, "--zygote") == 0) { zygote = true; niceName = ZYGOTE_NICE_NAME; } else if (strcmp(arg, "--start-system-server") == 0) { startSystemServer = true; } else if (strcmp(arg, "--application") == 0) { application = true; } else if (strncmp(arg, "--nice-name=", 12) == 0) { niceName.setTo(arg + 12); } else if (strncmp(arg, "--", 2) != 0) { className.setTo(arg); break; } else { --i; break; } } ... if (!niceName.isEmpty()) { runtime.setArgv0(niceName.string(), true /* setProcName */); } if (zygote) { // 进入 AppRunTime 中继续执行,准备穿越到 Java 世界 runtime.start("com.android.internal.os.ZygoteInit", args, zygote); } else if (className) { runtime.start("com.android.internal.os.RuntimeInit", args, zygote); } else { fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); } } 通过上述代码我们可以发现,zygote 进程通过 --zygote 参数标记,如果是 zygote 进程,最终会执行到 AppRuntime.start 中: frameworks/base/core/jni/AndroidRuntime.cpp 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 /* * Start the Android runtime. This involves starting the virtual machine * and calling the "static void main(String[] args)" method in the class * named by "className". * * Passes the main function two arguments, the class name and the specified * options string. */ void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) { ALOGD(">>>>>> START %s uid %d <<<<<<\n", className != NULL ? className : "(unknown)", getuid()); static const String8 startSystemServer("start-system-server"); // Whether this is the primary zygote, meaning the zygote which will fork system server. bool primary_zygote = false; ... /* start the virtual machine */ JniInvocation jni_invocation; jni_invocation.Init(NULL); JNIEnv* env; if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) { return; } onVmCreated(env); /* * Register android functions. */ if (startReg(env) < 0) { ALOGE("Unable to register all android natives\n"); return; } ... /* * Start VM. This thread becomes the main thread of the VM, and will * not return until the VM exits. */ char* slashClassName = toSlashClassName(className != NULL ? className : ""); jclass startClass = env->FindClass(slashClassName); if (startClass == NULL) { ALOGE("JavaVM unable to locate class '%s'\n", slashClassName); /* keep going */ } else { jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V"); if (startMeth == NULL) { ALOGE("JavaVM unable to find main() in '%s'\n", className); /* keep going */ } else { env->CallStaticVoidMethod(startClass, startMeth, strArray); #if 0 if (env->ExceptionCheck()) threadExitUncaughtException(env); #endif } } free(slashClassName); ALOGD("Shutting down VM\n"); if (mJavaVM->DetachCurrentThread() != JNI_OK) ALOGW("Warning: unable to detach main thread\n"); if (mJavaVM->DestroyJavaVM() != 0) ALOGW("Warning: VM did not shut down cleanly\n"); } 通过这部分代码我们了解到,jni 层的 AndroidRuntime 的 start 函数主要完成了三件事: 启动 zygote 进程的虚拟机 完成 JNI 方法注册(在 VM 上注册 Android 本地方法,只为了能够让 native 调用到 Java 层方法) 调用 Java 层的 com.android.internal.os.ZygoteInit#main 方法 至此,终于来到了我们熟悉的 Java 世界了,继续 zygote 的初始化逻辑: com.android.internal.os.ZygoteInit 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 /** * This is the entry point for a Zygote process. It creates the Zygote server, loads resources, * and handles other tasks related to preparing the process for forking into applications. * * This process is started with a nice value of -20 (highest priority). All paths that flow * into new processes are required to either set the priority to the default value or terminate * before executing any non-system code. The native side of this occurs in SpecializeCommon, * while the Java Language priority is changed in ZygoteInit.handleSystemServerProcess, * ZygoteConnection.handleChildProc, and Zygote.childMain. * * @param argv Command line arguments used to specify the Zygote's configuration. */ @UnsupportedAppUsage public static void main(String[] argv) { ZygoteServer zygoteServer = null; Runnable caller; try { ... boolean startSystemServer = false; String zygoteSocketName = "zygote"; boolean enableLazyPreload = false; for (int i = 1; i < argv.length; i++) { if ("start-system-server".equals(argv[i])) { startSystemServer = true; } else if ("--enable-lazy-preload".equals(argv[i])) { enableLazyPreload = true; } else if (argv[i].startsWith(ABI_LIST_ARG)) { abiList = argv[i].substring(ABI_LIST_ARG.length()); } else if (argv[i].startsWith(SOCKET_NAME_ARG)) { zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length()); } else { throw new RuntimeException("Unknown command line argument: " + argv[i]); } } // In some configurations, we avoid preloading resources and classes eagerly. // In such cases, we will preload things prior to our first fork. if (!enableLazyPreload) { preload(bootTimingsTraceLog); } zygoteServer = new ZygoteServer(isPrimaryZygote); if (startSystemServer) { Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer); // {@code r == null} in the parent (zygote) process, and {@code r != null} in the // child (system_server) process. if (r != null) { r.run(); return; } } Log.i(TAG, "Accepting command socket connections"); // The select loop returns early in the child process after a fork and // loops forever in the zygote. caller = zygoteServer.runSelectLoop(abiList); } catch (Throwable ex) { Log.e(TAG, "System zygote died with fatal exception", ex); throw ex; } finally { if (zygoteServer != null) { zygoteServer.closeServerSocket(); } } // We're in the child process and have exited the select loop. Proceed to execute the // command. if (caller != null) { caller.run(); } } ZygoteInit#main 中大致做了以下几件事: preload:预加载系统基础类、资源文件、共享库等。 ZygoteServer:创建 Socket 服务端,用于和其他进程通信。 forkSystemServer:fork 出 system_server 子进程,下面会分析到。 ZygoteServer#runSelectLoop:socket 循环监听 AMS 用于创建应用进程的请求。 那么我简单总结一下,zygote 进程负责创建虚拟机,并注册 JNI 方法,成为 Java 进程的母体,用于持续孵化 Java 进程。在创建完system_server 进程后调用 runSelectLoop,随时等待接收创建新进程的请求并立即唤醒开始执行相应创建工作。 system_server 进程 Zygote 的初始化过程涉及到 system_server 进程的创建和启动,那我们就来看下 system_server 进程的启动过程: com.android.internal.os.ZygoteInit 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 /** * Prepare the arguments and forks for the system server process. * * @return A {@code Runnable} that provides an entrypoint into system_server code in the child * process; {@code null} in the parent. */ private static Runnable forkSystemServer(String abiList, String socketName, ZygoteServer zygoteServer) { String[] args = { "--setuid=1000", "--setgid=1000", "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023," + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011", "--capabilities=" + capabilities + "," + capabilities, "--nice-name=system_server", "--runtime-args", "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT, // system_server 执行程序的类名 "com.android.server.SystemServer", }; ... ZygoteArguments parsedArgs; int pid; try { ... /* Request to fork the system server process */ pid = Zygote.forkSystemServer( parsedArgs.mUid, parsedArgs.mGid, parsedArgs.mGids, parsedArgs.mRuntimeFlags, null, parsedArgs.mPermittedCapabilities, parsedArgs.mEffectiveCapabilities); } catch (IllegalArgumentException ex) { throw new RuntimeException(ex); } /* For child process */ if (pid == 0) { if (hasSecondZygote(abiList)) { waitForSecondaryZygote(socketName); } zygoteServer.closeServerSocket(); return handleSystemServerProcess(parsedArgs); } return null; } /** * Finish remaining work for the newly forked system server process. */ private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) { if (parsedArgs.mNiceName != null) { Process.setArgV0(parsedArgs.mNiceName); } ... if (parsedArgs.mInvokeWith != null) { ... throw new IllegalStateException("Unexpected return from WrapperInit.execApplication"); } else { ClassLoader cl = getOrCreateSystemServerClassLoader(); if (cl != null) { Thread.currentThread().setContextClassLoader(cl); } /* * Pass the remaining arguments to SystemServer. */ return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, parsedArgs.mDisabledCompatChanges, parsedArgs.mRemainingArgs, cl); } /* should never reach here */ } public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges, String[] argv, ClassLoader classLoader) { if (RuntimeInit.DEBUG) { Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote"); } Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit"); RuntimeInit.redirectLogStreams(); // 设置异常处理机制 RuntimeInit.commonInit(); // 启动进程的Binder线程池 ZygoteInit.nativeZygoteInit(); return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv, classLoader); } 看到上面的参数信息 args 包含了 com.android.server.SystemServer 就可以大致猜到最后应该是反射创建的实例。上面方法 ZygoteInit#forkSystemServer 继续追踪下去,system_server 进程的创建最终通过 Zygote.forkSystemServer 实现的: 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 /** * Special method to start the system server process. In addition to the * common actions performed in forkAndSpecialize, the pid of the child * process is recorded such that the death of the child process will cause * zygote to exit. * * @param uid the UNIX uid that the new process should setuid() to after * fork()ing and and before spawning any threads. * @param gid the UNIX gid that the new process should setgid() to after * fork()ing and and before spawning any threads. * @param gids null-ok; a list of UNIX gids that the new process should * setgroups() to after fork and before spawning any threads. * @param runtimeFlags bit flags that enable ART features. * @param rlimits null-ok an array of rlimit tuples, with the second * dimension having a length of 3 and representing * (resource, rlim_cur, rlim_max). These are set via the posix * setrlimit(2) call. * @param permittedCapabilities argument for setcap() * @param effectiveCapabilities argument for setcap() * * @return 0 if this is the child, pid of the child * if this is the parent, or -1 on error. */ static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) { ZygoteHooks.preFork(); int pid = nativeForkSystemServer( uid, gid, gids, runtimeFlags, rlimits, permittedCapabilities, effectiveCapabilities); // Set the Java Language thread priority to the default value for new apps. Thread.currentThread().setPriority(Thread.NORM_PRIORITY); ZygoteHooks.postForkCommon(); return pid; } system_server 进程的最终实现是在 native 层,但是从注释来看,它的返回值如果是 0 则说明当前是子进程,否则可能依然是父进程或者 fork 失败。如果 system_server 进程创建成功,会依次执行到 handleSystemServerProcess 和 zygoteInit 方法,继续跟进到 RuntimeInit.applicationInit 一探究竟: frameworks/base/core/java/com/android/internal/os/RuntimeInit.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 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 protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges, String[] argv, ClassLoader classLoader) { ... // Remaining arguments are passed to the start class's static main return findStaticMain(args.startClass, args.startArgs, classLoader); } 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); } /* * This throw gets caught in ZygoteInit.main(), which responds * by invoking the exception's run() method. This arrangement * clears up all the stack frames that were required in setting * up the process. */ return new MethodAndArgsCaller(m, argv); } /** * Helper class which holds a method and arguments and can call them. This is used as part of * a trampoline to get rid of the initial process setup stack frames. */ static class MethodAndArgsCaller implements Runnable { /** method to call */ private final Method mMethod; /** argument array */ private final String[] mArgs; public MethodAndArgsCaller(Method method, String[] args) { mMethod = method; mArgs = args; } public void run() { try { mMethod.invoke(null, new Object[] { mArgs }); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } catch (InvocationTargetException ex) { Throwable cause = ex.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException(ex); } } } 最终不出我们所料,findStaticMain 中传递的 className 对应的就是我们之前的 com.android.server.SystemServer 类,我们来看看它里面都做了些什么(SystemServer#main 会执行到 run 方法): frameworks/base/services/java/com/android/server/SystemServer.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 47 48 49 50 51 52 53 54 55 56 private void run() { try { ... // Prepare the main looper thread (this thread). android.os.Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_FOREGROUND); android.os.Process.setCanSelfBackground(false); Looper.prepareMainLooper(); Looper.getMainLooper().setSlowLogThresholdMs( SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS); SystemServiceRegistry.sEnableServiceNotFoundWtf = true; ... // Initialize the system context. createSystemContext(); ... // Create the system service manager. mSystemServiceManager = new SystemServiceManager(mSystemContext); mSystemServiceManager.setStartInfo(mRuntimeRestart, mRuntimeStartElapsedTime, mRuntimeStartUptime); mDumper.addDumpable(mSystemServiceManager); LocalServices.addService(SystemServiceManager.class, mSystemServiceManager); // Prepare the thread pool for init tasks that can be parallelized SystemServerInitThreadPool tp = SystemServerInitThreadPool.start(); mDumper.addDumpable(tp); ... } finally { t.traceEnd(); // InitBeforeStartServices } // Setup the default WTF handler RuntimeInit.setDefaultApplicationWtfHandler(SystemServer::handleEarlySystemWtf); // Start services. try { t.traceBegin("StartServices"); startBootstrapServices(t); startCoreServices(t); startOtherServices(t); startApexServices(t); } catch (Throwable ex) { Slog.e("System", "******************************************"); Slog.e("System", "************ Failure starting system services", ex); throw ex; } finally { t.traceEnd(); // StartServices } ... // Loop forever. Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); } 通过分析上面代码可以发现 system_server 为我们做了很多事情: 创建系统服务管理器 SystemServiceManager 启动引导服务(AMS、PMS等)、核心服务和其他各种服务 创建主线程 Looper 并一直循环,等待其他线程通过 Handler 发送消息再唤醒并处理。 nativeZygoteInit 上面的 com.android.internal.os.ZygoteInit#forkSystemServer 中 ZygoteInit.nativeZygoteInit() 方法主要从 native 层创建了进程的 Binder 线程池。值得注意的,这一切发生在 system_server 进程创建之后。最终可以追溯到: frameworks/native/libs/binder/ProcessState.cpp 1 2 3 4 5 6 7 8 9 10 11 12 void ProcessState::spawnPooledThread(bool isMain) { if (mThreadPoolStarted) { String8 name = makeBinderThreadName(); ALOGV("Spawning new pooled thread, name=%s\n", name.string()); sp<Thread> t = sp<PoolThread>::make(isMain); t->run(name.string()); pthread_mutex_lock(&mThreadCountLock); mKernelStartedThreads++; pthread_mutex_unlock(&mThreadCountLock); } } 系统启动日志分析 从 Android 开机开始到我们系统初始化完毕,这中间设有大量的关键日志,如果能够好好利用,对于我们排查一些系统性问题或者性能问题会有巨大帮助。以下列举部分关键节点的 Tag: 可查看启动耗时 boot_progress_start:表示 kernel 启动完成,第一个用户空间进程 init 进程启动 boot_progress_preload_start:zygote 进程开始预加载 class 资源(记录于 ZygoteInit#preload 调用时机) boot_progress_preload_end:zygote 进程 class 资源预加载结束 boot_progress_system_run:表示 system_server 进程启动(记录于 SystemServer#run 调用时机) boot_progress_pms_start:表示 PMS 服务启动并开始扫描应用的动作 boot_progress_pms_ready:表示 PMS 服务应用扫描完毕 boot_progress_ams_ready:表示 AMS 已经进入 systemReady 状态可以启动桌面应用了 boot_progress_enable_screen:标志着锁频界面已经点亮显示,开机完成并统计耗时 更多 native 层相关统计的 tag 参照:system/logging/logcat/event.logtags 我们可以额外留意一些系统进程和服务的相关日志,比如 zygote 运行相关日志: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // Zygote64进程(Zygote): AndroidRuntime::start 11-23 14:36:51.260 557 557 D AndroidRuntime: >>>>>> START com.android.internal.os.ZygoteInit uid 0 <<<<<< // Zygote64进程: AndroidRuntime::startVm 11-23 14:36:51.304 557 557 D AndroidRuntime: CheckJNI is OFF // 执行ZygoteInit.preload() 11-23 14:36:52.134 557 557 D Zygote : begin preload // 执行ZygoteInit.preloadClasses(), 预加载3860个classes, 花费时长746ms 11-23 14:36:52.134 557 557 I Zygote : Preloading classes... 11-23 14:36:52.881 557 557 I Zygote : ...preloaded 3860 classes in 746ms. // 执行ZygoteInit.preloadClasses(), 预加载86组资源, 花费时长179ms 11-23 14:36:53.114 557 557 I Zygote : Preloading resources... 11-23 14:36:53.293 557 557 I Zygote : ...preloaded 86 resources in 179ms. // 执行ZygoteInit.preloadSharedLibraries() 11-23 14:36:53.494 557 557 I Zygote : Preloading shared libraries... 11-23 14:36:53.503 557 557 D Zygote : end preload // 执行com_android_internal_os_Zygote_nativeForkSystemServer(),成功fork出system_server进程 11-23 14:36:53.544 557 557 I Zygote : System server process 1274 has been created // Zygote开始进入runSelectLoop() 11-23 14:36:53.546 557 557 I Zygote : Accepting command socket connections system_server 进程运行相关日志: 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 //进入system_server, 建立跟Zygote进程的socket通道 11-23 14:36:53.586 1274 1274 I Zygote : Process: zygote socket opened, supported ABIS: armeabi-v7a,armeabi // 执行SystemServer.run() 11-23 14:36:53.618 1274 1274 I SystemServer: Entered the Android system server! <===> boot_progress_system_run // 等待installd准备就绪 11-23 14:36:53.707 1274 1274 I Installer: Waiting for installd to be ready. //服务启动 11-23 14:36:53.732 1274 1274 I ActivityManager: Memory class: 192 //phase100 11-23 14:36:53.883 1274 1274 I SystemServiceManager: Starting phase 100 11-23 14:36:53.902 1274 1274 I SystemServer: Package Manager 11-23 14:37:03.816 1274 1274 I SystemServer: User Service ... 11-23 14:37:03.940 1274 1274 I SystemServer: Init Watchdog 11-23 14:37:03.941 1274 1274 I SystemServer: Input Manager 11-23 14:37:03.946 1274 1274 I SystemServer: Window Manager ... 11-23 14:37:04.081 1274 1274 I SystemServiceManager: Starting com.android.server.MountService$Lifecycle 11-23 14:37:04.088 1274 2717 D MountService: Thinking about reset, mSystemReady=false, mDaemonConnected=true 11-23 14:37:04.088 1274 1274 I SystemServiceManager: Starting com.android.server.UiModeManagerService 11-23 14:37:04.520 1274 1274 I SystemServer: NetworkTimeUpdateService //phase480 && 500 11-23 14:37:05.056 1274 1274 I SystemServiceManager: Starting phase 480 11-23 14:37:05.061 1274 1274 I SystemServiceManager: Starting phase 500 11-23 14:37:05.231 1274 1274 I ActivityManager: System now ready <==> boot_progress_ams_ready 11-23 14:37:05.234 1274 1274 I SystemServer: Making services ready 11-23 14:37:05.243 1274 1274 I SystemServer: WebViewFactory preparation //phase550 11-23 14:37:05.234 1274 1274 I SystemServiceManager: Starting phase 550 11-23 14:37:05.237 1274 1288 I ActivityManager: Force stopping com.android.providers.media appid=10010 user=-1: vold reset //Phase600 11-23 14:37:06.066 1274 1274 I SystemServiceManager: Starting phase 600 11-23 14:37:06.236 1274 1274 D MountService: onStartUser 0 基于记录以上关键日志的目的,我们可以通过以下 logcat 命令在手机开机后使用: adb logcat -b events |grep “boot_progress” adb logcat -s Zygote|SystemServer|SystemServiceManager|ActivityManager 总结 笔者认为,源码分析过程中如果能输出关键类调用的流程,能够显著加强后续理解。关于普通应用进程的创建和启动过程将在后续 Activity 启动分析中一并介绍。下图就是关于本文各进程启动过程的方法调用链路总结。 参考 AOSP 架构概览 Android 系统启动综述 https://juejin.cn/post/6999491633129455653

2023/4/22
articleCard.readMore

AOSP在Mac上的编译实践(上)

AOSP,即 Android Open Source Project,对于 Android 开发者来说再熟悉不过的项目,本文将着重介绍如何在 MacOS Monterey 环境上下载完整的 AOSP 源码。 Git & Python 1. Git 安装和配置 在安装了 Homebrew 的情况下,安装 git 我们只需要执行如下命令: 1 brew install git 接着初始化一下 Git 的全局配置信息: 1 2 git config --global user.name "User Name" git config --global user.email "your@example.com" 2. 配置 python 环境 一般情况下我们电脑上都会安装了 python 环境,如果没有安装,可以前往 python官方站点下载。安装成功并做好解释器关联后执行以下命令查看版本信息: 1 2 ➜ ~ python --version Python 3.10.4 目前的 repo 都是基于 python 3.0,我们需要留意最新的 repo 版本中的 main.py 文件中定义的版本和我们本地的 python 环境是否一致。这里能跑通并不代表整个环境就万事大吉了,后面在安装 repo 的时候会提到因 python 配置而遇到的一个问题。 安装 repo Repo 是建立在 Git 上的一个多仓库管理工具,可以组织多个仓库的上传和下载。它是 Google 基于 Python 和 Git 编写的脚本工具,可以协助我们管理多个 Git 存储仓库。首先,我们通过如下命令安装和配置 repo: 1 2 3 ➜ mkdir ~/DevKit/bin ➜ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/DevKit/bin/repo ➜ chmod a+x ~/DevKit/bin/repo 如果提示网络问题可能需要翻墙,可尝试使用国内镜像: 1 2 curl https://mirrors.tuna.tsinghua.edu.cn/git/git-repo -o ~/DevKit/bin/repo chmod a+x ~/DevKit/bin/repo 接着执行 repo --version 查看是否安装成功: 1 2 3 4 5 6 7 8 9 10 ➜ ~ repo --version <repo not installed> repo launcher version 2.32 (from /Users/dorck/DevKit/bin/repo) git 2.32.0 (Apple Git-132) Python 3.10.4 (v3.10.4:9d38120e33, Mar 23 2022, 17:29:05) [Clang 13.0.0 (clang-1300.0.29.30)] OS Darwin 21.4.0 (Darwin Kernel Version 21.4.0: Fri Mar 18 00:46:32 PDT 2022; root:xnu-8020.101.4~15/RELEASE_ARM64_T6000) CPU arm64 (arm) Bug reports: https://bugs.chromium.org/p/gerrit/issues/entry?template=Repo+tool+issue ➜ ~ 若出现上述版本提示信息说明安装成功,可继续进行下一步操作。But,在笔者的 M1 电脑上遇到了一个坑,在执行 repo --version 时提示 python 配置问题: 1 env: python: No such file or directory 出现这类问题的常见原因一般是本地有多个 python 版本环境,导致链接出错,于是我先定位一下本地机器中的 python 解释器版本有哪些: 1 2 ➜ where python /Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10 可以看到我电脑指向的是 /Library/Frameworks 下的内置 python 环境,然而我又翻看了一下 ~/.bash_profile 中的 python 环境配置: 1 2 3 4 5 6 # Python 配置 alias python="/Library/Frameworks/Python.framework/Versions/3.10/bin/python3.10" export PATH="/Library/Frameworks/Python.framework/Versions/3.10/bin:$PATH" export PATH=/usr/local/bin:$PATH export PATH=/Users/{YOUR_NAME}/DevKit/bin:$PATH export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo' 可以看到,我这边配置了不止一项 python 环境,/usr/local/bin 下还存在一个 3.0 版本的 python,那么为什么 repo 中的 python 代码执行不了呢?简单看了下 repo 的内部代码,发现他里面是通过 sys 库来获取的当前运行的 python 解释器。以下是笔者的个人猜测:在 macOS 系统中,系统默认的 Python 解释器是 Python 2.x 版本,如果安装了 Python 3.x 版本,而且没有设置符号链接将其路径添加到系统 PATH 中,那么系统就无法找到 Python 3.x 版本的可执行文件。因此,当您在终端中输入 python3 命令时,系统会去默认的路径 /usr/bin/ 中查找 Python 解释器,而无法找到 Python 3.x 版本的解释器,从而导致 “env: python: No such file or directory” 错误的发生。 要解决这个问题,我们可以执行以下命令: 1 ln -sf /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 /usr/local/bin/python 这个命令的作用是将 Python 3.10 版本的可执行文件添加到了 /usr/local/bin 目录下,并创建了一个符号链接(软链接),使得 python 命令可以正确地指向 Python 3.10 版本的可执行文件,从而避免在使用 python 命令时出现因为找不到 Python 3.10 版本的可执行文件而报错的问题。这样,当我们在终端中输入 python 或 python3 命令时,系统会优先在 /usr/local/bin 目录下查找,找到该目录下的 python 或 python3 命令,然后使用其中的 Python 3.10 版本的可执行文件来执行程序,从而保证我们使用的是正确的 Python 版本。 接下来,安装完成 repo 后,我可以将以下 REPO_URL 地址替换为国内的镜像地址,方便后续下载 AOSP 源码,由于该项目较大,需要保证稳定的网络环境,不具备翻墙条件的此步骤为必选项: 1 2 export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo' export PATH=/Users/{YOUR_NAME}/DevKit/bin:$PATH 以上配置请复制到 ~/.bash_profile 中,并执行 source ~/.bash_profile 使其生效。这里需要将 repo 的 path 也放置到环境变量中,方便我们全局调用。 关于 repo 的具体用法可以参考:多仓库管理工具—Repo 下载 AOSP 接下来我们就可以愉快的下载 AOSP 代码了: 1 2 3 ➜ mkdir AOSP ➜ cd ~/AOSP ➜ repo init -u https://mirrors.tuna.tsinghua.edu.cn/git/AOSP/platform/manifest -b android-13.0.0_r30 通过 repo init 命令来初始化、关联具体的 repo 项目,并指定了下载分支为 Android 13 版本的代码。当然,事情可能并不会一帆风顺,出现了下面的报错信息: 1 2 fatal: Cannot get https://gerrit.googlesource.com/git-repo/clone.bundle fatal: error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590) 我们需要执行一下 Python 安装包下的 Install Certificates.command 脚本来安装证书,然后重新执行上述 init 命令: 1 2 3 4 5 6 7 8 Downloading Repo source from https://mirrors.tuna.tsinghua.edu.cn/git/git-repo remote: Enumerating objects: 4463, done. remote: Counting objects: 100% (4463/4463), done. remote: Compressing objects: 100% (2123/2123), done. remote: Total 8034 (delta 3979), reused 2340 (delta 2340), pack-reused 3571 Receiving objects: 100% (8034/8034), 3.74 MiB | 13.53 MiB/s, done. Resolving deltas: 100% (5171/5171), done. repo: Updating release signing keys to keyset ver 2.3 输出以上信息说明 repo 的 manifest 配置下载成功,接下来只需要同步一下代码即可: 1 ➜ repo sync 代码同步时间会比较长,我们能做的就是耐心等待了。最终下载完成的 AOSP 大小直逼 150 GB: 下一篇文章将会一起探索如何在 Mac 上编译 Android Open Source Project 项目,拭目以待。 参考 AOSP 中的代号、标记和 build 号

2023/4/21
articleCard.readMore

效率编程之快捷键篇

这是一个关于如何效率编程的系列专题,未来将会涉及诸如工具、脚本、插件、开源框架等各方面,只要能够帮助我们提升开发效率。 在日常当中,熟练地运用各种快捷键能够极大地提升我们的开发和工作效率。当看到其他同学通过快捷键两三下操作就帮你定位了问题代码并修改完毕,你是否也曾露出羡慕之色。正所谓磨刀不误砍柴工,快捷键也像宝刀一样,长时间弃而不用就会生锈,长此以往我们就会失去这一强大的臂助。 以下常用快捷键指令皆基于 MAC 系统。 基础文本操作 其实很多文本类工具都提供了通用的快捷键,比如文本选择、光标快速移动等等,毋以善小而不为,不要小看这些小小的效率提升,聚沙成塔,长久以后就会凝聚成我们自身强大工作流的一部分。 光标移动 光标移动到行头/尾:Cmd + ← / Cmd + → 光标移动到文本开头/结尾:Cmd + ↑ / Cmd + ↓ 光标向前/向后隔一词跳跃:Option + ← / Option + → 文本浏览 移动到文本开始/末尾:Fn + ← / Fn + →(浏览器和部分文本工具支持) 文本上/下翻页:Fn + ↑ / Fn + ↓ (大部分文本工具都支持) 文本选取 通过 Shift 搭配上方的光标移动快捷键,我们可以便捷实现文本选取。 从当前位置选取到行首/尾:Cmd + Shift + ← / Cmd + Shift + → 从当前位置选取到文本开头/结尾:Cmd + Shift + ↑ / Cmd + Shift + ↓ 或者使用 Fn + Shift + ← / Fn + Shift + → 从当前位置向前/向后选取一个单词:Option + Shift + ← / Option + Shift + → 从当前位置向上/下选取一页:Fn + Shift + ↑ / Fn + Shift + ↓ 文本删除 向前删除:Delete 向后删除:Fn + delete 向前删除一个词:Option + delete 向后删除一个词:Option + Fn + delete 删除至行首:Cmd + delete 文本查询 当前文稿内查询:Cmd + F 项目内查询:Cmd + Shift + F 查询并定位命中的下一个目标:Enter / Cmd + G 查询并定位命中的上一个目标:Cmd + Shift + G 文本样式 粗体:Cmd + B 斜体:Cmd + I 下划线:Cmd + U 文字超链接:Cmd + K 窗口操作 切换 隐藏屏幕正在显示的最上层窗口:Cmd + H 隐藏屏幕正在显示的底层窗口(除最上层以外的窗口):Cmd + Option + H 在多个全屏窗口之间前后切换:Control + ← / Control + → 将所有窗口进入/退出预览模式:Control + ↑ / Control + ↓ 应用程序 在多个应用程序间前后切换:Cmd + Tab / Cmd + Shift + Tab / Cmd + Tab + ← / Cmd + Tab + → 退出最上层的应用程序:Cmd + Q 退出除最上层以外的其他应用:Cmd + Shift + Q 关闭 关闭窗口:Cmd + W 文件操作 新建文件夹:Command + Shift + N 弹出窗口输入绝对路径课直达目标文件夹:Command + Shift + G 打开所选文件:Command + O 前往当前文件夹的上/下一层文件夹:Command + ↑ / Command + ↓ 将文件移至废纸篓:Command + Delete 清理废纸篓:Command + Shift + Delete 文件预览:Space 查看文件简介:Command + I 进入当前文件操作栈的上/下一层:Cmd + [ / Cmd + ] 最小化窗口:Cmd + M 查看最近使用的文件:Shift + Cmd + F 进入下载目录:Option + Cmd + L 进入个人目录:Shift + Cmd + H 其他系统快捷键 截图 截取整个屏幕内容到桌面:Command + Shift + 3 截取整个屏幕内容到剪贴板:Command + Shift + Control + 3 截取所选屏幕区域到桌面,或按空格键仅捕捉一个窗口:Command + Shift + 4 截取所选屏幕区域到剪贴板,或按空格键仅捕捉一个窗口:Command + Shift + Control + 4 撤销与恢复 很多时候,我们在经常编辑文本或处理文件的时候会大量用到撤销和恢复撤销两个动作。 撤销:Cmd + Z 恢复撤销:Cmd + Shift + Z 锁屏与关机 锁定屏幕:Control + Command + Q 重启:Control + Command + Power 关机:Command + Option + COntrol + Power Android Studio 快捷键 搜索&替换 快捷键说明 Windows | Linux MacOS 全局搜索(代码、文件、菜单) Shift + Shift Shift + Shift 当前文件内查找代码 Ctrl + F Command + F 当前文件内代码替换 Ctrl + R Command + R 全局搜索代码(路径、项目、模块) Ctrl + Shift + F Command + Shift + F 全局替换(路径、项目、模块) Ctrl + Shift + R Command + Shift + R 查找下一项内容 F3 Command + G 查找上一项内容 Shift + F3 Command + Shift+ G 查看最近打开的文件窗口 Ctrl + E Command + E 查看最近编辑过的文件窗口 Ctrl + Shift + E Command + Shift + E 关闭当前窗口 Ctrl + F4 Command + W 代码查看&编辑 快捷键说明 Windows | Linux MacOS 进入源代码 F4 或 Ctrl + Enter Command + ↓ 或 Command + 鼠标单击 跳转到代码指定行 Ctrl+G Command + L 跳转到上一个代码编辑的位置 Ctrl + Shift + 退格键 Command + Option + ← 或 Command+Shift+Delete 跳转到下一个代码编辑的位置   Command + Option + → 查看类层次结构(继承关系) Ctrl + H Ctrl + H 查看方法层次结构 Ctrl + Shift + H Command + Shift + H 查看调用层次结构 Ctrl + Alt + H Ctrl + Option + H 插入模版代码 Alt + Insert Command + N 前往子类实现该方法 Ctrl + I Ctrl + I 选择重写父类某个方法 Ctrl + O Ctrl + O 将代码放入条件语句中(if-else/try-catch..) Ctrl + Alt + T Command + Option + T 删除整行 Ctrl + Y Command + Delete 收起/展开当前代码块 Ctrl + 减号键Ctrl + 加号键 Command+减号键或Command+加号键 收起/展开所有代码块 Ctrl + Shift + 减号键或 Ctrl + Shift + 加号键 Command+Shift+减号键或 Command+Shift+加号键 复制当前行并另起一行粘贴(不会放到粘贴板) Ctrl + D Command + D 快速预览查看文档 Ctrl + Q Ctrl + J 预览查看选定方法的参数 Ctrl + P Command + P 显示或直接跳转到方法调用方 Ctrl + B 或 Ctrl + 点击 Command + B 或 Command + 点击 显示或直接跳转到方法实现处 Ctrl + Alt + B Command + Option + B 跳转到超类方法处 Ctrl + U Command + U 快速预览方法定义 Ctrl + Shift + I Command + Y 切换项目左侧工具窗口的可见性 Alt + 1 Command + 1 添加/取消行注释 Ctrl + / Command + / 添加/取消块注释 Ctrl + Shift + / Command + Shift + / 增加选中的代码块 Ctrl + W Option + 向上箭头 减少选中的代码块 Ctrl + Shift + W Option + 向下箭头 移动到代码块起始位置 Ctrl + [ Option + Command + [ 移动到代码块结束位置 Ctrl + ] Option + Command + ] 从当前位置选择到代码块起始位置 Ctrl + Shift + [ Option + Command + Shift + [ 从当前位置选择到代码块结束位置 Ctrl + Shift + ] Option + Command + Shift + ] 包名导入优化 Ctrl + Alt + O Control + Option + O 显示快速修复建议 Alt + Enter Option + Enter 重新格式化代码 Ctrl + Alt + L Command + Option + L 自动修复行缩进 Ctrl + Alt + I Ctrl + Option + I 缩进/取消缩进行 Tab 或 Shift + Tab Tab 或 Shift + Tab 另起一行 Shift + Enter Shift + Enter(注意和 Command + Enter 区别:前者光标自动跟进到下一行开头,而后者光标停留在原地) 版本控制 快捷键说明 Windows | Linux MacOS 将项目提交到 VCS(Push) Ctrl + K Command + K 从 VCS 更新项目(Pull) Ctrl + T Command + T 查看最近变更 Alt + Shift + C Option + Shift + C 打开 VCS 对话框 Alt +`(反引号) Ctrl + V 重构相关 快捷键说明 Windows | Linux MacOS 复制 F5 Fn + F5 移动 F6 FN + F6 安全删除 Alt+Delete Command + Delete 重命名(方法、属性、文件等) Shift+F6 Fn + Shift + F6 更改方法/属性签名 Ctrl+F6 Fn + Command + F6 方法内联(将方法内实现部分提取到方法调用处) Ctrl + Alt + N Command + Option + N 提取方法 Ctrl+Alt+M Command + Option + M 提取为局部变量 Ctrl+Alt+V Command + Option + V 提取为类成员字段 Ctrl+Alt+F Command + Option + F 提取为常量 Ctrl+Alt+C Command + Option + C 提取为方法参数(e.g,将方法内局部变量提取为通过外部传入) Ctrl+Alt+P Command + Option + P 代码调试 快捷键说明 Windows | Linux MacOS 调试 Shift + F9 Ctrl+D 单步跳过 F8 Fn + F8 单步进入 F7 Fn + F7 智能单步进入 Shift + F7 Fn + Shift + F7 单步退出 Shift + F8 Fn + Shift + F8 运行到光标位置 Alt + F9 Fn + Option + F9 评估表达式 Alt + F8 Fn + Option + F8 继续运行程序 F9 Command + Option + R 切换断点 Ctrl + F8 Fn + Command + F8 查看断点 Ctrl + Shift + F8 Fn + Command + Shift + F8 自定义快捷键 Android Studio 还有很多操作没有关联上具体的快捷键,而我们也可以通过自定义的方式来为高频操作绑定上快捷键。只需要到工具栏的 Android Studio 一栏找到 Preference(或用快捷键 Cmd + , )进入设置 keymap 选项中搜索对应的操作即可。这里以 Sync Project with Gradle Files 操作为例: 接下来只需选中目标项绑定快捷键即可,我这里绑定的快捷键是 CMd + Option + S。Android Studio 比较智能,如果绑定了已被使用的快捷键会提示冲突问题。 小结 开发效率即意味着生产力,长时间维持着 996 状态也并不代表生产力就高,相反,我们自身会一直处于高压状态,不利于身心健康。故而,我们应该着手于术,从实际出发,点滴汇聚,提升效率,把时间多专注在享受生活上,而不是庸庸碌碌的“机器命”。 参考 Android Studio shortcuts

2023/4/16
articleCard.readMore

Android系统中线程的创建过程

我们都知道,Android 中线程创建过程需要追溯到 Native 层面,最终是委托给一个 Linux 标准线程 pthread 来执行的,所以 Android 中线程状态本质上是 Native 线程的一种映射。Android 中运行的线程可以分为两种:一种是 attach 到虚拟机的,即虚拟机线程;另一种是没有 attach 到虚拟机的。今天我们就分别从源码层面来看看 Android 系统中 Java 和 Native 层线程的创建过程。 以下分析基于 Android 13 最新源码。 Java 线程创建过程 首先,我们需要知道的是:当我们通过 new 关键字创建一个 Thread 时其实并没有真正创建一个线程,只有调用 start 方法后才会去创建线程。先来看下 start 方法内部实现: 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 public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ // Android-changed: Replace unused threadStatus field with started field. // The threadStatus field is unused on Android. if (started) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); // Android-changed: Use field instead of local variable. // It is necessary to remember the state of this across calls to this method so that it // can throw an IllegalThreadStateException if this method is called on an already // started thread. started = false; try { // Android-changed: Use Android specific nativeCreate() method to create/start thread. // start0(); nativeCreate(this, stackSize, daemon); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } 可以看到,最终线程的创建和执行是在 nativeCreate 方法中,而它是个 native 方法,对应的实现在 /art/runtime/native/java_lang_Thread.cc 文件中,代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 static void Thread_nativeCreate(JNIEnv* env, jclass, jobject java_thread, jlong stack_size, jboolean daemon) { // There are sections in the zygote that forbid thread creation. Runtime* runtime = Runtime::Current(); if (runtime->IsZygote() && runtime->IsZygoteNoThreadSection()) { jclass internal_error = env->FindClass("java/lang/InternalError"); CHECK(internal_error != nullptr); env->ThrowNew(internal_error, "Cannot create threads in zygote"); return; } Thread::CreateNativeThread(env, java_thread, stack_size, daemon == JNI_TRUE); } 继续跟踪到 /art/runtime/thread.cc: 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 Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) { CHECK(java_peer != nullptr); Thread* self = static_cast<JNIEnvExt*>(env)->GetSelf(); ...... int pthread_create_result = 0; if (child_jni_env_ext.get() != nullptr) { pthread_t new_pthread; pthread_attr_t attr; child_thread->tlsPtr_.tmp_jni_env = child_jni_env_ext.get(); CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), "new thread"); CHECK_PTHREAD_CALL(pthread_attr_setdetachstate, (&attr, PTHREAD_CREATE_DETACHED), "PTHREAD_CREATE_DETACHED"); CHECK_PTHREAD_CALL(pthread_attr_setstacksize, (&attr, stack_size), stack_size); pthread_create_result = pthread_create(&new_pthread, &attr, gUseUserfaultfd ? Thread::CreateCallbackWithUffdGc : Thread::CreateCallback, child_thread); CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), "new thread"); if (pthread_create_result == 0) { // pthread_create started the new thread. The child is now responsible for managing the // JNIEnvExt we created. // Note: we can't check for tmp_jni_env == nullptr, as that would require synchronization // between the threads. child_jni_env_ext.release(); // NOLINT pthreads API. return; } } ...... } Thread::CreateNativeThread 方法实现比较多,过滤了一些代码,我们重点关注下里面调用了 pthread_create 方法: bionic/libc/bionic/pthread_create.cpp 1 2 3 4 int pthread_create(pthread_t* thread_out, pthread_attr_t const* attr, void* (*start_routine)(void*), void* arg) { ...... } POSIX 线程(POSIX threads)又简称 Pthreads 是线程的 POSIX 标准,该标准定义了创建和操纵线程的一整套 API,在类 Unix 操作系统(Unix、Linux、Mac OS X等)中都使用 Pthreads 作为操作系统的线程,Windows操作系统也有其移植版 pthreads-win32。简而言之该标准定义内部 API 创建和操纵线程, Pthreads 定义了一套 C 程序语言类型、函数与常量,它以 pthread.h 头文件和一个线程库实现,所以在 Android Studio 使用时直接在 C/C++ 文件中 #include < pthread.h > 引入即可。 该函数是一个线程阻塞函数,调用方将一直等待到线程结束为止,当函数返回时,被等待线程的资源被收回。如果执行成功,将返回 0,如果失败则返回一个错误码。 该函数参数释义如下: thread_out:线程标识符的指针,pthread_t 类型,即线程 ID(线程创建成功后会将分配的线程 ID 赋值给 thread_out) attr:它是一个结构体类型,用于表示创建线程的相关属性信息,如线程优先级、调度策略等等 start_routine:表示线程运行函数的地址 arg:代表线程运行函数的参数 看到这里,基本代表 Java 层面的线程创建路径分析完成了,想更多了解关于 pthread 内容可自行查阅资料。此外,Android 中还有一种 native 线程,即 C/C++ 平台的 Thread 特供版。 Native 线程创建 刚刚我们分析了 Java 层面的 Thread 创建流程,下面来简单看下 Android Native 层中的 Thread 是如何创建的。首先我们需要关注一下 system/core/libutils/Threads.cpp 这个文件,里面是针对 Android 平台的 native 层如何创建一个线程的相关封装。 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 status_t Thread::run(const char* name, int32_t priority, size_t stack) { Mutex::Autolock _l(mLock); if (mRunning) { // thread already started return INVALID_OPERATION; } ... mRunning = true; bool res; if (mCanCallJava) { // 创建能调用Java代码的Native线程 res = createThreadEtc(_threadLoop, this, name, priority, stack, &mThread); } else { // 创建只能调用C/C++代码的Native线程 res = androidCreateRawThreadEtc(_threadLoop, this, name, priority, stack, &mThread); } if (res == false) { return UNKNOWN_ERROR; } return NO_ERROR; } mCanCallJava 是在 Thread 对象创建时的构造参数,在构造函数中默认设置值为 true。 当 mCanCallJava 为 true 时,则代表创建的是不仅能调用 C/C++ 代码,还能调用 Java 代码的 Native 线程。 当 mCanCallJava 为 false 时,则代表创建的是只能调用 C/C++ 代码的 Native 线程。 有关两种创建线程模式的方法实现分别在 createThreadEtc 和 androidCreateRawThreadEtc 中,下面来简单分析一下它们的实现链路。 androidCreateRawThreadEtc 本方法用于创建仅允许调用 C/C++ 代码的线程。 Threads.cpp#androidCreateRawThreadEtc 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 int androidCreateRawThreadEtc(android_thread_func_t entryFunction, void *userData, const char* threadName __android_unused, int32_t threadPriority, size_t threadStackSize, android_thread_id_t *threadId) { pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); #if defined(__ANDROID__) /* valgrind is rejecting RT-priority create reqs */ if (threadPriority != PRIORITY_DEFAULT || threadName != NULL) { // Now that the pthread_t has a method to find the associated // android_thread_id_t (pid) from pthread_t, it would be possible to avoid // this trampoline in some cases as the parent could set the properties // for the child. However, there would be a race condition because the // child becomes ready immediately, and it doesn't work for the name. // prctl(PR_SET_NAME) only works for self; prctl(PR_SET_THREAD_NAME) was // proposed but not yet accepted. thread_data_t* t = new thread_data_t; t->priority = threadPriority; t->threadName = threadName ? strdup(threadName) : NULL; t->entryFunction = entryFunction; t->userData = userData; entryFunction = (android_thread_func_t)&thread_data_t::trampoline; userData = t; } #endif if (threadStackSize) { pthread_attr_setstacksize(&attr, threadStackSize); } errno = 0; pthread_t thread; int result = pthread_create(&thread, &attr, (android_pthread_entry)entryFunction, userData); pthread_attr_destroy(&attr); if (result != 0) { ALOGE("androidCreateRawThreadEtc failed (entry=%p, res=%d, %s)\n" "(android threadPriority=%d)", entryFunction, result, strerror(errno), threadPriority); return 0; } // Note that *threadID is directly available to the parent only, as it is // assigned after the child starts. Use memory barrier / lock if the child // or other threads also need access. if (threadId != nullptr) { *threadId = (android_thread_id_t)thread; // XXX: this is not portable } return 1; } 可以看到,上面代码首先做了一些线程对象的属性赋值,然后分配线程的栈空间,接着是通过 pthread 来创建一个线程,线程创建成功则返回 1。值得注意的是,这里的 entryFunction 其实就是之前代码中传进来的 _threadLoop 函数: 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 int Thread::_threadLoop(void* user) { Thread* const self = static_cast<Thread*>(user); sp<Thread> strong(self->mHoldSelf); wp<Thread> weak(strong); self->mHoldSelf.clear(); #if defined(__ANDROID__) // this is very useful for debugging with gdb self->mTid = gettid(); #endif bool first = true; do { bool result; if (first) { first = false; self->mStatus = self->readyToRun(); result = (self->mStatus == OK); if (result && !self->exitPending()) { // Binder threads (and maybe others) rely on threadLoop // running at least once after a successful ::readyToRun() // (unless, of course, the thread has already been asked to exit // at that point). // This is because threads are essentially used like this: // (new ThreadSubclass())->run(); // The caller therefore does not retain a strong reference to // the thread and the thread would simply disappear after the // successful ::readyToRun() call instead of entering the // threadLoop at least once. result = self->threadLoop(); } } else { result = self->threadLoop(); } // establish a scope for mLock { Mutex::Autolock _l(self->mLock); if (result == false || self->mExitPending) { self->mExitPending = true; self->mRunning = false; // clear thread ID so that requestExitAndWait() does not exit if // called by a new thread using the same thread ID as this one. self->mThread = thread_id_t(-1); // note that interested observers blocked in requestExitAndWait are // awoken by broadcast, but blocked on mLock until break exits scope self->mThreadExitedCondition.broadcast(); break; } } // Release our strong reference, to let a chance to the thread // to die a peaceful death. strong.clear(); // And immediately, re-acquire a strong reference for the next loop strong = weak.promote(); } while(strong != nullptr); return 0; } 通过上述代码不难发现:_threadLoop 内部会循环调用 Thread 对象的成员方法 threadLoop,该成员方法一般由派生类来自行实现。该线程将会在下面几种情况退出循环: 线程状态错误,即 mStatus != OK 线程即将退出,调用 Thread::requestExit() 时会触发 线程的强引用被释放,无法继续将弱引用提升为强引用 createThreadEtc 本方法用于创建允许调用 C/C++ 和 Java 代码的线程。 system/core/libutils/include/utils/AndroidThreads.h#createThreadEtc 1 2 3 4 5 6 7 8 9 10 11 // Create thread with lots of parameters inline bool createThreadEtc(thread_func_t entryFunction, void *userData, const char* threadName = "android:unnamed_thread", int32_t threadPriority = PRIORITY_DEFAULT, size_t threadStackSize = 0, thread_id_t *threadId = nullptr) { return androidCreateThreadEtc(entryFunction, userData, threadName, threadPriority, threadStackSize, threadId) ? true : false; } 继续跟踪 androidCreateThreadEtc 函数: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static android_create_thread_fn gCreateThreadFn = androidCreateRawThreadEtc; int androidCreateThreadEtc(android_thread_func_t entryFunction, void *userData, const char* threadName, int32_t threadPriority, size_t threadStackSize, android_thread_id_t *threadId) { return gCreateThreadFn(entryFunction, userData, threadName, threadPriority, threadStackSize, threadId); } void androidSetCreateThreadFunc(android_create_thread_fn func) { gCreateThreadFn = func; } 看到这里可以发现继续调用了 gCreateThreadFn 函数,而该函数会被调用方在其他地方赋值代理掉,所以我们需要找到调用 androidSetCreateThreadFunc 函数的地方,最终锁定在了 frameworks/base/core/jni/AndroidRuntime.cpp 这个文件: 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 /* * Register android native functions with the VM. */ /*static*/ int AndroidRuntime::startReg(JNIEnv* env) { ATRACE_NAME("RegisterAndroidNatives"); /* * This hook causes all future threads created in this process to be * attached to the JavaVM. (This needs to go away in favor of JNI * Attach calls.) */ androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc); ALOGV("--- registering native functions ---\n"); /* * Every "register" function calls one or more things that return * a local reference (e.g. FindClass). Because we haven't really * started the VM yet, they're all getting stored in the base frame * and never released. Use Push/Pop to manage the storage. */ env->PushLocalFrame(200); if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) { env->PopLocalFrame(NULL); return -1; } env->PopLocalFrame(NULL); //createJavaThread("fubar", quickTest, (void*) "hello"); return 0; } 由此看来,Android 系统会在向虚拟机中注册 native 方法时将 gCreateThreadFn 指定向 javaCreateThreadEtc 这个函数。那么我们就来看下 javaCreateThreadEtc 内部实现如何: frameworks/base/core/jni/AndroidRuntime.cpp#javaCreateThreadEtc 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 /* * This is invoked from androidCreateThreadEtc() via the callback * set with androidSetCreateThreadFunc(). * * We need to create the new thread in such a way that it gets hooked * into the VM before it really starts executing. */ /*static*/ int AndroidRuntime::javaCreateThreadEtc( android_thread_func_t entryFunction, void* userData, const char* threadName, int32_t threadPriority, size_t threadStackSize, android_thread_id_t* threadId) { void** args = (void**) malloc(3 * sizeof(void*)); // javaThreadShell must free int result; LOG_ALWAYS_FATAL_IF(threadName == nullptr, "threadName not provided to javaCreateThreadEtc"); args[0] = (void*) entryFunction; args[1] = userData; args[2] = (void*) strdup(threadName); // javaThreadShell must free result = androidCreateRawThreadEtc(AndroidRuntime::javaThreadShell, args, threadName, threadPriority, threadStackSize, threadId); return result; } 根据官方给出的注释可以知道:javaCreateThreadEtc 函数从 androidCreateThreadEtc 中通过 androidSetCreateThreadFunc 设置的回调来进行调用的,即在线程真正开始执行之前被 Hook 到虚拟机中。我们接着来看下真正实现的 androidCreateRawThreadEtc 函数: 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 int androidCreateRawThreadEtc(android_thread_func_t entryFunction, void *userData, const char* threadName __android_unused, int32_t threadPriority, size_t threadStackSize, android_thread_id_t *threadId) { pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); #if defined(__ANDROID__) /* valgrind is rejecting RT-priority create reqs */ if (threadPriority != PRIORITY_DEFAULT || threadName != NULL) { // Now that the pthread_t has a method to find the associated // android_thread_id_t (pid) from pthread_t, it would be possible to avoid // this trampoline in some cases as the parent could set the properties // for the child. However, there would be a race condition because the // child becomes ready immediately, and it doesn't work for the name. // prctl(PR_SET_NAME) only works for self; prctl(PR_SET_THREAD_NAME) was // proposed but not yet accepted. thread_data_t* t = new thread_data_t; t->priority = threadPriority; t->threadName = threadName ? strdup(threadName) : NULL; t->entryFunction = entryFunction; t->userData = userData; entryFunction = (android_thread_func_t)&thread_data_t::trampoline; userData = t; } #endif if (threadStackSize) { pthread_attr_setstacksize(&attr, threadStackSize); } errno = 0; pthread_t thread; int result = pthread_create(&thread, &attr, (android_pthread_entry)entryFunction, userData); pthread_attr_destroy(&attr); if (result != 0) { ALOGE("androidCreateRawThreadEtc failed (entry=%p, res=%d, %s)\n" "(android threadPriority=%d)", entryFunction, result, strerror(errno), threadPriority); return 0; } // Note that *threadID is directly available to the parent only, as it is // assigned after the child starts. Use memory barrier / lock if the child // or other threads also need access. if (threadId != nullptr) { *threadId = (android_thread_id_t)thread; // XXX: this is not portable } return 1; } 该函数实现在 androidCreateRawThreadEtc 章节就已经分析过了,只不过这里的 entryFunction 传过来的是 AndroidRuntime::javaThreadShell,我们来看下它内部如何实现的: frameworks/base/core/jni/AndroidRuntime.cpp 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 /* * When starting a native thread that will be visible from the VM, we * bounce through this to get the right attach/detach action. * Note that this function calls free(args) */ /*static*/ int AndroidRuntime::javaThreadShell(void* args) { void* start = ((void**)args)[0]; void* userData = ((void **)args)[1]; char* name = (char*) ((void **)args)[2]; // we own this storage free(args); JNIEnv* env; int result; /* hook us into the VM */ if (javaAttachThread(name, &env) != JNI_OK) return -1; /* start the thread running */ result = (*(android_thread_func_t)start)(userData); /* unhook us */ javaDetachThread(); free(name); return result; } javaThreadShell 函数比较重要,综合上下文不难发现,代码中的局部变量 start 代表的是 _threadLoop,userData 代表 Thread 对象,而 name 指向线程的名称。接着继续调用了 javaAttachThread 函数,用于将线程 hook 到当前进程的虚拟机中,进而执行 Java 代码。紧接着继续执行线程自身的逻辑,即调用成员函数 threadLoop;线程执行完毕后调用了 javaDetachThread 函数用于将线程从虚拟机中剥离。下面重点看下 javaAttachThread 内部是如何将线程 hook 至虚拟机的: 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 /* * Makes the current thread visible to the VM. * * The JNIEnv pointer returned is only valid for the current thread, and * thus must be tucked into thread-local storage. */ static int javaAttachThread(const char* threadName, JNIEnv** pEnv) { JavaVMAttachArgs args; JavaVM* vm; jint result; vm = AndroidRuntime::getJavaVM(); assert(vm != NULL); args.version = JNI_VERSION_1_4; args.name = (char*) threadName; args.group = NULL; result = vm->AttachCurrentThread(pEnv, (void*) &args); if (result != JNI_OK) ALOGI("NOTE: attach of thread '%s' failed\n", threadName); return result; } 可以看见 javaAttachThread 中又调用了 AttachCurrentThread 函数,该函数可以将 native 线程附加到进程的 VM 中,详细参见官方 JNI 文档:JNI 提示 接着可以在 runtime 中找到 CheckAttachThread 实现: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // Check whether the current thread is attached. This is usually required // to be the first check, as ScopedCheck needs a ScopedObjectAccess for // checking heap values (and that will fail with unattached threads). bool CheckAttachedThread(const char* function_name) { Thread* self = Thread::Current(); if (UNLIKELY(self == nullptr)) { // Need to attach this thread for a proper abort to work. We prefer this // to get reasonable stacks and environment, rather than relying on // tombstoned. JNIEnv* env; Runtime::Current()->GetJavaVM()->AttachCurrentThread(&env, /* thr_args= */ nullptr); std::string tmp = android::base::StringPrintf( "a thread (tid %" PRId64 " is making JNI calls without being attached", static_cast<int64_t>(GetTid())); Runtime::Current()->GetJavaVM()->JniAbort(function_name, tmp.c_str()); CHECK_NE(Runtime::Current()->GetJavaVM()->DetachCurrentThread(), JNI_ERR); return false; } return true; } 好吧,里面又调用了 GetJavaVM()-> AttachCurrentThread,看来我们还得继续跟下去,不过为了节省中间若干个类文件跳转环节,这里直接指出最终目的地在 Runtime::AttachCurrentThread 函数里面: art/runtime/runtime.cc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 bool Runtime::AttachCurrentThread(const char* thread_name, bool as_daemon, jobject thread_group, bool create_peer, bool should_run_callbacks) { ScopedTrace trace(__FUNCTION__); Thread* self = Thread::Attach(thread_name, as_daemon, thread_group, create_peer, should_run_callbacks); // Run ThreadGroup.add to notify the group that this thread is now started. if (self != nullptr && create_peer && !IsAotCompiler()) { ScopedObjectAccess soa(self); self->NotifyThreadGroup(soa, thread_group); } return self != nullptr; } 这里其实就是将线程在 JavaVM 层面包装成一个 VM 上的线程再返回给 self。接下来就不继续往下深究了,篇幅有限,本文先讲到这里,里面还涉及到很多 JavaVM 相关的细节及原理将在后续文章中做一个补充。 顺带提一嘴,Android SDK 中的 Thread 与 JDK 中的 java.lang.Thread 实际上是有区别的,虽然 Android 也使用 Java 语言开发,但Android 基于平台特殊性对 JDK 进行了一些删减和改造。我们都知道 Java 是具有跨平台特性的,同一套代码它完全可以在 Windows、Linux 等操作系统上正常运作,但其实内部的线程创建等细节是基于 OS 的特性进行各自实现的: 想了解更多关于 Java 线程的启动过程可以参考此文。 总结 我们通过在 Java 层面创建线程的时候,VM 会包装成一个 VM 的 Thread,然后启动 pthread,再调用 run 方法,但我们通过 pthread 独立创建的线程,是没有和 VM 里面的线程对象建立关联的,VM 压根不知道它的存在。但是如果该线程想要访问 Java代码,这就得需 VM 帮忙,故而需要将 native 层自己创建的线程包装成一个 VM 层面的 Thread 对象,然后添加到 VM 的 thread 集合中去,如此一来,Android 的虚拟机就可以感知到 native 中这个线程对象的存在了。 相关参考 runtime/native/java_lang_Thread.cc system/core/libutils/Threads.cpp system/core/libutils/include/utils/Thread.h Android native/C++层Thread线程实现源码分析 JNI为什么要调用AttachCurrentThread

2023/4/7
articleCard.readMore

简单聊聊 ChatGPT

关于国内如何注册和使用 ChatGPT 可以参考这篇文章:国内注册一个属于自己的免费ChatGPT账号。本文不是扫盲贴,只是通过这两天对 ChatGPT 的使用体验来随便聊聊。 首先,对于他所具备的能力想必大家已经心照不宣了,从它(ChatGPT 3.0)问世以来的各项 DAU、MAU 数据就可以知道其火爆程度了。它的出现及发展代表着自然语言处理提升到了新的高度,甚至有人称其为第三次工业革命的前兆。当然,目前大家对于 ChatGPT 的赞美之词是”甚嚣尘上“,其中可能夹杂着部分夸大之词,但不可否认的是: AI 即将步入寻常百姓家。 ChatGPT 能为开发做什么 说了这么多,那么对于我们普通开发人员来说,它的出现意味着什么呢?于我而言,它其实是一位可敬的对手,也是一位可靠的伙伴。 例如,我想找出 LeetCode 上排名 Hot 100 的题目,不出三十秒,ChatGPT 就能为我列举出来所有题目,比我们自己找题要节省很多时间。 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 Q:请整理出Leetcode hot100的题目 A:(From ChatGPT) 1. Two Sum 2. Add Two Numbers 3. Longest Substring Without Repeating Characters 4. Median of Two Sorted Arrays 5. Longest Palindromic Substring 6. ZigZag Conversion 7. Reverse Integer 8. String to Integer (atoi) 9. Palindrome Number 10. Regular Expression Matching 11. Container With Most Water 12. Integer to Roman 13. Roman to Integer 14. 3Sum 15. 3Sum Closest 16. Letter Combinations of a Phone Number 17. 4Sum 18. Remove Nth Node From End of List 19. Valid Parentheses 20. Merge Two Sorted Lists 21. Generate Parentheses 22. Merge k Sorted Lists 23. Swap Nodes in Pairs 24. Group Anagrams 25. Reverse Nodes in k-Group 26. Remove Duplicates from Sorted Array 27. Search Insert Position 28. Maximum Subarray 29. Length of Last Word 30. Climbing Stairs 31. Best Time to Buy and Sell Stock 32. Same Tree 33. Symmetric Tree 34. Maximum Depth of Binary Tree 35. Count and Say 36. Construct Binary Tree from Preorder and Inorder Traversal 37. Binary Tree Level Order Traversal 38. Maximum Depth of Binary Tree 39. Binary Tree Zigzag Level Order Traversal 40. Path Sum 41. Path Sum II 42. Sum Root to Leaf Numbers 43. Flatten Binary Tree to Linked List 44. Minimum Depth of Binary Tree 45. Balanced Binary Tree 46. Convert Sorted Array to Binary Search Tree 47. Unique Binary Search Trees 48. Unique Binary Search Trees II 49. Recover Binary Search Tree 50. Populating Next Right Pointers in Each Node 51. Populating Next Right Pointers in Each Node II 52. Sum Root to Leaf Numbers 53. Convert Sorted List to Binary Search Tree 54. Insert Interval 55. Merge Intervals 56. Longest Consecutive Sequence 57. LRU Cache 58. Gas Station 59. Jump Game 60. Jump Game II 61. Spiral Matrix 62. Permutations 63. Permutations II 64. Rotate Image 65. N-Queens 66. N-Queens II 67. Combinations 68. Combinations II 69. Minimum Path Sum 70. Unique Paths 71. Unique Paths II 72. Word Search 73. Restore IP Addresses 74. Evaluate Reverse Polish Notation 75. Sort Colors 76. Subsets 77. Subsets II 78. Scramble String 79. Partition Equal Subset Sum 80. Search a 2D Matrix 81. Search a 2D Matrix II 82. Remove Duplicates from Sorted List 83. Remove Duplicates from Sorted List II 84. Wildcard Matching 85. Edit Distance 86. Sum of Left Leaves 87. Find All Anagrams in a String 88. Remove Invalid Parentheses 89. Word Ladder 90. Sliding Window Maximum 91. Kth Largest Element in an Array 92. LRU Cache 93. Number of Islands 94. Verify Preorder Sequence in Binary Search Tree 95. Decode String 96. Sum of Two Integers 97. Design TinyURL 98. Maximum Product Subarray 99. Best Time to Buy and Sell Stock with Cooldown 100. Unique Binary Search Trees III 或者,我还可以让他模拟面试官的身份与我对话(场景模拟),考察我岗位职责内需要掌握的知识点,查漏补缺,协助我在后面的真实面试过程中取得好成绩。 上图来源于网络,仅做举例展示,侵权删。 又或者,我们在编码时经常会遇到各种各样的问题,ChatGPT 完全有能力可以协助我们做一些简单的编码、单元测试、函数功能检测等工作。前几天我遇到一个问题,一个两年前的项目中有一个功能函数(从 /proc/stat 中获取 CPU 数据)在高版本的机器上无法正常运行,抛出了异常,最终通过 ChatGPT 两分钟就排查清楚问题出现的原因。下面来展示一下 ChatGPT 与我们的对话过程: 请帮我检查一下下面的函数存在什么问题? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private fun readProcFile(file: String): Array<String>? { var procFile: RandomAccessFile? = null var procFileContents: String return try { procFile = RandomAccessFile(file, "r") procFileContents = procFile.readLine() val rightIndex = procFileContents.indexOf(")") if (rightIndex > 0) { procFileContents = procFileContents.substring(rightIndex + 2) } procFileContents.split(" ").toTypedArray() } catch (ioe: IOException) { ioe.printStackTrace() return null } finally { procFile?.close() } } Answer: 1 2 3 4 1. 没有处理读取文件可能抛出的异常; 2. 没有检查读取到的字符串是否为空; 3. 没有处理RandomAccessFile在关闭时可能抛出的异常; 4. 没有判断右括号 ')' 的位置是否合法,以防止越界访问。 上面函数的作用是什么? Answer: 1 这个函数的作用是从给定文件中读取内容,并根据空格分割成字符串数组返回。 一般用来获取什么? Answer: 1 一般用来获取系统信息,比如文件状态、进程信息等。 如果获取的 /proc/stat 虚拟文件信息,会有什么问题吗? Answer: 1 如果获取的是 /proc/stat 虚拟文件信息,那么可能会出现越界访问的问题,因为procFileContents.indexOf(")")的结果可能会大于procFileContents的长度。 在Android系统中会有版本兼容问题吗? Answer: 1 在Android系统中如果用到 /proc/stat 虚拟文件信息,可能会出现版本兼容问题,因为/proc/stat 虚拟文件的格式在不同的Android版本中可能会有所不同。 比如说呢? Answer: 1 2 比如:Android 8.0 之前的版本中,/proc/stat 虚拟文件的格式是:cpu user nice system idle iowait irq softirq ... 而Android 8.0 及以后的版本中,/proc/stat 虚拟文件的格式改变了:cpu user nice system idle iowait irq softirq steal guest guest_nice ... 那么 /proc/stat 文件访问权限有变化吗? Answer: 1 /proc/stat 文件的访问权限在Android 8.0 之前的版本中是公开的,任何应用都可以访问,但从Android 8.0 开始,/proc/stat 文件的访问权限仅限于系统应用。 自始至终,我们通过 7 轮问答最终得到了想要的答案。然而事实上,如果我们在向 ChatGPT 提问时能够尽可能描述清楚问题,提供足够多的现场信息,则能够大幅度缩短获取我们所期望答案的时间。上述问题在经过优化好的话术,例如开头给出问题发生的背景是在 Android 手机上,Android 6.0 执行没有问题等等,新一轮回答只经过 3 轮就得到了我们期望的结果。由此可见,“提问的艺术”对于 ChatGPT 来说也是至关重要的,它能够左右我们距离最终结果的长短以及结果的质量。 在 ChatGPT 的帮助下,单元测试将变得简单易得,收益率大幅提高。为什么这么说呢?实际上,据笔者统计,目前开发人员中日常编写单元测试的占比不到 30%,原因大抵是浪费时间,出力不讨好,不在公司绩效考察范围内等。而根据软件工程长期的历史数据统计和测试经验来看,单元测试却能帮我们解决 80% 的编码缺陷。单元测试重要性由此可见。我们让 ChatGPT 为上述代码编写单元测试: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // Q:请帮我为优化后的代码编写单元测试? // A:单元测试代码如下: @Test fun testReadProcFile() { val fileName = "/proc/stat" // 测试文件存在 val contents = readProcFile(fileName) assertNotNull(contents) // 测试文件不存在 val notExistsFileName = "/not/exists/file" val notExistsContents = readProcFile(notExistsFileName) assertNull(notExistsContents) } 自此,在 ChatGPT 的加持下,单元测试成本大幅度降低,开发人员也不用被繁琐的测试用例所缠身,只需要丢给 ChatGPT 去完成就好了,它有着得天独厚的优势。 ChatGPT 未来趋势 对于我们开发者而言,除以上能力以外,ChatGPT 还可以在源码阅读、API 文档生成、文档翻译、代码补全&纠错、协作编程等方面为我们提供帮助,能够显著提高我们的工作和学习效率。笔者预测,未来 ChatGPT 势必会走入寻常百姓家,每个人都会感受到 AI 带给我们的变化,行业可能会重新洗牌。以后每个人的身边势必会有一个私人定制的 “ChatGPT” 与我们形影不离,不论是开发者、医生、司机、金融工作者、学生还是老师,每个人都会享受 AI 的便利。 我们能做什么? 作为一名普通人,不要盲目的抵制,也无需毫无保留的拥抱。对技术心存敬畏,虚心向学,也要保持距离。AI 可以辅助我们走向辉煌,也可以引领我们走向灭亡,我们需要提防过度依赖 AI,避免基本语法能力、编程能力和动手能力的缺失,不要在安逸中停止了思考。毕竟,AI 的产生就是为了服务于人,倘若有一天你不能从 ta 身上感受到积极的变化或无法占据主导地位,那就疏远 ta 吧。

2023/3/29
articleCard.readMore

Android系统中关于/proc目录的点滴

Android 系统有着这么一个神奇的“文件“目录存放着 CPU 及设备所运行进程的相关数据,这对于我们从事 APM 应用性能监控有着莫大的帮助。当然这种监控应用及手机系统信息的方式并非无中生有,早在 Android Framework 中就已经涉及它的踪迹,比如 ANR 发生时,系统会去 dump 及上报打印相关 CPU、进程、线程等信息到 log 中。而这些信息是借助 ProcessCpuTracker 这个类来实现获取的,该类内部正是通过读取系统 /proc/ 目录的相关“文件”来提取和转化的。本文的重点当然不是去分析 ANR 如何产生,主要来看下 proc/ 的庐山真面目。 电脑连接上真机/模拟器后进入 adb shell 下,查看 /proc 目录中都有哪些文件: 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 OnePlus7T:/ $ ls /proc 1 1513 245 3099 440 551 752 965 10 1515 24501 31 441 552 753 969 100 1519 2456 310 442 553 754 97 1001 152 2457 311 444 554 755 974 1002 1522 246 31161 446 555 756 976 1003 1524 24638 3117 448 556 76 9777 10031 153 24640 312 45 558 7624 978 1006 1531 24658 31226 450 559 766 98 1008 15420 24680 313 451 56 767 9802 1009 1545 24695 314 452 560 768 981 101 1546 247 315 453 5610 769 983 1010 15537 24723 3151 454 57 77 9842 1011 1555 24755 317 455 571 770 985 1013 1556 24783 3175 456 572 771 988 1014 1558 248 3184 457 573 7712 9891 1019 15608 2488 319 458 574 772 99 102 15756 249 32 4581 575 773 990 1026 15821 2495 3224 459 576 774 991 103 16 24956 3237 4592 577 775 9915 1033 16005 2497 325 46 578 776 992 1036 1602 2499 326 460 579 777 993 104 16167 25 329 461 58 778 994 1040 1659 250 33 462 580 779 995 1045 1665 2500 330 4626 581 78 996 1046 1671 2502 331 463 582 780 997 1047 16726 2504 332 464 583 7801 OIS 105 1676 251 3331 465 584 7802 adj_chain_stat 1050 1682 2511 34 466 585 7803 adj_chain_verify 1051 1695 2512 340 4660 586 7806 asound 1054 17 2513 342 467 5860 781 ath_pktlog 1056 17180 2514 343 468 587 782 battery_exist 106 17181 25163 344 469 588 783 bootloader_log 1064 17230 252 345 47 589 784 brightness_for_sensor 1065 1727 2520 346 4703 59 785 buddyinfo 1068 1759 25203 347 4736 590 786 bus 1073 18 25248 348 4758 591 787 cc_ctl 1076 18153 253 349 476 592 788 cgroups 108 18220 254 35 477 593 789 cld 1084 1859 25401 350 4781 594 790 cmdline 1085 1860 2542 351 479 595 7903 config.gz 1086 18641 25478 352 48 596 791 consoles 1087 19 25479 353 480 597 792 cpuinfo 1089 190 25480 354 4801 598 793 crypto 109 191 25481 355 481 599 794 dash_3800_4p45_exit 1090 192 25483 356 482 600 795 dc_for_sensor 11 193 25484 357 4821 601 796 debugdriver 1104 194 25497 358 483 602 797 device-tree 1114 19440 25498 359 484 603 798 devices 1115 195 25499 360 485 604 799 diskstats 1116 196 255 361 4851 605 7998 driver 1117 19639 25500 362 486 606 8 enhance_dash 1120 197 25509 363 487 607 800 execdomains 1123 19725 2554 364 4899 608 8001 fb 1127 19760 25559 365 49 609 801 fg_info 1146 19761 256 366 490 61 8010 filesystems 1147 19763 25630 367 491 610 802 fresh_rate_for_sensor 1148 19764 257 368 4912 611 803 fs 1150 198 258 369 492 612 8038 fsc_allow_list 1153 19822 25933 37 493 613 804 fsc_dump 1156 19844 26 370 494 614 805 ht_group 1159 19845 260 371 495 615 806 ht_report 1160 199 26113 372 4954 617 807 interrupts 1162 19915 26114 373 496 618 808 iolimit_enable 12 2 26121 374 497 62 809 iomem 1206 200 26126 375 498 620 8113 ioports 1208 201 26131 376 499 623 8118 irq 12280 202 262 3769 5 63 8131 kallsyms 12348 203 26233 377 50 634 8133 key-users 1241 204 26246 3771 500 635 8146 keys 1243 205 26256 3773 501 636 8160 kmsg 1250 206 26277 3777 502 637 82 kpagecgroup 1251 20607 2635 378 5024 638 821 kpagecount 1254 2064 264 379 503 639 822 kpageflags 1258 2065 26440 38 504 64 8263 loadavg 1259 2066 266 380 5043 640 828 locks 12624 2067 26639 381 505 641 829 meminfo 1269 208 26765 382 506 642 83 misc 1290 209 26862 384 507 643 830 modules 1292 21 269 385 508 644 831 mounts 1297 211 27 386 509 645 84 net 13 21169 270 387 51 646 85 oneplus_healthinfo 1300 212 27066 388 510 647 859 pagetypeinfo 1301 21259 27088 389 511 65 86 partitions 1302 213 271 39 512 653 868 power 1303 214 27115 390 513 654 87 pressure 1307 215 27196 391 514 66 8720 qti_haptic 1311 216 272 392 515 664 876 qti_haptic_rf 13150 217 27220 393 516 665 877 restart_level_all 1316 218 27221 394 517 666 878 rf_cable_config 13174 219 27222 395 518 667 879 rf_factory_mode 1321 22 27237 396 519 668 88 sched_debug 1322 220 27278 397 520 6698 8880 schedstat 1324 221 273 398 521 67 89 scsi 1329 22175 274 399 522 69 9 self 1335 222 275 40 523 6915 90 ship_mode 1337 22277 27515 400 524 699 91 slabinfo 1345 223 276 401 525 7 915 softirqs 1346 22335 27657 403 528 70 917 stat 1348 224 277 404 529 700 919 swaps 1349 225 2771 405 53 701 92 sys 13500 226 278 406 530 71 920 sysrq-trigger 1358 227 279 407 531 714 925 thread-self 13583 228 280 408 532 718 927 timer_list 13601 22824 281 409 5321 719 929 touchpanel 13626 2286 2815 41 533 72 93 tty 1364 2287 282 410 534 720 932 tzdbg 13784 229 283 411 535 721 935 uid 13836 23 284 4177 536 722 9356 uid_concurrent_active_time 14 230 28433 42 537 7235 937 uid_concurrent_policy_time 14002 231 285 421 538 724 939 uid_cputime 14003 232 28504 422 539 725 940 uid_io 1412 233 286 423 54 726 942 uid_lru_info 1422 234 2873 424 540 727 945 uid_procstat 14327 23498 29 426 541 728 946 uid_time_in_state 1443 235 2935 427 542 729 948 ultrasound 1463 236 3 428 5429 73 949 uptime 1468 23695 30 43 543 732 95 version 1478 237 30111 430 544 733 951 vmallocinfo 14828 238 30175 431 545 734 952 vmstat 14851 239 30176 432 5456 735 953 warp_chg_exit 1493 24 30178 433 546 736 954 zoneinfo 1498 240 30180 434 5464 738 955 15 24056 30181 435 547 74 956 150 241 3035 436 548 740 958 1501 242 3084 437 549 743 96 151 243 309 438 55 744 962 1510 244 3095 439 550 75 963 下面来介绍以上各文件的作用,不重要的部分一笔带过。 /proc/cpuinfo 在Linux中,/proc/cpuinfo 是一个特殊的文件(虚拟文件系统),它提供了有关计算机处理器(CPU)的详细信息。通过读取 /proc/cpuinfo,可以访问关于 CPU 的诸多信息,包括CPU类型、内核版本、处理器数量、缓存大小、每秒钟取样数(MHz)等。 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 OnePlus7T:/ $ cat /proc/cpuinfo Processor : AArch64 Processor rev 14 (aarch64) processor : 0 BogoMIPS : 38.40 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp CPU implementer : 0x51 CPU architecture: 8 CPU variant : 0xd CPU part : 0x805 CPU revision : 14 processor : 1 BogoMIPS : 38.40 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp CPU implementer : 0x51 CPU architecture: 8 CPU variant : 0xd CPU part : 0x805 CPU revision : 14 processor : 2 BogoMIPS : 38.40 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp CPU implementer : 0x51 CPU architecture: 8 CPU variant : 0xd CPU part : 0x805 CPU revision : 14 processor : 3 BogoMIPS : 38.40 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp CPU implementer : 0x51 CPU architecture: 8 CPU variant : 0xd CPU part : 0x805 CPU revision : 14 processor : 4 BogoMIPS : 38.40 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp CPU implementer : 0x51 CPU architecture: 8 CPU variant : 0xd CPU part : 0x804 CPU revision : 14 processor : 5 BogoMIPS : 38.40 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp CPU implementer : 0x51 CPU architecture: 8 CPU variant : 0xd CPU part : 0x804 CPU revision : 14 processor : 6 BogoMIPS : 38.40 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp CPU implementer : 0x51 CPU architecture: 8 CPU variant : 0xd CPU part : 0x804 CPU revision : 14 processor : 7 BogoMIPS : 38.40 Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp CPU implementer : 0x51 CPU architecture: 8 CPU variant : 0xd CPU part : 0x804 CPU revision : 14 Hardware : Qualcomm Technologies, Inc SM8150 这里列出部分字段的解释: physical id:指的是物理封装的处理器的 id。 cpu cores:位于相同物理封装的处理器中的内核数量。 core id:每个内核的 id。 siblings:位于相同物理封装的处理器中的逻辑处理器的数量。 processor:逻辑处理器的 id。 /proc/stat /proc/stat 文件包含了所有 CPU 活动信息,该文件中的所有数据都是从系统启动开始累计到当前时刻。控制台通过 cat 命令将 /proc/stat 文件内容输出,其格式如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 OnePlus7T:/ $ cat /proc/stat cpu 25196598 1781900 22637410 42674331 57257 3187041 1268642 0 0 0 cpu0 3045634 384571 4278540 42644750 57213 791586 332762 0 0 0 cpu1 3040754 313257 4314010 3642 7 685228 300228 0 0 0 cpu2 2977001 292313 4281154 3652 8 652591 300217 0 0 0 cpu3 2942390 291336 4235718 3600 11 685009 270543 0 0 0 cpu4 4104628 125478 1744377 4560 2 120407 20116 0 0 0 cpu5 4019170 135445 1764951 4600 3 122060 19895 0 0 0 cpu6 3965905 128302 1770572 4566 8 121797 19330 0 0 0 cpu7 1101112 111194 248083 4959 1 8360 5548 0 0 0 intr 2542250650 0 0 0 0 393590413 0 17705983 0 34065007 0 0 0 0 445 591 0 164 0 2 0 1085 0 2 0 2 236 4 0 2 622 0 0 4013553 3744782 0 589966 0 0 0 0 0 0 0 0 0 0 0 135 0 0 0 0 210 0 0 0 0 252 0 0 0 41144 0 0 0 0 0 0 1575043 0 90 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 20281 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 22222933 13 2 71516113 230460 0 0 25 0 0 0 0 0 0 0 1872 637 11 11 0 43 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 176 4850 264 20441484 10090896 483505 0 0 0 0 0 8973265 6918507 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 6954 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 98 0 7564 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 239 0 0 0 2 0 0 0 0 0 0 0 4626736 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 4 5991 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 67030 455905 231 0 0 114153 8800628 444 56578741 0 0 0 6801531 0 23 0 0 19240988 0 0 0 0 0 0 0 0 0 0 0 0 996 1 0 1 0 0 1 0 1 0 0 1 0 1 0 1 0 1 0 0 0 2916 191126 0 10172 0 0 6803 4771 28 11 35 16 0 0 1891 0 0 0 0 0 0 0 0 1024007 0 0 0 402 0 0 0 0 0 0 0 0 0 0 0 1968 0 0 0 0 0 0 0 1292 141 144 2 104 0 52 2116 0 0 0 0 0 0 0 0 0 106 0 0 104 0 0 32514 16248 0 11857 2469 0 0 0 301093 43292230 18085331 9583240 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 95751 0 0 887447 ctxt 3608255233 btime 1676699925 processes 4168399 procs_running 1 procs_blocked 0 softirq 384813624 71797252 88382100 284662 37341924 21544982 0 10713335 82903892 0 71845477 第一行表示 CPU 总的使用情况,可以看到一共有 10 项数据,单位为 jiffies,它是内核中的一个全局变量,用来记录系统启动以来产生的节拍数,在 Linux 中,一个节拍大致可以理解为操作系统进程调度的最小时间片,不同的 Linux 系统内核这个值可能不同,通常在 1ms 到 10ms 之间。Linux 官方文档是这么解释的: 时间量,以 USER_HZ 为单位测量的(在大多数架构上为 1/100 秒,使用 sysconf(_SC_CLK_TCK) 获取正确的值),用于表示系统在各种状态下所花费的时间。 下面一一解读各项数据含义(字段解释以第一行数据为例)。 CPU user nice system idle iowait irq softirq steal guest guest_nice cpu 25196598 1781900 22637410 42674331 57257 3187041 1268642 0 0 0 cpu0 3045634 384571 4278540 42644750 57213 791586 332762 0 0 0 cpu1 3040754 313257 4314010 3642 7 685228 300228 0 0 0 user [25196598]:从系统启动开始累积到当前时刻,处于用户态的运行时间,不包含 nice 值为负的进程。 nice [1781900]:从系统启动开始累积到当前时刻,CPU 上 nice 值不大于 0 的用户态任务的运行时间,即代表低优先级进程的运行时间。 system [22637410]:从系统启动开始累积到当前时刻,处于内核态的运行时间。主要包括用户态任务系统调用、异常等陷入内核消耗时间,也包括内核线程消耗的时间, 但不包含中断和软中断的执行时间。 idle [42674331]:从系统启动开始累积到当前时刻,处于 idle 任务的时间,即除 IO 阻塞等待时间以外的其他等待时间。 iowait [57257]:从系统启动开始累积到当前时刻,由于 CPU 上 IO 阻塞等待导致 CPU 无可运行任务、处于 idle 的时间。需要强调的是, iowait 是在 CPU 处于 idle 状态下的一种特殊情况的时间,与上面的 idle 数据互补构成了 CPU 上真正处于 idle 的时间。 irq [3187041]:系统启动开始累积到当前时刻,硬中断时间。 softirq [1268642]:系统启动开始累积到当前时刻,CPU处理软中断的时间,包括 softirqd 中处理软中断的时间。 steal [0]:被盗时间,即在其他操作系统上的虚拟化环境中运行时花费的时间。 guest [0]:在 Linux 内核的控制下为客户操作系统运行虚拟 CPU 所花费的时间。 guest_nice [0]:同上,不做了解。 基于以上数据,可以得到 CPU 总运行时间为: totalCPUTime = user + nice + system + idle + iowait + irq + softirq + stealstolen + guest 关于其他数据字段的解释: intr:各个中断在所有 CPU 上发生的次数总和。 ctxt:对各个 CPU 进行遍历,将各个 CPU 上 cpu_rq(cpu) -> nr_switches 字段进行累加,即每个 CPU 上任务切换的次数总和。 btime:从 Epoch (自1970零时) 开始到系统启动所经过的时长,在内核中由 boottime.tv_sec 这个变量表示,单位秒。 processes:系统启动以来成功 fork() 的次数,由 total_forks 变量记录。 procs_running:系统中各个 CPU 上处于就绪/运行状态任务数量的总和,每个 CPU 上处于就绪/运行状态任务个数由 cpu_rq(cpu)->nr_running 表示。 procs_blocked:系统中各个 CPU 上处于 iowait 状态任务数量的总和,每个 CPU 上处于 iowait 状态的任务数量由 cpu_rq(cpu)->nr_iowait 记录。 softirq:与 intr 字段类似,表示各个软中断发生的总次数。 /proc/[pid]/stat 这个格式与上面的 /proc/stat 类似,猜测可能与具体进程的 CPU 信息有关,不多说,直接终端 cat 下看看具体的数据格式: 1 2 OnePlus7T:/ $ cat /proc/969/stat 969 (nfc@1.2-service) S 1 969 0 0 -1 1077952768 1080 0 27 0 1 9 0 0 20 0 1 0 3056 12650360832 346 18446744073709551615 1 1 0 0 0 0 0 0 1073775864 0 0 0 17 4 0 0 0 0 0 0 0 0 0 0 0 0 0 可以看到这个文件中涉及到的字段比较多,根据 man-pages-proc 可以查到这个文件用于记录进程的运行状态信息,具体字段含义如下: pid [969]:即进程的 ID。 comm [(nfc@1.2-service)]:可执行文件的文件名,被包含在小括号中。即应用程序或命令的名字。 state [S]:表示任务的状态。目前已知的主要状态有: R:即 Running,表示运行中。 S:即 Sleeping,表示休眠状态(在可中断的等待中)。 D:即等待不间断磁盘睡眠。 T:即 Stopped,表示任务停止。 X:即 Dead,任务消亡。 其他的诸如 t(Tracing stop)、W(Waking)、K(Wake kill)、P(Parked)等可以参考官方文档说明。 ppid [1]:父进程 ID。 pgrp [969]:进程的组 ID。 session [0]:进程的会话 ID。 tty_nr [0]:当前进程控制tty终端设备号。 tpgid [-1]:进程控制终端的前台进程组 ID。 flags [1077952768]:进程的内核标志字。 位含义参见Linux内核源文件 include/linux/sched.h 中的 PF_* 定义。 minflt [1080]:该任务不需要从硬盘拷数据而发生的缺页(次缺页)的次数。 cminflt [0]:当前进程等待子进程的次级缺页数。 majflt [27]:主缺页中断的次数,需要从磁盘加载内存页,比如map文件。 cmajflt [0]:当前进程等待子进程的主缺页次数。 utime [1]:该进程处于用户态的时间,单位 jiffies。 stime [9]:该进程处于内核态的运行时间,单位 jiffies。 cutime [0]:该进程等待子进程已在用户模式下运行的时间,单位 jiffies。 cstime [0]:该进程等待子进程已在内核态下运行的时间,单位 jiffies。 priority [20]:进程的动态优先级。 nice [0]:进程的静态优先级。取值范围:[-20, 19]。 num_threads [1]:表示进程正在运行的线程数。它包括主线程和任何附加线程。 itrealvalue [0]:每次用户空间进程被计划运行时,虚拟电量都会减少一个jiffy。当虚拟时钟计数减少到0时,内核会向进程发送一个定时器信号,用于执行计时器回调函数或执行某些其他操作。这个字段通常在实时应用程序和计时器相关的应用程序中使用,以确保任务按时完成。 starttime [3056]:指进程启动时刻的系统启动时间(开机时间)以来的时钟周期数。该字段单位为jiffies,因此需要除以系统的时钟频率才能转换为秒。可以使用 sysconf(_SC_CLK_TCK) 函数获取系统时钟频率。通常,starttime 和 /proc/uptime 文件中的当前系统启动时间(以秒为单位)一起使用,以确定进程启动时间。 vsize [12650360832]:指的是进程的虚拟内存大小,单位是字节。它表示了进程使用的虚拟地址空间的大小,包括进程使用的代码、库、堆、栈等空间。与进程的实际物理内存使用量(即RSS或Resident Set Size)不同,vsize 表示的是进程可以访问的地址空间的大小,而不是实际分配的内存空间,因此它往往会比进程的RSS大得多。vsize 字段可以帮助开发者和系统管理员监控和调整进程在虚拟内存和内存使用方面的性能优化。 rss [346]:指进程使用的物理内存大小,单位是页面数。它表示了进程当前实际使用的物理内存大小,通常会比进程的虚拟内存大小(即 vsize )小得多。 rsslim [18446744073709551615]:指进程的物理内存使用限制,以KB为单位。该值是通过使用 setrlimit() 函数或其等效方法设置的,以确保进程不会超出指定的内存使用限制。 startcode [1]:指进程的代码段开始位置的虚拟地址。它代表了代码段在进程虚拟地址空间中的起始位置,也就是代码段在进程内存地址映射的第一个字节的虚拟地址。 endcode [1]:即代码段在进程内存地址映射的最后一个字节的虚拟地址。 startstack [0]:指进程的栈的开始地址的虚拟地址。它代表了进程栈的起始位置,在进程的虚拟地址空间中的位置。栈是用于存储函数调用和本地变量的一种内存结构,它在进程内存地址映射中是一个单独的片段。 kstkesp [0]:kstkesp 字段保存了当前的内核栈指针位置,用于跟踪进程在内核中的执行状态和堆栈使用情况。 signal [0 0]:这列包含了两个数字。第一个数字表示进程挂起的当前信号编号。第二个数字表示进程挂起的自述信号计数器,即自进程启动以来,进程接受到的信号数量。 如果没有挂起任何信号,则此列为零。 blocked [0]:表示当前进程被阻塞的信号掩码。这个值是一个等于或大于零的整数。如果某个信号比特位被设置为1,则表示该信号被阻塞。 如果该位置为0,则表示该信号没有被阻塞。 …… 通过以下公式可以求出进程占用 CPU 的时间: processCPUTime = utime + stime + cutime + cstime /proc/[pid]/task/[tid]/stat 在Linux中,/proc/[pid]/task/[tid]/stat 文件用于查询线程的状态信息。在这个路径中,pid 表示进程ID,tid 表示线程ID。 该文件的内容格式与 /proc/[pid]/stat 类似,但是显示的是针对特定线程的系统状态信息,包括: pid:线程的进程ID tcomm:线程命令名(包括命令行参数,用括号包括) state:线程状态 ppid:线程的父进程ID pgid:线程所在进程组的ID sid:线程所在会话的ID tty_nr:线程所在的终端设备的ID tpgid:线程组ID(通常等于进程ID) flags:线程标志(以数字形式表示的位掩码) minflt:为了分配未映射页的次数(分页错误次数,在内存分配时) cminflt:作为子任务时分配未映射页的次数 majflt:为了分配映射页的次数(缺页错误次数,在内存分配和交换中) cmajflt:作为子任务时分配映射页的次数 utime:以时钟周期为单位的用户态运行时间 stime:以时钟周期为单位的内核态运行时间 cutime:作为子任务的用户态运行时间总和 cstime:作为子任务的内核态运行时间总和 priority:线程当前调度优先级 nice:线程的nice值(优先级的偏移量) num_threads:同属于同一进程的任务数量 itrealvalue:剩余时间片量。当进程超过了它的时间片,它会在一个固定的时间间隔之后被调度并获得新的时间片。这个值是一个到下一次调度为止剩余的时间 starttime:从系统启动到当前线程启动的时间 vsize:进程虚拟内存大小 rss:常驻内存集大小(以页面为单位) rsslim:常驻内存集大小的限制,以字节为单位 线程的 CPU 使用时间可以用下面公式来计算: threadCPUTime = utime + stime /proc/loadavg /proc/loadavg 是一个特殊的文件(虚拟文件系统),它提供了有关系统平均负载的信息。该文件中包含了关于最近1分钟、5分钟和15分钟内系统中运行的平均进程数量,以及正在运行的、等待CPU资源的和被阻止的进程数目。 /proc/loadavg 的文件的格式如下: 1 2 Copy code 0.09 0.20 0.22 1/136 10206 其中,前三个数字分别是最近1分钟、5分钟和15分钟内系统的平均负载。接下来的“1/136”表示当前运行进程/系统总进程数,最后一个数字是最近进程ID。 使用 /proc/loadavg 可以帮助管理员了解系统的负载情况,并通过相应的调整提高系统的性能。如果系统的平均负载太高,可能需要检查哪些进程或程序占用了 CPU 或内存资源,或者是否需要考虑增加硬件资源。 /proc/[pid]/sched 该“文件”提供了关于指定进程的调度器统计信息。 “/proc/[pid]/sched” 文件可以被用来监视和调整 Linux 进程的各种行为。它提供了关于进程调度器相关的信息,其中包括进程的状态、运行时间、调度策略以及调度优先级等数据。具体来说,该文件中包含以下信息: se.exec_start:进程实际开始执行时的时间戳 se.sum_exec_runtime:进程的累计执行时间统计值(单位为纳秒) se.statistics.wait_start:进程等待调度的开始时间戳 se.statistics.wait_max:进程等待调度的最长时间(单位为纳秒) se.statistics.wait_count:进程等待次数 se.nr_migrations:进程移动到另一个 CPU 上的次数 se.nr_voluntary_switches:进程主动放弃 CPU 执行权的次数 se.nr_involuntary_switches:进程被迫放弃 CPU 执行权的次数 通过读取 /proc/[pid]/sched 文件中的信息,可以精确地了解指定进程的调度器相关行为信息,帮助开发者调试进程问题和运维人员监控系统性能,以优化系统行为。需要注意的是,/proc/[pid]/sched 文件只能被root用户访问。 其他常见文件 在 Linux 系统中,/proc/ 目录下还有很多其他的特殊文件,这些文件可以提供关于系统内核及各进程运行时的详细信息。以下是一些常见的文件及其作用: /proc/meminfo:获取内存使用情况 /proc/mounts:获取当前正在使用的文件系统 /proc/net/dev:获取网络设备的状态信息 /proc/net/tcp:获取 TCP/IP 协议统计信息 /proc/net/udp:获取 UDP 协议统计信息 /proc/version:获取内核版本信息和系统架构信息 /proc/sys/kernel/hostname:获取主机名 /proc/sys/kernel/sem:获取当前的 IPC 信息 /proc/sys/fs/file-nr:获取文件描述符状态信息 /proc/sys/fs/nr_open:获取打开文件的最大数目 这些文件可以作为系统管理员和开发者了解系统运行状态和调优的有力工具,也可以被用来分析和诊断一些问题。同时,需要注意的是访问这些文件可能需要具有相应权限,且在使用时应特别小心,防止误操作。 版本兼容问题 值得注意的是,Android 8.0 开始,Google 开始限制应用访问 /proc/stat 等文件,只有系统应用方可访问,当前应用访问时会提示权限错误,具体参考 Google 的 issue tracker 描述:https://issuetracker.google.com/issues/37140047。针对 Android 8.0 开始的版本,如果想获取 CPU 运行数据可以参考 /sys/devices/system/cpu/ 目录下的信息,它提供了CPU的一些详细配置及活动信息,如 CPU 最小、最大频率、CPU 各频率活动时间、CPU Idle累计时间等。 参考 Linux Man Paegs Of proc Android 高版本采集系统CPU使用率的方式 Thanks Microsoft ChatGpt

2023/3/28
articleCard.readMore

把数组排成最小的数

本文是对于 常用排序算法合集 的扩展应用。 题目内容 把数组排成最小的数。要求如下: 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 附加说明: 0 < nums.length <= 100 输出结果可能非常大,所以你需要返回一个字符串而不是整数 拼接起来的数字可能会有前导 0,最后结果不需要去掉前导 0 题意分析 首先写几条测试用例来带入实际场景看一下情况: 输入 [2, 10, 6],输出拼接后的最小值是:1026。 输入 [48, 3, 305, 16],输出拼接后的最小值:16305348。 由上可知,想要拼接得到最小值,只需要将数组中的元素按某种规律依次“从小到大”排列即可。值得注意的是,这里的“大小”并不是简单数值比较,而是与目标元素拼接后哪个较小。举个例子,数字 3 与数字 305 比较大小,正常来说 3 < 305 ,而在本题情境下,显然 305 应该排在 3 之前,因为 3305 > 3053。那么,我们不难得出以下结论: 若 x + y > y + x,则 x “大于” y,本题情境下,y 应该排在 x 之前; 若 x + y < y + x,则 x “小于” y,x 应排在 y 之前。 代码实现 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 class Solution { public String minNumber(int[] nums) { String[] stringValues = new String[nums.length]; for (int i = 0; i < nums.length; i++) { stringValues[i] = String.valueOf(nums[i]); } quickSort(stringValues, 0, stringValues.length - 1); StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < stringValues.length; i++) { stringBuilder.append(stringValues[i]); } return stringBuilder.toString(); } private void quickSort(String[] array, int start, int end) { if (start >= end) return; int position = partition(array, start, end); quickSort(array, start, position-1); quickSort(array, position+1, end); } private int partition(String[] array, int l, int h) { // Last element as pivot. String pivot = array[h]; int i = l; for (int j = l; j < h; j++) { String target = array[j]; if ((array[j]+pivot).compareTo(pivot+array[j]) < 0) { if (i == j) { // No need swapping. ++i; } else { // Need to move by exchange. String temp = array[i]; array[i++] = target; array[j] = temp; } } } String temp = array[i]; array[i] = pivot; array[h] = temp; return i; } } 复杂度分析 解题思路基于快排算法实现,所以平均时间复杂度为 O(NlogN),最坏退化为 O(N^2),空间复杂度为 O(N),以为内额外申请了一个大小为 N 的 stringValues 数组用于存放字符串。

2023/3/19
articleCard.readMore