Android应用启动优化笔记整理

news2024/12/25 12:41:24

应用启动相关流程与优化

 

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

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

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

1.应用启动方式

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

 

冷启动

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

温启动

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

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

热启动

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

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

2.应用冷启动关键流程

 

  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三个流程。

3.归因分析

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

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

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

 

 

 

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 出去,这样治标不治本。

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

 

 

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

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

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

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

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

3.首页优化

懒加载

首页大致情况如下图:

 

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 的方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/389341.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【LinkedList】| 深度剥析Java SE 源码合集Ⅰ

目录一. 🦁 LinkedList介绍二. 🦁 结构以及对应方法分析2.1 结构组成2.1.1 节点类2.1.2 成员变量2.2 方法实现2.2.1 添加add(E e)方法2.2.2 头尾添加元素Ⅰ addFirst(E e)Ⅱ addLast(E e)2.2.3 查找get(int index)方法2.2.4 删除remove()方法三. &#x…

【云原生】整合K8s + SpringCloudK8s + gRpc + RocketMQ + Istio + Envoy

背景本文把前面的代码整理一遍,不仅仅是demo层面,而是考虑到放进生产中使用,且尽可能用高版本,关于这块技术,网上的文章真是一言难尽,要么就是个概念,要么就是把官网的demo拿过来跑一遍&#xf…

图神经网络 pytorch GCN torch_geometric KarateClub 数据集

图神经网络 安装Pyg 首先安装torch_geometric需要安装pytorch然后查看一下自己电脑Pytorch的版本 import torch print(torch.__version__) #1.12.0cu113然后进入官网文档网站 链接: https://pytorch-geometric.readthedocs.io/en/latest/install/installation.html 安装自己…

【小破站下载工具】Python tkinter 实现网站下载工具,所有数据一键获取

目录前言开发环境本次项目案例步骤先展示下完成品的效果界面导入模块先创建个窗口功能按键主要功能代码编写功能一功能二功能三前言 最近很多同学想问我,怎么把几个代码的功能集合到一起? 很简单,写一个界面就行了,想要哪个代码…

黑马程序员7

算数运算符重载 运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型 加号运算符 通过自己写函数,实现两个对象相加属性后返回新的对象 两种方式重载 成员函数方式重载 全局函数重载 上来 perso…

Go 内存分布

Go内存分布方式在C中,每个值在内存中只占据一个内存块(一段连续内存);但是,一些Go类型的值可能占据多个内存块。以后,我们称一个Go值分布在不同内存块上的部分为此值的各个值部(value part&…

网络安全平台测试赛 easyphp(phar脏数据处理)

昨天的比赛,14.00-17.00.时间有点紧张,比赛期间没拿下来这道 😭非常痛苦,很顺畅的思路 一步步想下来,卡在最后一步末尾脏数据处理了,最后时间到了 没打通,还需多练 这里本地复现一下&#xff1…

linux 进程及调度基础知识

引用Linux进程管理专题Linux进程管理与调度-之-目录导航Linux下0号进程的前世(init_task进程)今生(idle进程)----Linux进程的管理与调度(五)蜗窝科技-进程管理郭健: Linux进程调度技术的前世今生之“前世”郭健: Linux进程调度技术…

1.7 古典概型问题类型一——随机取数问题

(1)我的答案:一、信息首先7个数字全不相同二、分析七个数字全不相同意味着每次取出来的数都不一样,然后每次取出后选择少一种,为简单排列不含10和1这意味从8个数里面选且可以重复为重复排列3.10恰好出现两次隐含着两个问题,第一&a…

备考考研2数学

进度说明,开始形成自己的复习进度说明! 14:21 2023年2月28日星期二 武忠祥数学 截止目前,看完了01.高数基础01 14:21 2023年2月28日星期二 开始看02. 现在15:04 2023年2月28日星期二, 因为这2天的百度网盘不能进行解析了&…

初识HTML技术

文章目录一、为什么学习前端?二、第一个HTML文件VSCode三. HTML元素四. HTML页面一、为什么学习前端? 我们作为一个后端程序员,为什么还要学习前端,因为我们的终极目的是实现web开发,搭建网站,网站 前端 后端 比如我们随便…

最近几篇较好论文实现代码(附源代码下载)

《Towards Layer-wise Image Vectorization》(CVPR 2022) GitHub: github.com/ma-xu/LIVEInstallationWe suggest users to use the conda for creating new python environment.Requirement: 5.0<GCC<6.0; nvcc >10.0.git clone gitgithub.com:ma-xu/LIVE.gitcd LIVE…

一步一步学会给Fritzing添加元器件-丰富你的器件库

文章目录1、获取元器件文件2、单个添加元器件3、批量加入&#xff08;1&#xff09;、通过别人发布的bin文件加载&#xff08;2&#xff09;、终极大招&#xff08;拖&#xff09;4、制作自己器件文章出处&#xff1a; https://blog.csdn.net/haigear/article/details/12931545…

【C++】类和对象——六大默认成员函数

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;C的学习之路 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录前言一、类的6个默认成员函数二、构造…

错误异常捕获

1、React中错误异常捕获 在 React 中&#xff0c;可以通过 Error Boundaries&#xff08;错误边界&#xff09;来捕获错误异常。Error Boundaries 是一种 React 组件&#xff0c;它可以在其子组件树的渲染期间捕获 JavaScript 异常&#xff0c;并且可以渲染出备用 UI。React 提…

802.11 service服务类型

802.11 serviceservice定义service分类按照模块分为两类按照功能分为六类数据传输相关服务分布式服务DS&#xff08;Distribution Service&#xff09;整合服务IS&#xff08;Integration Service&#xff09;关联&#xff08;association&#xff09;重关联&#xff08;reasso…

RAD 11.3 delphi和C++改进后新增、废弃及优化的功能

RAD 11.3 delphi和C改进后新增和废弃的功能 目录 RAD 11.3 delphi和C改进后新增和废弃的功能 一、版本RAD 11.3 delphi和C改进后新增功能 1、官方视频位置&#xff1a; 2、官方文档的链接位置&#xff1a; 二、版本RAD 11.3 delphi和C改进后废弃的功能 2.1、编译器不再使…

Eureka注册中心和Nacos注册中心详解以及Nacos与Eureka有什么区别?

目录&#xff1a;前言Eureka注册中心Nacos注册中心Nacos与Eureka有什么区别&#xff1f;前言提供接口给其它微服务调用的微服务叫做服务提供者&#xff0c;而调用其它微服务提供的接口的微服务则是服务消费者。如果服务A调用了服务B&#xff0c;而服务B又调用了服务C&#xff0…

【iOS】设置背景渐变色

drawRect函数 主要负责iOS的绘图操作&#xff0c;程序会自动调用此方法进行绘图。我在这个函数中绘制渐变背景色。 方法定义&#xff1a; -(void)drawRect:(CGRect)rect; 重写此方法&#xff0c;执行重绘任务-(void)setNeedsDisplay; 标记为需要重绘&#xff0c;异步调用dra…

Mysql开发

Mysql开发 可以使用MySQL直接存储文件吗&#xff1f; 可以使用 BLOB (binary large object)&#xff0c;用来存储二进制大对象的字段类型。 TinyBlob 255 值的长度加上用于记录长度的1个字节(8位) Blob 65K值的长度加上用于记录长度的2个字节(16位) MediumBlob 16M值的长度加…