EasyDanmaku开发日志
2021年12月29日
TODO
- 实时添加 sendDanmaku 必显示;
- 头像; √
2022年1月2日
目前每种类型的弹幕都对应一个实现类。
考虑到在弹幕属性中已经存在 type
字段,完全可以只使用同一种类型,而通过 type
字段进行区分。
而在 Danmaku
类中,根据 type
字段,通过策略模式实现弹幕的动画效果。如果能够统一 Danmaku 类型,则 Danmaku 池就可以进行简化,并且提高回收对象的利用率。但是这个也同时影响到了 DanmakuLocator 的运作。
Bug排查
发现 Sample 中的弹幕偶尔会出现闪烁的情况,可能是 Esus 的问题,也可能是 EasyDanmaku 的问题。
首先单独创建项目,单独绘制一个 TextView,发现一切正常;然后看压力测试也是一切正常。所以最大的嫌疑是 Danmaku 类出现了问题。
因为闪烁的情况出现的极少,所以一直没有找到解决方案。通过观察发现,闪烁通常是在特定的时间点出现的。于是,对于一个特定的 Danmaku,一帧帧的进行 Debug,找到出问题的那帧。
最后发现,这个 Danmaku 的 lastDrawingTime
字段的值为 10460,即它最后一次绘制是 10460 毫秒;而其他 Danmaku 最后一次绘制的时间是 10476 毫秒,也就是说,出问题的 Danmaku 少绘制了一帧!那么,大概率是 Esus 的 ViewGroup 在 dispatchView
的时候出了问题。
经过断点测试,最终发现了问题的所在:在 ViewGroup.dispachDraw
中,会遍历当前的子 View;而当某一个子 View(假设其序号为 i),也就是某个 Danmaku 执行到最后一帧的时候,会将自身从父 ViewGroup 中移除。由于第 i 项移除,则后面的子 View 序号均向前一位;而原来的第 i + 1 项就变为了第 i 项。而遍历继续执行下去,接下来就会绘制第 i + 1 项,即原 i + 2 项,也就是说跳过了原 i + 1 项,也就是这个原因导致了原 i + 1 项在这一帧没有进行绘制,于是出现闪烁。
这个情况,类似于几乎每个人都碰到过的,java.util.ConcurrentModificationException
异常,即在遍历一个集合的时候修改了这个集合。
问题代码大致代码如下:
// ViewGroup
fun dispatchDraw() {
for(i in 0 until childCount) {
children[i].draw() // <=====
}
}
fun removeView(view: View) {
val i = getViewIndex(view)
System.arraycopy(children, i+1, children, i, childCount-i-1)
}
// Danmaku
fun onDraw() {
super.onDraw()
if (!more) {
parent.removeView(this) // <=====
}
}
问题找到了,就是因为 ViewGroup 在遍历子 View 的时候,子 View 调用了 parent.removeView(this)
,从而导致了遍历序号不一致的问题。
那么,Android 本身的 ViewGroup 是怎么样处理这种情况的呢?创建一个自定义 View,在 onDraw
中调用 parent.removeView(this)
,结果很遗憾,报错了。也就是说,原生 Android 也是不支持在 onDraw
的途中移除 View 的。
那么,如何才能在 Danmaku 生命结束之后,安全的将其移除呢?
这时候,我想到了 View.post
。因为 Choreographer
通知 ViewRootImpl
进行遍历也是通过发送消息的,所以使用 View.post
发送的事件会在本次遍历结束之后再进行。
将 Danmaku 类的 onDraw
方法修改:
// 修改前
override fun onDraw(canvas: Canvas, parent: ViewParent?, time: Long) {
super.onDraw(canvas, parent, time)
if (!more && parent is ViewGroup) {
parent.removeView(this)
}
}
// 修改后
override fun onDraw(canvas: Canvas, parent: ViewParent?, time: Long) {
super.onDraw(canvas, parent, time)
if (!more && parent is ViewGroup) {
post {
parent.removeView(this)
}
}
}
最后完美解决。
网友评论