You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SurfaceView游戏中Canvas调用DrawBitmap绘制缓慢问题求助

Fixing Slow Canvas Bitmap Drawing in Android SurfaceView

Hey there, let's break down why your scaled canvas is causing performance hits when drawing multiple background bitmaps, and walk through actionable fixes to get your 60 FPS target back on track. I've tackled similar SurfaceView bottlenecks before, so here's what to try:


1. Pre-Scale Bitmaps to Display Size (Skip Canvas Scaling)

The biggest issue here is scaling the canvas every frame—this forces the GPU to transform every pixel of each bitmap on the fly, which adds massive per-frame overhead. You mentioned trying pre-scaling, but let's make sure you're doing it at the right time and correctly:

Instead of scaling the canvas in draw(), resize your bitmaps once to match the actual view dimensions (use surfaceChanged() since that's where you get the final width/height):

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    // Handle size changes here—surfaceCreated might not have the final view size
    scaleBackgroundBitmaps(width, height);
}

private void scaleBackgroundBitmaps(int targetWidth, int targetHeight) {
    // Clean up old bitmaps to avoid memory leaks
    for (Bitmap bg : bgsrc) {
        if (bg != null && !bg.isRecycled()) {
            bg.recycle();
        }
    }

    // Load original background with optimized config (more on that next)
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Bitmap.Config.RGB_565; // No transparency = smaller memory footprint
    Bitmap originalBg = BitmapFactory.decodeResource(getResources(), R.drawable.mountain_background_v2, options);

    bgsrc = new Bitmap[5];
    for (int i = 0; i < bgsrc.length; i++) {
        // Crop the original to get the 800x399 slice
        Bitmap croppedSlice = Bitmap.createBitmap(originalBg, i * 800, 0, 800, 399);
        // Scale directly to the target display size
        bgsrc[i] = Bitmap.createScaledBitmap(croppedSlice, targetWidth, targetHeight, true);
        // Recycle the temporary cropped slice to free memory
        croppedSlice.recycle();
    }
    originalBg.recycle();

    // Refresh your backgrounds list with scaled bitmaps
    backgrounds.clear();
    for (int i = bgsrc.length - 1; i >= 0; i--) {
        if (i == 0 || i == 2) continue;
        backgrounds.add(new Background(bgsrc[i], -10 + i * 2));
    }
}

Then simplify your draw() method to skip canvas scaling entirely:

@Override
public void draw(Canvas canvas){
    super.draw(canvas);
    if (canvas != null) {
        for (Background background : backgrounds) {
            background.draw(canvas);
        }
    }
}

2. Optimize Bitmap Configuration

If your backgrounds don't have transparency, switching to RGB_565 instead of the default ARGB_8888 cuts bitmap memory usage in half (2 bytes per pixel vs 4). This reduces memory pressure and makes drawing faster, as the GPU has fewer bytes to process. We already added this in the code above, but it's worth calling out as a standalone fix.


3. Tweak Your GameThread for Efficient Frame Throttling

Make sure your thread's loop is properly throttling to 60 FPS and doesn't hold the canvas lock longer than necessary. A well-optimized loop looks like this:

public class GameThread extends Thread {
    private SurfaceHolder surfaceHolder;
    private GameController gameController;
    private boolean running = false;
    private final long targetFrameDuration = 1000 / 60; // ~16ms per frame for 60 FPS

    public GameThread(SurfaceHolder holder, GameController controller) {
        surfaceHolder = holder;
        gameController = controller;
    }

    public void setRunning(boolean run) {
        running = run;
    }

    @Override
    public void run() {
        long frameStartTime;
        long frameElapsedTime;
        long sleepTime;

        while (running) {
            Canvas canvas = null;
            frameStartTime = System.currentTimeMillis();

            try {
                // Lock the canvas only when needed
                canvas = surfaceHolder.lockCanvas();
                synchronized (surfaceHolder) {
                    gameController.update();
                    if (canvas != null) {
                        gameController.draw(canvas);
                    }
                }
            } finally {
                // Always unlock the canvas to avoid deadlocks
                if (canvas != null) {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }

            // Calculate sleep time to hit 60 FPS
            frameElapsedTime = System.currentTimeMillis() - frameStartTime;
            sleepTime = targetFrameDuration - frameElapsedTime;

            if (sleepTime > 0) {
                try {
                    sleep(sleepTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4. Optimize the Background Class's Draw Logic

Make sure your Background class isn't doing expensive operations in its draw() method. Reuse objects like Paint instead of creating new ones every frame:

public class Background {
    private Bitmap bitmap;
    private float xPos;
    private Paint drawPaint; // Reuse this instance

    public Background(Bitmap bitmap, float startX) {
        this.bitmap = bitmap;
        this.xPos = startX;
        drawPaint = new Paint();
        drawPaint.setFilterBitmap(false); // Disable filtering (safe since we pre-scaled)
    }

    public void update() {
        // Your position update logic here
    }

    public void draw(Canvas canvas) {
        canvas.drawBitmap(bitmap, xPos, 0, drawPaint);
    }
}

Disabling setFilterBitmap(false) removes anti-aliasing during drawing, which speeds things up—since we pre-scaled the bitmaps to the exact display size, you won't lose any quality.


5. Verify Hardware Acceleration

SurfaceViews are hardware-accelerated by default, but double-check your AndroidManifest.xml to make sure you haven't disabled it globally:

<application
    ...
    android:hardwareAccelerated="true">
    ...
</application>

These changes should eliminate the per-frame scaling overhead, reduce memory usage, and get your game running at a smooth 60 FPS. The key is shifting as much work as possible to initialization time instead of doing it every frame.

内容的提问来源于stack exchange,提问作者kabir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:49:19