android 卡顿代码定位,Android 性能优化实例:通过 TraceView 定位卡顿问题

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

背景

项目中使用了鸿洋大神的TreeView树状结构控件, 但是由于在主线程中使用了注解/反射来定位节点, 内容一多就有点卡顿。因此通过android device monitor中的性能分析工具定位并优化这一问题并记录过程。

准备工作打开tools菜单 —— android——android device monitor

选中要追踪的app进程——点击Start Method Profiling

操作想要追踪问题的界面或行为使问题暴露

在刚才的按钮点击stop

start_trace.png

分析

卡顿根本原因由于Ui渲染被阻塞造成,它运行在主线程, 因此主线程也被称为UI线程。

原因可能有很多,常见的有主线程中执行速度较慢的I/O操作造成CPU等待, 或主线程有其他大量任务占满了CPU, 比如程序没写好死循环了 - - 。

那么我们在这边主线程没有做I/O操作, 因此先按 Excl Cpu Time % 排序, 查看CPU占用百分比 。

定位到 no.18 的方法占用了30.4%, 点选他之后 , 看到 【1】main 主线程中后面大部分都变色, 表明是这个method的执行区间。 卡顿罪魁祸首就是它了!

cpu_consume_define.png

点开方法名左边小箭头, parent表示是被哪个父方法调用, children是表示调用的所有子方法。

点开children分析后面几个参数Incl Cpu Time 表示各子方法的总运行时长, 主要就看他定位具体方法

Incl Cpu Time % 就是各子方法的分配到的CPU占比

calls+RecurCalls/Total 表示被调用的次数 (大致符合逻辑就行 主要是看不是异常大 判断死循环)

找到性能表现有问题的方法后一个一个双击看下去就可以了, 同时结合上半部分界面的运行时图表也能直观看出资源占用的区间

child_cunsume_define.png

优化

看一下排前5的几个方法, 2个属于反射获取Annotation和Filed, 反射是众所周知的慢, 先放过他们, 另外3个我们看一下方法内容

/**

* 将我们的数据转化为树的节点

*

* @param datas

*

* @return

*

* @throws NoSuchFieldException

* @throws IllegalAccessException

* @throws IllegalArgumentException

*/

private static List convetData2Node(List datas)

throws IllegalArgumentException, IllegalAccessException

{

List nodes = new ArrayList();

Node node = null;

for (T t : datas) {

int id = -1;

int pId = -1;

int pnode = -1;

String label = null;

Class extends Object> clazz = t.getClass();

Field[] declaredFields = clazz.getDeclaredFields();

for (Field f : declaredFields) {

if (f.getAnnotation(TreeNode.class) != null) {

f.setAccessible(true);

pnode = f.getInt(t);

}

if (f.getAnnotation(TreeNodeId.class) != null) {

f.setAccessible(true);

id = f.getInt(t);

}

if (f.getAnnotation(TreeNodePid.class) != null) {

f.setAccessible(true);

pId = f.getInt(t);

}

if (f.getAnnotation(TreeNodeLabel.class) != null) {

f.setAccessible(true);

label = (String) f.get(t);

}

if (pnode != -1 && id != -1 && pId != -1 && label != null) {

break;

}

}

node = new Node(pnode, id, pId, label);

nodes.add(node);

}

/*

* 设置Node间,父子关系;让每两个节点都比较一次,即可设置其中的关系

*/

for (int i = 0; i < nodes.size(); i++) {

Node n = nodes.get(i);

for (int j = i + 1; j < nodes.size(); j++) {

Node m = nodes.get(j);

if (m.getpId() == n.getId()) {

n.getChildren().add(m);

m.setParent(n);

} else if (m.getId() == n.getpId()) {

m.getChildren().add(n);

n.setParent(m);

}

}

}

// 设置图片

for (Node n : nodes) {

setNodeIcon(n);

}

return nodes;

}

反射创建节点后通过for循环设置节点的上下级关系, 就是这个for循环有问题了。getId和getPid方法

鸿洋大神的Node类是个标准javaBean, 也就是属性全部private通过get, set操作。but, 这样会比直接读属性慢10倍左右, 其实也没啥大不了, but 如果被调用很多次。。。我把 id 和 pid 改成public 并删掉 get set方法

ArrayList.size()

这个也很简单,直接在for循环里定义下变量。改完后的for循环如下

/*

* 设置Node间,父子关系;让每两个节点都比较一次,即可设置其中的关系

*/

for (int i = 0, a = nodes.size(); i < a; i++) {

Node n = nodes.get(i);

for (int j = i + 1; j < a; j++) {

Node m = nodes.get(j);

if (m.pId == n.id) {

n.getChildren().add(m);

m.setParent(n);

} else if (m.id == n.pId) {

m.getChildren().add(n);

n.setParent(m);

}

}

}

效果如下图, 虽然cpu%提高了,但那是由于其他耗时方法不见了,因此总占比就高了,具体看children分类下面,那3个耗时较长的方法已经不见了,self分类的时间也有所降低。 因此incl Cpu Time从2938降低到了1096毫秒,效果还是比较显著的

after_optimize.png

虽然方法优化完了,但还是有1秒的卡顿存在, 因此我后来又改了下把转换节点的操作放到子线程执行了,这就不详细讲了。

总结使用Android Device Monitor工具, 捕获问题发生时的方法执行信息

按Excl Cpu Time % 排序, 找到需要优化的方法

查看方法的children列表, 分析Incl Cpu Time 和 calls+RecurCalls/Total是否合理

找到并修改方法

再次捕获信息, 对比分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值