TraceView 是 Android 平台配备一个很好的性能分析的工具。它可以通过图形化的方式让我们了解我们要跟踪的程序的性能,并且能具体到 method。详细内容参考:Profiling with Traceview and dmtracedump

TraceView 简介

TraceView 是 Android 平台特有的数据采集和分析工具,它主要用于分析 Android 中应用程序的 hotspot。TraceView 本身只是一个数据分析工具,而数据的采集则需要使用 Android SDK 中的 Debug 类或者利用 DDMS 工具。二者的用法如下:

  • 开发者在一些关键代码段开始前调用 Android SDK 中 Debug 类的 startMethodTracing 函数,并在关键代码段结束前调用 stopMethodTracing 函数。这两个函数运行过程中将采集运行时间内该应用所有线程(注意,只能是 Java 线程)的函数执行情况,并将采集数据保存到 /mnt/sdcard/ 下的一个文件中。开发者然后需要利用 SDK 中的 TraceView 工具来分析这些数据。
  • 借助 Android SDK 中的 DDMS 工具。DDMS 可采集系统中某个正在运行的进程的函数调用信息。对开发者而言,此方法适用于没有目标应用源代码的情况。

DDMS 中 TraceView 使用示意图如下,调试人员可以通过选择 Devices 中的应用后点击  按钮 Start Method Profiling(开启方法分析)和点击  Stop Method Profiling(停止方法分析)

开启方法分析后对应用的目标页面进行测试操作,测试完毕后停止方法分析,界面会跳转到 DDMS 的 trace 分析界面,如下图所示:

TraceView 界面比较复杂,其 UI 划分为上下两个面板,即 Timeline Panel(时间线面板)和 Profile Panel(分析面板)。上图中的上半部分为Timeline Panel(时间线面板),Timeline Panel 又可细分为左右两个 Pane:

  • 左边 Pane 显示的是测试数据中所采集的线程信息。由图可知,本次测试数据采集了 main 线程,传感器线程和其它系统辅助线程的信息。
  • 右边 Pane 所示为时间线,时间线上是每个线程测试时间段内所涉及的函数调用信息。这些信息包括函数名、函数执行时间等。由图可知,Thread-1412 线程对应行的的内容非常丰富,而其他线程在这段时间内干得工作则要少得多。
  • 另外,开发者可以在时间线 Pane 中移动时间线纵轴。纵轴上边将显示当前时间点中某线程正在执行的函数信息。

上图中的下半部分为 Profile Panel(分析面板),Profile Panel 是 TraceView 的核心界面,其内涵非常丰富。它主要展示了某个线程(先在 Timeline Panel 中选择线程)中各个函数调用的情况,包括 CPU 使用时间、调用次数等信息。而这些信息正是查找 hotspot 的关键依据。所以,对开发者而言,一定要了解 Profile Panel 中各列的含义。下表列出了 Profile Panel 中比较重要的列名及其描述。

TraceView 实战

了解完 TraceView 的 UI 后,现在介绍如何利用 TraceView 来查找 hotspot。一般而言,hotspot 包括两种类型的函数:

  • 一类是调用次数不多,但每次调用却需要花费很长时间的函数。
  • 一类是那些自身占用时间不长,但调用却非常频繁的函数。

测试背景:APP 在测试机运行一段时间后出现手机发烫、卡顿、高 CPU 占有率的现象。将应用切入后台进行 CPU 数据的监测,结果显示,即使应用不进行任何操作,应用的 CPU 占有率都会持续的增长。

按照 TraceView 简介中的方法进行测试,TraceView 结果 UI 显示后进行数据分析,在 Profile Panel 中,选择按 Cpu Time/Call 进行降序排序(从上之下排列,每项的耗费时间由高到低)得到如图所示结果:

图中 ImageLoaderTools$2.run() 是应用程序中的函数,它耗时为 1111.124。然后点击 ImageLoaderTools$2.run() 项,得到更为详尽的调用关系图:

上图中 Parents 为 ImageLoaderTools$2.run() 方法的调用者:Parents (the methods calling this method);Children 为 ImageLoaderTools$2.run()调用的子函数或方法:Children (the methods called by this method)。本例中 ImageLoaderTools$2.run() 方法的调用者为 Framework 部分,而 ImageLoaderTools$2.run() 方法调用的自方法中我们却发现有三个方法的 Incl Cpu Time % 占用均达到了 14% 以上,更离谱的是 Calls+RecurCalls/Total 显示这三个方法均被调用了 35000 次以上,从包名可以识别出这些方法为测试者自身所实现,由此可以判断ImageLoaderTools$2.run() 极有可能是手机发烫、卡顿、高 CPU 占用率的原因所在。

代码验证

大致可以判断是 ImageLoaderTools$2.run() 方法出现了问题,下面找到这个方法进行代码上的验证:

  1 package com.sunzn.app.utils;2 3 import java.io.File;4 import java.io.IOException;5 import java.io.InputStream;6 import java.lang.ref.SoftReference;7 import java.util.ArrayList;8 import java.util.HashMap;9 10 import android.content.Context;11 import android.graphics.Bitmap;12 import android.os.Environment;13 import android.os.Handler;14 import android.os.Message;15 16 public class ImageLoaderTools {17 18     private HttpTools httptool;19 20     private Context mContext;21 22     private boolean isLoop = true;23 24     private HashMap<String, SoftReference<Bitmap>> mHashMap_caches;25 26     private ArrayList<ImageLoadTask> maArrayList_taskQueue;27 28     private Handler mHandler = new Handler() {29         public void handleMessage(android.os.Message msg) {30             ImageLoadTask loadTask = (ImageLoadTask) msg.obj;31             loadTask.callback.imageloaded(loadTask.path, loadTask.bitmap);32         };33     };34 35     private Thread mThread = new Thread() {36 37         public void run() {38 39             while (isLoop) {40 41                 while (maArrayList_taskQueue.size() > 0) {42 43                     try {44                         ImageLoadTask task = maArrayList_taskQueue.remove(0);45 46                         if (Constant.LOADPICTYPE == 1) {47                             byte[] bytes = httptool.getByte(task.path, null, HttpTools.METHOD_GET);48                             task.bitmap = BitMapTools.getBitmap(bytes, 40, 40);49                         } else if (Constant.LOADPICTYPE == 2) {50                             InputStream in = httptool.getStream(task.path, null, HttpTools.METHOD_GET);51                             task.bitmap = BitMapTools.getBitmap(in, 1);52                         }53 54                         if (task.bitmap != null) {55                             mHashMap_caches.put(task.path, new SoftReference<Bitmap>(task.bitmap));56                             File dir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);57                             if (!dir.exists()) {58                                 dir.mkdirs();59                             }60                             String[] path = task.path.split("/");61                             String filename = path[path.length - 1];62                             File file = new File(dir, filename);63                             BitMapTools.saveBitmap(file.getAbsolutePath(), task.bitmap);64                             Message msg = Message.obtain();65                             msg.obj = task;66                             mHandler.sendMessage(msg);67                         }68                     } catch (IOException e) {69                         e.printStackTrace();70                     } catch (Exception e) {71                         e.printStackTrace();72                     }73 74                     synchronized (this) {75                         try {76                             wait();77                         } catch (InterruptedException e) {78                             e.printStackTrace();79                         }80                     }81 82                 }83 84             }85 86         };87 88     };89 90     public ImageLoaderTools(Context context) {91         this.mContext = context;92         httptool = new HttpTools(context);93         mHashMap_caches = new HashMap<String, SoftReference<Bitmap>>();94         maArrayList_taskQueue = new ArrayList<ImageLoaderTools.ImageLoadTask>();95         mThread.start();96     }97 98     private class ImageLoadTask {99         String path;
100         Bitmap bitmap;
101         Callback callback;
102     }
103
104     public interface Callback {
105         void imageloaded(String path, Bitmap bitmap);
106     }
107
108     public void quit() {
109         isLoop = false;
110     }
111
112     public Bitmap imageLoad(String path, Callback callback) {
113         Bitmap bitmap = null;
114         String[] path1 = path.split("/");
115         String filename = path1[path1.length - 1];
116
117         if (mHashMap_caches.containsKey(path)) {
118             bitmap = mHashMap_caches.get(path).get();
119             if (bitmap == null) {
120                 mHashMap_caches.remove(path);
121             } else {
122                 return bitmap;
123             }
124         }
125
126         File dir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
127
128         File file = new File(dir, filename);
129
130         bitmap = BitMapTools.getBitMap(file.getAbsolutePath());
131         if (bitmap != null) {
132             return bitmap;
133         }
134
135         ImageLoadTask task = new ImageLoadTask();
136         task.path = path;
137         task.callback = callback;
138         maArrayList_taskQueue.add(task);
139
140         synchronized (mThread) {
141             mThread.notify();
142         }
143
144         return null;
145     }
146
147 }

以上代码即是 ImageLoaderTools 图片工具类的全部代码,先不着急去研究这个类的代码实现过程,先来看看这个类是怎么被调用的:

 1 ImageLoaderTools imageLoaderTools = imageLoaderTools = new ImageLoaderTools(this);2 3 Bitmap bitmap = imageLoaderTools.imageLoad(picpath, new Callback() {4 5     @Override6     public void imageloaded(String picPath, Bitmap bitmap) {7         if (bitmap == null) {8             imageView.setImageResource(R.drawable.default);9         } else {
10             imageView.setImageBitmap(bitmap);
11         }
12     }
13 });
14
15 if (bitmap == null) {
16     imageView.setImageResource(R.drawable.fengmianmoren);
17 } else {
18     imageView.setImageBitmap(bitmap);
19 }

ImageLoaderTools 被调用的过程非常简单:1.ImageLoaderTools 实例化;2.执行 imageLoad() 方法加载图片。

在 ImageLoaderTools 类的构造函数(90行-96行)进行实例化过程中完成了网络工具 HttpTools 初始化、新建一个图片缓存 Map、新建一个下载队列、开启下载线程的操作。这时候请注意开启线程的操作,开启线程后执行 run() 方法(35行-88行),这时 isLoop 的值是默认的 true,maArrayList_taskQueue.size() 是为 0 的,在任务队列 maArrayList_taskQueue 中还没有加入下载任务之前这个循环会一直循环下去。在执行 imageLoad() 方法加载图片时会首先去缓存 mHashMap_caches 中查找该图片是否已经被下载过,如果已经下载过则直接返回与之对应的 bitmap 资源,如果没有查找到则会往 maArrayList_taskQueue 中添加下载任务并唤醒对应的下载线程,之前开启的线程在发现 maArrayList_taskQueue.size() > 0 后就进入下载逻辑,下载完任务完成后将对应的图片资源加入缓存 mHashMap_caches 并更新 UI,下载线程执行 wait() 方法被挂起。一个图片下载的业务逻辑这样理解起来很顺畅,似乎没有什么问题。开始我也这样认为,但后来在仔细的分析代码的过程中发现如果同样一张图片资源重新被加载就会出现死循环。还记得缓存 mHashMap_caches 么?如果一张图片之前被下载过,那么缓存中就会有这张图片的引用存在。重新去加载这张图片的时候如果重复的去初始化 ImageLoaderTools,线程会被开启,而使用 imageLoad() 方法加载图片时发现缓存中存在这个图片资源,则会将其直接返回,注意这里使用的是 return bitmap; 那就意味着 imageLoad() 方法里添加下载任务到下载队列的代码不会被执行到,这时候 run() 方法中的 isLoop = true 并且 maArrayList_taskQueue.size() = 0,这样内层 while 里的逻辑也就是挂起线程的关键代码wait() 永远不会被执行到,而外层 while 的判断条件一直为 true,就这样程序出现了死循环。死循环才是手机发烫、卡顿、高 CPU 占用率的真正原因所在。

解决方案

准确的定位到代码问题所在后,提出解决方案就很简单了,这里提供的解决方案是将 wait() 方法从内层 while 循环提到外层 while 循环中,这样重复加载同一张图片时,死循环一出现线程就被挂起,这样就可以避免死循环的出现。代码如下:

 1 private Thread mThread = new Thread() {2 3     public void run() {4 5         while (isLoop) {6 7             while (maArrayList_taskQueue.size() > 0) {8 9                 try {
10                     ImageLoadTask task = maArrayList_taskQueue.remove(0);
11
12                     if (Constant.LOADPICTYPE == 1) {
13                         byte[] bytes = httptool.getByte(task.path, null, HttpTools.METHOD_GET);
14                         task.bitmap = BitMapTools.getBitmap(bytes, 40, 40);
15                     } else if (Constant.LOADPICTYPE == 2) {
16                         InputStream in = httptool.getStream(task.path, null, HttpTools.METHOD_GET);
17                         task.bitmap = BitMapTools.getBitmap(in, 1);
18                     }
19
20                     if (task.bitmap != null) {
21                         mHashMap_caches.put(task.path, new SoftReference<Bitmap>(task.bitmap));
22                         File dir = mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
23                         if (!dir.exists()) {
24                             dir.mkdirs();
25                         }
26                         String[] path = task.path.split("/");
27                         String filename = path[path.length - 1];
28                         File file = new File(dir, filename);
29                         BitMapTools.saveBitmap(file.getAbsolutePath(), task.bitmap);
30                         Message msg = Message.obtain();
31                         msg.obj = task;
32                         mHandler.sendMessage(msg);
33                     }
34                 } catch (IOException e) {
35                     e.printStackTrace();
36                 } catch (Exception e) {
37                     e.printStackTrace();
38                 }
39
40             }
41
42             synchronized (this) {
43                 try {
44                     wait();
45                 } catch (InterruptedException e) {
46                     e.printStackTrace();
47                 }
48             }
49
50         }
51
52     };
53
54 };

最后再附上代码修改后代码运行的性能图,和之前的多次被重复执行,效率有了质的提升,手机发烫、卡顿、高 CPU 占用率的现象也消失了。

转载于:https://www.cnblogs.com/mzsoft/p/4464417.html

[高级]性能分析工具TraceView相关推荐

  1. 正确使用 Android 性能分析工具——TraceView

    前面唠叨 最近公司app中有些列表在滑动的时候会有卡顿现象,我就开始着手解决这些问题,解决问题之前首先要分析列表滑动的性能瓶颈在什么地方.因为之前不会正确使用TraceView这个工具,主要是看不懂T ...

  2. 正确使用Android性能分析工具——TraceView

    原址 前面唠叨 最近公司app中有些列表在滑动的时候会有卡顿现象,我就开始着手解决这些问题,解决问题之前首先要分析列表滑动的性能瓶颈在什么地方.因为之前不会正确使用TraceView这个工具,主要是看 ...

  3. android核心技术之性能分析工具TraceView

    前面的话 有一次,被一个高大上的公司面试问到TraceView是做什么的,不知道,于是被人鄙视了.当时觉得别人好高大上啊,这么牛的东西都知道,而我只是听过这个名字,完全不清楚是个什么东东,果然是好公司 ...

  4. 性能分析工具 Android TraceView

    1. TraceView 简介 Traceview是android平台配备一个很好的性能分析的工具.它可以通过图形化的方式让我们了解我们要跟踪的程序的性能,并且能具体到每个方法的执行时间 Tracev ...

  5. linux 解析pdf下载工具,Linux高级系统级性能分析工具-perf.pdf

    Linux高级系统级性能分析工具-perf Linux 的系统级性能剖析工具‐perf (二) 承刚 TAOBAO  Kernel Team chenggang.qin@ 第三章  Perf top ...

  6. Android 常用的性能分析工具详解:GPU呈现模式, TraceView, Systrace, HirearchyViewer(转)...

    此篇将重点介绍几种常用的Android性能分析工具: 一.Logcat 日志 选取Tag=ActivityManager,可以粗略地知道界面Displaying的时间消耗.当我们打开一个Activit ...

  7. 【宋红康 MySQL数据库 】【高级篇】【12】性能分析工具的使用

    持续学习&持续更新中- 学习态度:守破离 [宋红康 MySQL数据库 ][高级篇][12]性能分析工具的使用 数据库服务器的优化步骤 查看系统性能参数 统计SQL的查询成本:last_quer ...

  8. MySQL系列-高级-性能分析工具-EXPLAIN

    MySQL系列-高级-性能分析工具-EXPLAIN 1. EXPLAIN概述 1.1 官网介绍 1.2 EXPLAIN 基本语法 2. 基于函数和存储过程插入数据 2.1 创建表 2.2 创建函数和过 ...

  9. Android APP性能分析工具大全

    目录 官方推荐工具 第三方工具 一,官方工具 1.1,TraceView 1.2,StrictMode 1.3,Systrace 1.4,Hierarchy Viewer 1.5,AndroidStu ...

  10. 抖音 Android 性能优化:新一代全能型性能分析工具 Rhea!

    本文选自「抖音 Android 性能优化」系列文章. 「抖音 Android 性能优化」系列文章是由抖音 Android 基础技术部门技术专家倾力打造的技术干货内容,和大家分享基础技术团队在打造极致用 ...

最新文章

  1. PHP与MySQL通讯那点事
  2. swift_019(Swift 的类)
  3. 锁相环锁相原理简洁版
  4. 双显示器N卡安装ubuntu驱动以及解决办法
  5. Oracle 有long类型字段的表 使用insert into select 语句 ,出现:ORA-00997 错误
  6. 服务发现系统consul-HTTP API
  7. 单片机之硬件 软件仿真
  8. 【亲测有效】解决电脑不能复制粘贴的几种方法
  9. (二)大话深度学习编译器中的自动调优·DSL与IR
  10. shineblink BH1750光照强度传感器
  11. 宴会及会议座位排版软件
  12. 无需翻墙,快速接入免费网页版 ChatGPT 到 Java 应用程序中
  13. 更新资产折旧价值或AW01N查看资产价值提示消息:消息号AU390 - AS02 / AFAR
  14. 计算机管理恢复分区,Win10硬盘新增的恢复分区是什么?
  15. 计算机水平考试 初级,计算机水平考试-初级程序员下午试题模拟38
  16. [ARM] ARM处理器的7种工作模式和2种工作状态
  17. 第33篇 Android Studio实现五子棋游戏(四)棋子类和主类
  18. 生产工单收货控制增强
  19. 全国人工智能师资培训高校行重磅启动,飞桨携手7校开启AI学习之旅
  20. python自然语言处理_Python自然语言处理

热门文章

  1. Velocity模板基本常用语法
  2. hdfs 创建用户和用户组_HDFS原理 | 一文读懂HDFS架构与设计
  3. c#先进行uri解码_JavaScript、C# URL编码、解码总结
  4. SprinMVC解决URL多个参数
  5. 【渝粤教育】国家开放大学2018年春季 0161-21T教师职业道德 参考试题
  6. Ridge regression
  7. 算法与数据结构(一)-导学
  8. .net邮件发送帮助类
  9. POJ1064 Cable master 【二分找最大值】
  10. oracle数据的启动