应用启动相关流程与优化

Android 后台启动一个进程 android启动进程几种方式_Android启动优化

 

应用启动主要涉及SystemServer进程 和 app进程。

SystemServer进程负责app进程创建和管理、窗口的创建和管理(StartingWindow 和 AppWindow)、应用的启动流程调度等。

App进程被创建后,进行一系列进程初始化、组件初始化(Activity、Service、ContentProvider、Broadcast)、主页面的构建、View加载。

1.应用启动方式

启动方式:热启动、冷启动、温启动三种。

Android 后台启动一个进程 android启动进程几种方式_Android 后台启动一个进程_02

 

冷启动

冷启动耗时最多,冷启动常见场景APP首次启动或者APP完全被杀死。

温启动

当启动应用时,后台已有该应用的进程,Activity因为内存不足被回收。系统会从已有的进程中来启动这个Activity。

温启动场景:第一种是首先用户按连续按返回退出了app,最后重新启动app,第二种是首先系统收回了app的内存,最后重新启动app。

热启动

热启动时,系统将activity带回前台。如果应用程序的所有activity存在内存中,那么应用程序可以避免重复对象初始化、渲染、绘制操作。

热启动常见的场景如: 当我们按了Home键或其它情况app被切换到后台,再次启动app的过程。

2.应用冷启动关键流程

Android 后台启动一个进程 android启动进程几种方式_初始化_03

 

  1. 用户Click事件触发IPC操作,会执行Process的start方法来创建进程。
  2. ActivityThread执行main方法,是App进程的入口,相当于Java进程的main方法,在其中会执行消息循环的创建与主线程Handler的创建。
  3. Handler创建完成之后,就会执行到 bindApplication 方法,在这里使用了反射去创建 Application以及调用了 Application相关的生命周期。
  4. Application生命周期之后,便会执行Activity的生命周期,在Activity LifeCycle结束之后,就会执行到 ViewRootImpl,这时才会进行真正的一个页面的绘制。

优化方向

创建Application、启动主线程、创建HomeActivity、加载布局、布置屏幕和界面首帧绘制完成后,启动完成。

APP 层可以优化的相关流程:Application atttachBaseContext、Appication onCreate 和 Activity LifeCycle三个流程。

Android 后台启动一个进程 android启动进程几种方式_主线程_04

3.归因分析

程序运行最根本的是需要得到CPU时间片,如果一个任务需要较多的CPU时间执行,那么它将影响其他任务的执行,从而影响整体任务队列的运行;

线程切换涉及到 CPU调度,而CPU调度会有系统资源的开销,所以大量的线程频繁切换也会产生巨大的性能损耗;

IO和锁的等待会直接阻塞任务的执行,不能充分地利用CPU等系统资源。

 

Android 后台启动一个进程 android启动进程几种方式_主线程_05

 

 

4.启动指标

第一个是启动开始,启动开始是进程创建的时间

第二个是启动结束,首页首屏渲染完成的时间;

第三个是启动时长,启动时长是指启动结束的时间戳减去启动开始的时间戳;

5.启动优化价值

启动耗时增长可能缩减App用户的留存。

启动性能优化目标是以低端机为重点,辐射中高端机,通过技术和产品上的深度优化,可感知的提升用户体验,实现扩大用户规模、提升留存和提升收入。

6.优化方案整理

1.Application优化

Application 阶段通常用于初始化比较核心的业务库。在应用开发早期,我们并没有对这个阶段的启动任务进行管控,导致这里往往会堆积有大量的强业务相关的代码。精简基于的原则是:

  1. Application 中的任务应当是全局基础任务
  2. Application 创建时应当尽量减少网络请求操作
  3. Application 创建时不允许有强业务相关的任务
  4. Application 创建时尽量减少有 Json 解析处理和 IO 操作的工作

Application中的启动任务要尽可能的少,主要分为基础库初始化,功能配置和全局配置这三大类。基础类库主要是对网络库,日志库等基础库进行初始化配置,除了主进程外,其余的进程也依赖这些任务,移除它们会对全局的稳定性造成影响。它们也是启动任务中占比最大的,耗时最多的一类任务,因此降低它们的耗时也是后面持续优化的重点。功能配置主要是对一些全局相关的业务功能的前置配置,例如对业务缓存的预加载,特定业务前置等,移除它们会造成业务有损,在这种情况下,我们需要找到业务诉求和功能配置之间的平衡点。全局配置主要是对于全局 UI 配置,文件路径的处理操作,它们占比少,耗时少,是首页创建的前置任务,因此暂不处理。

任务排布的核心是处理好任务的前后依赖问题。

1.要基于进程进行任务排布,对 Application 的任务进行细致划分,将任务的运行精细到进程级,避免在非主进程中执行了不必要的任务。

2.懒加载。这里主要是对一些基础任务进行改造,将任务初始化和任务启动拆分开来,将启动工作移出 Application 创建流程,同时对其进行精简,去除冗余逻辑。在创建对象时,可以延迟其成员对象的创建,灵活使用 by lazy 等关键字,使对象轻量化。

3.进程收敛。多进程可以实现模块隔离,同时避免单个进程内存占比过高导致的内存上限限制,其劣势在于多进程会导致应用整体内存占用过多,触发低内存的概率更高。此外,如果应用启动时内存占比过高,可能会导致手机进行内存回收,占用大量的CPU资源,这反应在用户的体验上就是启动慢,应用卡。对比多进程的优劣,采用的策略是尽量延后主进程以外的进程启动,同时通过进程合并来减少进程的数量。

4.线程收敛。对于多核CPU来说,适当的线程数量能够提升效率,但是如果线程泛滥则会导致 CPU 负载过重。多线程并发,本质上就是多个线程轮流获取 CPU 使用权的过程。在负载超重的情况下,过多的线程争抢时间片,除了降低启动速度外,也会导致主线程卡顿,影响用户体验。在做这方面优化时,需要确保全局使用统一的线程池。同时很多二方和三方 SDK 也是创建子线程的大户,这时候需要和相关的技术部门进行沟通,去除不合理的线程创建。另一方面,避免在启动阶段进行网络请求,也是减少线程数的关键所在。

Application 优化是整个启动流程的关键,合理的任务编排不仅能够降低 Application 的创建时长,对后续的首页创建也有非常大的优化效果。

2.启动链路

执行完 Application 创建之后,应用进程的主要工作就是创建 Activity。这里需要注意的是,从 Application 到 Activity 里还藏有很多 post 到主线程中的任务,以及注册的 ActivityLifecycleCallbacks 的回调监听,它们会偷偷增加从 Application 到 Activity 的时间间隙。ActivityLifecycleCallbacks 注册通常和业务相关,它的注册比较隐蔽。

关于主线程消息耗时,使用 Profiler 和 Systrace 对启动流程进行耗时定位时,可以发现很多问题。因为各种原因,Application 中任务会将耗时的工作 post 到主线程中来,表面上看 Application 的创建时间缩短了,但是总体上启动时间却被扩大了。对于耗时点应该定位到根本原因进行解决,而不是一味地 post 出去,这样治标不治本。

缩短启动到首页的链路,是我们优化的重点。

Android 后台启动一个进程 android启动进程几种方式_主线程_06

 

 

在一般启动流程中,loading 页面启动页面,承担有路由和权限请求两个任务。

1.在通常情况下,用户启动 App,在 loading 页面判断是否登录,如果未登录,则进入登录页面。

2.如果用户已经登录,则判断是否需要展示开屏页,如果需要则进入开屏页,等到开屏结束,就跳回 loading 界面,再进入首页。

即使没有开屏页,用户启动 APP 到展示首页,最起码要经过两个 Activity 的启动。启动链路缩短的核心在于将 loading,main 和开屏页合并为一个页面。这样做不仅可以最起码减少一次 Activity 的启动,同时也可以在展示开屏页时并行处理其他的任务。

Android 后台启动一个进程 android启动进程几种方式_初始化_07

实现这个功能的代码逻辑就是将 main 页面设置为启动页,首页和开屏页封装为两个 fragment,根据业务逻辑进行展示。用户点击 icon 进入到首页,如果判断已登录,则执行首页前置任务和首页 UI 渲染,同时判断是否加载开屏页 fragment。

3.首页优化

懒加载

首页大致情况如下图:

Android 后台启动一个进程 android启动进程几种方式_Android启动优化_08

 

APP 会加载首页的四个 TabFragment,比较耗时。

延迟动态等另外3个Fragment 的创建和加载。考虑到首页展示时只有第一个 fragment 是可见的。对首页进行懒加载。首页使用的是通用的 ViewPager2+tabLayout 的架构形式。ViewPager2 天然支持懒加载的操作,为了避免在页面切换时,已有的fragment 被回收,增大 viewPager2 内部的 recyclerView 的缓存池大小。

 ((RecyclerView)mViewPager.getChildAt(0)).setItemViewCacheSize(mFragments.size());

该方案是将其他页面的创建和渲染推迟到了切换时,如果页面比较重并且手机性能比较差的话,在切换时会有明显的卡顿和白屏情况,这也是无法接受的。

所以要对首页和各个fragment进行了改造。

4.ViewStub实现懒加载。

View 的创建是首页渲染耗时的大户。使用 LayoutInflater 去加载 xml 文件,这里面涉及到了 xml 解析,然后进行反射生成实例的过程,总体上是比较耗时的。对其中比较简单的 xml,使用了代码进行构建,但是对于复杂的布局文件,使用代码构建耗时巨大,并且不可维护。建议通过 ViewStub 的形式延后加载。

5.Json解析处理

Json 解析操作也是需要进行优化的点。Json 解析耗时的原因本质上是在解析时,从 Json 数据到对象的创建是通过反射操作进行对象生成和赋值的,对象越复杂,那么耗时就越长。对于首页的主接口来说,返回对象的解析在低端机上的耗时可能超过 UI 的渲染的耗时。可以采取的方案是将首页的数据对象使用 Kotlin 进行重构,并对相关对象使用 @JsonClass(generateAdapter = true) 进行标注,它会在编译期间对标注的对象生成对应的解析适配器,从而缩短解析时间。

 

 6.XML 解析优化

1.XML异步解析。

2.compose 的方案。