Android Studio报错:调用SurfaceHolder.lockCanvas()时对象引用为空
Hey there! No need to apologize at all—we all start somewhere, and this is a super common issue when working with SurfaceView for rendering. Let's break down what's going wrong and how to fix it.
What's Causing the Null Pointer?
The error happens because you're calling lockCanvas() on a null SurfaceHolder. This usually occurs when your rendering thread starts running before the SurfaceView's underlying Surface has been fully created, or continues running after the Surface has been destroyed.
Step-by-Step Fixes
1. Tie Your Thread's Start to the Surface's Creation
Don't start your rendering thread in the SurfaceView's constructor or activity onCreate(). Instead, start it only after the Surface is ready, using the surfaceCreated() callback:
public class FPSSurfaceView extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder surfaceHolder; private Thread renderThread; private boolean running = false; public FPSSurfaceView(Context context) { super(context); surfaceHolder = getHolder(); surfaceHolder.addCallback(this); // Register the lifecycle callback } @Override public void surfaceCreated(SurfaceHolder holder) { // Now the Surface is ready—launch the rendering thread running = true; renderThread = new Thread(this::renderLoop); renderThread.start(); } }
2. Stop the Thread Safely When the Surface is Destroyed
When the Surface gets destroyed (like when the app is sent to the background), you need to shut down the thread and clear the SurfaceHolder reference to avoid invalid calls:
@Override public void surfaceDestroyed(SurfaceHolder holder) { running = false; // Wait for the thread to finish to prevent race conditions try { renderThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } surfaceHolder = null; // Clear the reference to stop future null access }
3. Add Null Checks in Your Render Loop
Even with lifecycle fixes, adding a quick null check before calling lockCanvas() adds an extra layer of safety for edge cases:
private void renderLoop() { while(running) { long startTime = System.nanoTime(); Canvas canvas = null; try { // Only proceed if SurfaceHolder is valid if (surfaceHolder != null) { canvas = surfaceHolder.lockCanvas(); synchronized(surfaceHolder) { if (canvas != null) { // Your FPS calculation and drawing logic here long elapsedTime = System.nanoTime() - startTime; float fps = 1_000_000_000f / elapsedTime; canvas.drawColor(Color.BLACK); Paint fpsPaint = new Paint(); fpsPaint.setColor(Color.WHITE); fpsPaint.setTextSize(48); canvas.drawText("FPS: " + String.format("%.1f", fps), 60, 120, fpsPaint); } } } } finally { // Only unlock the canvas if we successfully locked it if (canvas != null && surfaceHolder != null) { surfaceHolder.unlockCanvasAndPost(canvas); } } // Add a small delay to align with ~60 FPS and reduce CPU strain try { Thread.sleep(16); } catch (InterruptedException e) { e.printStackTrace(); } } }
Why This Works
SurfaceView's underlying Surface has a strict lifecycle: it only exists between surfaceCreated() and surfaceDestroyed(). By tying your thread's execution to these callbacks, you ensure you never try to access a SurfaceHolder that doesn't exist anymore. The null checks add a safety net for any unexpected edge cases.
Once you implement these changes, that null pointer exception should disappear, and your FPS counter should work as expected! Let me know if you hit any other snags along the way.
内容的提问来源于stack exchange,提问作者Quasarr

