GVKun编程网logo

Android性能优化(一):APP启动优化(android app启动优化)

16

如果您对Android性能优化(一):APP启动优化感兴趣,那么本文将是一篇不错的选择,我们将为您详在本文中,您将会了解到关于Android性能优化(一):APP启动优化的详细内容,我们还将为您解答a

如果您对Android性能优化(一):APP启动优化感兴趣,那么本文将是一篇不错的选择,我们将为您详在本文中,您将会了解到关于Android性能优化(一):APP启动优化的详细内容,我们还将为您解答android app启动优化的相关问题,并且为您提供关于58同城 Android App启动优化实践、Android APP启动方式、启动流程及启动优化分析、Android 勇闯高阶性能优化之启动优化篇、Android 性能优化之 App 应用启动分析与优化的有价值信息。

本文目录一览:

Android性能优化(一):APP启动优化(android app启动优化)

Android性能优化(一):APP启动优化(android app启动优化)

Android性能优化(一):APP启动优化

App启动的方式有三种:

  1. 冷启动:App没有启动过或App进程被killed, 系统中不存在该App进程, 此时启动App即为冷启动。
  2. 热启动:热启动意味着你的App进程只是处于后台, 系统只是将其从后台带到前台, 展示给用户。
  3. 介于冷启动和热启动之间, 一般来说在以下两种情况下发生:
    (1)用户back退出了App, 然后又启动, App进程可能还在运行,但是activity需要重建。
    (2)用户退出App后, 系统可能由于内存原因将App杀死, 进程和activity都需要重启,但是可以在onCreate中将被动杀死锁保存的状态(saved instance state)恢复。

这里主要针对冷启动进行优化。

一、先来看看冷启动的流程:

  1. Zygote进程中fork创建一个新的进程。
  2. 先创建和初始化Application类。
  3. 创建和初始化Launch Activity(onCreate onMesure onLayout,ondraw)。
  4. 调用setContetView方法后,将view添加到DecorView中,调用view的measuer/layotu/draw显示到界面上。

二、冷启动优化:

优化之前先说如何查看应用启动时间,方便进行对比:

  • 第一种方法:手机连接电脑,打开Android Studio,查看Logcat打印出来信息,筛选关键字displayed就可以看到应用启动时间。如下图:

    在这里插入图片描述

  • 第二种方法:使用adb shell命令来启动应用并查看启动时间adb shell am start -W [packageName]/[packageName.launchActivity] 如下图:

    在这里插入图片描述


    优化方法:

  • 针对Application的,不要在Application中进行业务操作和耗时操作,不要以静态变量的方式在Application中保存数据。

  • 针对Launch Activity,不要在Activity的onCreate方法进行耗时操作,如有必要则在线程中操作或者延时加载。

  • 针对Launch Activity的View的绘制,减少Activity布局view的层级,最好不要超过4层,减少View测量绘制的时间。

推荐使用下面这种延时加载,在窗口完成以后进行加载,这里面的run方法是在onResume之后运行的。

getwindow().getDecorView().post(new Runnable() {
    @Override
    public void run() {
        //Todo something
    }
});

58同城 Android App启动优化实践

58同城 Android App启动优化实践

1.前言

2.项目背景

3.优化分析

4.监控方案

5.优化方案

6.优化结果

7.总结展望


01

前言


App 启动是指用户从 App 之外的场景进入到当前 App 中的过程,按照 App 的进程是否存在以及主 Activity 的生命周期状态,App 启动主要包括冷启动、温启动和热启动三种。启动优化主要是针对冷启动过程,目标是减少用户从桌面点击 icon 启动 App 到展示出 App 主页的首帧画面或者从其他应用调起 App 首次启动到展示出业务的落地页首帧过程的耗时。

关于 App 启动优化的原理和检测工具的介绍,网上已经有很多分享的资料,有的分享技术深入内容全面但理论性太强不便于在项目中实践,有的只讲了某些方面的优化细节而不成体系。每个 App 都有自己特有的业务逻辑和代码实现,有必要针对自身 App 的特点,系统地把细碎的优化方法组织起来,形成一套适合本 App 维护的完整的优化方案体系。本文将主要介绍我们团队在 58同城 App 中进行启动优化的实践。


02

项目背景

随着业务需求不断迭代 App 内的代码逻辑越来越复杂,启动流程的逻辑也越来越复杂,导致 App 的启动性能逐渐劣化。通过启动流程的埋点简单监测了一个线上版本的启动时间,统计发现有大约 20% 的用户启动时间超过了5s,这对外部投放业务帖子的落地页到达率产生了不利影响。业务线团队找到我们无线团队,要求优化58 App 的启动时间。

58同城 App 中集合了招聘、汽车、房产和本地生活服务等业务模块,在启动过程中会初始化各业务模块,同时还会初始化大量的三方和自研的 SDK;除了通过点击桌面 icon 的方式启动, 58 同城 App 中大量的业务帖子落地页可能通过外部应用调起直达,这就要求 58 App 的启动优化既要关注正常流程的优化,还要关注外部调起过程的优化。


03

优化分析

在设计优化方案实施优化动作之前,需要先对 App 的现状进行摸底分析,针对现在的性能瓶颈进行有效的治理,谋定而后动,争取最大化的优化收益和投入产出比。

3.1 启动流程分析

首先从工程代码对 58 App 的启动流程做全路径分析,58同城 App 与大多数同类型 App 的启动路径类似,启动逻辑主要是从 Application 开始到 App 的首页主 Activity onResume 生命周期方法被执行。

冷启动的 App 进程由 zygote 进程 fork 出来后会执行 ActivityThread 的 main 方法,在该方法中执行 attach 方法,然后通过跨进程通信触发执行 bindApplication,这是被启动 App 的 Application 开始执行的起点,我们在应用中首先能触达到的方法是其 attachBaseContext 方法,一般应用层的业务初始化从这里开始。

接下来是 installProvider 阶段,一些三方 SDK 可能借助该时机进行初始化,58 App 对这一阶段没有特别处理。然后会执行到 Application 的 onCreate 方法,这是 58 App 中主要的业务初始化阶段,包括三方 SDK、业务线 Lib 库和公司以及部门自研的通用中间件的初始化,虽然已经将所有初始化模块进行了细粒度的任务化和异步执行,但没有做到按需和延迟初始化等。

执行了 Application onCreate 方法后,系统会调起应用的启动 Activity,在 58App 的 LaunchActivity 中处理了启动过程的引导页、开屏广告和 deeplink 的业务分发逻辑,这部分逻辑非常复杂。正常启动流程接下来会进入到 58App 主页面 HomeActivity 中,如果是外部调起的 deeplink 方式启动会进入到业务的落地载体页 Activity。接下来就是主页或者落地页布局的构建和渲染,当完成首帧 View 显示后,就完成了用户可感受到的应用启动过程。

在 58 App 启动过程中还有一个重要的逻辑是隐私权限检查,并在应用首次启动时在 LaunchActivity 之前弹出提示框要求用户选择是否同意隐私协议。这里使用了反射的方法,通过 SharedPreferences 保存了相关状态值,这部分逻辑在 Application attachBaseContext 方法中执行,对启动性能有必然的影响。

3.2 耗时归因分析

所有的耗时都是因为代码运行时不合理地消耗了系统资源而产生的,耗时归因分析就是要找出代码中不合理地消耗了系统资源的地方,消耗系统资源的方式包括占用过多 CPU 时间、频繁的 CPU 调度、I/O 等待和锁抢占等。

程序运行最根本的是需要得到 CPU 时间片,如果一个任务需要较多的 CPU 时间执行,那么它将影响其他任务的执行,从而影响整体任务队列的运行;线程切换涉及到 CPU 调度,而 CPU 调度会有系统资源的开销,所以大量的线程频繁切换也会产生巨大的性能损耗;IO 和 锁的等待会直接阻塞任务的执行,不能充分地利用 CPU 等系统资源。

我们基于原来的启动流程和启动任务,通过 trace 打点分析所有的启动任务,并将 58 App 的启动过程划分成3个阶段:

  • Trace-T1,从 Application 的 attachBaseContext 方法开始到 LaunchActivity 的 onCreate 方法被调用;

  • Trace-T2,从 LaunchActivity 的 onCreate 方法开始执行到 HomeActivity 的 onCreate 方法被调用;

  • Trace-T3,从 HomeActivity 的 onCreate 方法开始执行到首页 Fragment 的 onResume 方法结束。

通过 Profile 分析工具按上述执行阶段详细地分析启动过程中的任务耗时,梳理出启动过程的所有任务的具体耗时点。


04

监控方案

古人云“工欲善其事,必先利其器”,对于启动性能优化来说,如果能获取到各启动阶段以及所有启动任务的执行耗时的可视化数据,将有利于了解启动任务的耗时情况,帮助分析优化方向,同时也可以做优化对比。

Android Studio 中的 Profiler 工具以及在线性能分析工具 Perfetto 等都可以做非常详细的性能分析,但需要导出 app 运行的 traces 文件,然后加载到其中才能进行分析,不便于我们开发过程中快速地分析。我们希望可以在 App 运行后直接上报性能数据,然后在工具中可以直观地展示出详细的任务耗时和运行时序图。

于是我们基于 Profiler 的 API 开发了在线可视化性能监控工具,通过在客户端埋点打印执行任务的起止时间,然后在启动完成后(即主页首帧绘制完成)上报搜集的性能数据,在后端分析数据并以时序图展示出任务的执行耗时。

目前这套可视化的监控工具主要是团队内部用于在开发和测试阶段辅助分析优化点和优化结果,目前还没有实现对线上版本的启动过程监控,也没有对外公开这套工具。不过对于测试阶段的用户启动 58App,可以产出启动时间的统计数据,如下图所示。对于正式发布的线上版本,现阶段我们仍然采用对启动过程的关键点进行埋点统计启动耗时。


05

优化方案

根据我们对 58 App 启动流程的具体任务耗时问题的分析和梳理,结合工程代码结构的特点,秉着最小化侵入业务线代码的原则和小步快跑的迭代开发思路,我们进行了以下优化实践:重构组件化的任务启动框架、延迟不必要的初始化任务、合并启动页与首页逻辑、优化首页布局和相关逻辑、外部调起定制化启动。下面将对这些实践内容的具体优化方案分布做详细的阐述。

5.1 组件化的启动框架

优化前 58 App 中已经使用了一套将初始化任务 Task 化的启动任务管理框架,但该框架存在几个问题:

1、创建一个新的任务后必须手动添加到启动过程的任务队列中,并需要配置所属进程和优先级;

2、由于启动任务队列是在主 Module 的 Application 类中管理的,代码隔离的业务线如果要新增启动任务将比较麻烦;

3、应用主进程和子进程的任务管理全在 Application 类中管理,存在大量 if-else 控制逻辑。总的来说,该任务管理框架有违开放封闭和单一职责等设计原则,代码的扩展性和维护性都存在问题。

5.1.1 组件化启动框架原理

针对原有任务管理框架的问题以及对启动任务管理提出了新的需求,我们开发了一套新的组件化的启动框架来重构原有的启动任务。

新框架借鉴了 Jetpack WorkManager 库的设计思想,将启动任务抽象成 Worker 实体,并设计了 WorkGroup 的概念,任务在任务组中根据依赖关系形成任务链顺序执行,所有启动任务被构建成有向无环图。通过启动框架的 API 指定任务的执行线程,并在框架内统一管理 IO 和计算类线程池,一定程度上实现对项目的线程收敛。

任何一个任务都必须在一个特定的任务组中运行,任务组是在定义具体任务时指定的。在横向上任务组可以定义在不同的进程中,实现不同进程的启动任务逻辑隔离,将原先在同一个 Application 类中处理不同进程启动逻辑的代码解藕;在进程执行的时间线上可以按 Application 的生命周期或者自定义的执行时机定义任务组,这样可以根据任务的具体特点灵活地进行延迟执行。

5.1.2 组件化启动框架使用

使用新的启动框架定义任务通过继承 Worker 类,并通过 @WorkScheduler 注解指定任务的名称、执行的线程、所属任务组和依赖的其他任务,定义任务的模板代码如下。

在独立进程中启动该进程内的任务,需要继承 ProcessApplication 类,实现两个 Application 生命周期的方法,并在这些方法中通过 WorkManager 对象找到相关任务组,然后启动任务。

5.1.3 组件化启动框架的收益

重构组件化的启动框架是我们本次启动优化的基础,开发组件化的启动框架给我们带来了以下收益:

第一、通过将任务组件化地实现,各个业务线可以自行优化拆解本业务模块内的启动任务,方便任务扩展和独立维护;

第二、通过注解方式配置任务的执行环境,可以在编译期完成启动任务序列的创建而不必硬编码实现;

第三、将原框架中通过一个 Application 类管理所有启动任务的方式,改为通过注解指定进程并实现 Application 的生命周期的方式分别管理各进程的启动任务,让进程职责更明确,同时也提高独立进程启动任务的扩展性和可维护性。

5.2 延迟初始化部分任务

按照组件化启动框架设计,我们将原有的启动任务进行拆解重构。按照任务优先级和依赖关系,将任务细化为必需在 Application 的 attachBaseContext、onCreate 阶段执行的和可以延迟到首页加载后执行的。

以主进程为例,我们定义了 MainProcessAttach、MainProcessCreate 和 MainProcessDelay 这几个任务组,并在相关任务的注解参数中配置这些任务组名称。

延迟任务的执行逻辑在 DelayTaskManager 类中管理,在 Application 的 onCreate 方法中初始化该管理类,在这个类的初始化方法中会注册 Application.ActivityLifecycleCallbacks 监听 Activity 的生命周期,然后监控 Activity 首帧 View 渲染完成,在该时机触发延迟任务执行。

从以下监控时序图可以看出,优化后一些任务在首页首帧渲染完成后才开始执行。

5.3 合并启动页与首页

优化之前,58 App 的启动 Activity 是 LaunchActivity,从前面的优化分析中可知,优化前启动 58 App 到首页可见的流程中会启动两个 Activity。

其中 LaunchActivity 主要承载了开屏相关的逻辑,包括处理开屏的广告和活动策略、用户首次启动加载功能引导图、外部调起的业务逻辑分发等,LaunchActivity 的逻辑非常臃肿。

5.3.1 合并收益

将启动页 Activity 的逻辑和首页 Activity 合并可以得到至少两方面的收益:

  • 减少一次 Activity 的启动过程,避免系统 startActivity 过程的性能损耗;

  • 利用处理开屏过程的时间,执行一些与首页 Activity 强关联的并发任务,例如首页数据预加载。

启动 Activity 的过程从应用进程执行 Context 的 startActivity 方法,然后通过 Instrumentation 的 execStartActivity 调起 AMS 执行真正的 startActivity 相关方法,在完成了目标进程和 Activity 栈检测之后回调到应用进程,通过 ActivityThread 执行目标 Activity 的创建和启动,整个过程涉及到两次跨进程调用以及在应用进程的主线程通过消息机制执行 Activity 的生命周期方法。

在应用启动过程中如果连续启动多个 Activity,一方面会触发多次跨进程通信,另一方面会导致应用进程的主线程消息队列堆积大量消息,容易造成主线程性能损耗。

将启动页和首页合并后,开屏页成为展示在首页之上的一层 View,原先需要在开屏页退出后才能执行的首页异步任务或者与首页 Activity 关联的任务,比如首页金刚位和 Feed 流数据加载等,可以提前到开屏页展示过程中执行,减少总体启动耗时。

5.3.2 问题及解决方案

合并了启动页和首页的逻辑后,对应用的启动逻辑产生了一些新问题,主要有:

  1. 如何解决外部通过 LaunchActivity 名称调起 58同城 App 的问题?

  2. 如何解决 HomeActivity 的启动模式和任务栈管理的问题?

  3. 消息调起和外部调起的逻辑分发问题

第一个问题比较好解决,有两种方案可以使用:首先可以在 Manifest 文件中通过 activity-alias 标签配置 targetActivity 将 LaunchActivity 指向 HomeActivity 来解决,如下图所示;其次可以将原来的 LaunchActivity 改为空实现并继承自 HomeActivity。

第二个问题实际上关联了一些在不同场景的启动过程页面跳转的任务栈管理问题。

在合并启动页和首页逻辑之前,LaunchActivity 和 HomeActivity 的启动模式分别是 standard 和 singleTask,这种情况下能够确保 HomeActivity 只有一个 实例,并且当我们通过 Home 键将应用退到后台然后点击桌面图标再进入应用时,能够重新回到之前的页面。

但在合并之后,HomeActivity 变成了应用的启动 Activity,如果继续使用 singleTask 这个启动模式,当我们从二级页面点击 Home 键退出后点击 icon 再次进入时,我们将无法回到二级页面,而会回到 HomeActivity 的首页 tab,因此合并后的 HomeActivity 其 launchMode 将不能使用 singleTask。

经过调研,我们最终选择在 Manifest 文件中配置 HomeActivity 的 launchMode 为 standard。对于应用内启动 HomeActivity 的场景,我们通过在startActivity 的 Intent 中增加 FLAG_ACTIVITY_SINGLE_TOP 和 FLAG_ACTIVITY_CLEAR_TOP 的 flag,以实现类似于 singleTop 启动模式的特性,并实现 onNewIntent 方法执行一些页面设置的逻辑。

对于从外部调起 App 进入业务落地页的场景,我们设计了一个空实现的 Activity,并在 Manifest 中指定其 parentActivityName 属性为 HomeActivity,然后通过 TaskStackBuilder 将该空 Activity 添加到待启动的落地页 Activity 的任务栈中,这样可以从目标 Activity 回退时进入到 HomeActivity。

通过推送消息调起 App 主要是修改启动 Activity 的名称,对于外部 deeplink 方式调起 58 App 的逻辑分发问题,主要考虑将原来依赖 LaunchActivity 分发的逻辑迁移到落地页载体 Activity 中。

5.4 优化首页布局与逻辑

对首页布局的优化主要是减少首页布局树的渲染时间,首页的布局主要是在 setContentView 方法中通过 LayoutInflate 去加载布局 xml 文件,布局加载的过程主要包括 3 个步骤:

  1. 通过 XmlResourceParser 的 IO 过程将 xml 文件解析到内存中;

  2. 根据 XmlResourceParser 的 Tag name 获取 Class 对象的 Java 反射过程;

  3. 创建 View 实例,最终生成 View 树。

加载布局的整个过程比较耗时,58 同城 App 经过多年的业务迭代,布局结构已经变得非常复杂。在布局方面我们主要进行了以下优化动作:

首先,在布局设计层面,通过使用 merge 标签或者约束布局等方式优化 xml 布局层级,使用 ViewStub 标签进行按需加载布局。

其次,在业务层面,前期我们已经实现了将首页布局按模块进行封装以便独立加载,比如拆分出了搜索框、活动位、金刚位和 Feed 流布局等模块。

最后,我们在启动优化实践中实现了异步线程预加载布局的方案,通过自定义 AsyncLayoutInflater 将 MainLooper 设置到异步线程上,解决解析 xml 生成 View 之后还需要 post 到主线程的问题。

另外,我们还进行了一些基础逻辑的优化:

  • 治理子进程启动逻辑,针对 WebView 的沙箱进程和其他子进程,将其启动时机延迟到主进程主页或目标落地页渲染完成之后;

  • 根据网络请求耗时的监控,提高网络并发能力;

  • 优化网络请求 Header 的获取,降低构造 Header 的耗时,提早网络请求的发起时机。

通过首页布局和逻辑的优化实现了进入首页后更快地体验到加载完成的界面内容,整体提升了 58 同城 App 的用户体验效果。

5.5 外部调起优化

58 同城有很多业务活动是通过投放三方平台进行推广的,用户在其他三方平台通过引导链接进入活动帖子详情页面,通过前面的分析知道,这个过程也会经历 58 App 的启动流程。如果从外部调起进入业务帖子落地页的耗时较长,将会影响业务活动的用户到达率,所以业务线非常看重这个启动过程的性能问题。

通过外部调起时的跳转协议可以明确是什么业务线的帖子,58 同城 App 内的业务模块实现是相互独立的,这样可以根据调起的帖子类型确定启动过程需求执行的初始化任务。根据外部调起的特点,我们设计了一个沙箱进程用于执行业务线定制化的启动任务,可以在外部调起时只初始化与当前业务有关的任务,从而快速打开业务落地页,在沙箱进程中完成帖子页面展示后会立即通知主进程启动。


06

优化结果

经过前面分析设计的优化方案落地实践,58 同城 App 的冷启动时间总体平均提升50%左右。由于首次启动过程会执行隐私权限检查和弹框的逻辑,并且隐私权限弹框依赖用户交互,我们在测试启动优化效果时分别就首次启动和非首次的冷启动场景进行优化前后的对比测试。

首次安装启动的性能优化对比图(以下柱状图中纵坐标单位均为毫秒 ms):

非首次启动的冷启动性能对比图:

另外,我们还将优化后的 58 同城 App 与业内同类型的 App 同时期的官方版本进行了启动性能体验对比,以比较我们的优化效果相比业内优秀 App 的差距。这里的性能对比只是从桌面点击应用图标到各自首页完成加载的启动过程的感官体验对比,在相同设备上相同网络环境下分别启动并录制视频,然后对齐启动动作首帧画面,合成同步启动对比视频,效果如下:

App 的启动性能是一个综合性的体验指标,它不仅受 App 启动流程的业务逻辑治理的影响,还会受到 App 包体积以及手机运行环境等的影响。针对上述比较的几款 App 我们无法得知他们在启动流程中做了哪些具体的优化策略,仅从包体积上比较,上述几款应用中美团 App 的包体积相比其他几个小大约 20MB,因此它的启动速度也是肉眼可见最快的。


07

总结展望

以上我们的启动优化实践主要是基于业务层面的常规的系统性优化,优化结果基本达到了本阶段的优化目标,但相对于业内具有更优秀的启动体验的 App 还有些许差距。性能优化本身就需要持续改进、日益精进,参考业内同行分享的优秀经验,我们还有很多工作可以做。

7.1 持续迭代

目前我们已经实践了的优化动作粒度相对较粗,带来了不错的收益可能得利于本阶段进行了大量的对历史代码结构的重构优化,但还有很多细粒度的性能问题没有深入优化,比如 IO 优化、ContentProvider 优化、GC 优化、线程调度和锁优化等。这些优化点也许不能带来很大的优化效果,但是做好了这些方面将会给 App 整体性能带来质的提升,当然做好这些优化不能一蹴而就,唯有通过持续迭代,一个点一个点地优化解决。

7.2 防劣化

攻城容易守城难,我们本次优化达到了不错的效果,但随着业务迭代,新增的代码逻辑有可能会抵消一些现在的优化收益,这就需要我们做好性能防劣化的工作。线下分析工具可以帮助我们定位问题点,分析产生性能问题的原因,并且可以快速对比优化结果;而线上性能监控有助于我们掌握性能变化的趋势。更好地设计监控方案,可以帮助分析出性能优化或者劣化的原因,针对性地解决劣化的问题,从而保持启动性能优化的状态。

7.3 新方案探索

以上实践的优化方案都是常规的优化思路,在将常规方案做到极致后,我们是否可以开拓思路探索新的优化方案呢?比如结合端智能针对用户画像按用户的常用功能进行启动路径优化。这关键点在于 App 的具体用户画像,比如用户设备性能,常用哪些功能以及不用哪些功能等。后端收到相关上报统计后,就可以给符合某些画像的用户下发最佳的启动路径,比如启动的时候不初始化某些模块,或者 delay 初始化某些模块,或者增加对热点模块的预热等。

参考文献

[1] 应用启动时间:https://developer.android.com/topic/performance/vitals/launch-time?hl=zh-cn
[2] 抖音 Android 启动优化理论:https://juejin.cn/post/7058080006022856735


作者简介

  • 庞立:58集团-技术工程平台群 Android工程师

  • 赵聪颖:58集团-技术工程平台群 Android工程师

  • 孔校军:58集团-技术工程平台群 Android工程师



本文分享自微信公众号 - 58技术(architects_58)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

Android APP启动方式、启动流程及启动优化分析

Android APP启动方式、启动流程及启动优化分析

本文章向大家介绍Android app应用启动的一些相关知识,包括app启动方式、app启动流程和app启动优化等知识!

 app应用启动方式

1、冷启动

 当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化Application类,再创建和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。

2、热启动

当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。热启动因为会从已有的进程中来启动,所以热启动就不会走Application这步了,而是直接走MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个MainActivity就行了,而不必创建和初始化Application,因为一个应用从新进程的创建到进程的销毁,Application只会初始化一次。

 app应用启动流程

为方便排版,去掉部分方法中的参数:

  1. 从Activity类的startActivity()方法开始,这个方法会调用Activity类中的public void startActivityForResult()方法
  2. startActivityForResult()方法会调用Instrumentation类中的public ActivityResult execStartActivity()方法,这个方法加上了{@hide}对外是不可见的
  3. execStartActivity()方法中有如下的调用语句ActivityManagerNative.getDefault().startActivity(),它调用了IActivityManager类中的startActivity()方法
  4. 但IActivityManager其实只是一个接口,这里实际调用的是public abstract classActivityManagerNative这个类(它继承自Binder类)的内部类ActivityManagerProxy中的public int startActivity()方法,ActivityManagerProxy实现了IActivityManager接口
  5. ActivityManagerProxy类的public int startActivity()方法中有如下代码,mRemote.transact(START_ACTIVITY_TRANSACTION,data,reply,0),这里mRemote是一个IBinder对象,这个对象在ActivityManagerProxy构造方法中实例化,实际由外部类ActivityManagerNative的static public IActivityManager asInterface(IBinder obj)方法实例化,asInterface(IBinder obj)方法中参数实际在ActivityManagerNative类的static public IActivityManager getDefault()方法中,由ServiceManager.getService("activity")实例化
  6. mRemote.transact(START_ACTIVITY_TRANSACTION,0)这条语句通过IBinder的transact()方法,将方法中的参数跨进程传递给ActivityManagerService类
  7. 以上除ActivityManagerService类之外的类都位于android.app包下
  8. 下面进入ActivityManagerService类,它位于源码的/frameworks/base/services/java/com/android/server/am/路径下,包名是com.android.server.am
  9. ActivityManagerService继承了ActivityManagerNative类,从ActivityManagerProxy类的mRemote.transact()传递过来的参数,被传递到ActivityManagerService类的onTransact()方法来处理
  10. ActivityManagerService类的onTransact()方法实际上通过super.onTransact(code,flags)这条语句又调用了ActivityManagerNative类中的onTransact()方法
  11. super.onTransact(code,flags)这条语句会调用到ActivityManagerService类的public final int startActivity()方法
  12. ActivityManagerService类的startActivity()方法会调用到ActivityStack类的startActivityMayWait()方法
  13. ActivityStack类位于com.android.server.am包下,startActivityMayWait()方法final int startActivityLocked()方法
  14. startActivityLocked()方法最后会调用final boolean resumetopActivityLocked()方法
  15. resumetopActivityLocked()方法会调用private final void startSpecificActivityLocked()方法
  16. startSpecificActivityLocked()方法会调用startProcessLocked()方法
  17. startProcessLocked()方法会调用android.os.Process类的public static final int start()方法
  18. int pid = Process.start("android.app.ActivityThread",mSimpleProcessManagement ? app.processName : null,uid,gids,debugFlags,null)
  19. 下面进入android.os包下的Process类中
  20. start()方法会调用private static int startViaZygote()方法
  21. startViaZygote()方法会调用private static int zygoteSendArgsAndGetPid()方法
  22. zygoteSendArgsAndGetPid()方法会使用socket与zygote进程通信
  23. sZygoteSocket = new LocalSocket();
  24. sZygoteSocket.connect(new LocalSocketAddress(ZYGOTE_SOCKET,LocalSocketAddress.Namespace.RESERVED));
  25. 下面进入com.android.internal.os包下的ZygoteInit类
  26. ZygoteInit类里面含有LocalSocketServer的实例,会与上面提到的zygoteSendArgsAndGetPid()方法使用socket进行通信
  27. 实际逻辑在ZygoteConnection这个类中的boolean runOnce()方法中
  28. runOnce()方法会调用dalvik.system.Zygote这个类中的静态方法forkAndSpecialize()
  29. 下面进入dalvik.system包中的Zygote类
  30. forkAndSpecialize()最终调用了native的方法native public static int forkAndSpecialize()
  31. 在c代码中开启应用程序的进程
  32. 应用的进程从android.app包下的ActivityThread类开始运行
  33. ActivityThread类中含有main()方法
  34. ActivityThread.main()是应用的启动入口,在应用程序启动的时候就会调用

 app的启动优化:

基于上面的启动流程我们尽量做到如下几点

  1. Application的创建过程中尽量少的进行耗时操作
  2. 如果用到SharePreference,尽量在异步线程中操作
  3. 减少布局的层次,并且生命周期回调的方法中尽量减少耗时的操作

通过此文,希望能帮助到大家,谢谢大家对本站的支持!

Android 勇闯高阶性能优化之启动优化篇

Android 勇闯高阶性能优化之启动优化篇

背景

用户不会在乎你的项目是不是过大,里面是不是有很多初始化的逻辑。他只在乎你-慢了。

所以咱们这篇文章有两个目的:

启动速度提升(用户眼中的大神就是你)

优化代码逻辑和规范(别让自己成为继任者中的XX)

今天咱们就来了解一下应用启动内部机制和启动速度优化。

启动内部机制

应用有三种启动状态:

  • 冷启动;
  • 温启动;
  • 热启动。

冷启动

冷启动是指应用从头开始:冷启动发生在设备启动后第一次启动应用程序 (Zygote>fork>app) ,或系统关闭应用程序后。

在冷启动开始时,系统有三个任务。 这些任务是:

  • 加载和启动应用程序。
  • 启动后立即显示应用程序的空白启动页面。
  • 创建应用程序进程。

一旦系统创建了应用程序进程,应用程序进程就负责接下来的阶段:

  • 创建应用的实体。
  • 启动主线程。
  • 创建主页面。
  • 绘制页面上的View。
  • 布局页面。
  • 执行首次的绘制。

如下图:

  • Displayed Time:初始显示时间
  • reportFullyDrawn():完全显示的时间

注意:在创建 Application 和创建 Activity 期间可能会出现性能问题。

创建 Application

当应用程序启动时,空白启动页面保留在屏幕上,直到系统首次完成应用程序的绘制。

如果你重写了Application.onCreate(),系统将调用Application 上的onCreate()方法。之后,应用程序生成主线程,也称为UI线程,并将创建主Activity的任务交给它。

创建Activity

应用进程创建你的Activity后,Activity会执行以下操作:

  • 初始化值。
  • 调用构造函数。
  • 调用 Activity 当前生命周期状态的回调方法,如 Activity.onCreate()。

注意:onCreate() 方法对加载时间的影响最大,因为它执行开销最高的工作:加载UI的布局和渲染,以及初始化Activity运行所需的对象。

热启动

热启动时,系统将应用从后台拉回前台,应用程序的 Activity 在内存中没有被销毁,那么应用程序可以避免重复对象初始化,UI的布局和渲染。

如果 Activity 被销毁则需要重新创建。

和冷启动的区别: 不需要创建 Application。

温启动

温启动介于冷启动和热启动中间吧。例如:

用户按返回键退出应用,然后重新启动。进程可能还没有被杀死,但应用必须通过调用onCreate()重新创建 Activity。

系统回收了应用的内存,然后用户重新运行应用。应用进程和Activity都需要重新启动。

咱们看看他们共同消耗多长时间。

查询的启动时间

初始显示时间(Time to initial display)

在 Android 4.4(API 级别 19)及更高版本中,logcat 包含一个输出行,其中包含一个名为 Displayed 的值。 此值表示启动流程和完成在屏幕上绘制相应活动之间经过的时间量。 经过的时间包含以下事件序列:

  • 启动进程。
  • 初始化对象。
  • 创建并初始化Activity。
  • 加载布局。
  • 第一次绘制你的应用程序。

注意这里查看日志需要如下操作:

报告的日志行类,如下图:

//冷启动
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +1s355ms
//温启动(进程被杀死)
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +1s46ms
//热启动
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +289ms
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +253ms

图例讲解:

第一个时间,冷启动时间:+1s355ms;

然后我们在后台杀死进程,再次启动应用;

第二个时间,温启动时间:+1s46ms;

这里咱们在后台杀死进程所以:应用进程和Activity需要重新启动。

第三个时间:热启动时间:+289ms 和 +253ms;

按返回键,仅退出activity。所以耗时比较短。

当然整体看这个应用开启时间并不长,因为 Demo 的 Application 和 Activity 都没有进行太多的操作。

完全显示时间(Time to full display)

你可以使用 reportFullyDrawn() 方法来测量应用程序启动和所有资源和视图层次结构的完整显示之间经过的时间。在应用程序执行延迟加载的情况下,这可能很有价值。在延迟加载中,应用程序不会阻止窗口的初始绘制,而是异步加载资源并更新视图层次结构。

这里我在Activity.onCreate()中加了个工作线程。并在里面调用reportFullyDrawn() 方法。代码如下:

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.e(this.getClass().getName(), "onCreate");
    setContentView(R.layout.activity_main);
    ...
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
                reportFullyDrawn();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

报告的日志行类,如下图:

I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s970ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s836ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s107ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s149ms

图例讲解:

然后你会发现界面出来好一会才打这个日志。看到这里我觉得好多人已经知道怎么去优化启动速度了。

性能迟缓分析

看到上面的实验其实三种启动情况,受我们影响的方面在于 application 和 activity 。

Application 初始化

当你的代码覆盖 Application 对象并在初始化该对象时执行繁重的工作或复杂的逻辑时,启动性能可能会受到影响。 产生的原因包括:

  • 应用程序的初始onCreate()函数。如:执行了不需要立即执行的初始化。
  • 应用程序初始化的任何全局单例对象。如:一些不必要的对象。
  • 可能发生的任何磁盘I/O、反序列化或紧密循环。

解决方案

无论问题在于不必要的初始化还是磁盘I/O,解决方案都是延迟初始化。换句话说,你应该只初始化立即需要的对象。不要创建全局静态对象,而是转向单例模式,应用程序只在第一次需要时初始化对象。

此外,考虑使用依赖注入框架(如Hilt)

Activity初始化

活动创建通常需要大量高开销工作。 通常,有机会优化这项工作以实现性能改进。

产生的原因包括:

  • 加载大型或复杂的布局。
  • 阻止在磁盘或网络 I/O 上绘制屏幕。
  • 加载和解码Bitmap。
  • VectorDrawable 对象。
  • Activity 初始化任何全局单例对象。
  • 所有资源初始化。

解决方案如下。

布局优化

  • 通过减少冗余或嵌套布局来扁平化视图层次结构。
  • 布局复用(< include/>和 < merge/> )
  • 使用ViewStub,不加载在启动期间不需要可见的 UI 部分。

代码优化

  • 不必要的初始化还是磁盘I/O,延迟初始化
  • 资源初始化分类,以便应用程序可以在不同的线程上延迟执行。
  • 动态加载资源和Bitmap

关于这两块的优化后续会有单独的文章去写。

阻塞实验

Application 阻塞 2秒, Activity 阻塞 2秒

SccApp.class

public class SccApp extends Application {
    @RequiresApi(api = Build.VERSION_CODES.P)
    @Override
    public void onCreate() {
        super.onCreate();
        String name = getProcessName();
        MLog.e("ProcessName:"+name);
        getProcessName("com.scc.demo");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

MainActivity.class

public  class MainActivity extends ActivityBase implements View.OnClickListener {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e(this.getClass().getName(), "onCreate");
        setContentView(R.layout.activity_main);
        ...
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    reportFullyDrawn();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

报告的日志,如下:

//冷启动
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +5s458ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +8s121ms
//温启动(进程被杀死)
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +5s227ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +7s935ms
//热启动
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +2s304ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +5s189ms
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +2s322ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +5s169ms

将Appliacation 和Activity阻塞的2秒都放在工作线程去操作

这个就是把代码放在如下代码中执行即可,就不全部贴出来了。

        new Thread(new Runnable() {
            @Override
            public void run() {
                ...
            }
        }).start();

运行结果如下:

//冷启动
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +1s227ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s957ms
//温启动(进程被杀死)
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +1s83ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s828ms
//热启动
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +324ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s169ms
I/ActivityTaskManager: Displayed com.scc.demo/.actvitiy.MainActivity: +358ms
I/ActivityTaskManager: Fully drawn com.scc.demo/.actvitiy.MainActivity: +3s207ms

APP 启动黑/白屏

Android 应用启动时,尤其是大型应用, 经常出现几秒钟的黑屏或白屏,黑屏或白屏取决于主界面 Activity 的主题风格。

优雅的解决黑白屛

Android 应用启动时很多大型应用都会有一个广告(图片及视频)页或闪屏页(2-3S)。这并不是开发者想要放上去的,而是为了避免上述启动白屏导致用户体很差。当然你可以珍惜这2-3秒做一个异步加载或者请求。

写到这里。应用启动模式、启动时间、启动速度优化算是完事了。当然后面如果有更好的优化方案还会继续补充。

到此这篇关于Android 勇闯高阶性能优化之启动优化篇的文章就介绍到这了,更多相关Android 启动优化内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

您可能感兴趣的文章:
  • 详解Android性能优化之启动优化
  • Android性能之冷启动优化详析
  • Android启动优化之延时加载的步骤详解
  • Android性能图论在启动优化中的应用示例详解

Android 性能优化之 App 应用启动分析与优化

Android 性能优化之 App 应用启动分析与优化

前言:

     昨晚新版本终于发布了,但是还是记得有测试反馈 app 启动好长时间也没进入 app 主页,所以今天准备加个班总结一下 App 启动那些事!

app 的启动方式:

 1.)冷启动

     当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动。冷启动因为系统会重新创建一个新的进程分配给它,所以会先创建和初始化 Application 类,再创建和初始化 MainActivity 类(包括一系列的测量、布局、绘制),最后显示在界面上。

 2.)热启动

     当启动应用时,后台已有该应用的进程(例:按 back 键、home 键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动。热启动因为会从已有的进程中来启动,所以热启动就不会走 Application 这步了,而是直接走 MainActivity(包括一系列的测量、布局、绘制),所以热启动的过程只需要创建和初始化一个 MainActivity 就行了,而不必创建和初始化 Application,因为一个应用从新进程的创建到进程的销毁,Application 只会初始化一次。

app 的启动流程:

   通过上面的两种启动方式可以看出 app 启动流程为:

    Application 的构造器方法 ——>attachBaseContext ()——>onCreate ()——>Activity 的构造方法 ——>onCreate ()——> 配置主题中背景等属性 ——>onStart ()——>onResume ()——> 测量布局绘制显示在界面上

app 的启动优化:

    基于上面的启动流程我们尽量做到如下几点

  1. Application 的创建过程中尽量少的进行耗时操作

  2. 如果用到 SharePreference, 尽量在异步线程中操作

  3. 减少布局的层次,并且生命周期回调的方法中尽量减少耗时的操作

app 启动遇见黑屏或者白屏问题

  1.)产生原因

     其实显示黑屏或者白屏实属正常,这是因为还没加载到布局文件,就已经显示了 window 窗口背景,黑屏白屏就是 window 窗口背景。

    示例:

  

 2.)解决办法

     通过设置设置 Style

(1)设置背景图 Theme

    通过设置一张背景图。 当程序启动时,首先显示这张背景图,避免出现黑屏

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:screenOrientation">portrait</item>
        <item name="android:windowBackground">>@mipmap/splash</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowNoTitle">true</item>
</style>

(2)设置透明 Theme

   通过把样式设置为透明,程序启动后不会黑屏而是整个透明了,等到界面初始化完才一次性显示出来

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:screenOrientation">portrait</item>
</style>

两者对比:

  • Theme1 程序启动快,界面先显示背景图,然后再刷新其他界面控件。给人刷新不同步感觉。

  • Theme2 给人程序启动慢感觉,界面一次性刷出来,刷新同步。

(3)修改 AndroidManifest.xml

 1 <application
 2         android:name=".App"
 3         android:allowBackup="true"
 4         android:icon="@mipmap/ic_launcher"
 5         android:label="@string/app_name"
 6         android:supportsRtl="true">
 7         <activity android:name=".MainActivity"
 8          android:theme="@style/AppTheme">
 9             <intent-filter>
10                 <action android:name="android.intent.action.MAIN" />
11 
12                 <category android:name="android.intent.category.LAUNCHER" />
13             </intent-filter>
14         </activity>
15 
16     //......
17 
18 </application>

解决后示例:

3.)常见的 Theme 主题
 1 android:theme="@android:style/Theme.Dialog" //Activity显示为对话框模式
 2 
 3 android:theme="@android:style/Theme.NoTitleBar" //不显示应用程序标题栏
 4 
 5 android:theme="@android:style/Theme.NoTitleBar.Fullscreen" //不显示应用程序标题栏,并全屏
 6 
 7 android:theme="Theme.Light " //背景为白色
 8 
 9 android:theme="Theme.Light.NoTitleBar" //白色背景并无标题栏
10 
11 android:theme="Theme.Light.NoTitleBar.Fullscreen" //白色背景,无标题栏,全屏
12 
13 android:theme="Theme.Black" //背景黑色
14 
15 android:theme="Theme.Black.NoTitleBar" //黑色背景并无标题栏
16 
17 android:theme="Theme.Black.NoTitleBar.Fullscreen" //黑色背景,无标题栏,全屏
18 
19 android:theme="Theme.Wallpaper" //用系统桌面为应用程序背景
20 
21 android:theme="Theme.Wallpaper.NoTitleBar" //用系统桌面为应用程序背景,且无标题栏
22 
23 android:theme="Theme.Wallpaper.NoTitleBar.Fullscreen" //用系统桌面为应用程序背景,无标题栏,全屏
24 
25 android:theme="Theme.Translucent" //透明背景
26 
27 android:theme="Theme.Translucent.NoTitleBar" //透明背景并无标题
28 
29 android:theme="Theme.Translucent.NoTitleBar.Fullscreen" //透明背景并无标题,全屏
30 
31 android:theme="Theme.Panel " //面板风格显示
32 
33 android:theme="Theme.Light.Panel" //平板风格显示

 

关于Android性能优化(一):APP启动优化android app启动优化的介绍现已完结,谢谢您的耐心阅读,如果想了解更多关于58同城 Android App启动优化实践、Android APP启动方式、启动流程及启动优化分析、Android 勇闯高阶性能优化之启动优化篇、Android 性能优化之 App 应用启动分析与优化的相关知识,请在本站寻找。

本文标签:

上一篇Android学习笔记上下文菜单(android 上下文菜单)

下一篇pkg/client/ledger ledger包