写点什么

几乎包含了市面上所有启动优化方案,学习路线 + 知识点梳理

用户头像
Android架构
关注
发布于: 16 小时前

启动时需要初始化的重要方法,如数据库初始化,读取本地的一些数据。其他耗时的一些算法。


例如,启动时在 Application 和第一个 Activity 加入打点统计:


Application


@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);TimeMonitorManager.getInstance().resetTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START);}


@Overridepublic void onCreate() {super.onCreate();SoLoader.init(this, /* native exopackage */ false);TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).recordingTimeTag("Application-onCreate");}


第一个 Activity:


@Overrideprotected void onCreate(Bundle savedInstanceState) {TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).recordingTimeTag("SplashActivity-onCreate");super.onCreate(savedInstanceState);


initData();


TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).recordingTimeTag("SplashActivity-onCreate-Over");}


@Overrideprotected void onStart() {super.onStart();TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).end("SplashActivity-onStart", false);}


特点


精确,可带到线上,推荐使用。


注意


在上传数据到服务器时建议根据用户 ID 的尾号来抽样上报。onWindowFocusChanged 只是首帧时间,App 启动完成的结束点应该是真实数据展示出来的时候,如列表第一条数据展示,记得使用 getViewTreeObserver().addOnPreDrawListener(),它会把任务延迟到列表显示后再执行。


AOP(Aspect Oriented Programming)打点


面向切面编程,通过预编译和运行期动态代理实现程序功能统一维护的一种技术。


作用


利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合性降低,提高程序的可重用性,同时大大提高了开发效率。


AOP 核心概念


1、横切关注点


对哪些方法进行拦截,拦截后怎么处理。


2、切面(Aspect)


类是对物体特征的抽象,切面就是对横切关注点的抽象。


3、连接点(JoinPoint)


被拦截到的点(方法、字段、构造器)。


4、切入点(PointCut)


对 JoinPoint 进行拦截的定义。


5、通知(Advice)


拦截到 JoinPoint 后要执行的代码,分为前置、后置、环绕三种类型。


准备


首先,为了在 Android 使用 AOP 埋点需要引入 AspectJ,在项目根目录的 build.gradle 下加入:


classpath 'com.hujiang.aspectjx:gradle-android-plugin- aspectjx:2.0.0'


然后,在 app 目录下的 build.gradle 下加入:


apply plugin: 'android-aspectjx'implement 'org.aspectj:aspectjrt:1.8.+'


AOP 埋点实战


JoinPoint 一般定位在如下位置


1、函数调用 2、获取、设置变量 3、类初始化


使用 PointCut 对我们指定的连接点进行拦截,通过 Advice,就可以拦截到 JoinPoint 后要执行的代码。Advice 通常有以下几种类型:


1、Before:PointCut 之前执行 2、After:PointCut 之后执行 3、Around:PointCut 之前、之后分别执行


首先,我们举一个小栗子:


@Before("execution(* android.app.Activity.on**(..))")public void onActivityCalled(JoinPoint joinPoint) throws Throwable {...}


在 execution 中的是一个匹配规则,第一个*代表匹配任意的方法返回值,后面的语法代码匹配所有 Activity 中 on 开头的方法。


处理 Join Point 的类型:


1、call:插入在函数体里面 2、execution:插入在函数体外面


如何统计 Application 中的所有方法耗时?


@Aspectpublic class ApplicationAop {


@Around("call (* com.json.chao.application.BaseApplication.**(..))")public void getTime(ProceedingJoinPoint joinPoint) {Signature signature = joinPoint.getSignature();String name = signature.toShortString();long time = System.currentTimeMillis();try {joinPoint.proceed();} catch (Throwable throwable) {throwable.printStackTrace();}Log.i(TAG, name + " cost" + (System.currentTimeMillis() - time));}}


注意


当 Action 为 Before、After 时,方法入参为 JoinPoint。当 Action 为 Around 时,方法入参为 ProceedingPoint。


Around 和 Before、After 的最大区别:


ProceedingPoint 不同于 JoinPoint,其提供了 proceed 方法执行目标方法。


总结 AOP 特性:


1、无侵入性 2、修改方便

四、启动速度分析工具 — TraceView

使用方式


1、代码中添加:Debug.startMethodTracing()、检测方法、Debug.stopMethodTracing()。(需要使用 adb pull 将生成的**.trace 文件导出到电脑,然后使用 Android Studio 的 Profiler 加载)


2、打开 Profiler -> CPU -> 点击 Record -> 点击 Stop -> 查看 Profiler 下方 Top Down/Bottom Up 区域找出耗时的热点方法。


Profile CPU


1、Trace types


Trace Java Methods


会记录每个方法的时间、CPU 信息。对运行时性能影响较大。


Sample Java Methods


相比于 Trace Java Methods 会记录每个方法的时间、CPU 信息,它会在应用的 Java 代码执行期间频繁捕获应用的调用堆栈,对运行时性能的影响比较小,能够记录更大的数据区域。


Sample C/C++ Functions


需部署到 Android 8.0 及以上设备,内部使用 simpleperf 跟踪应用的 native 代码,也可以命令行使用 simpleperf。


Trace System Calls


检查应用与系统资源的交互情况。查看所有核心的 CPU 瓶。内部采用 systrace,也可以使用 systrace 命令。


2、Event timeline


显示应用程序中在其生命周期中转换不同状态的活动,如用户交互、屏幕旋转事件等。


3、CPU timeline


显示应用程序实时 CPU 使用率、其它进程实时 CPU 使用率、应用程序使用的线程总数。


4、Thread activity timeline


列出应用程序进程中的每个线程,并使用了不同的颜色在其时间轴上指示其活动。


绿色:线程处于活动状态或准备好使用 CPU。黄色:线程正等待 IO 操作。(重要)灰色:线程正在睡眠,不消耗 CPU 时间。

Profile 提供的检查跟踪数据窗口有四种

1、Call Chart


提供函数跟踪数据的图形表示形式。


水平轴:表示调用的时间段和时间。垂直轴:显示被调用方。橙色:系统 API。绿色:应用自有方法蓝色:第三方 API(包括 Java API)


提示:右键点击 Jump to source 跳转至指定函数。


2、Flame Chart


将具有相同调用方顺序的完全相同的方法收集起来。


水平轴:执行每个方法的相对时间量。垂直轴:显示被调用方。


注意:看顶层的哪个函数占据的宽度最大(平顶),可能存在性能问题。


3、Top Down


递归调用列表,提供 self、children、total 时间和比率来表示被调用的函数信息。Flame Chart 是 Top Down 列表数据的图形化。


4、Bottom Up


展开函数会显示其调用方。按照消耗 CPU 时间由多到少的顺序对函数排序。


注意点:


Wall Clock Time:程序执行时间。Thread Time:CPU 执行的时间。

TraceView 小结

特点


1、图形的形式展示执行时间、调用栈等。2、信息全面,包含所有线程。3、运行时开销严重,整体都会变慢,得出的结果并不真实。


作用


主要做热点分析,得到两种数据:


单次执行最耗时的方法。执行次数最多的方法。

五、启动速度分析工具 — Systrace

使用方式:


代码插桩


定义 Trace 静态工厂类,将 Trace.begainSection(),Trace.endSection()封装成 i、o 方法,然后再在想要分析的方法前后进行插桩即可。


在命令行下执行 systrace.py 脚本:


python /Users/quchao/Library/Android/sdk/platform-tools/systrace/systrace.py -t 20 sched gfx view wm am app webview -a "com.wanandroid.json.chao" -o ~/Documents/open-project/systrace_data/wanandroid_start_1.html


具体参数含义如下:


-t:指定统计时间为 20s。shced:cpu 调度信息。gfx:图形信息。view:视图。wm:窗口管理。am:活动管理。app:应用信息。webview:webview 信息。-a:指定目标应用程序的包名。-o:生成的 systrace.html 文件。


如何查看数据?


在 UIThread 一栏可以看到核心的系统方法时间区域和我们自己使用代码插桩捕获的方法时间区域。


Systrace 原理


在系统的


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


一些关键链路(如 SystemServcie、虚拟机、Binder 驱动)插入一些信息(Label);通过 Label 的开始和结束来确定某个核心过程的执行时间;把这些 Label 信息收集起来得到系统关键路径的运行时间信息,最后得到整个系统的运行性能信息;Android Framework 里面一些重要的模块都插入了 label 信息,用户 App 中可以添加自定义的 Lable。

Systrace 小结

特性


结合 Android 内核的数据,生成 Html 报告。系统版本越高,Android Framework 中添加的系统可用 Label 就越多,能够支持和分析的系统模块也就越多。必须手动缩小范围,会帮助你加速收敛问题的分析过程,进而快速地定位和解决问题。


作用


主要用于分析绘制性能方面的问题。分析系统关键方法和应用方法耗时。

六、启动监控

1、实验室监控:视频录制


80%绘制图像识别


注意


覆盖高中低端机型不同的场景。


2、线上监控


需要准确地统计启动耗时。


1、启动结束的统计时机


是否是使用界面显示且用户真正可以操作的时间作为启动结束时间。


2、启动时间扣除逻辑


闪屏、广告和新手引导这些时间都应该从启动时间里扣除。


3、启动排除逻辑


Broadcast、Server 拉起,启动过程进入后台都需要排除统计。


4、使用什么指标来衡量启动速度的快慢?


平均启动时间的问题一些体验很差的用户很可能被平均了。


建议的指标


1、快开慢开比


如 2s 快开比,5s 慢开比,可以看到有多少比例的用户体验好,多少比例的用户比较糟糕。


2、90%用户的启动时间


如果 90%用户的启动时间都小于 5s,那么 90%区间的启动耗时就是 5s。


5、启动的类型有哪几种?


首次安装启动覆盖安装启动冷启动(指标)热启动(反映程序的活跃或保活能力)


借鉴 Facebook 的 profilo 工具原理,对启动整个流程的耗时监控,在后台对不同的版本做自动化对比,监控新版本是否有新增耗时的函数。

七、启动优化常规方案

启动过程中的常见问题


点击图标很久都不响应:预览窗口被禁用或设置为透明。首页显示太慢:初始化任务太多。首页显示后无法进行操作:太多延迟初始化任务占用主线程 CPU 时间片。


优化区域


Application、Activity 创建以及回调等过程。


常规方案,省略了一些常规方案细节,感兴趣可以查看原文。


1、主题切换


2、第三方库懒加载


3、异步初始化


4、延迟初始化


5、Multidex 预加载优化


6、预加载 SharedPreferences


7、类预加载优化


在 Application 中提前异步加载初始化耗时较长的类。


如何找到耗时较长的类?


替换系统的 ClassLoader,打印类加载的时间,按需选取需要异步加载的类。


注意:


Class.forName()只加载类本身及其静态变量的引用类。new 类实例 可以额外加载类成员变量的引用类。


8、WebView 启动优化


1、WebView 首次创建比较耗时,需要预先创建 WebView 提前将其内核初始化。2、使用 WebView 缓存池,用到 WebView 的时候都从缓存池中拿,注意内存泄漏问题。3、本地离线包,即预置静态页面资源。


9、页面数据预加载


在主页空闲时,将其它页面的数据加载好保存到内存或数据库,等到打开该页面时,判断已经预加载过,就直接从内存或数据库取数据并显示。


10、启动阶段不启动子进程


子进程会共享 CPU 资源,导致主进程 CPU 紧张。此外,在多进程情况下一定要可以在 onCreate 中去区分进程做一些初始化工作。


注意启动顺序:


App onCreate 之前是 ContentProvider 初始化。


11、闪屏页与主页的绘制优化


1、布局优化。2、过渡绘制优化。


关于绘制优化可以参考 Android 性能优化之绘制优化。

八、启动优化黑科技

1、启动阶段抑制 GC


启动时 CG 抑制,允许堆一直增长,直到手动或 OOM 停止 GC 抑制。(空间换时间)


前提条件


1、设备厂商没有加密内存中的 Dalvik 库文件。2、设备厂商没有改动 Google 的 Dalvik 源码。


实现原理


在源码级别找到抑制 GC 的修改方法,例如改变跳转分支。在二进制代码里找到 A 分支条件跳转的”指令指纹”,以及用于改变分支的二进制代码,假设为 override_A。应用启动后扫描内存中的 libdvm.so,根据”指令指纹”定位到修改位置,然后用 override_A 覆盖。


缺点


白名单覆盖所有设备,但维护成本高。


2、CPU 锁频


在 Android 系统中,CPU 相关的信息存储在/sys/devices/system/cpu 目录的文件中,通过对该目录下的特定文件进行写值,实现对 CPU 频率等状态信息的更改。

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
几乎包含了市面上所有启动优化方案,学习路线+知识点梳理