【问题标题】:Synchronization of SurfaceHolder's lockCanvas and unlockCanvasAndPostSurfaceHolder的lockCanvas和unlockCanvasAndPost的同步
【发布时间】:2016-01-26 04:51:54
【问题描述】:

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

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

问题: 有时我会得到 java.lang.IllegalStateException: Surface has been released:

  • 调用 SurfaceHolder.lockCanvas() 时
  • 或在调用 SufraceHolder.unlockCanvasAndPost() 时

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

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

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

【问题讨论】:

    标签: java android multithreading surfaceview synchronized


    【解决方案1】:

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

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

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

    返回一个布尔值,指示接收器是否有待处理中断请求(真)或没有(假)

    (强调我的。)您的代码允许诸如“如果线程处于活动状态,但已为其引发中断,则继续并在旧线程仍在运行时创建一个新线程”之类的情况。

    有关使用 SurfaceView 的一些信息可以在this appendix 中找到。该链接指向 Grafika 中基于 Surface 回调启动和停止线程的示例,您可以找到 here

    【讨论】:

    • 感谢您的回答。我想我明白你在说什么。我将改变创建/销毁线程的方式,但我想重新表述我的主要问题:在我的绘图线程中调用holder.getSurface().isValid()holder.lockCanvas() 之间是否有可能表面被破坏,我该如何避免它?
    • Surface 完全有可能消失,因为您在不同的线程中运行并且没有告诉系统您正在使用它。你不回避它。你检查lockCanvas() 的返回值,如果它为空就不要尝试绘制。一旦你调用了lockCanvas(),系统就会保证 Surface 在你解锁之前不会消失。
    • 这就是我的想法和我试图做的,但有时我在尝试lockCanvas() 时得到这些IllegalStateException(表面已发布)。即使画布不为空,也不意味着在我调用 unlockCanvasAndPost() 时表面必须存在。如果系统在销毁表面时忽略了我对锁定/解锁表面的调用,我会非常满意,但是这些异常会搞砸一切。
    • Surface 拥有多个渲染器线程会导致不愉快。同样,解决这个问题的最佳方法是防止这种情况发生,而不是在它发生后添加同步来应对它。
    • 感谢您一直回复我。但我有点困惑。我的SurfaceView 仅在一个线程中绘制,没有其他线程这样做。但是视图本身是在 UI 线程中绘制的布局中。因此,表面在 UI 线程中被创建/销毁。根据开发人员指南,应该这样做,除非我遗漏了什么(请查看最后 2 个要点):link
    【解决方案2】:

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

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

    希望这可以帮助其他“菜鸟”。最后,这是我对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 澄清了有关在另一个线程中绘图的一些要点。

    【讨论】:

    • 如果画布有效且宽度为零,则您的代码不会解锁画布。这是为什么呢?
    • 我猜 getWidth() > 0 实际上不需要在那里,但 canvas != null 是必要的,因为在 lockCanvas() 文档中它说:“如果尚未创建表面,则返回 null否则无法编辑”。所以,如果你的表面在isValid()lockCanvas() 之间被破坏,你会从lockCanvas() 得到null,在这种情况下你不应该解锁你的画布。
    • 我现在将从我的最终代码中删除条件getWidth() > 0,以便需要它的人得到正确的版本。
    • @IvanBatalov 你从不打电话给drawingThread.quit()(即surfaceDestroyed())。
    【解决方案3】:

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

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

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

    【讨论】:

      【解决方案4】:

      将 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
      
            }
         }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-10-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-24
        • 1970-01-01
        相关资源
        最近更新 更多