Bullets are a very common function for video websites. At present, DanmakuFlameMaster of STATION B is the most famous ammunition library in the industry (but it has not been updated for a long time). The function of this ammunition library is very perfect and stable, and there are two main types of bullets in it:
- The user sends the video in real time while it is playing
- A collection of bullets delivered by the server during video loading
Since the whole bullet-screen library involves a lot of logic, this paper mainly analyzes the realization logic of users sending a right-to-left scrolling bullet-screen (excluding related logic such as time synchronization of video bullet-screen):
Below is the general working logic diagram of DanmakuFlameMaster when the user sends a barrage:
The general role of each class involved
R2LDanmaku
: a barrage object, which contains x and y coordinates, cachedBitmap
Attributes such asDanmakuView
: used to carry the barrage displayViewGroup
In addition to itDanmakuSurfaceView
,DanmakuTextureView
DrawHandler
: One binding is asynchronousHandlerThread
theHandler
To control the display logic of the whole barrageCacheManagingDrawTask
: Maintains the bullet-screen list to be drawn and controls the bullet-screen cache logicDrawingCacheHolder
: implementation of barrage cache, cache isBitmap
, andBaseDanmaku
The bindingDanmakuRenderer
: Do some filtering, collision detection, measurement, layout, cache, etcDisplayer
Held:Canvas
Canvas, draw barrage
When adding barrage to DanmakuView, the display process of barrage will be triggered:
DanmakuView.java
public void addDanmaku(BaseDanmaku item) {
if (handler != null) {
handler.addDanmaku(item);
}
}
Copy the code
The DrawHandler scheduler causes the DanmakuView to render
- Adds a barrage to
CacheManagingDrawTask
Barrage collectiondanmakuList
In the CacheManagingDrawTask.CacheManager
Create a barrage cacheDrawingCache
- through
Choreographer
To keep renderingDanmakuView
Actually the first step is to barrage is added to a collection, don’t look here, look directly DrawingCache. DrawingCacheHolder creation
Create a barrage cacheDrawingCacheHolder
In fact, the cache here is basically oneBitmap
Object, becauseDanmakuFlameMaster
The realization of bullet screen drawing is: first paint the bullet screen in aBitmap
Go, and then goBitmap
Painted onCanvas
on
CacheManagingDrawTask. There is a HandlerThread CacheManager, he will create DrawingCache asynchronous. DrawingCacheHolder, but before creating DrawingCache, Will first try to reuse from the cache pool (see if there are any bitmaps that can be reused):
byte buildCache(BaseDanmaku item, boolean forceInsert) { ... DrawingCache cache = null; BaseDanmaku danmaku = findReusableCache(item,true, mContext.cachingPolicy.maxTimesOfStrictReusableFinds); // Full reuseif(danmaku ! = null) { cache = (DrawingCache) danmaku.cache; }if(cache ! = null) { ... cache.increaseReference(); // Add references to item.cache = cache; // Add references to item.cache = cache; mCacheManager.push(item, 0, forceInsert);returnRESULT_SUCCESS; } danmaku = findReusableCache(item,false, mContext.cachingPolicy.maxTimesOfReusableFinds);
if(danmaku ! = null) { cache = (DrawingCache) danmaku.cache; }if(cache ! = null) { danmaku.cache = null; cache = DanmakuUtils.buildDanmakuDrawingCache(item, mDisp, cache, mContext.cachingPolicy.bitsPerPixelOfCache); //redraw item.cache = cache; mCacheManager.push(item, 0, forceInsert);returnRESULT_SUCCESS; }... cache = mCachePool.acquire(); / / directly created a barrage cache. = DanmakuUtils buildDanmakuDrawingCache (item, mDisp, cache, mContext.cachingPolicy.bitsPerPixelOfCache); item.cache = cache; boolean pushed = mCacheManager.push(item, sizeOf(item), forceInsert); . }Copy the code
The above method is actually divided into three steps:
- Search for fully reusable barrage, that is, the same cache can be reused when the content and color of the barrage are completely the same as those on the screen
- Look for something that is almost reusable. By “almost” I mean to find a barrage that is larger than the one to be drawn (but within a certain range).
- Create one if you don’t have one
The above two steps 2 and 3 to go a core method DanmakuUtils. BuildDanmakuDrawingCache () :
DrawingCache buildDanmakuDrawingCache(BaseDanmaku danmaku, IDisplayer disp, DrawingCache cache, int bitsPerPixel) {
...
cache.build((int) Math.ceil(danmaku.paintWidth), (int) Math.ceil(danmaku.paintHeight), disp.getDensityDpi(), false, bitsPerPixel);
DrawingCacheHolder holder = cache.get();
if(holder ! = null) { ... ((AbsDisplayer) disp).drawDanmaku(danmaku, holder.canvas, 0, 0,true); // Draw the content directly... }return cache;
}
Copy the code
Build, then draw:
DrawingCache.build()
:
public void buildCache(int w, int h, int density, boolean checkSizeEquals, int bitsPerPixel) {
boolean reuse = checkSizeEquals ? (w == width && h == height) : (w <= width && h <= height);
if(reuse && bitmap ! = null) { bitmap.eraseColor(Color.TRANSPARENT); canvas.setBitmap(bitmap); recycleBitmapArray(); // Do you like itreturn; }... bitmap = NativeBitmapFactory.createBitmap(w, h, config);if (density > 0) {
mDensity = density;
bitmap.setDensity(density);
}
if (canvas == null){
canvas = new Canvas(bitmap);
canvas.setDensity(density);
}else
canvas.setBitmap(bitmap);
}
Copy the code
If there is a Bitmap in the DrawingCache, erase it. If no Bitmap, then on the native heap to create a Bitmap, the Bitmap will and DrawingCache DrawingCacheHolder canvas tube.
Here innative heap
To create aBitmap
Will reducejava heap
Pressure to avoid OOM
AbsDisplayer.drawDanmaku()
This method invocation logic is quite long, is not the source code analysis, it is ultimately through DrawingCacheHolder. Canvas draw barrage in the DrawingCacheHolder. Bitmap:
SimpleTextCacheStuffer.java
@Override public void drawDanmaku(BaseDanmaku danmaku, Canvas canvas...) {... drawBackground(danmaku, canvas, _left, _top); . drawText(danmaku, lines[0], canvas, left, top - paint.ascent(), paint, fromWorkerThread); . }Copy the code
What the build and draw steps above do is simply: prepare Danmaku with a rendered Bitmap in an asynchronous thread
Ok, after the above steps, in fact, a Bitmap of the drawn barrage is ready. The next step is to draw the Bitmap to the Canvas that is actually displayed on the plane
throughChoreographer
To keep renderingDanmakuView
It has been known from the beginning that DrawHandler is used to control the entire barrage logic, and it will use Choreographer to cause DanmakuView to render (draw):
private void updateInChoreographer() {... Choreographer.getInstance().postFrameCallback(mFrameCallback); . d = mDanmakuView.drawDanmakus(); . }Copy the code
MFrameCallback is in fact a doll that constantly invoke updateInChoreographer, mDanmakuView. DrawDanmakus () is an abstract method, For DanmakuView, it will be called to the View. The postInvalidateCompat (), which triggers DanmakuView. Ontouch (), after here actually have very complex logic, and did not take the source code opened one by one, Finally, danmakurenderer.accept () is called:
//main thread
public int accept(BaseDanmaku drawItem) {
...
// measure
if(! drawItem.isMeasured()) { drawItem.measure(disp,false); }... // layout calculate x and Y coordinates mDanmakusRetainer. Fix (drawItem, disp, mVerifier); . drawItem.draw(disp); }Copy the code
Measure () is to measure how much space should be occupied according to the content of bullet screen. Mdanmakusretainer.fix () will eventually call r2lDanmaku.layout () :
public class R2LDanmaku extends BaseDanmaku {
@Override
public void layout(IDisplayer displayer, float x, float y) {
if(mTimer ! = null) { long currMS = mTimer.currMillisecond; long deltaDuration = currMS - getActualTime();if(deltaDuration > 0 && deltaDuration < duration.value) { this.x = getAccurateLeft(displayer, currMS); // Determine the x coordinate of the current display according to the time schedule and the width of the current displayif(! this.isShown()) { this.y = y; this.setVisibility(true);
}
mLastTime = currMS;
return; } mLastTime = currMS; }... }}Copy the code
Y coordinates are actually determined by a higher layer of the class, R2ldanmaku.layout is mainly to determine the logic of x coordinates, his core algorithm is: according to the time progress, and the width of the current display, to determine the current display of X coordinates
Now let’s see how to draw a barrage. This will actually call androiddisplayer.draw ().
public int draw(BaseDanmaku danmaku) {
boolean cacheDrawn = sStuffer.drawCache(danmaku, canvas, left, top, alphaPaint, mDisplayConfig.PAINT);
int result = IRenderer.CACHE_RENDERING;
if(! cacheDrawn) { ... drawDanmaku(danmaku, canvas, left, top,false); // Render bitmap result = irenderer.text_rendering; }}Copy the code
First of all, the canvas here is the canvas of DanmakuView.ondraw (canvas). Sstuffer.drawcache () actually draws the previously drawn Bitmap on this canvas. If there is no existing Bitmap to draw, Let me draw it directly on the Canvas.
In fact, it happens almost 90% of the timesStuffer.drawCache()
In the
Here is a simple analysis of the entire implementation process, the above may not be very detailed, but the basic process is talked about
DanmakuSurfaceView
A separate Surface is opened to deal with the drawing operation of bullets, that is, the drawing operation can be done in the sub-thread (DrawHandler), without causing the main thread to lag
public long drawDanmakus() {... Canvas canvas = mSurfaceHolder.lockCanvas(); . RenderingState rs = handler.draw(canvas); mSurfaceHolder.unlockCanvasAndPost(canvas); .return dtime;
}
Copy the code
DanmakuTextureView
Directly inheriting from TextureView, TextureView differs from View and SurfaceView in this way:
- Unlike SurfaceView, it can be zoomed, panned, and animated just like a regular View
- Unlike the hardware-accelerated rendering of regular Views, TextureView does not have Display lists, they are done through a method called
Layer Renderer
The object toOpen GL
In the form of a texture, but still synchronized with the main draw operation
Simple performance analysis
After running the DanmakuFlameMaster Demo for 1 minute, you can see from the CPU Memory Profiler: DanmakuView Graphics takes up a lot of memory, in fact, the main reason is that the View hardware accelerated rendering of a large amount of texture synchronization from CPU to GPU consumes a lot of memory
So how do you optimize?
Personally, I feel that I can use GLSurfaceView or GLTextureView to complete the rendering of barrage through Open GL on the existing basis.
For more articles on Android, see The Android Advanced Program