低性能的APP常见的体现有启动/界面切换慢、动画掉帧、卡慢、耗电,甚至出现应用无响应、程序崩溃的现象。当我们着手处理这些性能问题时,面对的第一个问题就是需要找到合适的工具来检测这些问题,用肉眼观察来判断定位这类问题是不靠谱的。理想的检测工具要能做到两点:
为了满足上面两个需求,我们需要使用到systrace
和CPU Profiler
这两个检测工具。
工具一:systrace
这个工具的作用从名字‘system trace’系统跟踪
就能看出一二。我们主要是通过这个工具来记录分析系统级函数的执行情况。它从android内核收集CPU调度、存储器访问、应用线程等信息,生成一张html格式的报表。这个工具还会自动分析这些数据,高亮警示开发者注意这些可能有问题的地方。比方下图UI掉帧的问题。但是systrace
只提供了系统方法调用信息,没有指明具体是由于调用我们APP程序哪个方法导致的,从systrace
中只能定位出大概是在首个Activity创立的时候发生的。想要获取关于APP执行的详细信息就得使用今天的主角CPU Profiler
了。
点击查看systrace更多介绍。
工具二:CPU ProfilerCPU 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 后,可以看到相似下图的少量内容。它将立即开始显示应用的 CPU 使用率和线程 Activity。
Event 时间线: 显示应用中在其生命周期不同状态间转换的 Activity,并表明客户与设施的交互,包括触摸事件、按键点击的事件。 如需理解有关 Event 时间线的更多信息,包括如何启用它,请阅读 启用高级分析。
CPU 时间线: 显示应用的实时 CPU 使用率(以占总可用 CPU 时间的百分比表示)以及应用使用的总线程数。 此时间线还显示其余进程的 CPU 使用率(如系统进程或者其余应用),以便您可以将其与您的应用使用率进行比照。 通过沿时间线的水平轴移动鼠标,您还可以检查历史 CPU 使用率数据。
线程活动时间线: 列出属于应用进程的每个线程并使用下面列出的颜色沿时间线标示它们的状态。 在记录一个函数跟踪后,可以从此时间线中选择一个线程以在跟踪窗格中检查其数据。
Record按钮:现在当我们与应用交互时,可以通过 CPU Profiler 监控 CPU 使用率和线程状态了。 不过,如想要理解应用执行代码的详细信息, 我们需要记录和检查函数跟踪。
要开始记录函数跟踪,从下拉菜单中选择 Sampled 或者 Instrumented 记录配置,或者选择您创立的自己设置记录配置,而后点击 Record 。 与应用交互并在完成后点击 Stop recording。 分析器将自动选择记录的时间范围,并默认在函数跟踪窗格中显示主线程函数跟踪信息,如下图所示。假如您想检查另一个线程的函数跟踪,只要从线程活动时间线中选中它,函数跟踪窗格就会切换成所选线程的函数跟踪信息。
上面把 CPU Profiler 工具的基本界面详情了一下。下面我们通过两个例子来进一步理解下 CPU Profiler 的用法。
问题:初次打开播放页时界面卡慢,待优化。
分析:界面卡慢,应该数主线有耗时操作导致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初始化逻辑放在主线程空闲的时候去执行的。还有一点注意下,可能出现从播放页打开到客户请求投屏时,主线程一直不空闲,也就是我们的初始化任务没有执行情况。所以加到闲时任务系统中的任务还得具有一个特点,就是假如显示任务没有执行,后续逻辑也要能正常运行。不过这个问题也很容易处理,就是在后续逻辑执行前先判断下能否初始化了。
问题:杀掉进程后,再次启动应用慢,待优化。
分析:有了上面的经验,分析这个问题也一样,先打开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 使用率,向德芙巧克力一样,在各种新旧设施上都能提供纵享丝滑的客户体验。