通过OpenGL优化WEBP动画的渲染

前言

在目前的项目中我们使用到了WEBP作为直播动画的方案,使用WEBP的优点就不用说了,缺点我觉得有一个就是Android上目前能够解码webp动画的库只有FB的 Fresco 库,所以目前只能使用这个库作为webp动画播放的库。

既然是直播比较蛋疼的问题就是目前手机屏幕上出现的各种元素会比较多,所以对于UI上的优化就显得比较重要了,在目前我们的项目中,在一个屏幕上用户可以发弹幕,点赞,发送礼物,而且还是在播放器界面,整个界面的元素相当复杂,所以这个时候每一个操作的优化都显得十分重要,不然就会造成UI线程的卡顿,影响用户体验。因此在这里的用户直播礼物的优化就会显得十分重要。但是在Freso库默认提供的 SimpleDraweeView 是继承于ImageView的,这也就说明这个库的渲染是在主线程当中完成的,那么这就提供了优化的空间,我们可以使用OpenGL在非主线程中来完成礼物的渲染。

自定义View

在Freso的使用说明中可以查找到如何使用自定义的View. 既然我们需要使用OpenGL,那么我们需要继承GLSurfaceView 这个类,这个类具体的使用说明可以自行百度,继承之后按照文档里面相关的说明完成一系列方法的重写。

接下来,按照Fresco的教程定义完成View之后,就需要完成OpenGL 当中render的 编写了。代码的核心就是完成onDrawFrame的编写。核心代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public void onDrawFrame(GL10 gl) {
cacheBitmap.eraseColor(Color.TRANSPARENT);
Canvas canvas = new Canvas(cacheBitmap);
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BITS);
texture = Util.loadTexture(cacheBitmap, texture, false);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, position.length,
GLES20.GL_UNSIGNED_SHORT, shortBuffer);
}

其中的主要逻辑梳理一下,在自定义的View当中我们可以通过 mDraweeHolder.getTopLevelDrawable() 拿到当前需要绘制的Drawable对象,但是在OpenGL中,Drawable对象不管用,我们只能将其转为一个Bitmap 对象,所以需要创建一个Canvas,通过drawable .draw 方法来得到其中的图像数据。拿到bitmap之后将其转为opengl中的纹理对象即可。

如果你完全按照Fresco中的代码来定义View, 运行上诉的代码会直接崩溃。

从代码的异常堆栈里面可以看出看我们在WebpGLView这个类中导致了 invalidateDrawable 这个方法的调用,这个方法定义在Drawable.Callback当中

1
2
3
4
5
6
7
8
/**
* Called when the drawable needs to be redrawn. A view at this point
* should invalidate itself (or at least the part of itself where the
* drawable appears).
*
* @param who The drawable that is requesting the update.
*/
void invalidateDrawable(@NonNull Drawable who);

从注释中我们可以看到这个方法是在Drawable出现更新的时候view应该更新自己。

而在Fresco的教程当中也需要我们调用

1
mDraweeHolder.getTopLevelDrawable().setCallback(this);

这行代码,这里之所以能够直接使用this, 就是因为View默认实现了这个接口,我们再来看下View当中是怎么实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Invalidates the specified Drawable.
*
* @param drawable the drawable to invalidate
*/
@Override
public void invalidateDrawable(@NonNull Drawable drawable) {
if (verifyDrawable(drawable)) {
final Rect dirty = drawable.getDirtyBounds();
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidate(dirty.left + scrollX, dirty.top + scrollY,
dirty.right + scrollX, dirty.bottom + scrollY);
rebuildOutline();
}

从View实现的代码中我们可以看出来,View调用了 invalidate 方法来实现View的更新。

在我们上线的代码中我们在一个OpenGL线程中调用draw(canvas)最终导致invalidateDrawable 的调用,从而在非主线程调用了invalidate,所以出现了上面的崩溃。

所以为了避免这种情况我们只需自己重写这个方法即可,重写十分简单,直接调用requestRender() 即可。

所有的代码都在LNeway上可以找到。