2.App启动优化
1 2 3 4 5 //App启动优化 1.获取启动耗时: 1.手动埋点,aop统一处理,systrace/traceview获取 2.优化手段:延迟加载或者异步加载(充分利用CPU,将任务放到子线程中执行分担主线程任务,jetpack的App Startup框架,或者自定义使 用线程池异步加载)
一、启动优化介绍
背景介绍
1 2 1.第一体验(应用启动的速度是第一感受) 2.八秒定律(如果一个页面8秒还没有响应,70%的用户将放弃等待)
启动分类
1 2 3 4 App startup time - 冷启动 - 热启动 - 温启动
冷启动: 耗时最多,衡量标准 (经历了一系列的流程,因此耗时较多)
热启动:最快
温启动:较快 (重走activity的生命周期,不会重走进程的创建或application的创建等)
相关任务
1 2 3 4 5 6 7 8 9 10 11 12 1.冷启动之前(系统的操作) - 启动App - 加载空白Window - 创建进程 2.随后任务 - 创建Application - 启动主线程 - 创建MainActivity 3.随后任务 - 加载布局 - 布置屏幕 - 首帧绘制
优化方向
1 Application和Activity生命周期这个阶段
二、启动时间的测量方式
adb命令
1 2 3 4 5 6 7 adb shell am start -W packagename/首屏Activity - ThisTime:最后一个Activity启动耗时 - TotalTime:所有Activity启动耗时 - WaitTime:AMS启动Activity的总耗时 缺点: - 线下使用方便,不能带到线上 - 非严谨、精确时间
手动打点
1 2 3 1. 精确,可带到线上,推荐使用2. 避开误区,采用Feed第一条展示3. addOnDrawListener要求API16
1 2 3 4 5 6 7 8 9 10 11 public class LaunchTimer { private static long sTime; public static void startRecord () { sTime = System.currentTimeMillis (); } public static void endRecord () { long cost = System.currentTimeMillis () - sTime; Log.e ( "tyl" ,"cost=" + cost); } }
1 2 3 4 5 6 7 8 startTime的执行地方: - Application的attachBaseContext方法中 endTime的执行地方: - 误区: activity的onWindowFocusChanged只是首帧时间,用户还没有看到画面 - 正解:真实数据展示,Feed第一条展示 (recycleView的adapter的onBindViewHolder()-> holder.linearLayout.getViewTreeObserver().addOnPreDrawListener()-> onPreDraw()中执行 //onWindowFocusChanged和Feed中执行结束相差100多ms
三、启动优化工具选择
注意
1 2 - 两种方式互相补充 - 正确认识工具及不同场景选择合适的工具
traceview和systrace各自的作用和区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Traceview 和 Systrace 都是用于 Android 应用程序性能分析和调试的工具,但它们在功能和用途上有一些区别: Traceview: Traceview 是 Android 开发工具包(SDK)中提供的一种效率分析工具,用于分析应用程序的方法调用、线程执行时间等信息。 开发人员可以使用 Traceview 工具来查看应用程序的性能数据,包括方法的执行时间、CPU 使用率、线程的执行情况等。 Traceview 会生成一个包含方法调用图、执行时间和线程等信息的 trace 文件,开发人员可以通过 Android Studio 或其他工具进行分析和 查看。 主要用于分析应用程序代码的性能瓶颈、方法执行耗时等问题,帮助优化应用程序的性能。 Systrace: Systrace 是 Android 系统级的跟踪工具,用于分析整个系统的性能数据,包括应用程序、系统服务、内核等的运行情况。 开发人员可以使用 Systrace 工具来查看应用程序与系统之间的交互情况、系统资源占用情况、视图绘制耗时等信息。 Systrace 通过采集系统事件、函数调用、线程、CPU 使用情况等信息,生成一个详细的时间线,展示系统运行时的各种活动。 主要用于分析应用程序与系统交互导致的性能问题,如绘制卡顿、I/O 操作慢、系统资源竞争等,帮助优化应用程序与系统的性能。 总的来说,Traceview 主要用于分析应用程序代码的性能问题,而 Systrace 则更侧重于分析整个系统的性能问题,包括应用程序与系统之 间的交互。开发人员可以根据具体情况选择合适的工具进行性能分析和调试,以优化应用程序的性能和稳定性。
traceview
1 2 3 traceview的优点: 图形的形式展示执行时间、调用栈等 信息全面,包含所有线程
使用方式(查看指定方法执行时间)
1 2 3 4 5 6 7 8 9 10 11 1.String tracPath=getExternalCacheDir()+"/myTrac.trace"; Debug.startMethodTracing(tracPath);//tracPath 生成文件的文件路径,需要读写权限 ........ 要测试的方法 ...... 2.Debug.stopMethodTracing(); 3.生成文件在sd卡://tracPath=storage/emulated/0/Android/data/com.withub.android.cloudsharingcourt/cache/myTrac.trace //如在application的oncreate()方法中第一行加入Debug.startMethodTracing("");, //在oncreate()最后一行加入Debug.stopMethodTracing(); 4.点击在androidStudio的右侧有个Device File explorer,这个工具是方便打开手机中的文件; //根据传入的指定路径找到生成trace文件
trace文件打开后如下图:
底部4个菜单栏的含义:
1.CallChart:垂直方向是方法的调用栈(如A调用B则B在A的下面),另外系统API的颜色是橙色,应用自身的代码调用是绿色的,其他第三方的api调用则是蓝色;
2.FlameChart:火焰图,收集相同调用顺序完全相同的函数,相对callChart作用要小很多;
3.TopDowm:函数的调用列表,相当于CallChart的文字版;
选中方法点击右键可以点击Jump to Source跳转到对应代码;
可以查看指定方法的执行时间,Total=self+Children,self等于执行这个A方法的时间children则是A方法内其他方法的所用时间;
右上角有个下拉选项:Wall Clock Time(所在线程真正所用的时间),threadTime(cpu所执行的时间)
4.BottomUp:和FlameChar类似重要性不高,显示的方法的被调用列表,点击B查看B被谁调用的;
traceview的缺点
1 2 3 1.运行时开销严重,整体都会变慢 2.可能会带偏优化方向 3.traceview与cpu profiler(2者结合来分析)
systrace
1 2 3 4 5 6 7 8 9 10 11 12 1.结合Android内核的数据,生成Html报告 2.API18以上使用,推荐TraceCompat 3.使用方式 python systrace.py -t 10 [other-options] [categories] //官方文档:https://developer.android.com/studio/commandline/systrace#command options 代码中埋点: TraceCompat.beginSection("AppOncreate"); ........ 要测试的方法 ...... TraceCompat.endSection();
脚本示例:
-b 收集包的大小 ,-t 时间,-a 包名,-o 输出生成的文件名
systrace默认图:
kernel:不同时间段只运行了4核,有时8核(如何高效的运用cpu);
可以查看不同线程的运行情况,点击具体方法可以展开方法的详细运行时间
1 2 3 4 5 6 7 8 systrace优点: 轻量级,开销小 直观反映cpu利用率 注意: cputime与walltime区别 - walltime是代码执行时间 - cputime是代码消耗cpu的时间(重点指标) 举例:锁冲突
四、优雅获取方法耗时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 常规方式 -背景:需要知道启动阶段所有方法耗时 -实现:手动埋点 -long time = System.currentTimeMillis (); -long cost = System.currentTimeMillis () - time; - 缺点:侵入性强,工作量大 AOP介绍 -Aspect Oriented Programming,面向切面编程 -针对同一类问题的统一处理 -无侵入添加代码 AOP实战 -AspectJ使用(辅助实现AOP) -classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0//旧版本在build.gradle buildScripe->dependencies中 -implementation ' org.aspectj:aspectjrt:1.8 .+ -apply plugin: 'android-aspectjx//旧版本app build.gradle的顶部 -Join Points使用 -程序运行时的执行点,可以作为切面的地方 -函数调用、执行 -获取、设置变量 -类初始化 -PointCut -带条件的JoinPoints -Advice -一种Hook,要插入代码的位置 -Before : PointCut之前执行 -After:PointCut之后执行 -Around:PointCut之前、之后分别执行 -语法简介 @Before("execution(*android.app.Activity.on**(..))" public void onActivityCalled(JoinPoint joinPoint) throws Throwable { ... } -Before:Advice,具体插入位置 -execution :处理Join Point的类型,call、execution -(*android.app.Activity.on**(..)):匹配规则 -onActivityCalled :要插入的代码
实战代码:任意自建的一个类
AOP的优点:无侵入性,修改方便
五、异步优化
优化小技巧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Theme切换:感官上变快了;(实际启动时间未变快) 1. <layer-list xmIns:android="http://schemas.android.com/apk/res/androidandroid:opacity="opaque"><!-- The background color, preferably the same as your normal theme --><item android:drawable="@android:color/white"/><!-- Your product logo - 144dp color version of your app icon --><item> 4 <bitmap android:src="@drawable/product_ogo_144dp' android:gravity="center"/> </item> </layer-list> 2. <activity ...android:theme="@style/AppTheme.Launcher”/> 3. @Override protected void onCreate(Bundle savedInstanceState){ // super.onCreate之前切换回来 setTheme(R.style.Theme_MyApp); super.onCreate(savedInstanceState); }
异步优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class MyApp extends MultiDexApplication { //同步工具类,用来协调多个线程之间的同步,用来作为线程间的通信而不是互斥作用 private CountDownLatch countDownLatch = new CountDownLatch(1); @Override public void onCreate() { super.onCreate(); //动态获取设备核心数量,老版本生效新版本未验证; int CORE_POOL_SIZE = Math.max(2,Math.min(Runtime.getRuntime().availableProcessors() - 1,4)); //创建线程池,java原生代码传入核心数; ExecutorService executorService = Executors.newFixedThreadPool(CORE_POOL_SIZE); //一个任务一个线程,多个任务则submit多个 executorService.submit(new Runnable() { @Override public void run() { initTask1(); } }); executorService.submit(new Runnable() { @Override public void run() { initTask2(); //initTask2执行完毕后通知countDownLatch; countDownLatch.countDown(); } }); executorService.submit(new Runnable() { @Override public void run() { initTask3(); } }); ... try { countDownLatch.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
异步优化注意
1 2 3 1.不符合异步要求 2.需要在某阶段完成 3.区分CPU密集型和IO密集型任务
六、异步优化启动器
常规异步痛点
1 2 3 1.代码不优雅 2.场景不好处理(依赖关系) 3.维护成本高
启动器介绍:
1 核心思想: 充分利用CPU多核,自动梳理任务顺序
启动器流程
1 2 3 1.代码Task化,启动逻辑抽象为Task 2.根据所有任务依赖关系排序生成一个有向无环图 3.多线程按照排序后的优先级依次执行
启动器流程图:
启动器起源码(启动器代码太多):https://gitee.com/cq_tyl/performance-optimization/tree/master(utils/TaskDispatcher.java)
1 2 3 4 5 6 7 8 9 10 示例: TaskDispatcher.init( context:PerformanceApp.this); TaskDispatcher dispatcher = TaskDispatcher.createInstance(); dispatcher.addTask(new InitAMapTask()).addTask(new InitStethoTask())InitweexTask()) .addTask(newaddTask(newInitBuglyTask())addTask(newInitFrescoTask()) .addTask(newInitJPushTask()) .addTask(newInitUmengTask()) .addTask(newGetDeviceIdTask()) .start(); dispatcher.await();//如有需要等待完成后才继续往下走
七、更优的延迟初始化的解决方案
常规方案:将延迟操作放到显示之后执行
1 2 3 4 5 1.New Handler().postDelayed 2.Feed展示后调用 缺点: - 时机不便控制 - 导致Feed卡顿
更优方案:对延迟任务进行分批初始化
实战源码:DelayInitDispatcher.java及其引用 (https://gitee.com/cq_tyl/performance-optimization/tree/master)
八、启动优化其他方案
优化总方针:
注意事项
1 2 3 4 5 6 7 8 1.wall time与cpu time 2.cpu time才是优化方向 3.按照systrace及cpu time跑满cpu 4.监控的完善 - 线上监控多阶段时间(App、Activity、生命周期间隔时间 ) - 处理聚合看趋势 5.收敛启动代码修改权限 - 结合Ci修改启动代码需要Review或通知
其他方案
1 2 3 4 5 6 7 8 9 10 11 1.提前加载SharedPreferences - Multidex之前加载,利用此阶段CPU - 覆写getApplicationContext0)返回this 2.启动阶段不启动子进程 - 子进程会共享CPU资源,导致主进程CPU紧张 - 注意启动顺序:App onCreate 之前是ContentProvider 3.类加载优化:提前异步类加载 - Class.forName0只加载类本身及其静态变量的引用类 - new 类实例 可以额外加载类成员变量的引用类 4.启动阶段抑制GC 5.CPU锁频
九、问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 1.你做启动优化是怎么做的 - 分析现状、确认问题 - 针对性优化 - 长期保持优化效果 2.是怎么异步的,异步遇到问题没有 - 体现演进过程 - 详细介绍启动器 3.你做了启动优化,觉得有哪些容易忽略的注意点 - cpu time与wall time - 注意延迟初始化的优化 4.版本迭代导致的启动变慢有好的解决方式吗 - 启动器 - 结合CI - 监控完善