1. privateIDrawTask createDrawTask(boolean useDrwaingCache, DanmakuTimer timer, Contextcontext, int width, int height, booleanisHardwareAccelerated, IDrawTask.TaskListener taskListener)
useDrwaingCache为true,则创建CacheManagingDrawTask绘制任务
2. 构造方法中NativeBitmapFactory.loadLibs()加载了用于创建bitmap的so文件,就是用skia图形处理库直接创建bitmap,缓存创建的是bitmap文件,如果直接调用drawTast()创建的是drawText()
3. 接着该调用CacheManagingDrawTask的prepare方法:
public void prepare() {
assert (mParser != null);
loadDanmakus(mParser);
mCacheManager.begin();
}
4. 然后分两条主线去调用绘制缓存,一条是loadDanmakus()方法,另一个是mCacheManager.begin()方法。
5. 首先说是loadDanmakus()方法,首先从前面解析的parse中取出数据,保存在danmakuList中,parser设置完DanmakuContext,AndroidDisplayer,DanmakuTimer之后,再调用getDanmakus取出弹幕信息,解析方法中从JSONSource里解析每一条弹幕,包括弹幕的时间、颜色、类型、字号、用户id等内容。然后通过弹幕工厂DanmakuFactory创建弹幕,createDanmaku(int type, int viewportWidth, int viewportHeight, floatviewportScale, float scrollSpeedFactor),该方法为弹幕工厂中创建弹幕的方法,其中的功能包括修正弹幕宽高、缩放比、弹幕时长,滚动弹幕的时间设置,固定弹幕的时间设置。修正弹幕最长时长等功能。至此loadDanmakus()方法执行完毕。
6. 接着是第二个方法mCacheManager.begin()方法的执行流程,
两个函数mThread = new HandlerThread("DFM Cache-Building Thread")和mHandler =new CacheHandler(mThread.getLooper());其中创建了一个HandlerThread,然后创建了一个CacheHandler,所以CacheHandler发送消息后,处理消息内容都是在子线程。然后调用sendEmptyMessage(PREPARE)方法,调用CacheManager中CacheHandler的回调方法handleMessage()。
第一个casePREPARE中, 函数evictAllNotInScreen()负责清除所有不在屏幕内的缓存
for (inti = 0; i < 300; i++) {
mCachePool.release(new DrawingCache());
}//在池里放300个预留缓存,以链式存储方式存放,在前面的代码会创建一个缓存个数上限为800的FinitePool池
第二个case DISPATCH_ACTIONS, 函数sendEmptyMessageDelayed(DISPATCH_ACTIONS,delayed),//会每隔半条弹幕时间发送一次DISPATCH_ACTIONS消息
其中具体的逻辑包括:如果上一次buildCache完成后得到的缓存弹幕末尾项的时间(上面分析过,这个值存在mCacheTimer.currMillisecond中)和主定时器当前时间之间的时间差值已经大于一条弹幕时间, 则会清除所有不在屏幕内的缓存,然后重新buildCache建立缓存;
如果缓存弹幕的第一项出现时间大于当前时间超过半屏,并且总缓存大小在规定最大值一半以下, 就要重新建立缓存;
如果总缓存大小在规定最大值一半以上,并且上一次建立缓存距离现在已经超过两条弹幕时间了,就要清除超时缓存;
如果总缓存大小快达到规定最大值,就等待下一次清除超时缓存;
缓存的第一条弹幕已经过时了,并且缓存弹幕末尾时间和现在时间差值已经超过一条弹幕时间了,先清除过时缓存,再重组缓存;
如果缓存的最后一条弹幕时间距离现在还有双倍弹幕时间多,则啥都不做;
剩余情况就是重组缓存。
第三个caseBUILD_CACHE,其中的逻辑包括:
1>先测量弹幕的宽高
2>在mCaches缓存的20条内查找和目标弹幕样式完全一样的弹幕(文字、大小、边框、下划线、颜色完全相同)
3>如果上述的查找样式完全相同的弹幕没有找到,则在前50条缓存中查找对比当前时间已经过时的,没有被重复引用的(只有上面那种情况才会增加引用计数,其他情况都不会),而且宽高和目标弹幕差值在规定范围内的弹幕,再根据目标弹幕样式,重新设置缓存(为每条弹幕创建一个bitmap和canvas,然后画出边框、下划线、文字等等)
4>如果上述两次查找缓存都没找到,则从FinitePool中取出一个,没有就new一个,然后同上配置DrawingCache
HandleMessage中处理消息的流程主要是以上三个,总结:子线程从发送PREPARE消息开始,然后接着发送了DISPATCH_ACTIONS消息;DISPATCH_ACTIONS消息处理逻辑内部又会发送DISPATCH_ACTIONS消息,时间间隔为半条弹幕时间就这样不断循环发送;DISPATCH_ACTIONS消息处理会调用dispatchAction方法,dispatchAction方法会发送BUILD_CACHES消息;BUILD_CACHES消息处理会调用prepareCaches方法,prepareCaches方法内部会调用buildCache方法为从当前时间开始的3倍弹幕时间内所有的弹幕做缓存
第四个case UPDATE:,其中的逻辑包括:updateInNewThread();该方法用于更新主定时器时间,然后执行postInvalidate,通知DanmakuView重绘,然后再发UPDATE消息,重复上述过程。
DanmakuView绘制过程分析:
如果弹幕还没有到出现时间,则检查它有没有缓存,如果没有则为它建立缓存;
measure 测量,在之前prepareCache已经为他们在buildCache时测量过了;
layout 布局,计算弹幕在屏幕上应该显示的位置;
draw 绘制弹幕。
总结,其中还有一些缓存的清除略,缓存池的更新策略,弹幕位置的计算方法,弹幕的碰撞检测方法等,在缓存机制中都用到了
参考文献:
http://windrunnerlihuan.com/2016/07/02/DanmakuFlameMaster%E7%AE%80%E5%8D%95%E5%88%86%E6%9E%90/