Android优化之内存优化倒计时篇
本文来自网易云社区
作者:聂雷震
本篇文章介绍的内容是如何在安卓手机上实现高效的倒计时效果,这个高效有两个标准:1、刷新频率足够高,让用户觉得这个倒计时的确是倒计时,而不是幻灯片;2、不能占用太多的内存资源和CPU资源,让用户有一种“我手机真吊,倒计时效果全开一点不卡”的错觉。如果本文内容有很明显的错误,请各位及时指出。如果各位有更好的思路,也希望能够拿出来和大家分享一下。(不能设置段落的首行缩进,排版看起来很不舒服)
一、实现的功能
倒计时就是倒计时,举个简单的例子,每隔30ms刷新一次剩余时间的显示

图1. 倒计时控件显示效果
二、优化的效果
鱼与熊掌不可兼得,控件刷新频率的提高必然会导致资源消耗的增加,既然本文的定位是内存优化,那我们就以控件刷新频率为常量,对比下优化前后倒计时效果对内存资源的消耗。

图2. 优化前倒计时效果对内存的占用情况

图3. 优化后倒计时效果对内存的占用情况
有图有真相,在刷新间隔30ms的情况下,优化前内存消耗每分钟大约会增加2.52MB,优化后则几乎不会对内存消耗有任何影响。
三、问题的思路
先把代码贴出来,因为代码比较简单,所以全贴出来吧。全文如下:
public class MainActivity extends Activity { private static final int MSG = 0; private final static int INTERVAL = 30; private TextView textView; private long timeUp = 0; @Override
protected void onCreate(Bundle savedInstanceState) {
textView = new TextView(this);
textView.setTextSize(60);
setContentView(textView); super.onCreate(savedInstanceState);
} @Override
protected void onResume() {
timeUp = System.currentTimeMillis() + 3600 * 1000;
update(); super.onResume();
} private void update() { long time = timeUp - System.currentTimeMillis(); if (time < 0) { return;
}
String str = String.format("%02d:%02d:%02d:%03d", time / 60 / 60000 % 60, time / 60000 % 60, time / 1000 % 60, time % 1000);
textView.setText(str);
handler.sendEmptyMessageDelayed(MSG, INTERVAL);
} private Handler handler = new Handler() { @Override
public void handleMessage(android.os.Message msg) { if (msg.what == MSG) {
update();
}
}
};
由于倒计时功能需要不停的刷新倒计时的数字,大量占用资源的原因主要有两种:一、耗时函数重复执行(占用CPU);二、没有及时释放类对象(占用内存)。那么优化的方法也相应的有两种:一、优化关键函数的复杂度;二、及时释放非必须的类对象。显然优化复杂度是不可能的,因为一开始说的问题就在内存上,而且update函数好像也没有可以优化的空间了吧。另一方面,如果要及时释放类的对象,就必须触发GC,不管是手动触发还是自动触发,GC操作的优先级都是高于UI刷新的,频繁GC必然会导致卡顿。于是脑洞大开,想着是不是有什么办法可以每次执行的时候都复用旧的对象,而不创建新的对象。
四、最初的尝试
看了一下String的源码,发现不创建新的对象是很难的,因为我们随随便便修改一下String的内容,java就会自动替我们创建一个新的对象,而不是修改原来的对象。(java虽然在使用上极大的方便了使用者,但是其精确控制的能力缺远不如C呀)为了解决问题,还是不怕麻烦使用了传说中的反射机制,这种看起来不是很安全的方法还是在一定程度上解决了重复创建对象的问题。
public final class String implements Serializable, Comparable, CharSequence { private final char[] value; private final int offset; private final int count;
}
String源码里主要的属性如上所示,其中value是保存String内容的char型数组,offset和count分别是String内容在数组中保存的起始位置和长度。由于倒计时功能显示内容的字数是固定的,所以我们通过反射的方法获取到value之后,只需要修改相应位置的字符即可。另一方面,我们没有通过setText的方法设置textView的属性,必须通过TextView的invalidate方法通知页面需要重新刷新View。
在自己的手机(Android 4.4.4)上跑了一下,感觉还不错,Mission Complete,正开心的时候测试的同学告诉我说,“倒计时有问题,时间不会变”,然后甩了一个Android 6.0给我。
单步分析问题,发现反射没有获取到value,再检查代码,发现没有问题,还是怀疑自己反射相关的代码写错了,于是打印出来所有通过反射获得的成员变量和方法,发现Android 6.0之后String类不在使用value+offset+count的方式存储字符串了。
五、当前的方法
在穷途末路的时候点开了TextView的源码,无意中发现了一个函数,顿时觉得不会再爱了,这个函数的注释如下:
/**
* Sets the TextView to display the specified slice of the specified
* char array. You must promise that you will not change the contents
* of the array except for right before another call to setText(),
* since the TextView has no way to know that the text
* has changed and that it needs to invalidate and re-layout.
*/public final void setText(char[] text, int start, int len)
查了一下文档,发现以char数组作为参数设置text的方法从api 1里面就加入了,可见Google公司最初考虑的还是蛮周全的。
最终实现的代码如下:
public class MainActivity extends Activity { private static final int MSG = 0; private final static int INTERVAL = 30; private TextView textView; private char[] value = new char[] { '0', '0', ':', '0', '0', ':', '0', '0', ':', '0', '0', '0', '\0' }; private int offset = 0; private int count = 12; private long timeUp = 0; private Handler handler = new Handler() { @Override
public void handleMessage(android.os.Message msg) { if (msg.what == MSG) {
update();
}
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
textView = new TextView(this);
textView.setTextSize(60);
setContentView(textView);
textView.setText(value, offset, count); super.onCreate(savedInstanceState);
} @Override
protected void onResume() {
timeUp = System.currentTimeMillis() + 3600 * 1000;
update(); super.onResume();
} private void update() { long time = timeUp - System.currentTimeMillis(); if (time < 0) { return;
}
value[0] = (char) ('0' + time / 60 / 60000 % 60 / 10);
value[1] = (char) ('0' + time / 60 / 60000 % 60 % 10);
value[3] = (char) ('0' + time / 60000 % 60 / 10);
value[4] = (char) ('0' + time / 60000 % 60 % 10);
value[6] = (char) ('0' + time / 1000 % 60 / 10);
value[7] = (char) ('0' + time / 1000 % 60 % 10);
value[9] = (char) ('0' + time % 1000 / 100);
value[10] = (char) ('0' + time % 1000 % 100 / 10);
value[11] = (char) ('0' + time % 1000 % 10);
textView.invalidate();
handler.sendEmptyMessageDelayed(MSG, INTERVAL);
}
}
六、补充
开发期间很多同事都问我为什么一定要把刷新间隔设置为30ms,这个原因主要是跟人眼的视觉残留有关(胡诌一下,能忽悠几个算几个)。大家都知道很多电影的帧率是24fps(虽然很多电影为了更好的视觉效果都将帧率提高到60fps,但是动画始终是动画,不是高清电影呀),刷新间隔大概是1000/24=41.6ms。另一方面,由于目前很多倒计时功能的毫秒都只显示两位,也就是10ms的级别,如果刷新频率设置为40ms的话,很可能出现最后一位循环出现0,4,8,2,6,0,4,8,2,6,0...这很明显从视觉上是不合理的。综上两个原因,我觉得把刷新间隔设置成30是最合适的。如果要把毫秒显示为三位的话,可以尝试把刷新间隔设置为27ms或者33ms,这样将会避免最低位始终不变的情况。
网易云产品免费体验馆,无套路试用,零成本体验云计算价值。
本文来自网易云社区,经作者聂雷震授权发布
相关文章:
【推荐】 基于Impala平台打造交互查询系统
【推荐】 在一台服务器上搭建相对高可用HiveServer实践
【推荐】 年轻设计师如何做好商业设计
Android优化之内存优化倒计时篇的更多相关文章
- [Android 性能优化系列]内存之基础篇--Android怎样管理内存
大家假设喜欢我的博客,请关注一下我的微博,请点击这里(http://weibo.com/kifile),谢谢 转载请标明出处(http://blog.csdn.net/kifile),再次感谢 原文地 ...
- [Android 性能优化系列]内存之提升篇--应用应该怎样管理内存
大家假设喜欢我的博客,请关注一下我的微博,请点击这里(http://weibo.com/kifile),谢谢 转载请标明出处(http://blog.csdn.net/kifile),再次感谢 原文地 ...
- 转 iOS和android游戏纹理优化和内存优化(cocos2d-x)
iOS和android游戏纹理优化和内存优化(cocos2d-x) (未完成) 1.2d游戏最占内存的无疑是图片资源. 2.cocos2d-x不同平台读取纹理的机制不同.ios下面使用CGImage, ...
- 微擎开启性能优化里面的性能优化memcache内存优化及数据库读写分离
http://www.mitusky.com/forum.php?mod=viewthread&tid=3135 [微擎 安装使用] 微擎开启性能优化里面的性能优化memcache内存优化及数 ...
- 关于android性能,内存优化
转:http://www.starming.com/index.php?action=plugin&v=wave&tpl=union&ac=viewgrouppost& ...
- KVM总结-KVM性能优化之内存优化
我们说完CPU方面的优化(http://blog.csdn.net/dylloveyou/article/details/71169463),接着继续第二块内容,也就是内存方面的优化.内存方面有以下四 ...
- Linux性能优化之内存优化(二)
前言 不知道大家看完前面一章关于CPU优化,是否受到相应的启发呢?如果遇到任何问题,可以留言和一起探讨这方面的问题.接下来我们介绍一些关于内存方面的知识.内存管理软件包括虚拟内存系统.地址转换.交换. ...
- 2017版:KVM 性能优化之内存优化
我们说完CPU方面的优化,接着我们继续第二块内容,也就是内存方面的优化.内存方面有以下四个方向去着手: EPT 技术 大页和透明大页 KSM 技术 内存限制 1. EPT技术 EPT也就是扩展页表,这 ...
- python性能优化、内存优化、内存泄露;与其他语音比较效率如何?
1.内存泄露:http://www.cnblogs.com/xybaby/p/7491656.html 2.内存优化:http://www.cnblogs.com/xybaby/p/7488216.h ...
随机推荐
- unicat,multicast,broadcast区别
单播.多播和广播单播”(Unicast).“多播”(Multicast)和“广播”(Broadcast)这三个术语都是用来描述网络节点之间通讯方式的术语.那么这些术语究竟是什么意思?区别何在? 1.单 ...
- ES6系列_3之变量的解构赋值
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构. 解构有三种类型: 1.数组的解构赋值 (1)简单的数组解构 以前,我们给变量赋值是直接单个指定值,比如: let a=0; ...
- Java——jxl读取Excel文件
1.创建文件流,打开EXCEL文件(jxi不支持.xlsx文件,支持.xls) FileInputStream excelFile = new FileInputStream(excelPath); ...
- 现象级AR营销助力“口碑双十二”,蚂蚁特工在全国数万商户掀起“AR捉四宝”
领取阅读奖励金 今年双十二,全国人民吃喝玩乐放飞自我,嗨出了新纪元.除了见证你国人民的财力,这个“双十二”还诞生了教科书级的“AR营销”.无论是在口碑商户门口,还是在各大购物广场,都能看到举着手机,正 ...
- leetcode205
public class Solution { public bool IsIsomorphic(string s, string t) { if (s.Length != t.Length) { r ...
- JavaScript中call,apply,bind方法
why?call,apply,bind干什么的?为什么要学这个? 一般用来指定this的环境,在没有学之前,通常会有这些问题. var a = { user:"追梦子", fn:f ...
- blocking network call
[blocking network call] 阻塞的网络调用: 1.gethostbyname(): does not return until it has succeeded or failed ...
- win10如和设置远程桌面
最近几天一直在搞远程桌面问题,电脑一直连不上远程,今天终于出来了. 前提前提 条件,要电脑允许Administrator用户,先把电脑切换到那个用户,然后进行下面操作. 1.在桌面,我的电脑图标,鼠 ...
- 自制精排 ePub 集、不定期更新(UPDATA-2015-8-2)
说明 排版不说最好,上乘是必须的. 段落空行 首行缩进 具备清爽元数据 包含高清封面.目录 图片居中,图片标题以下标形式居中位于图片下* 支持多看/Kindle原生系统弹出注释,其他软件跳转注释* 各 ...
- curl模拟多线程抓取网页(优化)
通过上篇文章清楚了通过curl_multi_*函数可以一次请求多个url,但是也留下了问题,就是结果要等所有数据请求结束一起返回,才能逐个处理数据.优化代码,使先成功请求的url先返回处理结果,而不是 ...