本文来自网易云社区

作者:聂雷震

本篇文章介绍的内容是如何在安卓手机上实现高效的倒计时效果,这个高效有两个标准: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;  @Overrideprotected void onCreate(Bundle savedInstanceState) {textView = new TextView(this);textView.setTextSize(60);setContentView(textView);    super.onCreate(savedInstanceState);}  @Overrideprotected 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() {    @Overridepublic 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() {    @Overridepublic void handleMessage(android.os.Message msg) {      if (msg.what == MSG) {update();}}};  @Overrideprotected void onCreate(Bundle savedInstanceState) {textView = new TextView(this);textView.setTextSize(60);setContentView(textView);textView.setText(value, offset, count);    super.onCreate(savedInstanceState);}  @Overrideprotected 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优化之内存优化倒计时篇相关推荐

  1. Android 系统性能优化(55)---Android 性能优化之内存优化

    Android 性能优化之内存优化 前言 Android App优化这个问题,我相信是Android开发者一个永恒的话题.本篇文章也不例外,也是来讲解一下Android内存优化.那么本篇文章有什么不同 ...

  2. Android性能优化之内存优化 1

    导语 智能手机发展到今天已经有十几个年头,手机的软硬件都已经发生了翻天覆地的变化,特别是Android阵营,从一开始的一两百M到今天动辄4G,6G内存.然而大部分的开发者观看下自己的异常上报系统,还是 ...

  3. App性能优化(布局优化,线程优化,app瘦身优化,页面切换优化,App启动优化,内存优化)

    Android APP性能优化(最新总结) 在目前Android开发中,UI布局可以说是每个App使用频率很高的,随着UI越来越多,布局的重复性.复杂度也随之增长,这样使得UI布局的优化,显得至关重要 ...

  4. Lua性能优化—Lua内存优化

    原文链接https://blog.uwa4d.com/archives/usparkle_luaperformance.html 这是侑虎科技第236篇原创文章,感谢作者舒航供稿,欢迎转发分享,未经作 ...

  5. Android性能优化之内存优化浅析

    一.背景 Android由于是以Java语言为主要开发语言,所以它的内存管理并不像C语言那样由开发者去管理内存的分配以及回收等,而是交由JVM虚拟机的内存回收机制去处理.这就导致我们在开发过程中难免会 ...

  6. Unity3D性能优化 之 内存优化篇

    性能优化主要围绕CPU.GPU和内存三大方面 之 内存优化篇 无论是游戏还是VR应用,内存管理都是其研发阶段的重中之重. 然而,在我们测评过的大量项目中,90%以上的项目都存在不同程度的内存使用问题. ...

  7. Android面试-Android性能优化和内存优化、APP启动速度一线大厂的实战案例解析

    一.Android 内存管理机制 二.优化内存的意义 三.避免内存泄漏 四.优化内存空间 五.图片管理模块的设计与实现 六.总结 深入探索Android内存优化 第一章.重识内存优化 第二章.常见工具 ...

  8. Android App优化:内存优化、电量优化、网络优化等 (2)

    -- 优化专题: 整理一系列的专题:比如APK瘦身.插件化.程序架构.性能优化.自定义view.增量升级.移动开发各种技术解决方案等.   Android后期发展的五大趋势:一.性能优化:二.高级UI ...

  9. android—性能优化2—内存优化

    文章目录 性能优化: 工具: memory profiler LeakCanary arthook epic 库 java内存管理机制 java 内存回收机制 Android内存管理机制 Dalvik ...

最新文章

  1. 转载:JAVA中获取项目文件路径
  2. pytorch安装-Windows(pip install失败)
  3. NYOJ 633 幂
  4. linux Fedora35 grub2 改变启动顺序
  5. 重写toString()方法(Java篇)
  6. TC的文件拷贝/移动
  7. 动态规划6个题目总结比较
  8. Hadoop(二)搭建Hadoop集群
  9. python面试题No6
  10. python怎样画立体图-如何用Matplotlib 画三维图的示例代码
  11. 蓄电池单格电压多少伏_直流屏蓄电池电压的常见问题小结
  12. Ubuntu 定时锁屏改进
  13. 串口屏还是并口屏好用?
  14. raspberry pi 4检查ch340/ch341驱动
  15. 怎么将PPT中的视频文件保存下来
  16. vue项目引入高德地图
  17. Eggjs入门系列-基础全面讲解(完结)
  18. 直播网站是怎么实现的
  19. java-net-php-python-ssm高校学生学业分析及预警系统查重PPT计算机毕业设计程序
  20. 商品的SKU与SPU

热门文章

  1. android 自动休眠时间设置在哪里,Android休眠设置时间
  2. SAP RETAIL初阶之商品主数据WM视图
  3. What is Listing in SAP Retail?
  4. 轻量级NLP工具开源,中文处理更精准,超越斯坦福Stanza
  5. 「深入浅出」了解语音识别的技术原理和应用价值?
  6. 谷歌出品EfficientNet:比现有卷积网络小84倍,比GPipe快6.1倍
  7. 集成学习——Adaboost分类
  8. 揭秘丨“北京八分钟”里中国制造的科技力量
  9. 免费教材丨第49期:数学基础课程----漫画线性代数、微积分超入门
  10. python中一些常用函数和库的介绍(getattr、id、type、sys)