在我的Android应用程序中,我使用SurfaceView
来绘制东西。它已经在成千上万的设备上运行良好--除了现在用户开始在以下设备上报告ANR:
所以我买了一部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中的LunarLander
示例,更具体地说是LunarView.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) { }
但这些都不是真正的修复,不是吗?这不是很奇怪吗?
(根据我对代码所做的更改,我还可以在错误日志中看到异常。有许多developers with the same problem,但不幸的是,没有一个能够为我的(特定于设备的)情况提供解决方案。
你能帮上忙吗?
发布于 2015-12-19 13:25:46
目前对我有效的方法,虽然没有真正解决问题的原因,但只是表面上与症状作斗争:
1.移除Canvas
操作
我的呈现线程调用SurfaceView
子类上的自定义方法doDraw(Canvas canvas)
。
在该方法中,如果我删除Canvas
上对Canvas.drawBitmap(...)
、Canvas.drawRect(...)
和其他操作的所有调用,应用程序就不会再冻结。
方法中可能只剩下一个对Canvas.drawColor(int color)
的调用。甚至像BitmapFactory.decodeResource(Resources res, int id, Options opts)
和读/写我的内部Bitmap
缓存这样代价高昂的操作也没问题。没有冻结。
显然,如果没有任何绘图,SurfaceView
并没有真正的帮助。
2.在渲染线程的run循环中休眠10ms
我的渲染线程执行的方法:
@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.将渲染线程的开始延迟10ms
这就是我从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,索尼Xperia Z4,华为Ascend Mate 7,HTC M9 (可能还有其他几款设备),这款应用在成千上万的设备上运行良好。
这会不会是设备特定的故障?人们肯定听说过这个..。
所有这些“解决方案”都是老生常谈。我希望有更好的解决方案--我打赌一定会有的!
发布于 2015-12-17 01:40:04
看看ANR轨迹。它似乎卡在哪里了?ANR意味着主UI线程无法响应,所以你在渲染器线程上所做的事情是无关紧要的,除非这两个线程正在争夺一个锁。
你报告的症状听起来像是一场比赛。如果你的主UI线程停留在mRunningLock
上,那么可以想象你的渲染器线程只会让它在很短的时间内处于解锁状态。添加日志消息或休眠调用使主线程有机会在呈现器线程再次抓住它之前唤醒并执行工作。
(这对我来说没有实际意义--您的代码看起来应该在等待显示刷新的同时等待lockCanvas()
--所以您需要查看ANR中的线程跟踪。)
顺便说一下,你不需要在mSurfaceHolder
上同步。一个早期的例子做到了这一点,从那时起,每个例子都克隆了它。
解决了这个问题后,您可能想要阅读有关game loops的内容。
发布于 2021-03-28 22:51:03
在小米5,安卓6上也遇到了同样的问题。画布的活动在开始时和退出此活动后的一段时间内冻结。用lockHardwareCanvas()
代替lockCanvas()
解决了这个问题。这种方法在Android6中不能直接使用,所以我调用了sHolder.getSurface().lockHardwareCanvas();
和sHolder.getSurface().unlockCanvasAndPost(canvas);
,现在不需要延迟,工作正常
https://stackoverflow.com/questions/34305937
复制相似问题