在Canvas中绘制位图如何获得更好的帧率?

4

我正在制作一个动画壁纸,以下是代码。它基本上遵循 CubeWallpaper 示例:

    void drawFrame() {
        final SurfaceHolder holder = getSurfaceHolder();
    final BufferedInputStream buf;
    final Bitmap bitmap, rbitmap;

        Canvas c = null;
        try {
            c = holder.lockCanvas();
            if (c != null) {
        try {
        buf = new 
            BufferedInputStream(assets.
                    open(folder+"/"
                         +imageList[ilen++])
                    );
        bitmap = BitmapFactory.
            decodeStream(buf);
        rbitmap = Bitmap.createBitmap
            (bitmap,
             0,0,imageWidth,imageHeight,
             transMatrix,false);
        c.drawBitmap(rbitmap,
                 paddingX,
                 paddingY,
                 null);
        if ( ilen >= imageCount ) ilen=0;
        }
        catch (Exception e) { e.printStackTrace(); }
            }
        } finally {
            if (c != null) holder.unlockCanvasAndPost(c);
        }

        // Reschedule the next redraw
        mHandler.removeCallbacks(mDrawCube);
        if (mVisible) {
            mHandler.postDelayed(mDrawCube, fps);
        }
    }

其中,“transMatrix”是预先定义的缩放和旋转矩阵。

它应该以30fps渲染,但实际上并没有。我的初步猜测是BufferedInputStream是一个因素。我可能应该在进行位图操作时缓存一些数据。但还有其他想法吗?我是否需要使用OpenGL hack来制作动态壁纸?

2个回答

4

是的,BufferedInputStreamBitmapFactory应该完全不在drawFrame()中。您正在每个帧上加载和创建资源,这是巨大的浪费。就像您所说的那样,尽可能多地缓存资源,并且如果您需要在绘图过程中加载更多资源,请使用单独的线程来执行它,以便不会减缓绘图速度。


2
Geobits是正确的。而且,你编写的代码会引起很多垃圾回收,进一步减慢速度。 - George Freeman
好的,我为缓存做了一个单独的线程,但仍然没有加速。这是我的解决方案概述:1. 预加载约1秒钟的动画到位图数组中,2. 绘制每个位图,3. 在绘制时将每个单元格标记为“空”,4. 单独的线程“refilss”使用下一个动画重新填充空单元格。 - U Avalos
抱歉...这个评论框不喜欢“回车”,所以我需要重新输入我的评论:好的,为缓存做一个单独的线程,但我仍然没有得到加速。以下是我的解决方案概述:1. 预加载约1秒钟的动画到位图数组中,2. 绘制每个位图,3. 在绘制时将每个单元格标记为“空”,4. 单独的线程“填充”下一个动画的空单元格。问题在于即使使用线程,位图加载始终很慢,因此最终会出现瓶颈。我还尝试一次性加载“块”位图,但问题相同。我需要使用OpenGL吗? - U Avalos
1
不,OpenGL不会加速位图加载-完全不会-相信我。事实是,在播放过程中加载、创建和销毁那么多资源永远不会很快。这就是为什么大多数具有大量图形的游戏在开始时都有很长的加载时间,以便不会减慢游戏玩法。你要加载多少张图片(以文件大小计算)? - Geobits
这就是我担心的——位图加载是问题所在。大概有150个位图(5秒动画),这太多了,原始分辨率无法预加载。 - U Avalos

3

我遇到了同样的问题:在实时壁纸的上下文中,画布渲染速度很慢。

我同意其他人的说法,即在渲染过程中不应该进行任何cpu/io重操作,例如在UI线程上加载图像。

但是还有一件事情需要注意。您会在帧被渲染之后请求重新绘制(mHandler.postDelayed(...))。如果您要求30 fps并且因此在(1000/30)33ms内请求重新绘制,则不会导致每秒30帧。为什么?

让我们假设渲染所有内容到画布需要28ms。完成后,您在33毫秒后请求重新绘制。也就是说,重绘之间的时间间隔为61毫秒,相当于每秒16帧。

您有两个选项来解决这个问题:

1)将mHandler.postDelayed(...)代码放在drawFrame(...)方法的开头。这似乎可以,但它有一些缺点:如果您的实际FPS非常接近实际设备的最大可能FPS(换句话说,UI线程一直忙于画布渲染),那么UI线程就没有时间做其他工作。这不一定意味着您的LWP或主屏幕会出现滞后,但您的LWP可能会错过一些触摸事件(就像我的LWP一样)。

2)更好的解决方案是在创建表面时启动一个单独的线程,并将其引用传递给SurfaceHolder。在此单独的线程中进行渲染。该线程中的渲染方法如下:

private static final int DESIRED_FPS = 25;
private static final int DESIRED_PERIOD_BETWEEN_FRAMES_MS = (int) (1000.0 / DESIRED_FPS + 0.5);



@Override
public void run() {
    while (mRunning) {
        long beforeRenderMs = SystemClock.currentThreadTimeMillis();            
        // do actual canvas rendering
        long afterRenderMs = SystemClock.currentThreadTimeMillis();
        long renderLengthMs = afterRenderMs - beforeRenderMs;
        sleep(Math.max(DESIRED_PERIOD_BETWEEN_FRAMES_MS - renderLengthMs, 0));
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接