性能优化之启动优化
启动状态
应用有三种启动状态,每种状态都会影响应用向用户显示所需的时间:冷启动、温启动与热启动。在冷启动中,应
用从头开始启动。在另外两种状态中,系统需要将后台运行的应用带入前台。建议始终在假定冷启动的基础上进行
优化。这样做也可以提升温启动和热启动的性能。
- 冷启动是指应用从头开始启动:系统进程在冷启动后才创建应用进程。发生冷启动的情况包括应用
自设备启动后或系统终止应用后首次启动 - 在热启动中,系统的所有工作就是将 Activity 带到前台。只要应用的所有 Activity 仍驻留在内存中,应用就不必重复执行对象初始化、布局加载和绘制。
- 温启动包含了在冷启动期间发生的部分操作;同时,它的开销要比热启动高。有许多潜在状态可视
为温启动。
冷启动耗时统计
在性能测试中存在启动时间2-5-8原则:
- 当用户能够在2秒以内得到响应时,会感觉系统的响应很快;
- 当用户在2-5秒之间得到响应时,会感觉系统的响应速度还可以;
- 当用户在5-8秒以内得到响应时,会感觉系统的响应速度很慢,但是还可以接受;
- 而当用户在超过8秒后仍然无法得到响应时,会感觉系统糟透了,或者认为系统已经失去响应。
如何查看app启动的时间
使用adb命令:adb shell am start -W com.xxx.xx/com.xx.xx.MainActivity
- WaitTime:总的耗时,包括前一个应用Activity pause的时间和新应用启动的时间;
- ThisTime表示一连串启动Activity的最后一个Activity的启动耗时;
- TotalTime表示新应用启动的耗时,包括新进程的启动和Activity的启动,但不包括前一个应用Activity pause
的耗时。
开发者一般只要关心TotalTime即可,这个时间才是自己应用真正启动的耗时。
CPU Profile/TraceView
如果发现显示时间比希望的时间长,则可以继续尝试识别启动过程中的瓶颈。 查找瓶颈的一个好方法是使用
Android Studio CPU 性能剖析器。
Traceview是android平台配备一个很好的性能分析的工具。它可以通过图形化的方式让我们了解我们要跟踪
的程序的性能,并且能具体到每个方法的执行时间。但是目前Traceview 已弃用。如果使用 Android Studio
3.2 或更高版本,则应改为使用 CPU Profiler
要在应用启动过程中自动开始记录 CPU 活动,请执行以下操作:
- 依次选择 Run > Edit Configurations。
- 在 Profiling 标签中,勾选 Start recording CPU activity on startup 旁边的复选框,并选择Trace Java Methods
Sample Java Methods
对 Java 方法采样:在应用的 Java 代码执行期间,频繁捕获应用的调用堆栈。分析器会比较捕获的数据集,
以推导与应用的 Java 代码执行有关的时间和资源使用信息。如果应用在捕获调用堆栈后进入一个方法并在下
次捕获前退出该方法,分析器将不会记录该方法调用。如果您想要跟踪生命周期如此短的方法,应使用检测
跟踪。
Trace Java Methods
跟踪 Java 方法:在运行时检测应用,以在每个方法调用开始和结束时记录一个时间戳。系统会收集并比较这
些时间戳,以生成方法跟踪数据,包括时间信息和 CPU 使用率。
Sample C/C++ Functions
对 C/C++ 函数采样:捕获应用的原生线程的采样跟踪数据。要使用此配置,您必须将应用部署到搭载
Android 8.0(API 级别 26)或更高版本的设备上。
Trace System Calls
跟踪系统调用:捕获非常翔实的细节,以便您检查应用与系统资源的交互情况。您可以检查线程状态的确切时间和持续时间、直观地查看所有内核的 CPU 瓶颈在何处,并添加要分析的自定义跟踪事件。要使用此配置,您必须将应用部署到搭载 Android 7.0(API 级别 24)或更高版本的设备上。
此跟踪配置在 systrace 的基础上构建而成。您可以使systrace 命令行实用程序指定除 CPU Profiler 提供的选项之外的其他选项。systrace 提供的其他系统级数据可帮助您检查原生系统进程并排查丢帧或帧延迟问
题。
- 依次选择 Run > Profile,将您的应用部署到搭载 Android 8.0(API 级别 26)或更高版本的设备上。
- 跑了一会了,注意不要run太久,然后点击stop结束跟踪
注意:用真机测试时,要用android8.0以上的;用华为的会出现闪退,我换成小米的就没事了,当你点击stop时,需要等待一下
Call Chart
以图形来呈现方法跟踪数据或函数跟踪数据,其中调用的时间段和时间在横轴上表示,而其被调用方则在纵轴上显
示。对系统 API 的调用显示为橙色,对应用自有方法的调用显示为绿色,对第三方 API(包括 Java 语言 API)的调
用显示为蓝色。 (实际颜色显示有Bug)
Flame Chart
提供一个倒置的调用图表,用来汇总完全相同的调用堆栈。也就是说,将具有相同调用方顺序的完全相同的方法或函数收集起来,并在火焰图中将它们表示为一个较长的横条 。
横轴显示的是百分比数值。由于忽略了时间线信息,Flame Chart 可以展示每次调用消耗时间占用整个记录时长的
百分比。 同时纵轴也被对调了,在顶部展示的是被调用者,底部展示的是调用者。此时的图表看起来越往上越窄,
就好像火焰一样,因此得名: 火焰图。
说白了就是将Call Chart上下调用栈倒过来。
Top Down Tree
如果我们需要更精确的时间信息,就需要使用 Top Down Tree。 Top Down Tree显示一个调用列表,在该列表中
展开方法或函数节点会显示它调用了的方法节点。
对于每个节点,三个时间信息:
- Self Time —— 运行自己的代码所消耗的时间;
- Children Time —— 调用其他方法的时间;
- Total Time —— 前面两者时间之和。
此视图能够非常方便看到耗时最长的方法调用栈。
Bottom Up Tree
方便地找到某个方法的调用栈。在该列表中展开方法或函数节点会显示哪个方法调用了自己。
下面是实际应用实战的结果
一般情况都是大概看下Call Chart,详细看Top Down Tree,
可以看出,在application中腾讯的TBS,IM,友盟,bugly这些比较耗时:
tbs
https://x5.tencent.com/docs/access.html
查看官网,tbs对于冷启动有优化,使用TBS的 ”dex2oat优化方案“
缩短时间:2394->2313
友盟
https://developer.umeng.com/docs/119267/detail/118588#h1-u521Du59CBu5316u53CAu901Au7528u63A5u53E31
查看友盟开发文档,有UMConfigure.preInit预初始化函数
缩短时间:2522->2442 (因为升级了友盟sdk,所以总的时间加大了)
ARouter
https://github.com/alibaba/ARouter
添加插件可以提高速度apply plugin: ‘com.alibaba.arouter’
缩短时间:2442->2379
Bugly
https://bugly.qq.com/docs/user-guide/advance-features-android-beta/
通过延迟初始化Beta.initDelay = 1 * 1000;设置启动延时为1s(默认延时3s),APP启动1s后初始化SDK,避免影响APP启动速度;
缩短时间:2379->2351
其他
可以将一些初始化尝试异步加载或懒加载,第一个页面布局优化等等
StrictMode严苛模式
StrictMode是一个开发人员工具,它可以检测出我们可能无意中做的事情,并将它们提请我们注意,以便我们能够
修复它们。
StrictMode最常用于捕获应用程序主线程上的意外磁盘或网络访问。帮助我们让磁盘和网络操作远离主线程,可以
使应用程序更加平滑、响应更快。
fun setStrictMode() {
if (BuildConfig.DEBUG) {
//线程检测策略
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectDiskReads() //读、写操作
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build()
);
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects() //Sqlite对象泄露
.detectLeakedClosableObjects() //未关闭的Closable对象泄露
.penaltyLog() //违规打印日志
.penaltyDeath() //违规崩溃
.build()
);
}
}
启动黑白屏问题
当系统加载并启动 App 时,需要耗费相应的时间,这样会造成用户会感觉到当点击 App 图标时会有 “延迟” 现象,为了解决这一问题,Google 的做法是在 App 创建的过程中,先展示一个空白页面,让用户体会到点击图标之后立马就有响应。
如果你的application或activity启动的过程太慢,导致系统的BackgroundWindow没有及时被替换,就会出现启动时白屏或黑屏的情况(取决于Theme主题是Dark还是Light)。消除启动时的黑/白屏问题,大部分App都采用自己在Theme中设置背景图的方式来解决。
<style name="AppTheme.Launcher">
<item name="android:windowBackground">@drawable/bg</item> </style>
<activity
android:name=".activity.SplashActivity" android:screenOrientation="portrait" android:theme="@style/AppTheme.Launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
@Override protected void onCreate(Bundle savedInstanceState) {
//替换为原来的主题,在onCreate之前调用
setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
}
Debug API
通过CPU profiler这种方式,会发现很卡很卡,并且应用启动的时间也会大大的拉长,所以我们可以换一种方式,借助Debug API生成trace文件。
在Application的构造方法中
// 由于用startMethodTracing也会特别的卡
// 所以用startMethodTracingSampling采样的方式
Debug.startMethodTracingSampling(
File(android.os.Environment.getExternalStorageDirectory(), "enjoy").absolutePath,
8 * 1024 * 1024,
1_000
)
在Activity中
@Override public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Debug.stopMethodTracing();
}
运行App,则会在sdcard中生成一个enjoy.trace文件(需要sdcard读写权限)。将手机中的trace文件保存至电
脑,随后拖入Android Studio即可。
注意:
- 提前打开sd卡权限
- 当你用startMethodTracing(),目标文件路径是:/sdcard/Android/data/[YOUR_PACKAGE_NAME]/files
- 发现生成的trace文件大小为0时,看是不是app在多进程下,因为多个进程都调用了android.os.Debug#startMethodTracing(),导致文件被重复持有,在写入的时候无法写入成功,这时判断下在主进程调用即可,亲测ok
通过IdeHandler可以大大的优化启动
实际项目发现友盟和腾讯TBS需要时间较长,在考虑业务的同时,是可以将这些放到cpu空闲后初始化的,所以将这些第三方的初始化都放到了这里
Looper.myQueue().addIdleHandler {
// 初始化
false
}
IdleHandler是一个回调接口,可以通过MessageQueue的addIdleHandler添加实现类。当MessageQueue中的任务暂时处理完了(没有新任务或者下一个任务延时在之后),这个时候会回调这个接口,返回false,那么就会移除它,返回true就会在下次message处理完了的时候继续回调。