你想要的Android性能优化系列:启动优化 !

news2025/1/7 5:37:19

App启动优化

为什么要做App的启动优化?

网页端存在的一个定律叫8秒定律:即指用户访问一个网站时,如果等待打开的时间超过8秒,超过70%的用户将会放弃等待。

同样的,移动端也有一个8秒定律:如果一个App的启动时间超过8秒或有明显的卡顿,80%的用户将会退出应用并对程序员进行口吐芬芳。当然这是我瞎编的,但却不代表是不存在的。最起码肯定会影响App在市场上的评分,进而让更多的用户在对比过程中选择竞品。

知道了启动优化的重要性,那么接下来我们就来分析下如何优化App的启动,本文内容主要分为以下三部分:

分析优化方向

App应用主要有三种启动状态:冷启动、热启动和温启动

1、 冷启动:耗时最长,也是主要的优化点;(恋爱前的女人)

  • 冷启动前,系统主要做了三件事:

  • 加载并启动应用。

  • 在启动后立即显示应用的空白启动窗口。

  • 创建应用进程。

  • 创建应用进程后:

  • 创建应用对象。

  • 启动主线程。

  • 创建主 Activity。

  • 扩充视图。

  • 布局屏幕

  • 执行初始绘制

2、热启动:耗时最短,将activity从后台带到前台;(热恋中的女人)

3、温启动:耗时较长,重走了Actiivty的生命周期。(结婚后的女人)

从应用的启动状态中,我们可以分析得出,剥除系统本身的任务动作外(这部分我们是无法进行操作修改的),其实我们的启动优化方向主要就是:Application和Activity的生命周期、主视图的布局优化(这部分我们放到UI优化系列来讲)。

相关数据测量

优化App的启动速度前,我们得先获取App的一些启动数据,根据这些数据才能准确找到优化的点,才能对优化后的操作做一个准确的评估。(下面的相关代码我将会拿之前的一个旧项目来做演示,一是更贴近实际开发情况,比demo更加直观;二是顺手给优化了,何乐而不为呢?)

  1. 获取启动时间

adb命令法:adb shell am start -S -W packagename/activity(含包名)
  • ThisTime:最后一个 Activity 启动时间;

  • TotalTime:所有 Activity 启动耗时(这里只启动了一个 MainActivity);

  • WaitTime:AMS 启动 Activity 的总耗时;

adb 命令虽然简单好用,但还是有不少缺点的:

  • 只能线下使用,而在实际开发过程中,用户的启动时间才是最好的参考指标;

  • 非精确的时间,这里只是显示了 Activity 启动完毕的时间,但对于用户的直观体验来说,只有首页的数据展示出来,才算是真正的启动完成。

  • 手动打点法

public class LaunchTimer {
private static long mTime;
//开启时间
public static void startTime() {
mTime = System.currentTimeMillis();
}
//结束时间
public static void endTime() {
LoggerManager.d("启动时间:" + (System.currentTimeMillis() - mTime));
}
}

在Application类的attachBaseContext()方法中打入开始启动时间点:

protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
LaunchTimer.startTime();
}

在我们的首页第一条数据展示成功后打入结束时间点:(ps:网上很多文章都在onwindowfocuschanged()方法中打入结束时间,其实这个方法只是首帧时间,并不代表我们的页面数据等全部展示出来了。我们做优化,还是得以用户的实际体验来作为参考价值,不能仅仅KPI化)

//是否已经记录启动时间
private boolean mIsRecord = false;
@Override
protected void convert(BaseViewHolder helper, final HomeListBean.DataBean
item) {
if (helper.getPosition() == 1 && !mIsRecord) {
mIsRecord = true;
final View contentView = helper.getView(R.id.home_item_rl);
//监听第一条数据的绘制完成时间
contentView.getViewTreeObserver().addOnPreDrawListener(new
ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
contentView.getViewTreeObserver().removeOnPreDrawListener(this);
LaunchTimer.endTime();
return true;
}
});
}
}

运行我们的代码,可以看到启动时间是3111毫秒,正常来说,是会比用adb命令打出的时间要长点。

手动打点的好处:

  • 可以线上使用,统计真实用户的启动时间

  • 时间准确,结合用户真实体验,参考价值更高

2、其他优化分析工具

我经常使用的启动优化工具主要有Traceview(官方文档)和systrace(官方文档),Traceview虽然比较全面,但性能消耗太大,这里不做过多介绍,有兴趣的朋友可以自行查看官方文档,这里主要介绍systrace这个工具(使用前需得先安装python)。

先打点:

public void onCreate() {
super.onCreate();
//使用兼容的TraceCompat打入开始点
TraceCompat.beginSection("AppBegin");
if (instance == null) {
instance = this;
}
if (IS_DEBUG_ABLE) {
initLogger();
}
initBugly();
Tiny.getInstance().init(this);//初始化tiny图片压缩工具
initJPush();
initSkin();
RichText.initCacheDir(this);//设置缓存
initFragmentation();
MMKV.initialize(this);
TraceCompat.endSection();
//使用兼容的TraceCompat记录结束点
TraceCompat.endSection();
}
安装App,使用systrace命令:python systrace.py -b 32768 -t 10 -a packagename -o
outputfile.html sched gfx view wm am app (命令执行过程中点击启动App)

运行操作后,打开我们的html文件,可以看到我们app的启动相关数据

运行操作后,打开我们的html文件,可以看到我们app的启动相关数据:

图中红圈部分是我们需要注意的地方,AppBegin就是我们打点的代表区间,可以看到这段区间时间是732.127毫秒。最下面有两个数值,一个是WallDuration,这个就是我们代码的执行时间,另一个

CPUDuration是我们的CPU执行时间。

为什么两个会有时间差异呢?打个比方:CPU在执行代码的时候,遇到一些需要等待回调的数据才能继续往下执行情况的时候,CPU会处在等待情景,这个时候是不计算CPU执行时间,只有等回调数据回来了,再往下执行时,才算是调用了CPU的资源。

所以这里也点明了我们的优化方向之一:就是如何更好的利用CPU的资源。

PS:其实这段的数据主要都是启动时间的数据,但在实际开发中,我们可能还会去监测每个方法
所用的时间,看看有没有可优化的余地。方法监测的时间除了给每个方法单独打点外,还可以
使用Traceview工具来更好的监测。

优化技巧

终于讲到我们的优化技巧了,具体优化我们可以分为以下几种方式:

闪屏优化

业务优化

线程优化

UI优化

1、闪屏优化

在我们的App启动中,其实是低端机中,其实会有点击了应用,要等待一段时间,才会打开App页面的时间。这个现象对用户的体验非常不友好!那要怎样优化这个现象呢,这里可以采用theme切换的方式来达到视觉上的快速启动。

<!-- 新建layer-list的xml文件 -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:opacity="opaque">
<item android:drawable="@android:color/white"/>
<item>
<bitmap
//这里是我们想要展示的开屏图片
android:src="@mipmap/longkong_splash"
android:gravity="fill"/>
</item>
</layer-list>

然后新建Theme:

<item name="android:windowBackground">@drawable/lanucher</item>
<item name="windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="windowNoTitle">true</item>
</style>

在我们的启动页设置这个Theme:

<activity
android:name=".business.MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:hardwareAccelerated="true"
//设置theme
android:theme="@style/ThemeActivitySplash"
android:windowSoftInputMode="stateUnspecified">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

记得在页面onCreat中切换回我们原用的Theme:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
setTheme(R.style.ThemeActivity);
super.onCreate(savedInstanceState);
}

现在点击桌面图标,简直是秒开App。当然,这只是视觉上的秒开,实际的启动时间还是没什么变化,下面才是真正优化启动时间的方法。

2、业务优化

我们在一接到优化任务的时候,不要想着立马着手就做一些异步线程优化之类的。第一步应该是先梳理我们的业务。

梳理清楚我们启动的每一个模块,看看哪些是必要的,哪些是可以切掉的,哪些是可以延迟加载的。

①可以切掉的:没什么可说的直接删除;

②可以延迟加载处理的:比如地图SDK、扫一扫等,这些个模块很多时候不一定是在首页就需要用到的,我们可以做一些延迟加载甚至是只有在使用到时才进行初始化的处理。

延迟加载的时机:可以在首页用户数据加载完成时去进行

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
//执行延迟加载的代码
return false;
}
});
IdleHandler的运行机制是:只有在CPU空闲的时候才会去执行操作,这样就不会造成首页用户操作时卡顿的情况。

必要的:对于必要的代码,我们可以做以下业务相关优化操作:

使用更加优秀框架,比如我们的SharedPreferences,可以尝试夫换成腾讯的MMKV框架,在数据量大的情况下,优化效果非常明显;

使用更加优秀的算法,我们有些代码比如文件操作之类的,或许有更加优秀的算法代码,可以大大减少计算步骤;

作一些取舍,比如一些中低端的机型,或者其本身的性能没办法很好的运行我们的某些功能,在这个时间,我们可以尝试去跟产品经理沟通,做一些功能上的取舍。

3、线程优化

上面我们讲了一些必要代码的业务优化,在一些确实没有业务优化空间,或者优化了还不是很理想的代码,我们可以进行线程优化。

线程优化其实就是合理利用CPU的核心数,将几个耗时的任务进行并发处理,可以极大减少总的运行时间。

线程优化需要注意几点:

  • 合理控制线程的数量:每台机子的核心数都不同,如果我们线程开得太多,可能会相互竞争CPU资源,除了要用线程池进行统一管理外,设置合适的线程数也很重要。

我们可以参照AsyncTask的源码来设置线程池的线程数:

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1,
4));//线程数

任务的依赖关系:有些任务可能是需要前一个任务执行完后再进行操作,如果在线程优化中,没有处理好这个相关,可能会造成空转,如下图:

如何处理好线程的依赖关系,可以参照或使用市面上的一些启动框架,比如阿里开源的Alpha启动框架。

我们这里使用自己自写的Task启动器,其原理都是一样的,最终都是构成一个有向无环图。

TaskDispatcher.init(this);
TaskDispatcher dispatcher = TaskDispatcher.createInstance();
dispatcher.addTask(new InitBulyTask())
.addTask(new InitJPushTask())
.addTask(new InitRichTextTask())
.addTask(new InitSkinTask())
.addTask(new InitTinyTask())
.addTask(new InitFragmentTask())
.start();
dispatcher.await();

经过上面的优化技巧后,我们再来看一下现在的启动时间是多少:

从上面的三张图中,我们可以看出,优化的启动时间缩短了可观的50%左右。

4、UI优化

UI优化主要是对我们的视图布局进行优化,尽量减少绘制时间,对于一些界面复杂的项目,效果也是非常的显著,这里我们暂时不讨论,留待UI优化的文章来讲。

其他优化

除了上面的优化之处,还是有很多其他的优化技巧。比如根据我们具体的业务,还可以做一些类加载的优化,I/O上的优化,GC的优化,磁盘文件的优化,还可以通过保活来达到快速重启,甚至还有一些CPU锁频的黑科技。

总结

App启动优化是门无尽的学文,还是很多可以继续深挖的点。我们在实际开发中,也可以通过监控APM上的数据来进行更加针对性的优化。只有不断的进行实操,才会发现更多可以优化的方向。

最后

整理了一份 Android 性能优化的学习手册文档 ,包含了:启动优化,UI 布局优化,卡顿优化和布局优化,优化 Glidel 加载超大 gif 图等等,有需要的可以私信【性能优化】或者【点击这里

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

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

相关文章

华为OD机试题【乱序整数序列两数之和绝对值最小】用 C++ 编码,速通 (2023.Q1)

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明乱序整…

嵌入式学习笔记——概述

嵌入式系统概述前言“嵌入式系统”概念1.是个啥&#xff1f;2.可以干啥&#xff1f;3.有哪些入坑方向&#xff1f;4.入坑后可以有多少薪资&#xff1f;单片机1.什么是单片机&#xff1f;2.架构简介3.基于ARM架构的单片机结构简介总结前言 断更很长时间了&#xff0c;写博客确实…

【Leedcode】栈和队列必备的面试题(第四期)

【Leedcode】栈和队列必备的面试题&#xff08;第四期&#xff09; 文章目录【Leedcode】栈和队列必备的面试题&#xff08;第四期&#xff09;一、题目二、思路图解1.声明结构体2.循环链表开辟动态结构体空间3.向循环队列插入一个元素4.循环队列中删除一个元素5. 从队首获取元…

STM32C0介绍(1)----概述

概述 STM32C0系列微控制器是意法半导体公司推出的一款低功耗、高性能的微控制器产品。它们被设计用于需要小型、低功耗和高度可集成的应用程序&#xff0c;如传感器、消费品、电池供电设备、家庭自动化和安全等应用。该系列的微控制器采用ARM Cortex-M0内核&#xff0c;具有丰…

软件测试之缺陷

缺陷 1. 软件缺陷的概述 1.1 软件缺陷定义 软件缺陷, 通常又被叫做bug或者defect, 即为软件或程序中存在的某种破坏正常运行能力的问题、错误,其存在会导致软件产品在某种程度上不能满足用户的需求. 软件缺陷是指存在于软件(程序、数据、文档中的)那些不符合用户需求的问题.…

Ubuntu 交叉编译工具链安装

Ubuntu 交叉编译工具链安装 1 交叉编译器安装 ARM 裸机、Uboot 移植、Linux 移植这些都需要在 Ubuntu 下进行编译&#xff0c;编译就需要编译器&#xff0c;我们在第三章“Linux C 编程入门”里面已经讲解了如何在 Liux 进行 C 语言开发&#xff0c;里面使用 GCC 编译器进行代…

如何使用bomber扫描软件物料清单(SBOM)以查找安全漏洞

关于bomber bomber是一款针对软件物料清单&#xff08;SBOM&#xff09;的安全漏洞扫描工具&#xff0c;广大研究人员可以通过该工具扫描和检测软件物料清单&#xff08;SBOM&#xff09;。 当你向一家供应商索要了他们的一个封闭源代码产品的软件材料清单&#xff0c;而他们…

Spring6全面详解

Spring6全面详解 自2022年11月&#xff0c;Spring6正式发布。在Spring6中&#xff0c;对于JDK的要求最低为 17。&#xff08;17 - 19&#xff09; 部分文本摘抄于尚硅谷视频&#xff08;bilibili&#xff09;做了一定的排版和个人的理解。如果不是很熟悉&#xff0c;可以去看 …

ABAP 辨析 标准表|排序表|哈希表

1、文档介绍 本文档将介绍内表的区别和用法&#xff0c;涉及标准表、排序表、哈希表 2、用法与区别 2.1、内表种类 内表顶层为任意表&#xff0c;任意表分为索引表和哈希表&#xff0c;索引表又可分为标准表和排序表&#xff0c;结构如图&#xff1a; 2.2、内表用法 2.2.1…

GeoTools 存在 sql 注入漏洞

漏洞描述 GeoTools 是一个用于处理地理空间数据&#xff08;如地理信息系统: GIS&#xff09;的开源代码库&#xff0c;并且支持 OGC 过滤器表达式语言的解析和编码。PostGIS是PostgreSQL数据库的扩展程序&#xff0c;增加了数据库对地理对象的支持。PostGIS DataStore 为GeoT…

Android Framework-操作系统基础

最近在看《深入理解Android内核设计思想&#xff08;第2版&#xff09;》&#xff0c;个人感觉很不错&#xff0c;内容很多&#xff0c;现将书里个人认为比较重要的内容摘录一下&#xff0c;方便后期随时翻看。 计算机体系结构 硬件是软件的基石&#xff0c;所有的软件功能最…

【蓝桥杯嵌入式】点亮LED灯,流水灯的原理图解析与代码实现——STM32

&#x1f38a;【蓝桥杯嵌入式】专题正在持续更新中&#xff0c;原理图解析✨&#xff0c;各模块分析✨以及历年真题讲解✨都在这儿哦&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列专栏 - 蓝…

不用PS,也能实现抠图的工具

对于非设计专业的同学来说&#xff0c;专门下载 PS 抠图有点大材小用&#xff0c;而且运用 PS 对电脑配置一定要求。不过现在有了更多选择&#xff0c;市面上出现了越来越多的抠图软件&#xff0c;不过越多的抠图软件选择也意味着需要花费时间试错因此本文将给大家推荐 3 款非常…

递归算法(recursion algorithm)

递归算法 什么是递归算法 在过程或者函数里调用自身的算法&#xff1b; 递归算法&#xff08;recursion algorithm&#xff09;&#xff0c;通过重复将问题分解为同类的子问题而解决问题的方法&#xff0c; Java中函数可以通过调用自身来进行递归&#xff0c;大多数编程语句…

jQuery 属性操作

jQuery 属性操作 Date: February 28, 2023 Sum: jQuery属性操作、文本属性值、元素操作、尺寸、位置操作 jQuery 属性操作 设置或获取元素固有属性值 prop() 所谓元素固有属性就是元素本身自带的属性&#xff0c;比如 元素里面的 href &#xff0c;比如 元素里面的 type。 …

以太网调试经验总结

1.MDC时钟捕获 在bringup时&#xff0c;首先需要确认MDC/MDIO控制通道是否正常&#xff0c;通过捕获MDC时钟以确认MDC/MDIO的工作状态是否正常&#xff0c;MDC时钟频率由具体的PHY芯片决定&#xff0c;不同的PHY芯片支持的MDC时钟频率范围不通。 注意1&#xff1a;MDC时钟频率不…

【3.1】MySQL锁、动态规划、Redis缓存,过期删除与淘汰策略

5.4 MySQL死锁了&#xff0c;怎么办&#xff1f; RR隔离级别下&#xff0c;会存在幻读的问题&#xff0c;InnoDB为了解决RR隔离级别下的幻读问题&#xff0c;就引出了next-key 锁&#xff0c;是记录锁和间隙锁的组合。 Record Lock&#xff0c;记录锁&#xff0c;锁的是记录本身…

常用的“小脚本“-json数据处理

小背景&#xff1a; 我们公司项目中的小脚本是一些工具类&#xff0c;比如常用的是MapUtil工具类的一些方法 写公司的MapUtil工具类的方法要注意&#xff0c;方法名的命名&#xff0c;因为方法名&#xff0c;在公司的项目的某个业务流程有对方法名的进行String截取开头字符串然…

考研机试 | C++ | 王道复试班 | map专场

目录关于map查找学生信息&#xff08;清华上机&#xff09;题目代码&#xff1a;魔咒词典&#xff08;浙大机试&#xff09;题目&#xff1a;代码英语复试常用话题关于map map的一些基本操作&#xff1a; 判空 &#xff1a;map.empty()键值对的个数&#xff1a; map.size()插入…

进程、线程、协程详解

目录 前言&#xff1a; 一、进程 进程的概念 进程内存空间 二、线程 线程的定义 内核线程 用户线程 内核线程和用户线程的比较 线程的状态 三、协程 协程的定义 协程序相对于线程优势 运用场景 四、线程、协程、进程切换比较 前言&#xff1a; 有时候无法…