文章大纲

  • 引言
  • 一、确定启动时间
    • 1、API19之后直接使用Displayed过滤Log和配合reportFullyDrawn
      • 1.1、DisplayTime
      • 1.2、reportFullyDrawn()
    • 2、通过AM shell命令——adb shell am start -W 包名/包名的apk计算启动时间
  • 二、通过trace文件日志精准定位找出造成性能不佳的罪魁祸首
    • 1、生成trace文件
      • 1.1、通过代码直接生成
      • 1.2使用 Android Studio 生成 trace 文件
    • 2、读懂trace文件
    • 3、借助trace文件定位影响性能的罪魁祸首
  • 三、APP启动常见优化措施
    • 1、优化Activity 自身的View 的层次结构,尽量减小布局的层次,没有需要用到weight时尽量优先考虑使用Linearlayout,复杂层次考虑使用ConstraintLayout替代其他常见布局。
    • 2、针对项目中完成一些初始化工作的静态代码区Static Block,特别是ContentProvider中在Static Block中初始化一些UriMatcher,结合具体情况使用懒加载模式。
    • 3、尽量减轻Application的负担
      • 3.1、使用Application的一些注意事项
      • 3.2、具体的优化方向和措施
    • 4、资源优化
      • PS

引言

前篇文章花了相当大的篇幅从理论和源码角度总结了APP启动背后的故事和原理,明确了我们可以优化的地方,但是要想真正实现完美的优化,得准确定位到罪魁祸首,对症下药方能药到病除,这篇就总结下实战中APP启动优化的措施和经验分享。性能优化系列文章链接:

  • Android进阶——性能优化之APP启动时黑白屏的根源解析及对应的优化措施小结(一)
  • Android进阶——性能优化之APP启动过程相关源码解析(二)
  • Android进阶——性能优化之APP启动速度优化实战总结(三)
  • Android进阶——性能优化之布局渲染原理和底层机制详解(四)
  • Android进阶——性能优化之布局优化实战经验小结(五)
  • Android进阶——性能优化之内存管理机制和垃圾采集回收机制(六)
  • Android进阶——性能优化之内存泄漏和内存抖动的检测及优化措施总结(七)
  • Android进阶——性能优化之进程提权与保活原理及手段完全解析(八)
  • Android进阶——性能优化之进程提权与拉活原理及手段完全解析(九
  • Android进阶——性能优化之一种更高效更轻量的序列化方案Protocol Buffer完全攻略(十)

一、确定启动时间

俗话说工欲善其事,必先利其器。如果想要优化 App 的启动速度,首先得明确知道当前Activity的启动时间,方法有多种。

1、API19之后直接使用Displayed过滤Log和配合reportFullyDrawn

1.1、DisplayTime


在API19之后Android在系统Log中自动增加了DisplayTime对应的Log信息,过滤ActivityManager以及Display这两个关键字,可以找到系统中的这个Log

$ adb logcat | grep “ActivityManager”
04-21 19:34:50.997 879-904/? I/ActivityManager: Displayed com.android.systemui/.recent.RecentsActivity: +846ms
04-21 19:35:02.277 879-904/? I/ActivityManager: Displayed com.crazymo.ubxtech/.view.activity.MainActivity: +2s514ms


通过这种方式得到时间其实Activity启动,到对应Layout全部显示的耗费的时间,但并不包括具体业务数据的加载,因为很多App在加载时会使用懒加载模式,在异步拉取数据之后,再刷新对应的界面。

1.2、reportFullyDrawn()


上面的系统日志中的Display Time只是布局的显示时间,并不包括一些数据的懒加载等消耗的时间,如果我们需要计算懒加载部分的时间,可以在UI刷新方法里手动调用reportFullyDrawn()上报时间。

public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Void> {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}@Overridepublic void onLoadFinished(Loader<Void> loader, Void data) {// 加载数据// 上报reportFullyDrawnreportFullyDrawn();}@Overridepublic Loader<Void> onCreateLoader(int id, Bundle args) {return null;}@Overridepublic void onLoaderReset(Loader<Void> loader) {}
}

2、通过AM shell命令——adb shell am start -W 包名/包名的apk计算启动时间

本质上adb shell am start -W 命令是执行了om.android.commands.am.AM类里的相应方法,由ActivityRecord的reportLaunchTimeLocked赋值的并返回到AM的runStart方法内部(具体就是这个地方:result = mAm.startActivityAndWait(null, null, intent, mimeType,null, null, 0, mStartFlags, profilerInfo, null, mUserId),事实上这句代码执行完毕之后Activity才会显示出来)

D:\BizProject\UBXTech>adb shell am start -W com.crazymo.ubxtech/.view.activity.MainActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.crazymo.ubxtech/.view.activity.MainActivity }
Warning: Activity not started, its current task has been brought to the front
Status: ok
Activity: com.crazymo.ubxtech/.view.activity.MainActivity
ThisTime: 602
TotalTime: 602
WaitTime: 766
Complete

此法获取的启动时间非常精准,可精确到毫秒,另外当启动时间过长(一般是超过10s,status就会为timeout,不显示thisTime和TotalTime)

  • ThisTime——最后一个启动的Activity的启动耗时
  • TotalTime—— 自己的所有Activity的启动耗时
  • WaitTime—— ActivityManagerService启动App的Activity时的总时间(包括当前Activity的onPause()和自己Activity的启动)

举个例子:1.上一个Activity的onPause()——2.系统调用AMS耗时——3.第一个Activity(也许是闪屏页)启动耗时——4.第一个Activity的onPause()耗时——5.第二个Activity启动耗时那么,ThisTime表示5(最后一个Activity的启动耗时)。TotalTime表示3.4.5总共的耗时(如果启动时只有一个Activity,那么ThisTime与TotalTime应该是一样的)。WaitTime则表示所有的操作耗时,即1.2.3.4.5所有的耗时。每次给出的时间可能并不一样,而且应用从首次安装启动到后面每次正常启动,时间都会不同,区别于系统是否要分配进程空间。

二、通过trace文件日志精准定位找出造成性能不佳的罪魁祸首

通过上面的确定时间,只能知部分Activity的启动时间,还不能精确定位到具体是哪一个方法造成的,幸运的是Android 已经内置了内置的一个工具——TraceView 它可以加载 trace 文件,用图形的形式展示代码的执行时间、次数及调用栈,便于我们分析。trace 文件是 log 信息文件的一种,可以通过代码,Android Studio或者 DDMS 生成,配合使用 Android SDK 提供的其他工具可以生成很多 log 文件,便于我们分析当前应用的内存、布局等状况

1、生成trace文件

生成trace文件的方法一共有两种:通过代码Debug.startMethodTracing和直接通过AndroidStudio的界面操作(其实还有一种可以通过DDMS来生成)

1.1、通过代码直接生成

代码很简单,当你调用开始代码**Debug.startMethodTracing(“tracename”)的时候,系统会生产 trace 文件,并且产生追踪数据,当你调用结束代码Debug.stopMethodTracing()**时,会将追踪数据写入到 trace 文件中,也通过Debug.startNativeTracing()和Debug.stopNativeTracing()来开始和停止追踪本地的方法,需要注意下系统版本Android 26的时候好像不能用了,调用的时候直接抛异常?

Debug.startMethodTracing("cmotrace"); //开始 trace
//一大段可疑的代码逻辑...
Debug.stopMethodTracing(); //结束代码

1.2使用 Android Studio 生成 trace 文件

Android Studio 内置的Android Profiler可以快速生成trace文件,在 CPU 监控的那栏会有一个小红点按钮,启动应用后,点击一次后开始追踪相当于代码调用 startMethodTracing,再点一次停止追踪。

2、读懂trace文件

通过traceView我们了解到一些方法的执行时间、次数以及调用关系,也可以搜索过滤特定的内容。

另外鼠标悬浮到黄色的矩形上,会显示对应方法的开始、结束时间,以及自己占用和调用其他方法占用的时间比例。

3、借助trace文件定位影响性能的罪魁祸首

所谓技巧只有一条:先找到消耗时间最长的矩形框,然后结合代码分析,这里要注意的是有时候并不是说找到最长的矩形对应的方法就可以了,还需要逐层查找,查找出到底具体是什么原因造成的,内部的某个方法还是自身的逻辑太过复杂造成的。

三、APP启动常见优化措施

通常APP 优化不宜过早,也不宜太过频繁,否则会徒增成本所以得把握好这个时间。

1、优化Activity 自身的View 的层次结构,尽量减小布局的层次,没有需要用到weight时尽量优先考虑使用Linearlayout,复杂层次考虑使用ConstraintLayout替代其他常见布局。

2、针对项目中完成一些初始化工作的静态代码区Static Block,特别是ContentProvider中在Static Block中初始化一些UriMatcher,结合具体情况使用懒加载模式。

3、尽量减轻Application的负担

Application是程序的主入口,特别是很多第三方SDK都会需要在Application的onCreate里面做很多初始化操作,再加上自己的一些库的初始化,会让整个Application不堪重负。

3.1、使用Application的一些注意事项

  • Application的构造器方法、attachBaseContext()、onCreate()方法中不要进行耗时操作的初始化,一些数据预取放在异步线程中,可以采取Callable实现

  • 对于把对sp的初始化放在异步线程中处理,因为sp的特性在初始化时候会对数据全部读出来存在内存中,所以这个初始化放在主线程中不合适,反而会延迟应用的启动速度;

  • 对于MainActivity,由于在获取到第一帧前,需要对contentView进行测量布局绘制操作,尽量减少布局的层次,考虑StubView的延迟加载策略,所以需要避免在onCreate、onStart、onResume方法中做耗时操作。

3.2、具体的优化方向和措施

除了需要在开发时候遵守一些规范,还可以从以下几个方向考虑:

  • 多线程初始化——利用多线程让Application的onCreate里面尽可能的少做事情,把一些无特殊要求的第三方框架的初始化工作放到子线程,最简单的形式直接new Thread()然后放到run()方法,当然也可以通过公共的线程池来进行异步的初始化工作,这个是最能够压缩启动时间的方式,不过有些库或SDK是必须放在主线程初始化的,这时候可以考虑延迟初始化

  • 延迟初始化 ——延迟初始化并不是减少了启动时间,而是让耗时操作让位、让资源给UI绘制,将耗时的操作延迟到UI加载完毕后,所以,这里建议通过mDecoView.post方法,来进行延迟加载,因为ContentView就是通过mDecoView.addView加入到根布局的,所以,通过这种方式,可以让延迟加载的内容,在ContentView初始化完毕后,再进行执行,保证了UI绘制的流畅性。

getWindow().getDecorView().post(new Runnable() {@Override public void run() {...耗时操作}
});

另一方面也可以设置为单例,然后再使用的时候再进行初始化。

  • IntentService异步初始化
    其实也算是多线程的一种,IntentService是继承于Service并处理异步请求的一个类,在IntentService的内部,有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要去手动控制。
public class InitIntentService extends IntentService {public static final String ACTION = "com.crazymo.performance.upgrade";public InitIntentService() {super("InitIntentService");}public static void start(Context context) {Intent intent = new Intent(context, InitIntentService.class);intent.setAction(ACTION);context.startService(intent);}@Overrideprotected void onHandleIntent(Intent intent) {//处理耗时操作}
}

将耗时任务丢到IntentService中去处理,系统会自动开启线程去处理,同时,在任务结束后,还能自己结束Service,最后只需要在Application或者Activity的onCreate中去启动这个IntentService即可,最后别忘了在清单中声明Service。

@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);InitIntentService.start(this);
}
  • 使用ActivityLifecycleCallbacks监控Activity的生命周期
    监控到所有Activity的生命周期,在这里,我们就可以通过onActivityCreated这样一个回调,来将一些UI相关的初始化操作放到这里,同时,通过unregisterActivityLifecycleCallbacks来避免重复的初始化。同时,这里onActivityCreated回调的参数Bundle,可以用来区别是否是被系统所回收的Activity。
public class MainApplication extends Application {@Overridepublic void onCreate() {super.onCreate();// 初始化基本内容// ……registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {@Overridepublic void onActivityCreated(Activity activity, Bundle savedInstanceState) {unregisterActivityLifecycleCallbacks(this);// 初始化UI相关的内容// ……}@Overridepublic void onActivityStarted(Activity activity) {}@Overridepublic void onActivityResumed(Activity activity) {}@Overridepublic void onActivityPaused(Activity activity) {}@Overridepublic void onActivityStopped(Activity activity) {}@Overridepublic void onActivitySaveInstanceState(Activity activity, Bundle outState) {}@Overridepublic void onActivityDestroyed(Activity activity) {}});}
}

4、资源优化

优化布局、布局层级,一个是优化资源,尽可能的精简资源、避免垃圾资源,经过混淆、使用webp或者其他工具尽量优化资源等等

PS

从早上11点开始一直到晚上1点终于把APP黑白屏以及APP启动背后的原理流程基本理清,这里插一句有些人说黑白屏机制可以避免,个人觉得这是不能避免的,所谓的避免仅仅是障眼法,因为从向系统申请内存到系统准备好内存从宏观角度上来说是需要消耗一定的时间的,但是如果某个ROM进行了优化能使这个时间在一个很小的阈值范围内,人眼都无法察觉,那可以说是避免了,至于文章内跨进程通信(在这一部分主要体现在于怎么从调用这个类的方法缺可以突然回调到另一个类的方法里)部分会留到后面分析Binder机制时专门讲解,最大的一个感受就是世上无难事只怕有心人,沉下心慢慢啃总有一天你会肯清楚明白的,书山有路勤为径,学海无涯乐作舟,共勉!!

Android进阶——性能优化之APP启动速度优化实战总结(三)相关推荐

  1. Android进阶——性能优化之内存管理机制和垃圾采集回收机制(六)

    文章大纲 引言 一.内存泄漏和内存溢出概述 二.Java运行时内存模型 1.线程私有数据区 1.1.程序计数器PC 1.2.虚拟机栈 1.3 本地方法栈 2.所有线程共享数据区 2.1.Java堆 2 ...

  2. App 启动速度优化

    前言​​​​​​​ APP打开的一瞬间速度快慢:就好比人的第一印象,快速的打开一个应用往往给人很舒服的体验.app经常性卡顿启动速度很慢,这无疑是对用户的流失. 启动方式介绍 APP启动的方式分为3种 ...

  3. Android面试-Android性能优化和内存优化、APP启动速度一线大厂的实战案例解析

    一.Android 内存管理机制 二.优化内存的意义 三.避免内存泄漏 四.优化内存空间 五.图片管理模块的设计与实现 六.总结 深入探索Android内存优化 第一章.重识内存优化 第二章.常见工具 ...

  4. Android 进阶——性能优化之电量优化全攻略及实战小结(二)

    文章大纲 引言 一.在低电耗模式和应用待机模式下进行测试 1.在低电耗模式下测试您的应用 2.在应用待机模式下测试您的应用 3.列入白名单的可接受用例 4.确定当前充电状态 5.监控充电状态变化 6. ...

  5. App性能优化(布局优化,线程优化,app瘦身优化,页面切换优化,App启动优化,内存优化)

    Android APP性能优化(最新总结) 在目前Android开发中,UI布局可以说是每个App使用频率很高的,随着UI越来越多,布局的重复性.复杂度也随之增长,这样使得UI布局的优化,显得至关重要 ...

  6. android 应用性能监控软件,App性能监控工具,卡顿

    (609条消息) android 应用性能监控软件,App性能监控工具_weixin_39940154的博客-CSDN博客 APP性能监测的各种工具 - ClareBaby01 - 博客园 (cnbl ...

  7. MATLAB App Designer入门实战(三)

    这期文章中的问题虽然比较少,但是综合性较强. 系列文章目录: MATLAB 手把手带你制作第一个APP designer程序(电子词典) MATLAB App Designer入门实战(一) MATL ...

  8. Android性能调优:App启动速度优化

    一.App启动分类 1.冷启动 Cold start 在启动应用前,系统还没有App的任何进程.比如设备开机后应用的第一次启动,系统杀掉应用进程 (如:系统内存吃紧引发的 kill 和 用户主动产生的 ...

  9. 性能优化:Android App启动速度优化

    一.App启动分类 1.冷启动 Cold start 在启动应用前,系统还没有App的任何进程.比如设备开机后应用的第一次启动,系统杀掉应用进程 (如:系统内存吃紧引发的 kill 和 用户主动产生的 ...

  10. Android开机启动速度优化 app启动速度优化

    众所周知Android开机启动速度较慢,于是如何加快启动速度便成为一个值得讨论的问题.在查阅过许多资料后(特别是Google Group的android-platform),我整理总结出下面几点基本看 ...

最新文章

  1. 关于捕获键盘信息的processDialogkey方法
  2. 分享一首诗歌关于人生 时间 成就 得失的
  3. Linux上jdk的安装
  4. python【力扣LeetCode算法题库】2-两数相加
  5. 删除-Trustzone-TEE-ATF
  6. OpenGL indirect material间接材料的实例
  7. 数仓备机DN重建:快速修复你的数仓DN单点故障
  8. Beta版本(有更改)
  9. JavaScript设计模式之创建型设计模式
  10. Box2D实现Super Mario之关键技术分析——mario下蹲通过低矮障碍物
  11. 给Debian浏览器安装flash播放插件
  12. C# 文件查询管理器
  13. php buildconf,PHP Extension开发 Unix Build System配置 conf
  14. Python数据分析(二) —— 进阶绘制双折线图
  15. MySQL的函数以及相关案例与练习
  16. python画饼图柱状图_荐【python数据分析(24)】Matplotlib库基本图形绘制(1)(线形图、柱状图、堆叠图、面积图、填图、饼图)...
  17. 单细胞基础教程:跨条件整合分析
  18. 初识selenium--发送QQ邮件
  19. 数组传参(一维数组、二维数组)
  20. js 中 throttle 的实现

热门文章

  1. TVB十大女星比美十大名花
  2. 工具:dlf 强制删除文件或文件夹
  3. 二分查找边界问题总结
  4. 数据结构之队列的应用-超好玩的汽车加油站模拟器(C语言)
  5. shift键计算机功能,电脑shift键常用快捷键使用攻略
  6. 新网站如何才能被百度快速收录?
  7. Django(十二):django支付(微信支付宝)+项目部署(虚拟机、docker、云服务器)
  8. 有关世界上第一台计算机的知识,计算机基础知识 1、世界上第一台电子计算机诞生于 A) 1943年 B) 1946年.doc...
  9. 美国华盛顿州立大学计算机排名,2019上海软科世界一流学科排名计算机科学与工程专业排名华盛顿州立大学排名第301-400...
  10. 张亮:十万级并发任务调度框架 ElasticJob 的定位与设计理念