以前做手机的时候,我非常重视app的性能优化。其实一直以来,在工作中我总会去强调性能优化的重要性。但是,很多时候,由于一些外界因素,我们对app的一些性能指标不会那么重视。但是,性能优化依然是做好一个产品的重中之重。试想一下,如果用户费了很多时间和流量下载了我们的app,当人家安装好启动app时,却发现我们的app点了之后,很长时间没反应。那如果我是用户,我会二话不说卸载掉。因此,app的性能优化还是很重要且很有必要的,我接下来会总结一下Android性能优化的一些相关技术和知识,这篇博客主要总结一下启动优化。

一、前言

在我总结启动优化之前,我先说些题外话。可能有很多朋友,工作很多年了,也没接触过或者没有实际做过性能优化。我说这个,并不是想展示自己多牛逼,相反,恰恰是因为自己初入职场时太菜。我自己接触性能优化算是职业生涯中比较早的时候吧,而这竟完全是因为自己第一份工作时,写出了非常烂的代码。校招入职后一个多月,公司6个月的培养计划我早就提前执行完了,信心满满的我,主动找主管要了一个开发工作,是基于某公司的算法库实现某种照片美化的功能。当然,那时候还没有正式排期,只是我先拿到算法库开始集成。JNI,NDK,查一些资料,最后搭建好了NDK开发环境。用了没几天时间,开发好了。拿去给我主管看,我主管看后说,你这速度太慢了,掐着秒表给我计算,足足7S!!!我当时不解了,做功能不就是把功能做出来就好了吗,什么是性能,不知道啊。我主管就让我去尝试优化一下,看看能不能优化到1S以内。我当时一听,7S到1S,虽然我数学不好,但是听到这个,我心里还是咯噔了一下,这怎么可能,这已经是动用了我所有的能力了。好,也就是从那时候开始,正式接触性能优化,这也伴随了我很长一段时间的职业生涯。

二、app启动

首先,我们引用一下谷歌给出的app启动的三种方式:冷启动,热启动,温启动。啥?app还有这么多启动方式,冷,热,温,难道是跟启动app时的环境温度有关?当然了,这么说我觉得也是说的过去的。只不过,这个环境,不是我们理解的室内外环境,而是系统环境。

1、冷启动

什么是冷启动,就是在系统中不存在当前app进程的情况下,点击app图标启动app。比如初次安装完app启动app或者清除app数据后启动app,这样app的启动需要经过两个步骤:(1)application的创建(2)activity生命周期。在当前系统中不存在任何该app的进程实例,不存在任何的activity实例,所以说,当前的系统环境是“冷”的,这样的app启动速度也是最慢的。

2、温启动

温启动,其启动速度是介于冷启动和热启动之间的。温启动,就是说在application存在的情况下去启动app,这样只会走activity的生命周期,也就是冷启动的第二阶段。例如:某些手机系统的app,点击系统返回键退出app,再重新启动app。这种时候,app的进程还是存在的,只执行activity的生命周期。所以说,当前的系统环境是“温”的,因为进程还在。

3、热启动

毫无疑问,这是启动最快的了。热启动,就是在application和activity都存在的情况下启动app,这样只会走activity生命周期的一部分。例如,最常见的就是点击系统home键或者recent键后再次进入app,其实就是前后台的切换。所以说呢,当前的系统环境是热的,因为我的进程和activity都在。

三、优化方向

在说优化防线之前,再详细说一下冷启动的过程。其实,我们上面说要经过两个步骤,有点不太准确。在创建application之前,系统还会做一些准备工作。

(1)创建application前,系统会做一些准备工作,具体如下:启动app——>创建空白Window——>创建进程。

(2)创建了application后,接下来的一系列流程如下:创建进程——>启动主线程——>启动Activity

(3)启动Activity后,我们就知道基本的流程了,那就是执行Activity生命周期,在各个生命周期中加载布局,展示布局。

通过上面对冷启动流程的总结,毫无疑问,我们优化的方向,就是针对application和Activity来进行,更详细点,就是针对application和activity的生命周期来进行。

四、启动时间的测量

1、adb命令

使用如下adb命令可以获取app启动的时间:adb shell am start -W [package]/[.MainActivity]。例如:

adb shell am start -W com.example.tuduoptimize/.MainActivity

使用上述命令,启动我的tuduoptimize项目,打印出的信息如下:

(1)ThisTime:是打开最后一个Activity的时间。

(2)TotalTime:是打开所有Activity的时间。

(3)WaitTime:AMS启动activity的总耗时。

2、打印activity启动时间

这种方式,其实就是在我们认为开始启动的时间点打印当前系统时间,在我们认为启动完成后的地方打印当前系统时间,取二者的和,得到启动耗时。

对于上面打印系统时间的方法,写一个工具类,方便我们打印:

package com.example.tuduoptimize;import android.util.Log;public class LunchTimeUtil {private static final String TAG = "LunchTimeUtil";private static long startTime;public static void startRecord() {startTime = System.currentTimeMillis();}public static void endRecord(String msg) {long costTime = System.currentTimeMillis() - startTime;Log.d(TAG, "CostTime:" + costTime + "msg:" + msg);}}

(1)开始启动

开始启动的时间,我们一般放在Application的attachBaseContext方法中。

@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);LunchTimeUtil.startRecord();}

(2)结束启动

这个结束启动的时间,我们需要根据我们具体的项目来确定。例如我的空项目,只是加载了一个布局,那么我放在onWindowsFocusChanged()中。

@Overridepublic void onWindowFocusChanged(boolean hasFocus) {super.onWindowFocusChanged(hasFocus);LunchTimeUtil.endRecord("onWindowFocusChanged");}

五、性能优化工具TraceView

进行性能优化,需要借助一些工具,帮助我们分析app启动耗时以及耗时的地方。相信大家都或多或少的用过或者听说过一些性能优化工具。在这里呢,我介绍一下TraceView的使用。

在使用TraceView分析trace文件之前,我们首先得得到一个trace文件。那么如何生成trace文件呢?其实很简单,我们在我们需要生成trace文件的方法中,简单的两行代码即可生成trace文件。例如我在Application的onCreate中,分析initSdk这个方法:

@Overridepublic void onCreate() {super.onCreate();Debug.startMethodTracing("tudu");initSdk();Debug.stopMethodTracing();}

写好上述两行代码后,运行我们的app,即可生成trace文件。生成文件的路径:Android/data/packagename/file,记得在运行后刷新一下:

双击打开trace文件,进入TraceView主界面:

(1)最上面浅蓝色区域:就是我们选取抓trace文件的起止时间段

(2)THREADS:显示当前所有的线程,后面的长条是耗时

(3)下面的四个tab,可以显示方法及所耗时间,不同的tab有不同的作用,主要介绍Call Chart和Top Down:

Call Chart:

大家可以发现颜色不一样,这个颜色是有讲究的:绿色的就是我们自己写的方法,蓝色的是系统的方法。

Top Down:

在我的测试app中,我写了四个方法模拟sdk初始化(均让主线程休眠一段时间),这四个方法又放在了initSdk中,通过这个tab,我们可以很清晰的看到各个方法的耗时。

六、启动优化

在这个章节中,介绍几种启动优化的技巧。当然,我是以一个demo项目来介绍,这与我们实际的项目开发过程中会有点不同。但是,基本的思路是一样的。不知道大家有没有遇到测试提过如下问题:应用启动白屏(或黑屏时间)太久,希望优化。这个启动白屏(黑屏)时间太久,就是我们启动优化要做的工作。

1、闪屏

什么是闪屏?上面我们提到过,在application启动前,系统会创建一个空白的window,而我们的闪屏,就是在这个空白的window上做文章。

首先,我们看一下优化之前,我的demo运行效果,如下图所示。可以看到,点击图标启动app后,有2秒多的白屏时间。当然,这个是因为我在代码中动了手脚,在Application中模拟耗时操作2秒钟。

接下来,我们操作一下,如何通过闪屏来优化这个体验:

(1)我们在drawable中创建一个drawable,命名为splash_bg:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"><item><bitmapandroid:gravity="center"android:src="@drawable/splash" /></item></layer-list>

(2)自定义Theme,并且在Manifest中为我们的MainActivity配置好:

    <style name="lunchTheme" parent="Theme.AppCompat.Light.DarkActionBar"><item name="android:windowBackground">@drawable/splash_bg</item></style>
<activity android:name=".MainActivity"android:theme="@style/lunchTheme"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity>

(3)在MainActivity的onCreate中,手动把Theme修改为我们原有的Theme:

protected void onCreate(Bundle savedInstanceState) {setTheme(R.style.AppTheme);super.onCreate(savedInstanceState);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}setContentView(R.layout.activity_main);}

我们看一下优化后的效果,是不是给人一种秒开的错觉?对,实际上,我们只是把原来的白屏替换为我们自己定义的一个drawable,并且在activity的onCreate中,再替换为正确的Theme。但,这样会让用户更好去接受。

2、异步优化

先简单的介绍一下异步优化,异步优化,顾名思义,就是把线性执行的操作改为异步执行。例如,我们在主线程中做了2000ms的耗时操作。假如我们创建几个子线程,让子线程同步去进行sdk初始化等耗时操作。当然,为了比较优雅的实现多线程,我们简单的使用一下线程池。

private void initSdkWithThreadPool(){ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);service.submit(new Runnable() {@Overridepublic void run() {initSdk1();}});service.submit(new Runnable() {@Overridepublic void run() {initSdk2();}});service.submit(new Runnable() {@Overridepublic void run() {initSdk3();}});service.submit(new Runnable() {@Overridepublic void run() {initSdk4();}});}

我们看一下这样做后的优化效果:

看到上面的优化结果,大家是不是非常开心?是不是觉得不管有多少个耗时操作,我们都可以使用线程池异步来加载,就能达到上面秒开的效果?不好意思,答案是否定的。在很多情况下,我们是不能简单地使用线程池来达到我们的优化目的的。那么,什么情况呢?答案就是,我们的某些操作必须要在主线程中进行或者我们必须在主线程中用到该操作的某个产物。

针对上面的答案,可能有些朋友不太明白。我举个简单的例子,我们要在splash界面使用initSdk1方法的某个产物,而由于是异步执行,很有可能我们需要这个产物的时候,InitSdk1方法尚未执行完成。那这样的情况下,肯定会得到我们不想要的结果。例如,我们的initSdk1方法会给我们返回一个String值,而我们会通过Toast展示这个字符串,如下:

private String initSdk1() {try {//模拟sdk初始化等耗时操作Thread.sleep(500);result = "sdk1 init success";} catch (InterruptedException e) {e.printStackTrace();}mCountDownLatch.countDown();return result;}

如果我们异步执行,并且在异步方法后面去打印Toast,那么肯定拿不到“sdk1 init success”,拿到的是null,这样是不对的。那么,initSdk1这个方法我们就不让他异步执行了。我们单独把他拿出来,让他执行完后,我们再去打印我们的Toast。

initSdk1();
initSdkWithThreadPool();
Toast.makeText(this, result, Toast.LENGTH_SHORT).show();

这篇文章总结了app启动优化的一些知识,包括启动的几种方式,获取启动时间以及启动优化用到的一个性能分析工具TraceView,并且通过一个简单的demo总结了两种启动优化的方法:闪屏和异步优化。其实闪屏和异步优化只是启动优化的最常规方式,相信很多一线团队都有自己的一些启动优化的独门秘笈。

Android性能优化(一)启动优化相关推荐

  1. 安卓性能优化之启动优化

    安卓性能优化之启动优化 两个定律 2-5-8原则 八秒定律 启动方式 冷启动 热启动 温启动 启动耗时统计 系统日志 adb命令 启动耗时分析 CPU Profile 工具介绍 使用方式 数据分析 C ...

  2. Android 性能优化(一) —— 启动优化提升60%

    本文已授权微信公众号:鸿洋(hongyangAndroid)原创首发. 转载请标明出处: https://blog.csdn.net/qian520ao/article/details/8190850 ...

  3. Android性能优化:启动优化深入解析,助你全面掌握启动优化知识点

    一.启动优化概念 1.1.为什么要做启动优化? APP优化是我们进阶高级开发工程师的必经之路,而APP启动速度的优化,也是我们开启APP优化的第一步.用户在使用我们的软件时,交互最多最频繁的也就是AP ...

  4. Android性能优化之启动优化实战篇,积累总结

    目录 (1)必备Java基础 (2)设计思想解读开源框架 (3)360°全方位性能优化 (4)Android框架体系架构 (5)NDK模块开发 (6)Flutter学习进阶 (7)微信小程序开发 一. ...

  5. 安卓开发者模式!Android性能优化之启动优化实战篇,深度好文

    除了Bug,最让你头疼的问题是什么?单身?秃头?996?面试造火箭,工作拧螺丝? 作为安卓开发者,除了Bug,经常会碰到下面这些问题: 应用卡顿,丢帧,屏幕画面撕裂,操作界面刷新缓慢,UI不美观,布局 ...

  6. Android 系统性能优化(30)---Android性能全面分析与优化方案研究

    Android 性能优化 1.结合以下四个部分讲解: 性能问题分类 性能优化原则和方法 借助性能优化工具分析解决问题 性能优化指标 2性能问题分类 1.渲染问题:过度绘制.布局冗杂 2.内存问题:内存 ...

  7. 王学岗性能优化————APP启动优化(黑白屏问题的解决,trace工具的使用,热启动与冷启动的区别)

    一:手机启动 这个时候会启动HomeActivity;而各种APP图标就是HomeActivity中的控件,这些控件是可以点击的.与之对应的是,Launcher(继承了Activity)中有onCli ...

  8. Android 性能监测工具,优化内存、卡顿、耗电、APK的方法

    导语     安卓大军浩浩荡荡,发展已近十个年头,技术优化月新日异,如今 Android 9.0 代号P  都发布了,Android系统性能已经非常流畅了.但是,到了各大厂商手里,改源码自定系统,使得 ...

  9. 几乎是史上最全最实用的Android性能全面分析与优化方案研究

    结合以下四个部分讲解: 性能问题分类 性能优化原则和方法 借助性能优化工具分析解决问题 性能优化指标 性能问题分类 1.渲染问题: 过度绘制.布局冗杂 2.内存问题: 内存浪费(内存管理).内存泄漏 ...

  10. Android O 7.0 启动优化的一些思路

    启动优化其实是一个比较大的命题,在一些特地的场景下,快速启动有比较强烈的需求,这篇博客主要简单的介绍一些在android O平台上的进行启动优化的思路与想法. 而优化启动的一个很重要的东西就是如何来准 ...

最新文章

  1. Android中线程同步之Mutex与Condtion的用法
  2. WebAssembly 系列(五)为什么 WebAssembly 更快?
  3. [YTU]_2476(E3 继承了,成员函数却不可访问)
  4. 9行代码满分 【C语言】 L1-062 幸运彩票 (15分)
  5. Boost中的Timer的使用——计算时间流逝
  6. 怎样恢复oracle函数,Oracle闪回恢复误删除的表、存储过程、包、函数...
  7. java bitmap jar_Java面试中常用的BitMap代码
  8. 树--树的基本性质(JAVA)
  9. java三目运算符简化代码_如何使用传播运算符简化代码
  10. (转)Spring Boot 2 (三):Spring Boot 开源软件都有哪些?
  11. 使用Kotlin的Android AsyncTask
  12. Kail Linux渗透测试教程之ARP侦查Netdiscover端口扫描Zenmap与黑暗搜索引擎Shodan
  13. Android 添加水印View
  14. 怎样调整计算机视角,迷你世界电脑版怎么调整视角 | 手游网游页游攻略大全
  15. QXRService:基于高通QXRService获取SLAM Camera图像
  16. 【CTDB】什么是CTDB(Cluster Trivial Database)
  17. op07数据手册分析
  18. 北大方正集团收入突破千亿,在中国企业500强排名提升24位
  19. 利用f2py实现python调用fortran
  20. 正点原子STM32-串口中断服务函数USART1_IRQHandler关于USART_RX_STA的学习笔记

热门文章

  1. Win7文件夹怎么加密
  2. 对计算机的一点点看法
  3. Android实现序列化方式
  4. 局域网内查询嵌入式设备IP的几种方式
  5. 回退到首页,返回浏览器窗口历史第一页 js
  6. 升级Win10注意事项个人总结
  7. jfreechart-x轴刻度倾斜45度
  8. 游戏引擎Flax Engine分析(八)渲染
  9. java idle 机制_Flink中Idle停滞流机制(源码分析)
  10. MySQL窗口函数OVER()