Android性能优化之CPU Profiler
低性能的APP常见的表现有启动/界面切换慢、动画掉帧、卡顿、耗电,甚至出现应用无响应、程序崩溃的现象。当我们着手解决这些性能问题时,面对的第一个问题就是需要找到合适的工具来检测这些问题,用肉眼观察来判断定位这类问题是不靠谱的。理想的检测工具要能做到两点:
工具二:CPU Profiler
CPU Profiler
可实时检查应用的 CPU 使用率和线程运行情况,并记录函数跟踪,以便我们优化和调试相关代码。简单点说就是我们可以通过CPU Profiler查看应用在某段时间里某个线程执行了哪些方法,并且还定量的展示了执行这些方法所耗费的时间及其方法的调用堆栈。稍有经验的程序员都知道应用启动慢、界面切换慢、动画不流畅卡顿等类似问题基本都是UI刷新不及时的表现,UI刷新不及时就是因为UI线程被其他逻辑方法长时间占用导致。呐,
CPU Profiler
简直就是为了解决这个问题而生的,轻松的分析出在卡顿(或者其他)过程中主线程都执行了哪些耗时操作。
Google官方提供的Android开发工具Android Studio附带了很多开发调试工具,这其中就包括了一系列的性能分析工具。在Android Studio 3.0之前这些工具叫Android Monitor tools,Android Studio 3.0开始成为Android Profiler tools。我现在使用的Android Studio版本是3.2.1,这篇文章主要介绍的是3.2.1版本Android Profiler tools中的CPU Profiler - CPU分析器。下面我们来看下CPU Profiler的使用方法。
一、CPU Profiler概览
1、打开CPU Profile界面
Event 时间线: 显示应用中在其生命周期不同状态间转换的 Activity,并表明用户与设备的交互,包括触摸事件、按键点击的事件。 如需了解有关 Event 时间线的更多信息,包括如何启用它,请阅读 启用高级分析 。
CPU 时间线: 显示应用的实时 CPU 使用率(以占总可用 CPU 时间的百分比表示)以及应用使用的总线程数。 此时间线还显示其他进程的 CPU 使用率(如系统进程或其他应用),以便您可以将其与您的应用使用率进行对比。 通过沿时间线的水平轴移动鼠标,您还可以检查历史 CPU 使用率数据。
线程活动时间线: 列出属于应用进程的每个线程并使用下面列出的颜色沿时间线标示它们的状态。 在记录一个函数跟踪后,可以从此时间线中选择一个线程以在跟踪窗格中检查其数据。
绿色区域: 表示线程处于活动状态或准备使用 CPU。 即,它正在“运行中”或处于“可运行”状态。 黄色区域: 表示线程处于活动状态,但它正在等待一个 I/O 操作(如磁盘或网络 I/O),然后才能完成它的工作。 灰色区域: 表示线程正在休眠且没有消耗任何 CPU 时间。 当线程需要访问尚不可用的资源时偶尔会发生这种情况。 线程进入自主休眠或内核将此线程置于休眠状态,直到所需的资源可用。Record按钮: 现在当我们与应用交互时,可以通过 CPU Profiler 监控 CPU 使用率和线程状态了。 不过,如想要了解应用执行代码的详细信息, 我们需要记录和检查函数跟踪。
2、记录和检查函数跟踪
要开始记录函数跟踪,从下拉菜单中选择
Sampled
或
Instrumented
记录配置,或选择您创建的
自定义记录配置
,然后点击
Record
。 与应用交互并在完成后点击
Stop recording
。 分析器将自动选择记录的时间范围,并默认在函数跟踪窗格中显示主线程函数跟踪信息,如下图所示。如果您想检查另一个线程的函数跟踪,只需从线程活动时间线中选中它,函数跟踪窗格就会切换成所选线程的函数跟踪信息。
二、CPU Profiler 使用示例
上面把 CPU Profiler 工具的基本界面介绍了一下。下面我们通过两个例子来进一步了解下 CPU Profiler 的用法。
示例1:界面切换卡顿
问题:
首次打开播放页时界面卡顿,待优化。
分析:
界面卡顿,应该数主线有耗时操作导致UI刷新不及时。所以我们是用CPU Profiler来定位问题。
onCreate()
里的
MultiscreenManager.init();
耗用了绝大多数时间。仔细查看这一方法作用是初始化投屏相关功能。(初始话方法的具体逻辑去掉了,调用了sleep方法来模拟)
Call Chart 标签提供函数跟踪的图形表示形式,其中,水平轴表示函数耗费的时间,垂直轴显示其被调用者。 对系统 API 的函数调用显示为橙色,对应用自有函数的调用显示为绿色,对第三方 API(包括 Java 语言 API)的函数调用显示为蓝色。
解决方案: 问题找到了解决就很容易了,投屏功能不是播放页启动的必须初始化项目,但是如果后置到用户点击投屏功能以后再去初始化投屏SDK,则会影响投屏功能的用户体验。这种问题有一个国际标准解决方案,把该任务添加到闲时任务系统中去。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
MultiscreenManager.init();
return false;
CPU Profiler 检测时测量的函数耗时会大于真正线上包运行的时间。一方面是函数跟踪工具会加入额外的计时逻辑,另一方面它还会关闭虚拟机的JIT功能。所以我们一般是对比优化前后测量的两组数据来判断优化的效果。
修改后再测试下数据,onCreate()
方法耗时明显降低,投屏sdk初始化逻辑放在主线程空闲的时候去执行的。还有一点注意下,可能出现从播放页打开到用户请求投屏时,主线程一直不空闲,也就是我们的初始化任务没有执行情况。所以加到闲时任务系统中的任务还得具备一个特点,就是如果显示任务没有执行,后续逻辑也要能正常运行。不过这个问题也很容易解决,就是在后续逻辑执行前先判断下是否初始化了。
示例2:应用冷启动慢
问题:杀掉进程后,再次启动应用慢,待优化。
分析:有了上面的经验,分析这个问题也一样,先打开APP,选择要调试的进程,点击record按钮跟踪卡顿过程中函数调用信息。好了,问题来了,我们这里是要抓取应用冷启动过程函数调用信息,但是要用CPU Profiler工具抓取信息得先指定进程。应用启动前又没有进程信息可以指定。愁,挠头,程序员的头就是这么给挠秃顶的。
这个时候就该Debug
类出场了。我们可使用 Debug
类精确地控制设备何时开始和停止记录函数跟踪信息,来生成一份函数跟踪信息文件。然后再使用 Android Studio 或 Traceview 查看各个跟踪日志。
Traceview 有点过时了。如果我们使用的是3.2及其更新的Android Studio,就没有必要用Traceview了。
在开始生成跟踪日志之前,要确保应用有权限写入外部存储WRITE_EXTERNAL_STORAGE
,以便将跟踪日志保存至该设备。创建跟踪日志,在想系统开始记录跟踪数据的位置调用 Debug.startMethodTracing()
,要停止跟踪的位置请调用 Debug.stopMethodTracing()
。系统将在getExternalFilesDir()
目录下生成 .trace
文件,一般都在 ~/sdcard/Android/data/$packname/files
目录中。用Android Studio的Device File Explorer工具找到这个文件双击即可打开。
public class DymApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
SimpleDateFormat date = new SimpleDateFormat("dd_MM_yyyy_hh_mm_ss");
String logDate = date.format(new Date());
// 在Application创建的时候开始函数跟踪
// 传入的参数是函数跟踪信息文件名,加时间戳保证文件不会被覆盖
Debug.startMethodTracing("sample-" + logDate);
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
try {
// TODO: 2018/12/7 怎么又是这个倒霉的sleep,为了测试APP启动卡顿问题
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
// 在首页Activity创建完成停止函数跟踪
Debug.stopMethodTracing();
函数跟踪日志生成了,分析就和之前没什么两样了。
CPU Profiler 中还有一些信息也是很有用的,比如Top Down 和 Bottom Up ,这里没有讲到,大家可以自己去看下,有什么不明白的,也可以在下面留言讨论。
总的来说就是 CPU Profiler 可以让我们查看应用进程中的每个线程,某段时间内执行了哪些函数,以及在其执行期间每个函数消耗的 CPU 资源(文章中耗时只得就是占用CPU的时间)。 还可以使用函数跟踪来识别调用方和被调用方。 据此可以确定哪些函数负责调用常常会消耗大量特定资源的任务,并尝试优化应用代码以避免不必要的开支。
大家努力,最大限度减少应用的 CPU 使用率,向德芙巧克力一样,在各种新旧设备上都能提供纵享丝滑的用户体验。