StrictMode
是Strict
和Mode
的合并,在英语中,strict
表示“严格的”,mode
表示“模式”,因此,StrictMode
就是“严格的模式”,或叫“严苛模式”,是用来检测程序中违例情况的开发者工具。通过指定一系列策略(policy
)和对应的规则(rule
)进行检查并且做出不同的反应,比如打印、弹窗、崩溃等。一般用来检测主线程中的耗时操作和阻塞,诸如读写文件, 网络访问等操作以及内存泄漏。
严格模式的开启可以放在Application
或者Activity
以及其他组件的onCreate
方法之后super.onCreate()之前。为了更好地分析应用中的问题,建议放在Application
的onCreate
方法中。
private void enabledStrictMode() {
if(BuildConfig.DEBUG){
//调试模式下开启
//开启Thread策略模式
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectNetwork()//监测主线程使用网络io
.detectCustomSlowCalls()//监测自定义运行缓慢函数
.detectDiskReads() // 检测在UI线程读磁盘操作
.detectDiskWrites() // 检测在UI线程写磁盘操作
.penaltyLog() //写入日志
.penaltyDialog()//监测到上述状况时弹出对话框
.build());
//开启VM策略模式
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects()//监测sqlite泄露
.detectLeakedClosableObjects()//监测没有关闭IO对象
.setClassInstanceLimit(MainActivity.class, 1) // 设置某个类的同时处于内存中的实例上限,可以协助检查内存泄露
.detectActivityLeaks()
.penaltyLog()//写入日志
.penaltyDeath()//出现上述情况异常终止
.build());
}
}
StrictMode
分为线程策略(ThreadPolicy
)和虚拟机策略(VmPolicy
)
线程策略
规则:
detectNetwork
:监测主线程使用网络(重要)
detectCustomSlowCalls
:监测自定义运行缓慢函数
detectDiskReads
:检测在UI线程读磁盘操作 (重要)
detectDiskWrites
:检测在UI线程写磁盘操作(重要)
detectResourceMismatches
:检测发现资源不匹配 (api>22)
detectAll
:检测所有支持检测等项目(如果太懒,不想一一列出来,可以通过这个方式)
permitDiskReads
:允许UI线程在磁盘上读操作
permitXXX
:其他允许项类似…
permitAll
:禁用所有侦测
处罚:
penaltyLog
:违规时,输出日志
penaltyDialog
:违规时,弹出对话框
penaltyDeath
:违规时,应用就会崩溃
虚拟机策略
规则:
detectActivityLeaks
:检测Activity 的内存泄露情况(重要)(api>10)
detectCleartextNetwork
:检测明文的网络 (api>22)
detectFileUriExposure
:检测file://或者是content:// (api>17)
detectLeakedClosableObjects
:检测资源没有正确关闭(重要)(api>10)
detectLeakedRegistrationObjects
:检测BroadcastReceiver、ServiceConnection是否被释放 (重要)(api>15)
detectLeakedSqlLiteObjects
:检测数据库资源是否没有正确关闭(重要)(api>8)
detectAll
:检测所有支持检测等项目(如果太懒,不想一一列出来,可以通过这个方式)
setClassInstanceLimit
:设置某个类的同时处于内存中的实例上限,可以协助检查内存泄露(重要)
处罚:
penaltyLog
:违规时,输出日志
penaltyDeath
:违规时,应用就会崩溃
在已经存在严格模式规则下,可以对规则进行扩充。用
getThreadPolicy()
和getVmPolicy()
获得当前策略,用setThreadPolicy()
和setVmPolicy()
来扩充它。
StrictMode.ThreadPolicy oldThreadPolicy = StrictMode.getThreadPolicy();
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(oldThreadPolicy)
.permitDiskWrites() // 在原有策略的规则基础上,不监测读写磁盘
.build());
StrictMode.VmPolicy oldVmPolicy = StrictMode.getVmPolicy();
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder(oldVmPolicy)
.detectFileUriExposure() // 在原有策略的规则基础上,监测文件URI暴露
.build());
adb logcat | grep StrictMode
下面的的代码如果再主线程执行的话,会报Caused by: android.os.StrictMode$StrictModeViolation: policy=262175 violation=2
错误。根据严格模式,IO操作要放在工作线程当中。
public void writeToExternalStorage() {
File externalStorage = Environment.getExternalStorageDirectory();
File destFile = new File(externalStorage, "dest.txt");
OutputStream output = null;
try {
output = new FileOutputStream(destFile, true);
output.write("test".getBytes());
output.flush();
output.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
//我们使用流的时候,经常没有写finally的逻辑,当output.write出现异常会导致这个流没有关闭。
if (null != output) {
try {
//保证无论什么情况下output流都能正常关闭
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Release
版本不建议开启严格模式。JNI
中的磁盘IO
和网络请求。IO
操作必须在主线程中进行。当我们需要确保APP的流畅度,也就是保证APP
能连续不间断地提供每秒60
帧的运行状态,当出现掉帧时(也可称为Jank
),如果我们需要知道当前整个APP
所处的状态,Systrace
便是最佳的选择。它能手机检测Android
系统各个组件随着时间的运行状态,并能给出该如何有效地修复掉帧问题的建议。
想要启动Systrace
,需要按照下面几个步骤:
Android Dvice Monitor
在
Android Studio
更新之后,Android Dvice Monitor
找不到了,在Android SDK
的tools
目录下有monitor
工具,双击即可启动。
Systrace
内容启动分三步走:
1、选中要测试的应用:如果有异常看不到,可以重启adb
(依次执行:adb kill-server
和adb start-server
)。
2、清除内存:避免抓取过多次trace,导致数据丢失,需要及时清除缓存中的内容。
3、启动Systrace
:这部主要是配置需要抓取Systrace
的内容选项。
systrace
的时间,通常设置5
秒,并在5
秒内重现问题,时间太短会导致问题重现时没有被抓到,时间太长会导致JavaHeap
不够而无法保存,因此在能抓到问题点的情况下,时间越小越好。Buffer Size
是存储systrace
的size
,同样的,太小会导致信息丢失,时间太长会导致Java Heap
不够而无法保存,建议20480
。systrace log
,如下:Trace.beginSection("tag");
Trace.endSection();
那么此处必须选择这个应用对应的进程名字,否则新加的systrace log
不会被抓到。Bionic C Library
、 CPU Frequency
、CPU Idle
、CPU load
. 如果设备可以root
,root
后可以看到更多:eMMC commands
、 Synchonization
、Kernel Workqueues
等。Chrome
分析trace
抓取的
Trace
报告提供了Android
系统进程在特定时间段内的整体情况。 例如在显示Activity
或动画时卡顿,Systrace
会提供关于如何解决这些问题的建议。
但是,Systrace
不会在应用程序进程中收集有关代码执行的信息。 有关您的应用程序执行哪些方法以及使用多少CPU资源的更多详细信息,请使用Android Studio
的内置CPUProfiler
,或生成跟踪日志并使用Traceview
查看它们。
我们用Chrome
浏览器打开,纵轴代表着时间线,事件记录按照进程分组,同一个进程内按线程进行纵向拆分,每个线程记录自己的工作,可收缩/展开。我们只需要关心Alerts
与Frame
两个属性即可。
Alerts(区域1)
Systrace
能自动分析trace
中的事件,并能自动高亮性能问题作为一个Alerts
,建议调试人员下一步该怎么做(区域4)。比如对于丢帧是,点击黄色或红色的Frames
圆点(区域2)便会有相关的提示信息;另外,在systrace
的最右侧,有一个Alerts tab
可以展开,这里记录着所有的的警告提示信息(区域3)。当我们点击了Alerts
或者点击右边的Alerts
列表中的任何一点我们可以看到在界面的最底部会相对应的优化提示以及可能会出现优化的视频教程链接,下面会讲。
Frame
在每个app
进程,都有一个Frames
行,正常情况以绿色的圆点表示。当圆点颜色为黄色或者红色时,意味着这一帧超过16.6ms
(即发现丢帧),这时需要通过放大那一帧进一步分析问题。对于Android 5.0(API level 21)
或者更高的设备,该问题主要聚焦在UI Thread
和Render Thread
这两个线程当中。对于更早的版本,则所有工作在UI Thread
。我们可以点击一块Frames
中的F
点来查看,同样的它会生成一份跟Alerts
类似的报告结果并放在界面的最低端。我们可以通过按下M
键查看这一帧到下一帧所花费的时间以及哪个方法被调用的最长。
常用快捷键
按键 | 描述 |
---|---|
M | 这一帧到下一帧所花费的时间 |
w | 放大 |
S | 缩小 |
A | 左移动 |
D | 右移动 |
G | 切换是否显示60hz的网格线 |
举例子1
findViewById(R.id.login).setOnClickListener(v -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
对于上面的代码,Systrace
会生成一个Alert
,给出优化建议:
制作这一帧的工作被推迟了几毫秒,造成了
jank
。确保UI线程上的代码不会阻止在其他线程上完成的工作,并且后台线程(例如网络或位图加载)在android.os.Process#thread#PRIORITY@background
或更低版本上运行,这样它们就不太可能中断UI
线程。这些后台线程应该以130
或更高的优先级出现在内核进程下的调度部分。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
对于上面的代码,Systrace
会生成一个Alert
,给出优化建议:
记录无效视图的绘图命令需要很长时间。避免在视图或可绘制的自定义绘图中进行重要工作,特别是分配或绘制到位图。
trace
标签这个主要用在手动查看某段代码之间的耗时。正常情况我们会在开始结束打印出系统时间,然后作减法算出耗时,但是相对比较麻烦。现在我们可以用
Trace
来手动记录了。
Trace.beginSection("aaa");//生成的trace文件中,会在跟踪的代码段执行对应时间轴区间打上一个tag标记
SystemClock.sleep(1000);
SystemClock.sleep(1000);
Trace.endSection();//Trace的begin与end必须在同一线程之中执行
如果用户有自己在应用程序中加入自己的systrace log
(如上述代码),么此处必须选择这个应用对应的进程名字,否则新加的systrace log
不会被抓到。
按照以前的方法生成systrace
,在右上角查找标签aaa
,按回车:
如果多次调用beginSection
时,调用endSection
只会结束最近调用的beginSection
方法。 因此,对于嵌套的调用,您需要确保通过调用endSection
将每个调用正确匹配到beginSection
。
Traceview
是Android sdk
中的一个工具,用于分析计算性能,跟踪方法耗时导致的卡顿问题。它将Traceview
文件转为图形,直观的反应出代码的执行时间、执行次数,便于我们分析。与前面说的systrace
的主要区别是Traceview
能显示具体的函数的定位,而systrace不能。
Traceview
,开始记录默认是1000ms采样一次,可自行设置。点击OK
,步骤二会变成灰色,表示开始记录中,可以再点击灰色按钮结束记录。
Traceview
点击一次下面的按钮即可停止记录。
Traceview
我们在开始记录期间,APP
要被操作起来,在操作过程中会记录所有方法的耗时,并通过图形化的方式展现。比如下面的代码,问题是函数writeToExternalStorage
调用了四次耗时操作:
public void writeToExternalStorage() {
SystemClock.sleep(1000);
SystemClock.sleep(1000);
SystemClock.sleep(1000);
SystemClock.sleep(1000);
}
比如上述的函数writeToExternalStorage
,调用了四次的1
秒的sleep
方法,它的Cpu Time/Cal
是7.959ms
,RealTime/Call
是4009ms
,sleep()
函数的Calls+Recur Calls/Total
是4
次。
最重要的两个指标是Incl Cpu Time
和Calls+Recur Calls/Total
,其他的指标可以为辅助。正常情况下我们只需要按照默认排序, 也就是Incl Cpu Time
排序, 用包名在底部"Find
“框搜索”com.yhd.structure
", 按回车一个一个查找从前往后看耗时最多的自己写的方法,看有没有优化的空间。还要注意调用次数过多的函数,往往可能是因为性能问题。单击某一个方法, 在线程列表中会抖动显示该方法是在哪个线程上被执行的。其中Parent
表示调用这个方法的方法,可以叫做父方法,Children
表示这个方法中调用的其他方法,可以叫做子方法。
在怀疑性能问题的代码段外层,包裹追踪的的壳
//低版本SDK保存文件到 "/sdcard/myTag.trace"
//高版本SDK保存到"/sdcard/Android/data/packageName/files/myTag.trace"
Debug.startMethodTracing("myTag");开始trace,
writeToExternalStorage()
Debug.stopMethodTracing();//结束
导出trace文件到电脑
adb pull /sdcard/Android/data/packageName/files/myTag.trace C:\Users\haide.yin\Desktop
Hierarchy Viewer提供了一个可视化的界面来观测布局的树状层级图,直观显示测量、布局、绘制的消耗时间,让我们可以优化布局层级,删除多余的不必要的View层级。主要用来做View层级分析, 可以分析出View Tree中的性能阻塞点, 以便对症下药, 提升布局性能。
Hierarchy Viewer
上图红框标出的三个点是关键分析数据. 左起依次代表View的Measure, Layout和Draw的性能. 另外颜色表示该View的该项时间指数, 分为:
View
的此项性能比该View Tree
中超过50%的View
都要快。View
的此项性能比该View Tree
中超过50%的View
都要慢。View
的此项性能是View Tree
中最慢的。C:\Users\Administrator\.android
中删除monitor-workspace
文件,重新打开monitor
就行android studio
与DDMS
的端口冲突,所以我们可以打开DDMS
中的windows->preference->Android->DDMS
,将8600
换为8601
,同时选中ADBHOST
,设为127.0.0.1
,然后再将windows
系统中的host
文件中添加127.0.0.1 localhost
。Android studio
也占用了这个端口,把studio
关掉重新打开ddms
即可解决~adb shell service call window 3
查下状态:Result: Parcel(00000000 00000000 '........')"
说明View Server
处于关闭状态Result: Parcel(00000000 00000001 '........')"
说明View Server
处于开启状态View Server:
adb shell service call window 1 i32 4939
也可以使用使用以下命令关闭View Server:
adb shell service call window 2 i32 4939
view Hierarchy
root
权限下cmd
执行 :adb kill-server
adb start-server
view Hierarchy
root
system/build.prop
中添加ro.secure==0
和 ro.debuggable==1
,保存重启Android Studio 3.0
及更高版本中的 Android Profiler
取代了 Android Monitor
工具。Android Profiler
工具可提供实时数据,帮助您了解应用的 CPU
、内存、网络和电池资源使用情况。
要打开 Profiler
窗口,请依次选择 View > Tool Windows > Profiler
,或者在底部工具栏选择Profiler
,或点击工具栏中的 Profile
图标。
如果 Select Deployment Target
对话框提示,请选择要在哪个设备上分析您的应用。如果您通过 USB
连接了设备但系统未列出该设备,请确保您已启用 USB
调试。如果您使用的是 Android Emulator
或已取得 root
权限的设备,Android Profiler
将列出所有正在运行的进程,即使这些进程可能无法调试也会列出。当您启动可调试的应用时,默认情况下会选择该进程。Android Profiler
会持续收集分析数据,直到您断开设备连接或点击 End Session
。
① Android Profiler
显示当前正在分析的进程和设备。
② 在 Sessions
窗格中,选择要查看的会话,或启动一个新的分析会话。
③ 使用缩放按钮控制要查看时间轴范围,或使用 Attach to live
按钮跳转到实时更新。
④ 事件时间轴显示与用户输入相关的事件,包括键盘 Activity
、音量控制更改和屏幕旋转。
⑤ 共享时间轴视图,包括 CPU
、内存、网络和耗电量图表。
此共享时间轴视图只显示时间轴图表。包含三部分:CPU Profiler
、Memory Profiler
、Network Profiler
、Energy Profiler
。要使用详细分析工具,请点击与您要检查的性能数据对应的图表。例如,要使用相关工具检查堆数据和跟踪内存分配,请点击 Memory
图表。
并非所有分析数据在默认情况下都可见。如果您看到一条消息,显示“Advanced profiling is unavailable for the selected process”
,您可以在运行配置中启用高级分析以查看其他数据。
启用高级分析
要在运行搭载 Android 7.1
或更低版本的设备时向您显示高级分析数据,Android Studio
必须将监控逻辑注入您编译的应用。高级分析提供的功能包括:
Memory Profiler
中已分配对象的数量Memory Profiler
中的垃圾回收事件Network Profiler
中有关所有已传输文件的详细信息注意:如果您的设备搭载的是
Android 8.0
或更高版本,则默认情况下可以使用这些功能。
要启用高级分析功能,请按以下步骤操作:
Run > Edit Configurations
。Profiling
标签,然后勾选 Enable advanced profiling
。高级分析配置会使编译过程变慢,所以仅在您想要开始分析您的应用时,才应启用该配置.。
注意:高级分析功能不可用于原生代码。如果您的应用是纯原生应用(不含
Java Activity
类),则不可使用高级分析功能。如果您的应用使用JNI
,则可使用部分高级分析功能(如事件时间轴、垃圾回收事件、Java 分配对象和基于 Java 的网络Activity
),但不能检测基于原生代码的分配和网络Activity
。
Sessions会话
您可以将分析器数据另存为会话,这些会话将一直保留,直到您退出 Android Studio。通过在多个会话中记录分析信息并在它们之间进行切换,您可以比较各种场景中的资源使用情况。
Start a new profiling session
按钮 ,然后从出现的下拉菜单中选择一个应用进程。Android Studio
会将相应数据(以及您应用的网络 Activity
)作为单独的条目添加到当前会话。Stop the current profiling session
图标(如果一直不停止,时间久了会卡) 。Android Studio
时导出的轨迹,请点击 Start new profiler session
图标 ,然后选择 Load from file
。优化应用的 CPU
使用率能带来诸多好处,如提供更快、更顺畅的用户体验,以及延长设备电池续航时间。您可以使用 CPU Profiler
在与应用交互时实时检查应用的 CPU
使用率和线程活动,也可以检查记录的方法跟踪数据、函数跟踪数据和系统跟踪数据的详细信息。
当您打开 CPU Profiler
时,它会立即开始显示应用的 CPU
使用率和线程活动。
① 事件时间轴:显示应用中的 Activity
在其生命周期内不断转换而经历各种不同状态的过程,并指示用户与设备的交互,包括屏幕旋转事件。
② CPU 时间轴:显示应用的实时 CPU
使用率(以占总可用 CPU
时间的百分比表示)以及应用当前使用的线程总数。 此时间轴还会显示其他进程(如系统进程或其他应用)的 CPU
使用率,以便您可以将其与您应用的 CPU
使用率进行对比。您可以通过沿时间轴的横轴方向移动鼠标来检查历史 CPU
使用率数据。
③ 线程活动时间轴:列出属于应用进程的每个线程,并使用下面列出的颜色在时间轴上指示它们的活动。记录跟踪数据后,您可以从此时间轴上选择一个线程,以在跟踪数据窗格中检查其数据。
CPU
。也就是说,线程处于正在运行或可运行状态。I/O
操作(如磁盘或网络 I/O
),然后才能完成它的工作。CPU
时间。 当线程需要访问尚不可用的资源时,就会出现这种情况。在这种情况下,要么线程主动进入休眠状态,要么内核将线程置于休眠状态,直到所需的资源可用。
CPU Profiler
还会报告Android Studio
和Android
平台添加到应用进程的线程的CPU
使用率,这些线程包括JDWP
、Profile Saver
、Studio:VMStats
、Studio:Perfa
和Studio:Heartbeat
等(不过,它们在线程活动时间轴上显示的确切名称可能有所不同)。Android Studio
将报告此数据,以便您确定什么时候线程活动和CPU
使用率实际上是由您的应用的代码引发的。
对 Java 方法采样(Sample Java Methods):在应用的 Java
代码执行期间,频繁捕获应用的调用堆栈。分析器会比较捕获的数据集,以推导与应用的 Java
代码执行有关的时间和资源使用信息。
基于采样的跟踪存在一个固有的问题,那就是如果应用在捕获调用堆栈后进入一个方法并在下次捕获前退出该方法,分析器将不会记录该方法调用。如果您想要跟踪生命周期如此短的方法,应使用检测跟踪。
跟踪 Java 方法(Trace Java Methods):在运行时检测应用,以在每个方法调用开始和结束时记录一个时间戳。系统会收集并比较这些时间戳,以生成方法跟踪数据,包括时间信息和 CPU
使用率。
请注意,与检测每个方法相关的开销会影响运行时性能,并且可能会影响分析数据;对于生命周期相对较短的方法,这一点更为明显。此外,如果应用在短时间内执行大量方法,则分析器可能很快就会超出其文件大小限制,因而不能再记录更多跟踪数据。
对 C/C++ 函数采样(Sample C/C+ Functions):捕获应用的原生线程的采样跟踪数据。
要使用此配置,您必须将应用部署到搭载
Android 8.0
(API
级别26
)或更高版本的设备上。
跟踪系统调用(Trace System Calls):捕获非常翔实的细节,以便您检查应用与系统资源的交互情况。您可以检查线程状态的确切时间和持续时间、直观地查看所有内核的 CPU
瓶颈在何处,并添加要分析的自定义跟踪事件。 当您排查性能问题时,此类信息至关重要。
要使用此配置,您必须将应用部署到搭载
Android 7.0
(API
级别24
)或更高版本的设备上。使用此跟踪配置时,您可以通过检测代码,在分析器时间轴上直观地标记重要的代码例程。要检测C/C++
代码,请使用由trace.h
提供的原生跟踪API
。要检测Java
代码,请使用Trace
类。如需了解详情,请参阅检测您的应用代码。此跟踪配置建立在systrace
的基础之上。您可以使用systrace
命令行实用程序指定除CPU Profiler
提供的选项之外的其他选项。systrace
提供的其他系统级数据可帮助您检查原生系统进程并排查丢帧或帧延迟问题。
要开始记录跟踪数据,请从 CPU Profiler
顶部的下拉菜单中选择记录配置,然后点击 Record
。与您的应用交互,然后在完成时点击 Stop
。分析器将自动选择记录的时间范围,并在跟踪数据窗格中显示其跟踪信息,如图所示。如果要检查其他线程的跟踪数据,请从线程活动时间轴上选择相应线程。
① 选定范围:确定要在跟踪数据窗格中检查所记录时间的哪一部分。当您首次记录跟踪数据时,CPU Profiler
会自动在 CPU
时间轴上选择记录的完整长度。 要仅检查已记录的时间范围中的一部分的跟踪数据,请拖动突出显示区域的边缘。
② 时间戳:指示所记录跟踪数据的开始和结束时间(相对于分析器开始收集 CPU
使用率信息的时间)。要选择完整的记录,请点击时间戳。
③ 跟踪数据窗格:显示您选择的时间范围和线程的跟踪数据。此窗格要在您至少记录一条跟踪数据后才会显示。 在此窗格中,您可以选择如何查看每个堆栈轨迹(使用跟踪数据标签页),以及如何测量执行时间(使用时间参考下拉菜单)。
④ 跟踪数据窗格标签页:选择如何显示跟踪数据详细信息。如需详细了解各个选项,请参阅检查跟踪数据。
⑤ 时间参考菜单:选择以下选项之一,以确定如何测量每次调用的时间信息:
CPU
资源的那部分时间。对于任何给定的调用,其线程时间始终小于或等于其挂钟时间。使用线程时间可以让您更好地了解线程的实际 CPU
使用率中有多少是给定方法或函数占用的。⑥ 过滤器:按函数、方法、类或软件包名称过滤跟踪数据。例如,如果您要快速识别与特定调用相关的跟踪数据,请点击 Filter
图标 或按 Ctrl + F
(在 Mac
上,按 Command + F
键),然后在搜索字段中输入相应的名称。在 Call chart
和 Flame chart
标签页中,会突出显示包含符合搜索查询条件的调用、软件包或类的调用堆栈。在 Top down
和 Bottom up
标签页中,这些调用堆栈优先于其他跟踪结果。您还可以通过勾选搜索字段旁边的相应方框来启用以下选项:
CPU Profiler
中的跟踪数据窗格提供多个标签页,供您选择如何查看所记录的跟踪数据中的信息。要查看方法跟踪数据和函数跟踪数据,您可以从 Call Chart
、Flame Chart
、Top Down
和 Bottom Up
标签页中进行选择。要查看系统跟踪数据(Trace System Calls
),您可以从 Trace Events
、Flame Chart
、Top Down
和 Bottom Up
标签页中进行选择。
Call Chart
标签页会以图形来呈现方法跟踪数据或函数跟踪数据,其中调用的时间段和时间在横轴上表示,而其被调用方则在纵轴上显示。对系统 API
的调用显示为橙色,对应用自有方法的调用显示为绿色,对第三方 API
(包括 Java
语言 API
)的调用显示为蓝色。下图显示了一个调用图表示例,说明了给定方法或函数的 Self
时间、Children
时间和 Total
时间的概念(下面会讲)。
提示:要跳转到某个方法或函数的源代码,双击或者右键点击该方法或函数,然后选择
Jump to Source
。在跟踪数据窗格的任意标签页中都可以执行此操作。
Flame Chart
标签页提供一个倒置的调用图表,用来汇总完全相同的调用堆栈。也就是说,将具有相同调用方顺序的完全相同的方法或函数收集起来,并在火焰图中将它们表示为一个较长的横条(而不是将它们显示为多个较短的横条,如调用图表中所示)。这样更方便您查看哪些方法或函数消耗的时间最多。不过,这也意味着,横轴不代表时间轴,而是表示执行每个方法或函数所需的相对时间。
为帮助说明此概念,不妨考虑下图的调用图表。请注意,方法 D
多次调用 B
(B1
、B2
和 B3
),其中一些对 B
的调用也调用了 C
(C1
和 C3
)。
由于 B1
、B2
和 B3
具有相同的调用方顺序 (A
→ D
→ B
),因此系统将它们汇总在一起,如下图所示。同样,也将 C1
和 C3
汇总在一起,因为它们也具有相同的调用方顺序 (A
→ D
→ B
→ C
)。请注意,C2
不包括在内,因为它具有不同的调用方顺序 (A
→ D
→ C
)。
汇总的调用用于创建火焰图,如下图所示。 请注意,对于火焰图中的任何给定调用,占用最多 CPU
时间的被调用方最先显示。
Top Down
标签显示一个调用列表,在该列表中展开方法或函数节点会显示它的被调用方。下图显示了上面的调用图表的自上而下图。图中的每个箭头都是从调用方指向被调用方。在 Top Down
标签页中展开方法 A
的节点会显示它的被调用方,即方法 B
和 D
。在此之后,展开方法 D
的节点会显示它的被调用方,即方法 B
和 C
,依此类推。
图 9. 图 8 中方法 C 的“Bottom Up”树。
Bottom Up
标签页显示一个调用列表,在该列表中展开函数或方法的节点会显示它的调用方。下图提供了方法 C
的“Bottom Up
”树。在该“Bottom Up
”树中打开方法 C
的节点会显示它独有的各个调用方,即方法 B
和 D
。请注意,尽管 B
调用 C
两次,但在“Bottom Up
”树中展开方法 C
的节点时,B
仅显示一次。在此之后,展开 B
的节点会显示它的调用方,即方法 A
和 D
。
Bottom Up
标签页用于按照占用的 CPU
时间由多到少(或由少到多)的顺序对方法或函数排序。您可以检查每个节点以确定哪些调用方在调用这些方法或函数上所花的 CPU
时间最多。 与“Top Down
”树相比,“Bottom Up
”树中每个方法或函数的时间信息参照的是每个树顶部的方法(顶部节点)。 CPU
时间也可表示为在该记录期间占线程总时间的百分比。下表说明了如何解读顶部节点及其调用方(子节点)的时间信息。
节点类型 | Self | Children | Total |
---|---|---|---|
Bottom Up 树顶部的方法或函数(顶部节点) |
表示方法或函数在执行自己的代码(而非被调用方的代码)上所花的总时间。与Top Down 树相比,此时间信息表示在记录的持续时间内对此方法或函数的所有调用时间的总和。 |
表示方法或函数在执行它的被调用方(而非自己的代码)上所花的总时间。与Top Down 树相比,此时间信息表示在记录的持续时间内对此方法或函数的被调用方的所有调用时间的总和 |
Self 时间和 Children 时间的总和。 |
调用方(子节点) | 表示被调用方在由调用方调用时的总 Self 时间。以上图的Bottom Up 树为例,方法 B 的 Self 时间将等于每次执行由方法 B 调用的方法 C 所用的 Self 时间的总和。 |
表示被调用方在由调用方调用时的总 Children 时间。以上图的Bottom Up 树为例,方法 B 的 Children 时间将等于每次执行由方法 B 调用的方法 C 所用的 Children 时间的总和。 |
Self 时间和 Children 时间的总和。 |
注意:对于给定的记录,当分析器达到文件大小限制时,
Android Studio
会停止收集新数据(不过,不会停止记录)。在执行检测跟踪时,这种情况通常发生得更快,因为与采样跟踪相比,此类跟踪会在更短的时间内收集更多的数据。如果您将检查时间范围延长至达到限制后的记录时段,则跟踪数据窗格中的时间数据不会发生变化(因为没有新数据可用)。此外,当您仅选择没有可用数据的那部分记录时,对于时间信息,跟踪数据窗格将显示NaN
。
检查系统跟踪数据时,您可以使用 Trace Events
标签查看每个线程上发生的事件的详细信息。要查看某个线程的详细信息,请在 Threads
窗格中选择该线程。这样将在 Kernel
窗格中突出显示该线程在每个 CPU
内核上的活动,并在 Trace Events
标签页中显示该线程的事件。在 Trace Events
标签页中将鼠标指针悬停在某个事件上可查看该事件的名称以及在每种状态下所用的时间。
您可以检查应用在主线程和 RenderThread
上渲染每个帧所用的时间,以调查造成界面卡顿和帧速率低的瓶颈。要查看帧渲染数据,请使用可让您跟踪系统调用的配置记录跟踪数据。记录跟踪数据后,在名为 FRAMES
的部分下查找有关每个帧的信息,每个用时超过 16 毫秒的帧都以红色显示。如何检查系统跟踪信息,来源于上文提到的 systrace
。
除了上述提到的功能,你也可以使用 Debug API
记录 CPU 活动。这个功能我们在上文中提到。当您的应用调用 startMethodTracing(String tracePath)
时,CPU Profiler
将开始记录;当您的应用调用 stopMethodTracing()
时,CPU Profiler
将停止记录。在记录使用此 API
触发的 CPU
活动时,CPU Profiler
会将 Debug API
显示为作用中的 CPU
记录配置。
您可以将相应数据导出为 .trace
文件,您可以导入使用 Debug API
或 CPU Profiler
创建的 .trace
文件。
Memory Profiler
是 Android Profiler
中的一个组件,可帮助您识别可能会导致应用卡顿、冻结甚至崩溃的内存泄露和内存抖动。它显示一个应用内存使用量的实时图表,让您可以捕获堆转储、强制执行垃圾回收以及跟踪内存分配。
为什么应分析您的应用内存
Android
提供了托管内存环境 - 当它确定您的应用不再使用某些对象时,垃圾回收器会将未使用的内存释放回堆中。虽然 Android
查找未使用内存的方式在不断改进,但对于所有 Android
版本,系统都必须在某个时间点短暂地暂停您的代码。大多数情况下,这些暂停难以察觉。不过,如果您的应用分配内存的速度比系统回收内存的速度快,则当回收器释放足够的内存以满足您的分配需要时,您的应用可能会延迟。此延迟可能会导致您的应用跳帧,并使系统明显变慢,也就是内存抖动现象。
尽管您的应用不会表现出变慢,但如果存在内存泄露,则即使应用在后台运行也会保留该内存。此行为会强制执行不必要的垃圾回收事件,因而拖慢系统其余部分的内存性能。最后,系统被迫终止您的应用进程以回收内存。然后,当用户返回您的应用时,它必须完全重启。
为帮助防止这些问题,您应使用 Memory Profiler
执行以下操作:
当您首次打开 Memory Profiler
时,您将看到一条表示应用内存使用量的详细时间轴,并可使用各种工具来强制执行垃圾回收、捕获堆转储以及记录内存分配。
① 用于强制执行垃圾回收事件的按钮。
② 用于捕获堆转储的按钮。
注意:只有在连接到搭载
Android 7.1
(API
级别25
)或更低版本的设备时,才会在堆转储按钮右侧显示用于记录内存分配的按钮。
③ 用于指定分析器多久捕获一次内存分配的下拉菜单。选择适当的选项可帮助您 在分析时提高应用性能。
④ 用于缩放时间轴的按钮。
⑤ 用于跳转到实时内存数据的按钮。
⑥ 事件时间轴,显示活动状态、用户输入事件和屏幕旋转事件。
⑦ 内存使用量时间轴,它会显示以下内容:
y
轴以及顶部的彩色键所示。y
轴所示。您在 Memory Profiler
顶部看到的数字基于您的应用根据 Android 系统机制所提交的所有私有内存页面。此计数不包含与系统或其他应用共享的页面。
Java:从 Java
或 Kotlin
代码分配的对象的内存。
Native:从 C
或 C++
代码分配的对象的内存。
即使您的应用中不使用
C++
,您也可能会看到此处使用的一些原生内存,因为Android
框架使用原生内存代表您处理各种任务,如处理图像资源和其他图形时,即使您编写的代码采用Java
或Kotlin
语言。
Graphics:图形缓冲区队列向屏幕显示像素(包括 GL
表面、GL
纹理等等)所使用的内存。(请注意,这是与 CPU
共享的内存,不是 GPU
专用内存。)
Stack:您的应用中的原生堆栈和 Java
堆栈使用的内存。这通常与您的应用运行多少线程有关。
Code:您的应用用于处理代码和资源(如 dex
字节码、经过优化或编译的 dex
代码、.so
库和字体)的内存。
Others:您的应用使用的系统不确定如何分类的内存。
Allocated:您的应用分配的 Java/Kotlin
对象数。此数字没有计入 C
或 C++
中分配的对象。
如果连接到搭载
Android 7.1
及更低版本的设备,只有在Memory Profiler
连接到您运行的应用时,才开始此分配计数。因此,您开始分析之前分配的任何对象都不会被计入。不过,Android 8.0
及更高版本附带一个设备内置分析工具,该工具可跟踪所有分配,因此,在Android 8.0
及更高版本上,此数字始终表示您的应用中待处理的Java
对象总数。
内存分配为您显示内存中的每个 Java
对象和 JNI
引用是如何分配的。具体而言,Memory Profiler
可为您显示有关对象分配的以下信息:
Android 8.0
或更高版本的设备时)。如果您的设备搭载的是 Android 8.0
或更高版本,您可以随时查看对象分配,具体操作步骤如下:在时间轴上拖动以选择要查看哪个区域的分配。不需要开始记录会话,因为 Android 8.0
及更高版本附带设备内置分析工具,可持续跟踪您的应用分配。
如果您的设备搭载的是 Android 7.1
或更低版本,请点击 Memory Profiler
工具栏中的 Record memory allocations
图标 。记录时,Memory Profiler
会跟踪您的应用中发生的所有分配。完成后,请点击 Stop recording
图标以查看分配。
选择时间轴的某个区域后(或者使用搭载 Android 7.1
或更低版本的设备完成记录会话后),已分配对象的列表将显示在时间轴下方,按类名称进行分组,并按其堆计数排序。
注意:在 Android 7.1 及更低版本上,您最多可以记录 65535 个分配。 如果您的记录会话超出此限制,则记录中仅保存最新的 65535 个分配。(在 Android 8.0 及更高版本上,则没有实际的限制。)
Class Name
列标题以按字母顺序排序。然后,点击一个类名称。此时右侧将出现 Instance View
窗格,显示该类的每个实例。此外,您也可以快速找到对象,方法是点击 Filter
图标 ,或按 Ctrl+F
键(在 Mac
上,按 Command+F
键),然后在搜索字段中输入类或软件包名称。如果从下拉菜单中选择 Arrange by callstack
,还可以按方法名称搜索。如果要使用正则表达式,请勾选 Regex
旁边的复选框。如果您的搜索查询区分大小写,请勾选 Match case
旁边的复选框。Instance View
窗格中,点击一个实例。此时下方将出现 Call Stack
标签页,显示该实例被分配到何处以及在哪个线程中。Call Stack
标签页中,右键点击任意行并选择 Jump to Source
,以在编辑器中打开该代码。从左侧的菜单中,选择要检查的堆:
Android
系统中派生的。JNI
) 引用被分配和释放到什么位置的堆。从右侧的菜单中,选择如何安排分配:
为了在分析时提高应用性能,Memory Profiler
在默认情况下会定期对内存分配进行采样。在运行 API
级别 26
或更高级别的设备上进行测试时,您可以使用 Allocation Tracking
下拉菜单来更改此行为。可用选项如下:
Android Studio 3.2
及更低版本中的默认行为。如果您有一个分配了大量对象的应用,则可能会在分析时观察到应用的运行速度明显减慢。Java
原生接口 (JNI
) 是一个允许 Java
代码和原生代码相互调用的框架。
JNI
引用由原生代码进行管理,因此原生代码使用的 Java
对象可能会保持活动状态太长时间。如果丢弃了 JNI
引用而未先明确将其删除,Java
堆上的某些对象可能会变得无法访问。此外,还可能会达到全局 JNI
引用限制。
要排查此类问题,请使用 Memory Profiler
中的 JNI heap
视图来浏览所有全局 JNI
引用,并按 Java
类型和原生调用堆栈对其进行过滤。借助此信息,您可以了解创建和删除全局 JNI
引用的时间和位置。
堆转储显示在您捕获堆转储时您的应用中哪些对象正在使用内存。特别是在长时间的用户会话后,堆转储会显示您认为不应再位于内存中却仍在内存中的对象,从而帮助识别内存泄露。
捕获堆转储后,您可以查看以下信息:
Android 7.1
及更低版本,只有在记录分配期间捕获堆转储时,才会显示调用堆栈的堆转储。)要捕获堆转储,请点击 Memory Profiler
工具栏中的 Dump Java heap
图标 。 在转储堆期间,Java
内存量可能会暂时增加。 这很正常,因为堆转储与您的应用发生在同一进程中,并需要一些内存来收集数据。堆转储出现在内存时间轴下方,显示堆中的所有类类型,如图:
Android 7.0
及更高版本时,才会看到此列。 您会在此处看到采用 Java
分配的某些对象的内存,因为 Android
对某些框架类(如 Bitmap
)使用原生内存。
- 针对非数组类型的对象,它的大小就是对象与它所有的成员变量大小的总和。当然这里面还会包括一些java语言特性的数据存储单元,比如
alignment
位数对齐。- 针对数组类型的对象,它的大小是数组元素对象的大小总和。
Retained Size
是指, 当实例被回收时, 可以同时被回收的实例的Shallow Size之
和。所以进行内存分析时, 我们应该重点关注Retained Size
较大的实例; 或者可以通过Retained Size
判断出某实例内部使用的实例是否被其他实例引用。例如在Android中, 如果某个Bitmap
实例的Retained Size
很小, 证明它内部的byte
数组被复用了, 有另一个Bitmap
实例指向了同一个byte
数组。换句话说,Retained Size
就是当前对象被GC
后,从Heap
上总共能释放掉的内存。不过,释放的时候还要排除被其他对象直接或间接引用的对象。
点击一个类名称可在右侧打开 Instance View
窗口。列出的每个实例都包含以下信息:
这个参数非常重要,只有Depth = 0 的时候对象才能被回收。当我们在页面销毁的时候执行
Dump Java Heap
,发现某个应该销毁的对象的Depth
不为0
,那么证明该对象存在泄露。
Android 7.0
及更高版本时,才会看到此列。我们可以将堆转储另存为 HPROF
文件,也可以导入一个 HPROF (.hprof)
文件。
Network Profiler
会在时间轴上显示实时网络活动,包括发送和接收的数据以及当前的连接数。这便于您检查应用传输数据的方式和时间,并适当优化底层代码。
目前,
Network Profiler
仅支持HttpURLConnection
和OkHttp
网络连接库。如果您的应用使用的是其他网络连接库,您可能无法在Network Profiler
中查看网络活动。如果 Network Profiler 检测到流量值,但无法识别任何受支持的网络请求,您会收到以下错误消息:Network Profiling Data Unavailable:There is no information for the network traffic you've selected.
当您的应用向网络发出请求时,设备必须使用高功耗的移动或 WLAN 无线装置来收发数据包。无线装置不仅要消耗电力来传输数据,而且还要消耗额外的电力来开启并且不锁定屏幕。
使用 Network Profiler
,您可以查找频繁出现的短时网络活动峰值,这些峰值意味着,您的应用要求经常开启无线装置,或要求无线装置长时间不锁定屏幕以处理集中出现的大量短时请求。这种模式说明您可以通过批量处理网络请求,减少必须开启无线装置来发送或接收数据的次数,从而优化应用,改善电池性能。这种方式还能让无线装置切换到低功耗模式,延长批量处理请求之间的间隔时间,节省电量。
窗口顶部显示的是事件时间轴。在时间轴 (①) 上,您可以点击并拖动以选择时间轴的一部分来检查网络流量。
在时间轴下方的窗格 (②) 中,您可以选择以下某个标签页,以详细了解时间轴上选定时段内的网络活动:
CPU
线程发送或接收的文件。对于每个请求,您可以检查大小、类型、状态和传输时长。 您可以通过点击任意列标题来对此列表排序。您还会看到时间轴上选定时段的明细数据,从而了解每个文件的发送或接收时间。CPU
线程的网络活动。 如下图所示,您可以在此视图中检查应用的哪些线程负责每个网络请求。从 Connection View
或 Thread View
中,点击请求名称可检查有关已发送或已接收数据的详细信息 (③)。点击各个标签页可查看响应标头和正文、请求标头和正文或调用堆栈。
在 Response
和 Request
标签页中,点击 View Parsed
链接可显示格式化文本,点击 View Source
链接可显示原始文本。
注意:如果您使用的是
HttpURLConnection API
,则不会在Request
标签页中看到标头,除非您使用setRequestProperty
方法将其添加到您的代码中:urlConnection.setRequestProperty("Accept-Encoding", "identity");
Energy Profiler
可帮助您了解应用在哪里耗用了不必要的电量;会监控 CPU
、网络无线装置和 GPS
传感器的使用情况,并直观地显示其中每个组件消耗的电量; 还会显示可能会影响耗电量的系统事件(唤醒锁定、闹钟、作业和位置信息请求)的发生次数。Energy Profiler
并不会直接测量耗电量,而是使用一种模型来估算设备上每项资源的耗电量。
当您在搭载 Android 8.0 (API 26)
或更高版本的关联设备或 Android
模拟器中运行您的应用时,Energy Profiler
便会显示为 Profiler
窗口中的一个新行。当您打开 Energy Profiler
时,它会立即开始显示应用的估算耗电量。
① Event时间轴:显示应用中的 Activity
在其生命周期内不断转换而经历各种不同状态的过程。此时间轴还会指示用户与设备的交互,包括屏幕旋转事件。
② Energy时间轴:显示应用的估算耗电量。
③ System时间轴:显示可能会影响耗电量的系统事件。
要查看 CPU、网络和位置信息 (GPS) 资源,以及相关系统事件的具体耗电量情况,请将鼠标指针放在 Energy 时间轴中的条形上方。
您可以使用 Energy Profiler 查找可能会影响耗电量的系统事件,包括唤醒锁定、作业和闹钟:
CPU
或屏幕保持开启状态。例如,播放视频的应用可以使用唤醒锁定,以便在用户未与设备交互时使屏幕保持开启状态。请求唤醒锁定不是一项耗电量很高的操作,但未撤消唤醒锁定会导致屏幕或 CPU
保持开启状态的时间超过必要时间,从而加快电池耗电速度。JobBuilder
创建作业,并使用 JobScheduler
对这些作业进行调度。在许多情况下,建议您使用 JobScheduler
对作业进行调度,而不是使用闹钟或唤醒锁定。借助 Energy Profiler
,您可以轻松找到应用使用各项功能的位置,以便您就如何使用各项功能做出明智的决策。Energy Profiler
会在 Energy
时间轴下的 System
时间轴中显示一个彩色编码的条形,以表示系统事件处于活动状态的时间范围。唤醒锁定用红色条形表示,作业和闹钟用黄色条形表示,位置信息事件用浅紫色条形表示。
下图显示了 Energy Profiler
,并在代码编辑器中定位到了未释放唤醒锁定对应的源代码。
① 要打开 System Event 窗格并显示唤醒锁定等事件的详细信息,请在 Energy 时间轴中选择一个时间范围。
② 要打开 Wake Lock Details 窗格并显示特定唤醒锁定的详细信息,请在 System Event 窗格中选择该唤醒锁定。
③ 要打开代码编辑器并跳转到唤醒锁定的源代码,请在 Wake Lock Details 窗格中双击调用堆栈顶部的调用方法条目。
④ 用于获取唤醒锁定的调用会在源代码编辑器中突出显示。
Square
出品,必属精品。类似与APP
探针的内存泄露监测工具,不用写任何代码就能引入APP
的内存泄漏检测机制,这里不做深入。
详情点击:https://github.com/square/leakcanary