Canvas restore()在极少数情况下导致下溢异常

时间:2014-05-27 15:52:49

标签: android android-canvas underflow

通过ACRA,我收到了来自alpha版软件的少量报告,这些报告显示在Canvas.restore()的特定呼叫期间发生了异常。例外是java.lang.IllegalStateException: Underflow in restore

我很清楚,如果进行的restore()次调用次数超过save(),则会发生此异常。但是,通过非常仔细的代码检查,我绝对肯定所有对canvas.save()canvas.restore()的调用都是平衡的。也就是说,通过调用canvas.save(),所有对canvas.restore()的调用肯定会在稍后阶段得到平衡。我还可以确认方法中没有条件,异常或早期返回可能导致丢失canvas.save()导致堆栈下溢。

此外,这个问题似乎是一个罕见的边缘情况,导致异常在图形代码中发生的次数很少,每秒渲染很多次。

发生这种情况的代码的结构类型是:

protected void onDraw(Canvas canvas) {
    ...
    someMethod(canvas);
    ...
}

void someMethod(Canvas canvas)
{
    ....
    canvas.save();
    ....
    someOtherMethod(Canvas canvas);
    ....
    canvas.restore();  
    ....
}

void someOtherMethod(Canvas canvas)
{

    ....
    canvas.save();
    ....
    for ( ... ) {

        ....
        canvas.save();
        ...
        canvas.restore();
        ...
    }
    ....
    canvas.restore();   // *** exception here ***   
    ....
}

有些地方在循环中使用save() / restore(),但呼叫也是平衡的。

报告来自运行4.3和4.4.2的设备。

我试图谷歌这个问题,我能找到的唯一有趣的质量保证来自clearly had unbalanced save() / restore() calls in his code;在我的情况下,这绝对不是问题。

这发生在自定义View子类“onDraw(Canvas canvas)方法的调用堆栈中,所有这些都发生在UI线程上。我没有触及Canvas对象的其他线程。

在调用负责异常的特定restore()之前,我可以通过调用getSaveCount()来检查堆栈,但这真的只是在问题上坚持使用Elastoplast。我宁愿理解边缘情况究竟是什么造成了下溢,但这让我感到困惑。

有任何已知问题吗?当UI线程在Canvas的{​​{1}}调用的上下文中时,任何类型的系统配置更改都可能影响此View吗? onDraw()堆栈大小是否有任何已知限制?可能有任何操作系统图形调用已知会滥用画布堆栈吗?

2 个答案:

答案 0 :(得分:2)

这不是解释根本原因的答案,遗憾的是我仍然不知道原因。我在这里展示的是解决方案让我能够应对它。

非常简单,整个渲染过程被try / catch包围如下:

        try {
            ...
            // calls go within here to render to the canvas many times
            ...

        }
        catch (java.lang.IllegalStateException exception) {
            // Attempt to catch rare mysterious Canvas stack underflow events that have been reported in
            // ACRA, but simply should not be happening because Canvas save()/restore() calls are definitely
            // balanced. The exception is: java.lang.IllegalStateException: Underflow in restore
            // See: stackoverflow.com/questions/23893813/
            if (exception.getMessage() != null && (//
                    exception.getMessage().contains("Underflow in restore") || //
                    exception.getCause().getMessage().contains("Underflow in restore"))) { //
                DebugLog.getInstance().e("Caught a Canvas stack underflow! (java.lang.IllegalStateException: Underflow in restore)");
            }
            else {
                // It wasn't a Canvas underflow, so re-throw.
                throw exception;
            }
        }

请注意,DebugLog.getInstance().e是我自己的SD卡调试记录器类,我在此不详述;您可以将该调用替换为您要记录异常的任何内容。

一些用户'设备已经向我发送了有关完全不同问题的ACRA报告,我注意到其中一些报告包含附加日志,表明这些Canvas例外已被捕获。这似乎来自三星Galaxy设备。从日志来看,设备可能会抛出此异常两到三次,但用户可以正常使用该应用程序。所以似乎没有持久的负面影响来捕捉并基本上掩盖这个例外。

答案 1 :(得分:0)

此错误仅来自android 6.0+,因此您希望在使用画布时放置行。

if(Build.VERSION.SDK_INT!= Build.VERSION_CODES.M)                     canvas.restore();