要想优化进程的启动,需要懂得进程启动流程会调用对应的什么方法,以及通过profile查看对应的方法消耗了多少时间
而一个进程启动包括了系统进程之间的调用方法,还有用户自己本身进程内部的调用方法,所以就要懂得看
整个链路对应的方法都消耗了多少时间,用户自己本身进程内部调用方法,比如常见的在主线程io 操作网络操作或者
布局嵌套太深等都会影响启动时间,以及要如何根据当前业务去优化排这些任务的顺序和依赖问题
- 进程启动流程
简略如下
a.用户点击桌面app,Launcher 进行通过 Binder 和system_server 通信 要 开起一个进程
b.system_server 通过 Socker通信 与 Zygote 进程 要fork 一个新的进程, Zygote做了fork 一个新的进程
c.新创建出来的进程 通过 Binder 通信 告诉 system_server进程要 和你绑定在一起,毕竟 system_server进程是 统一调度管理
d.system_server进程绑定成功后 通过 Binder 通信 新进程绑定成功了,你可以起开启了
e.新进程 Binder线程 接收到 system_server进程 消息后, 通过Handle 通信 发消息 H.LAUNCH_ACTIVITY 消息给 主线程 的消息队列中 (因为Binder线程不做ui操作)
f. 页面第一次显示有画面是system_server的wms 创建Starting Window,而这个Starting Window显示的画面是从你第一个
activity设置的theme 的属性windowSplashScreenBackground(不同版本不同属性如上图)配置的
<!--LaunchTheme:第一个启动actvity 设置的主题StartingWindow显示的内容--><style name="Theme.MyApplication.Launch"parent="Theme.SplashScreen"><!--API31+:系统SplashScreen(背景色+中间图标)--><item name="windowSplashScreenBackground">@color/splash_background</item><item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher</item><item name="windowSplashScreenIconBackgroundColor">@color/purple_500</item><!--StartingWindow设置完这个主题后,下一个主题是@style/Theme.MyApplication--><item name="postSplashScreenTheme">@style/Theme.MyApplication</item><!--API24~30:老设备StartingWindow整屏背景--><item name="android:windowBackground">@drawable/launch_background</item></style>如果你不设置这些属性,那么默认是默认白/黑(Material 浅色主题多为白),而这个白/黑/启动屏画面会持续显示到
你第一个activity setContentView设置的画面流程走完,Starting Window被移除,显示真正第一个页面内容为止,
如上图是显示到大概 onResume 执行完 ,measure / layout / draw,绘制结束,onWindowFocusChanged获取到焦点能被用户触发点击。那么如果在Starting Window显示再到第一个activity 显示的view显示中间对应的链路和方法耗时太长,那么就会
导致一直卡在启动页。
- 如上图,当第一个activity界面被显示出来, Logcat:ActivityTaskManager: Displayed … +XXXms 大体上可以理解为代表进程的启动时间
或者通过 adb 开启一个进程启动activity 也可以看到 对应的时间adb shell am start -S -W +包名 + 启动activity名字
- 从上面应用启动链路中我们知道一个app从启动到界面完全显示可交互有不同阶段,一个是framework阶段,一个是应用层阶段,这里只讲应用阶段
在应用层阶段,我们有Application的onCreate阶段,我们通常在这个阶段去做整个应用的sdk和网络初始化,数据库,或者对第一个activty或者fragment要显示的内容进行初始化,当然不应该把任务全都堆在这里,
那么要如何 处理这部分的内容呢,是直接开一个线程异步去对所有的初始化?这会造成时间和cpu资源的浪费,
其实对应业务,每个任务与每个任务之间都有对应的依赖性,也就是一个任务需要等另外一个任务执行完,任务执行之间是可以并行还是并发,进而来启动优化,
也就是对所有任务之间做一个管理,顺序,任务之间的依赖,是同步?异步?,本质上其实就是数据结构算法问题,
核心思路上可以利用DAG + 拓扑排序 管理启动任务,任务与任务之间 同步/异步协调可用CountDownLatch, CompletableFuture / 协程 / App Startup框架 更好维护。
阶段来到 第一个activity的oncreate 到onResume这个链路,这个过程,尽量保持布局不要多度绘制,为了快速加载MainActivity所需要数据,可以采用数据从缓存获取的方式如果数据异步获取到结果后就更新缓存,所要数据接口最好只有一个不要弄太多了接口,需要显示多少 获取多少,采用懒加载的模式还有一个办法看场景所需,只适合「可以晚点做」的轻量任务
Looper.myQueue().addIdleHandler{false// // false:执行一次后移除;true:下次空闲的时候 还会再执行//一般都是false,true慎用}IdleHandler = 主线程 MessageQueue 空档回调, 我们知道主线程有很多消息message,有个消息队列,而当线程空闲没事做等待的时候,就会去调用这个idle,但是他还是在主线程,所以你不要指望在这里去做很耗时的任务,任务太重照样卡 UI,可以把 非紧急初始化 丢进 IdleHandler,会 排到首帧之后、队列空档 再跑,不挤占启动关键路径。
override funonCreate(...){super.onCreate(savedInstanceState)setContentView(...)Looper.myQueue().addIdleHandler{// 首帧画完后、主线程空档再跑initThirdPartySdk()preloadData()false// 只执行一次}}总结就是
别「一条线程异步全包」,也别「Application 里同步全做」—— 按依赖和优先级管任务 才是正路。
首页activity画面必须尽快展示,别堵在中间的链路。
a. 列出所有启动任务(SDK、DB、配置、账号…)
b. 画依赖图(谁依赖谁)
c. 拓扑排序 → 执行顺序
d. 同一层无依赖 → 线程池并行
e. 有依赖 → 前置完成再启动后置,DAG 管,不能乱并行
f. 非首屏必须 → 延后到首帧后 / IdleHandler
g. 需要「等多路完成合适调度」→ CountDownLatch / CompletableFuture / 协程
- 通过cpu profile 工具 录制 查看每个方法跑了多少时间,如图总有3个工具
下面的弹窗代表录制时机点,now 代表,进程app已经开启运行中了,你要现在就开始录制
Process start 代表,进程重新从0 开启冷启动运行,并且进行录制
System Trace 我理解为他是看系统进程级别别对应的方法的
- CPU 各核调度、线程状态(Running / Sleeping / Blocked)
- 主线程、RenderThread、Binder 线程活动
- VSYNC、Choreographer、doFrame、布局、绘制
- 系统事件 + 你代码里的 Trace.beginSection(“xxx”) 自定义片段
适合场景:
- 启动慢:看 bindApplication、handleLaunchActivity、首帧
- 滑动卡顿:看 Choreographer#doFrame 是否超 16ms
Callstack Sample 定时采样录制(例如每 ms 级)拍一次 当前调用栈快照
Java/Kotlin Method Recording 我理解是能够很细记录进程app内我们自己写的代码层级对应方法
总结下3个工具就是
System Trace → 看「整条流水线」(App + 系统 + 渲染)
Callstack Sample → 看「谁最常出现在 CPU 上」(抽查,一般不常用)
Method Recording → 看「每个 Java 方法精确待了多久」
- 开发阶段采用StrictMode 模式,StrictMode 是 Android 自带的开发期检测工具,用来发现「不该在主线程 / 进程里做的事」,并在 Logcat 里报警(或让 App 崩溃),
// Application.onCreate中调用if(BuildConfig.DEBUG){// 主线程违规检测,当磁盘读写,网络操作StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder().detectDiskReads()// 磁盘读.detectDiskWrites()//磁盘写.detectNetwork()//网络操作.penaltyLog()// 打 Logcat 警告// .penaltyDialog() // 弹窗(可选)// .penaltyDeath() // 直接崩溃(开发期可开,很激进).build())// 对整个进程级泄漏等进行检测StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects()//SQLite 用完没关.detectLeakedClosableObjects()//InputStream、Cursor 等 Closeable 没关.detectActivityLeaks()//Activity 销毁了还被静态变量、单例等持有.detectLeakedRegistrationObjects()//广播、Service 等注册后没反注册.penaltyLog().penaltyDeath()// 泄漏建议开发期直接 crash,好定位.build())}学习了上面的知识后,启动慢推荐流程
- adb am start -W → 总耗时多少
- System Trace / Method Recording → 慢在哪个方法 根据上面的启动流程查找可疑的方法
- 改代码 → 再测 TotalTime 对比