仅在特定设备上的SurfaceView中的ANR - 唯一的解决方法是睡眠时间短

在我的Android应用程序中,我使用a来绘制东西。它在数千台设备上工作正常 - 除了现在用户开始在以下设备上报告ANR:SurfaceView

  • LG G4
    • 安卓 5.1
    • 3 GB 内存
    • 5.5 英寸显示屏
    • 2560 x 1440 px 分辨率
  • 索尼Xperia Z4
    • 安卓5.0
    • 3 GB 内存
    • 5,2“ 显示屏
    • 1920 x 1080 px 分辨率
  • 华为昇腾伴侣7
    • 安卓 5.1
    • 3 GB 内存
    • 6.0 英寸显示屏
    • 1920 x 1080 px 分辨率
  • 宏达电 M9
    • 安卓 5.1
    • 3 GB 内存
    • 5.0“ 显示屏
    • 1920 x 1080 px 分辨率

所以我买了一台LG G4,并且确实能够验证这个问题。它与 .SurfaceView

现在猜猜经过数小时的调试后,是什么解决了问题?它正在取代...

mSurfaceHolder.unlockCanvasAndPost(c);

...跟。。。

mSurfaceHolder.unlockCanvasAndPost(c);
System.out.println("123"); // THIS IS THE FIX

这怎么可能?

以下代码是我的渲染线程,除上述设备外,它一直工作正常:

import android.graphics.Canvas;
import android.view.SurfaceHolder;

public class MyThread extends Thread {

    private final SurfaceHolder mSurfaceHolder;
    private final MySurfaceView mSurface;
    private volatile boolean mRunning = false;

    public MyThread(SurfaceHolder surfaceHolder, MySurfaceView surface) {
        mSurfaceHolder = surfaceHolder;
        mSurface = surface;
    }

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

    @Override
    public void run() {
        Canvas c;
        while (mRunning) {
            c = null;
            try {
                c = mSurfaceHolder.lockCanvas();
                if (c != null) {
                    mSurface.doDraw(c);
                }
            }
            finally { // when exception is thrown above we may not leave the surface in an inconsistent state
                if (c != null) {
                    try {
                        mSurfaceHolder.unlockCanvasAndPost(c);
                    }
                    catch (Exception e) { }
                }
            }
        }
    }

}

该代码部分来自Android SDK中的示例,更具体地说。LunarLanderLunarView.java

更新代码以匹配 Android 6.0(API 级别 23)中的改进示例,将产生以下结果:

import android.graphics.Canvas;
import android.view.SurfaceHolder;

public class MyThread extends Thread {

    /** Handle to the surface manager object that we interact with */
    private final SurfaceHolder mSurfaceHolder;
    private final MySurfaceView mSurface;
    /** Used to signal the thread whether it should be running or not */
    private boolean mRunning = false;
    /** Lock for `mRunning` member */
    private final Object mRunningLock = new Object();

    public MyThread(SurfaceHolder surfaceHolder, MySurfaceView surface) {
        mSurfaceHolder = surfaceHolder;
        mSurface = surface;
    }

    /**
     * Used to signal the thread whether it should be running or not
     *
     * @param running `true` to run or `false` to shut down
     */
    public void setRunning(final boolean running) {
        // do not allow modification while any canvas operations are still going on (see `run()`)
        synchronized (mRunningLock) {
            mRunning = running;
        }
    }

    @Override
    public void run() {
        while (mRunning) {
            Canvas c = null;

            try {
                c = mSurfaceHolder.lockCanvas(null);
                synchronized (mSurfaceHolder) {
                    // do not allow flag to be set to `false` until all canvas draw operations are complete
                    synchronized (mRunningLock) {
                        // stop canvas operations if flag has been set to `false`
                        if (mRunning) {
                            mSurface.doDraw(c);
                        }
                    }
                }
            }
            // if an exception is thrown during the above, don't leave the view in an inconsistent state
            finally {
                if (c != null) {
                    mSurfaceHolder.unlockCanvasAndPost(c);
                }
            }
        }
    }

}

但是,此类仍然不适用于上述设备。我出现黑屏,应用程序停止响应。

唯一(我发现的)解决问题的是添加调用。在循环结束时添加较短的睡眠时间,结果结果显示相同的结果:System.out.println("123")

try {
    Thread.sleep(10);
}
catch (InterruptedException e) { }

但这些都不是真正的解决方案,不是吗?这不是很奇怪吗?

(根据我对代码所做的更改,我还能够在错误日志中看到异常。有许多开发人员遇到同样的问题但不幸的是,没有人为我(特定于设备)的情况提供解决方案。

你能帮忙吗?


答案 1

目前对我有用的是,虽然没有真正解决问题的原因,但表面上与症状作斗争:

1. 删除操作Canvas

我的渲染线程调用子类上的自定义方法。doDraw(Canvas canvas)SurfaceView

在该方法中,如果我删除对 的所有调用,以及 上的其他操作,则应用不会再冻结。Canvas.drawBitmap(...)Canvas.drawRect(...)Canvas

方法中可能会保留一个 to 调用。即使是昂贵的操作,如读取/写入我的内部缓存也是可以的。不会冻结。Canvas.drawColor(int color)BitmapFactory.decodeResource(Resources res, int id, Options opts)Bitmap

显然,没有任何绘图,并没有真正的帮助。SurfaceView

2. 在渲染线程的运行循环中休眠 10 毫秒

我的渲染线程执行的方法:

@Override
public void run() {
    Canvas c;
    while (mRunning) {
        c = null;
        try {
            c = mSurfaceHolder.lockCanvas();
            if (c != null) {
                mSurface.doDraw(c);
            }
        }
        finally {
            if (c != null) {
                try {
                    mSurfaceHolder.unlockCanvasAndPost(c);
                }
                catch (Exception e) { }
            }
        }
    }
}

只需在循环中添加较短的睡眠时间(例如,在最后)即可修复LG G4上的所有冻结问题:

while (mRunning) {
    ...

    try { Thread.sleep(10); } catch (Exception e) { }
}

但是谁知道为什么这有效,以及这是否真的解决了问题(在所有设备上)。

3. 打印一些东西System.out

奇怪的是,与上面相同的事情也与 一起使用。Thread.sleep(...)System.out.println("123")

4. 渲染线程的启动延迟 10 毫秒

这就是我如何从以下位置启动渲染线程:SurfaceView

@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
    mRenderThread = new MyThread(getHolder(), this);
    mRenderThread.setRunning(true);
    mRenderThread.start();
}

将这三行包装在以下延迟执行中时,应用不会再冻结:

new Handler().postDelayed(new Runnable() {

    @Override
    public void run() {
        ...
    }

}, 10);

这似乎是因为在一开始就只有一个大概的僵局。如果清除此项(延迟执行),则不会有其他死锁。之后,该应用程序运行良好。

但是当离开 时,应用程序再次冻结。Activity

5.只需使用其他设备

除了LG G4,Sony Xperia Z4,华为Ascend Mate 7,HTC M9(可能还有其他一些设备)之外,该应用程序在数千台设备上运行良好。

这可能是特定于设备的故障吗?人们肯定会听说过这个...


所有这些“解决方案”都是黑客。我希望有一个更好的解决方案 - 我敢打赌有!


答案 2

查看 ANR 跟踪。它似乎卡在哪里?ANR 意味着主 UI 线程无法响应,因此您在渲染器线程上执行的操作无关紧要,除非两者争夺锁。

你报告的症状听起来像是一场竞赛。如果你的主 UI 线程卡在 上,比如说,可以想象你的渲染器线程只会在很短的窗口内解锁它。添加日志消息或睡眠调用使主线程有机会在渲染器线程再次抓住它之前唤醒并完成工作。mRunningLock

(这对我来说实际上没有意义 - 你的代码看起来应该在等待显示刷新时停止等待 - 所以你需要在ANR中查看线程跟踪。lockCanvas()

FWIW,您不需要在 上进行同步。一个早期的例子做到了这一点,从那以后的每一个例子都克隆了它。mSurfaceHolder

一旦你解决了这个问题,你可能想阅读有关游戏循环的信息


推荐