强迫症的研究——MediaPlayer播放进度条的优化
如何做一个优美、流畅而且准确的播放进度条,也许很多人觉得很简单,但实际上,这个问题在大部分时间都被忽略了。
计时方式的比较
计时方式——主线程中使用Handler
– 这种方式最简单,在主线程中通过handler.postDealyed(……, 1000),并在onHandleMessage中继续post消息,这样就实现了每隔1000ms进行一次消息循环。
计时方式——使用单独计时线程
– 单独创建一个计时线程,每秒发出time tick事件,主线程通过该事件来更新进度。这种方式比较麻烦,但是不麻烦怎么装逼呢?
如何高雅、准确的实现
对于Handler方式
自身误差
这种方式下,如果使用handler.postDealyed(……, 1000)方式来进行每秒的计时,是不准确的,是的,有很大误差,误差的原因在于在你收到消息,到你重新发出handler.postDealyed的时间,并不是瞬间完成的,这里面有很多逻辑处理的时间,即使没有逻辑处理的时间,handler本身也是耗损性能的,所以消息并不可能按照理想的1000延迟来进行发送,这就导致了误差的累积。
- 线程调度误差
我们知道,当音乐线程启动,到handler发出消息,这一段时间内,存在进程调度或者其它逻辑的耗时操作,导致这两个时间并不是同时发生的。所以,我们每次在post的时候,都需要对计时进行下补偿,但是,怎么做呢?
对于Handler方式的优化
我们知道,Android中有很多计时的控件,首先想到的是DigitalClock,结果发现已经废弃,好吧,看被什么替换了,OK,发现了TextClock,代码多了不少,感觉更牛逼了。我们直接看他是怎么处理这个问题的:
同样是通过程序员的嗅觉找到这里:
private final Runnable mTicker = new Runnable() {
public void run() {
onTimeChanged();
long now = SystemClock.uptimeMillis();
long next = now + (1000 - now % 1000);
getHandler().postAtTime(mTicker, next);
}
};
哎呦,有点意思,我们之前是通过postDelay来触发消息事件的,但这里系统使用了postAtTime,这是为什么呢?很自然我们会想到前面两行代码,其实也不用想太多,你代个值进去试下就知道了,假如now取出来是1200,那么next = 1200 + (1000 - 1200 % 1000)也就是next= 2000。你看,虽然我们前一次本该在1000触发的事件,被各种逻辑延迟到1200,那么如果你用postDelay,这个延迟就被累积了,但如果用这种方式,误差就被补偿了。
我们就叫他误差补偿算法吧~
对于单独计时线程方式
对于单独计时的线程,由于时间点的触发事件和主线程已经分开了,计时线程就不会受主线程逻辑的阻塞了,所以,只要保证开始时对起始时间差进行下同步就OK了。
对于单独计时线程方式的优化
其实对于单独计时线程来说,已经没有什么好优化的了,而且优点还能再列举不少:
- 计时逻辑与UI逻辑分离,方便拓展
- 计时准确,可以将计时线程封装,暴露接口,方便拓展
- 解耦、装逼
如果你还要再进一步优化的话,可以在计时的时候,使用时间差的方式来统计,虽然没什么乱用。