在 Canvas 中绘制位图的帧速率更好?

发布于 2024-11-10 00:42:39 字数 1360 浏览 3 评论 0原文

我正在拍摄动态壁纸中的动画。这是代码。它几乎遵循 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 来制作动态壁纸吗?

I'm shooting for an animation in a live wallpaper. Here's the code. It pretty much follows the CubeWallpaper demo:

    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);
        }
    }

where "transMatrix" is a scaling and rotation matrix predefined before.

It's supposed to render at 30fps but of course it doesn't do that. My initial guess is that the BufferedInputStream is one factor. I should probably cache a few of these as I go along along with the Bitmaps. But any other ideas? Will I have to use the OpenGL hack for live wallpapers?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

漆黑的白昼 2024-11-17 00:42:39

是的,BufferedInputStreamBitmapFactory 确实根本不应该位于 drawFrame() 中。您在每一帧上加载和创建资源,这是一种巨大的浪费。就像你说的那样,预先缓存尽可能多的数据,如果你发现在绘制过程中需要加载更多数据,请使用单独的线程来执行此操作,这样就不会减慢绘制速度。

Yes, the BufferedInputStream and BitmapFactory really shouldn't be in drawFrame() at all. You're loading and creating resources on every single frame, and that's a huge waste. Like you said, cache as many as you can beforehand, and if you find the need to load more during drawing, use a separate thread to do it so it doesn't slow the drawing.

遮云壑 2024-11-17 00:42:39

我遇到了同样的问题:在动态壁纸的背景下画布渲染速度缓慢。

我同意其他人的说法,即在渲染(例如加载图像,尤其是在 UI 线程上)时不应该执行任何 cpu/io 繁重的操作。

但是,您还应该注意一件事。您请求在渲染帧后重绘(mHandler.postDelayed(...))。如果您想要 30 fps,因此您请求在 (1000 / 30) 33 毫秒内重绘,那么它不会导致每秒 30 帧。为什么?

假设将所有内容渲染到画布需要 28 毫秒。完成后,您在 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));
    }
}

I had the same problem: slow canvas rendering in context of live wallpapers.

I agree with others saying that you shouldn't do any cpu/io heavy while rendering e.g. loading images especially on the UI thread.

However there is one more thing you should note. You request a redraw (mHandler.postDelayed(...)) AFTER the frame was rendered. If you desire a 30 fps and thus you request a redraw in (1000 / 30) 33ms then it will NOT result in 30 frames per sec. Why?

Let's assume it takes 28ms to render all your stuff to the canvas. After it's done you request a redraw after 33 millis. That is, the period between redraws is 61 ms which equals with 16 frames per sec.

You have two options to solve it:

1) Put the mHandler.postDelayed(...) code to the beginning of the drawFrame(...) method. This seems OK but it has some disadvantages: If your actual FPS is very close to the maximal possible FPS on an actual device - with other words the UI thread is busy all the time with you canvas rendering - then there won't be time for the UI thread to do other stuff. It doesn't necesseraly mean that your LWP or the home screen will lag but you (your LWP) might start missing some touch events (as my LWP did).

2) The better solution is to start a separate thread when the surface is created and pass it the reference to the SurfaceHolder. Do the rendering in this separate thread. The render method in this thread would look like this:

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));
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文