In my Android application, I use a SurfaceView
to draw things. It has been working fine on thousands of devices -- except that now users started reporting ANRs on the following devices:
- LG G4
- Android 5.1
- 3 GB RAM
- 5.5" display
- 2560 x 1440 px resolution
- Sony Xperia Z4
- Android 5.0
- 3 GB RAM
- 5,2" display
- 1920 x 1080 px resolution
- Huawei Ascend Mate 7
- Android 5.1
- 3 GB RAM
- 6.0" display
- 1920 x 1080 px resolution
- HTC M9
- Android 5.1
- 3 GB RAM
- 5.0" display
- 1920 x 1080 px resolution
So I got an LG G4 and was indeed able to verify the problem. It's directly related to the SurfaceView
.
Now guess what fixed the issue after hours of debugging? It is replacing ...
mSurfaceHolder.unlockCanvasAndPost(c);
... with ...
mSurfaceHolder.unlockCanvasAndPost(c);
System.out.println("123"); // THIS IS THE FIX
How can this be?
The following code is my render thread that has been working fine except for the mentioned devices:
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) { }
}
}
}
}
}
The code is, in parts, from the LunarLander
example in the Android SDK, more specifically LunarView.java
.
Updating the code to match the improved example from Android 6.0 (API level 23) yields the following:
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);
}
}
}
}
}
But still, this class does not work on the mentioned devices. I get a black screen and the application stops responding.
The only thing (that I have found) that fixes the problem is adding the System.out.println("123")
call. And adding a short sleep time at the end of the loop turned out to provide the same results:
try {
Thread.sleep(10);
}
catch (InterruptedException e) { }
But these are no real fixes, are they? Isn't that strange?
(Depending on what changes I make to the code, I'm also able to see an exception in the error log. There are many developers with the same problem but unfortunately none does provide a solution for my (device-specific) case.
Can you help?