SurfaceHolder的lockCanvas和unlockCanvasAndPost的同步

Synchronization of SurfaceHolder's lockCanvas and unlockCanvasAndPost

我知道关于这个话题有很多问题,但是,我对所提供的答案仍然不完全满意。

情况: 我使用 SurfaceHolder 在另一个线程中通过绘图实现了 SurfaceView,就像开发人员指南中建议的那样:http://developer.android.com/guide/topics/graphics/2d-graphics.html

问题: 有时我得到 java.lang.IllegalStateException:Surface 已经发布:

这意味着我的表面有时会在我锁定 canvas 之前被释放,有时 - 在锁定和解锁之间。

我的解决方案: 我在同步块中执行表面检查和 locking/unlocking canvas,因此我确定表面不会在这些操作之间被破坏。但是,我从来没有使用过同步块,我想问问它是否有什么问题。到目前为止代码运行良好,但您永远不知道同步问题何时会出现,所以我不完全相信这是最好的方法。

private class DrawingThread extends Thread{
    @Override
    public void run() {
        super.run();
        while (!isInterrupted() && holder != null) {
            Canvas drawingCanvas = null;
            synchronized (this) {
                if (holder.getSurface().isValid()) {
                    drawingCanvas = holder.lockCanvas();
                }
            }
            if (drawingCanvas != null && drawingCanvas.getWidth() > 0) {
                drawThisView(drawingCanvas);
                synchronized (this) {
                    if(holder.getSurface().isValid()) {
                        holder.unlockCanvasAndPost(drawingCanvas);
                    }
                }
            }
        }
    }
}


@Override
public void surfaceCreated(SurfaceHolder holder) {
    if(drawingThread != null){
        drawingThread.interrupt();
    }
    drawingThread = new DrawingThread();
    drawingThread.start();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    if(drawingThread.isInterrupted()){
        drawingThread = new DrawingThread();
        drawingThread.start();
    }
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    drawingThread.interrupt();
}

synchronized语句用于多线程间的互斥访问。如果您 synchronized (this) 在线程 #1 中,那么任何其他尝试执行相同操作的线程都将阻塞,直到线程 #1 退出同步块。

如果你只在一个线程中使用它,它没有任何用处。当然,如果您有多个线程试图锁定和解锁 Surface 的 Canvas,您应该首先不这样做来解决问题,而不是事后尝试强制执行独占访问。

您对 interrupt()isThreadInterrupted() 的使用没有多大意义。如果你想举起一个标志告诉线程是时候停止 运行,volatile boolean 就可以了。 interrupt() 的唯一优点是它会在等待对象发出信号时唤醒线程。此外,请考虑 isInterrupted() 调用的文档:

Returns a boolean indicating whether the receiver has a pending interrupt request (true) or not ( false)

(强调我的。)您的代码允许 "if the thread is alive, but an interrupt has been raised for it, go ahead and create a new thread while the old one is still running".

等情况

有关使用 SurfaceView 的一些信息可以在 this appendix. That links to an example in Grafika that starts and stops the thread based on the Surface callbacks, which you can find here 中找到。

经过几个小时的试验和错误,我想我已经弄明白了。毕竟,我所要做的就是阅读 android 参考资料。以下是我弄错的一些重要事项:

  • SurfaceHolder.getSurface.isValid() 只检查表面是否可以 被锁定,所以这个检查可以(并且确实)失败,因为看到是 canvas 可以解锁。
  • 检查是否可以调用 unlockCanvasAndPost(), 您只需检查 lockCanvas() 返回的 canvas 是否不是 null.
  • 如果 canvasnull,你不应该解锁它(如果你 尝试它会抛出异常)。
  • 如果它不为空,则必须解锁它,否则如果您 activity 试图停止(在 onPause() 之后 SurfaceHolder 试图破坏 Surface,但是由于 Canvas 被锁定,它不能,所以你陷入了僵局。

希望这可以帮助其他 "noobs"。最后,这是我对 SurfaceView 的最终代码(不再需要 try/catch 块):

DrawingThread drawingThread;

private class DrawingThread extends Thread{
    public volatile boolean canDraw = true;

    @Override
    public void run() {
        try {
            while (canDraw) {
                Canvas drawingCanvas = null;
                if (canDraw && holder.getSurface().isValid()) {
                    drawingCanvas = holder.lockCanvas();
                    if (drawingCanvas != null) {
                        drawThisView(drawingCanvas);
                        holder.unlockCanvasAndPost(drawingCanvas);
                    }
                }
            }
        }catch(IllegalStateException e){
            e.printStackTrace();
            canDraw = false;
        }
    }
}


@Override
public void surfaceCreated(SurfaceHolder holder) {
    if(drawingThread != null){
        drawingThread.canDraw = false;
    }
    drawingThread = new DrawingThread();
    drawingThread.start();
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    if(!drawingThread.canDraw){
        drawingThread = new DrawingThread();
        drawingThread.start();
    }
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    if(drawingThread != null) {
        drawingThread.canDraw = false;
    }
}

感谢 fadden 在另一个线程中澄清了一些关于绘图的要点。

我有同样的问题 - 使用后台线程在 SurfaceView 中绘图,有时,应用程序在 activity 关闭时挂起。追踪显示,在这种情况下,表面在 lockCanvasunlockCanvasAndPost 之间被破坏。

造成这种行为的原因似乎是我对文档的理解有点错误。 Callback.surfaceDestroyed 必须用于防止 surface 被破坏(通过不从它返回)直到后台线程完成 surface。

DrawingThread drawingThread;
ReentrantLock paintLock = new ReentrantLock();

private class DrawingThread extends Thread{
    public volatile boolean canDraw = true;

    @Override
    public void run() {
        try {
            while (canDraw) {
                Canvas drawingCanvas = null;
                paintLock.lock();
                if (canDraw)) {
                    drawingCanvas = holder.lockCanvas();
                    if (drawingCanvas != null && drawingCanvas.getWidth() > 0) {
                        drawThisView(drawingCanvas);
                        holder.unlockCanvasAndPost(drawingCanvas);
                    }
                }
                paintLock.unlock();
            }
        }catch(IllegalStateException e){
            e.printStackTrace();
            canDraw = false;
        }
    }
}

...

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    paintLock.lock();
    if(drawingThread != null) {
        drawingThread.canDraw = false;
    }
    paintLock.unlock();
}

在单个同步语句中放置 Lock、Draw 和 Unlock 修复了异常。

我是在读完这个帖子后才知道同步语句的,如果这是个坏主意,请纠正我。

private class DrawingThread extends Thread{
@Override
public void run() {
    super.run();
    while (!isInterrupted() && holder != null) {
        Canvas drawingCanvas = null;
        synchronized (this) {
            if (holder.getSurface().isValid()) {
                drawingCanvas = holder.lockCanvas();
            }

            if (drawingCanvas != null && drawingCanvas.getWidth() > 0) {
                drawThisView(drawingCanvas);
            }

            if(holder.getSurface().isValid()) {
                 holder.unlockCanvasAndPost(drawingCanvas);
            }
        } // Lock -> Draw -> Unlock in a single synchronised statement

      }
   }
}