java - studio - ANR en SurfaceView solo en dispositivos específicos: la única solución es un tiempo de suspensión breve
view en android studio (2)
En mi aplicación de Android, uso un SurfaceView
para dibujar cosas. Ha funcionado bien en miles de dispositivos, excepto que ahora los usuarios comenzaron a informar ANR en los siguientes dispositivos:
- LG G4
- Android 5.1
- 3 GB de RAM
- Pantalla de 5.5 "
- Resolución de 2560 x 1440 px.
- Sony Xperia Z4
- Android 5.0
- 3 GB de RAM
- Pantalla de 5,2 "
- Resolución 1920 x 1080 px
- Huawei Ascend Mate 7
- Android 5.1
- 3 GB de RAM
- Pantalla de 6.0 "
- Resolución 1920 x 1080 px
- HTC M9
- Android 5.1
- 3 GB de RAM
- Pantalla de 5.0 "
- Resolución 1920 x 1080 px
Así que conseguí un LG G4 y de hecho pude verificar el problema. Está directamente relacionado con el SurfaceView
.
Ahora adivina qué solucionó el problema después de horas de depuración? Está reemplazando ...
mSurfaceHolder.unlockCanvasAndPost(c);
... con ...
mSurfaceHolder.unlockCanvasAndPost(c);
System.out.println("123"); // THIS IS THE FIX
¿Cómo puede ser esto?
El siguiente código es mi subproceso de procesamiento que ha funcionado bien, excepto por los dispositivos mencionados:
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) { }
}
}
}
}
}
El código es, en parte, del ejemplo de LunarLander
en el SDK de Android, más específicamente LunarView.java
.
Actualizar el código para que coincida con el ejemplo mejorado de Android 6.0 (nivel de API 23) produce lo siguiente:
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);
}
}
}
}
}
Pero aún así, esta clase no funciona en los dispositivos mencionados. Aparece una pantalla negra y la aplicación deja de responder.
Lo único (que he encontrado) que soluciona el problema es agregar la llamada a System.out.println("123")
. Y agregar un breve tiempo de sueño al final del ciclo resultó proporcionar los mismos resultados:
try {
Thread.sleep(10);
}
catch (InterruptedException e) { }
Pero estas no son soluciones reales, ¿verdad? ¿No es eso extraño?
(Dependiendo de los cambios que realice en el código, también puedo ver una excepción en el registro de errores. Hay muchos developers with the same problem pero desafortunadamente ninguno brinda una solución para mi caso (específico del dispositivo).
¿Puede usted ayudar?
Lo que actualmente funciona para mí, aunque en realidad no soluciona la causa del problema sino que combate los síntomas de manera superficial:
1. Eliminando las operaciones de Canvas
Mi subproceso de procesamiento llama al método personalizado doDraw(Canvas canvas)
en la subclase SurfaceView
.
En ese método, si Canvas.drawBitmap(...)
todas las llamadas a Canvas.drawBitmap(...)
, Canvas.drawRect(...)
y otras operaciones en el Canvas
, la aplicación ya no se congela.
Una sola llamada a Canvas.drawColor(int color)
puede dejarse en el método. E incluso operaciones costosas como BitmapFactory.decodeResource(Resources res, int id, Options opts)
y leer / escribir en mi caché de Bitmap
interno está bien. No se congela.
Obviamente, sin ningún dibujo, el SurfaceView
no es realmente útil.
2. Durmiendo 10ms en el bucle de ejecución del hilo de render
El método que ejecuta mi hilo de render:
@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) { }
}
}
}
}
Simplemente agregando un tiempo de reposo corto dentro del bucle (por ejemplo, al final) se corrige todo el congelamiento en el LG G4:
while (mRunning) {
...
try { Thread.sleep(10); } catch (Exception e) { }
}
Pero quién sabe por qué funciona esto y si realmente soluciona el problema (en todos los dispositivos).
3. Imprimiendo algo a System.out
Lo mismo que funcionó con Thread.sleep(...)
anterior también funciona con System.out.println("123")
, de forma extraña.
4. Retrasando el inicio del hilo de renderizado en 10 ms.
Así es como comienzo mi subproceso de procesamiento desde SurfaceView
:
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mRenderThread = new MyThread(getHolder(), this);
mRenderThread.setRunning(true);
mRenderThread.start();
}
Al ajustar estas tres líneas dentro de la siguiente ejecución demorada, la aplicación ya no se congela:
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
...
}
}, 10);
Esto parece ser porque al principio solo hay un punto muerto presumible. Si esto se borra (con la ejecución diferida), no hay otro interbloqueo. La aplicación funciona bien después de eso.
Pero al salir de la Activity
, la aplicación se congela de nuevo.
5. Solo usa un dispositivo diferente
Aparte de los LG G4, Sony Xperia Z4, Huawei Ascend Mate 7, HTC M9 (y probablemente algunos otros dispositivos), la aplicación funciona bien en miles de dispositivos.
¿Podría ser este un fallo específico del dispositivo? Uno seguramente habría oído hablar de esto ...
Todas estas "soluciones" son hacky. Desearía que hubiera una solución mejor, ¡y apuesto a que la hay!
Mira la traza de ANR. ¿Dónde parece estar atascado? Los ANR significan que el subproceso de la interfaz de usuario principal no responde, por lo que lo que estás haciendo en el subproceso del renderizador es irrelevante a menos que los dos luchen por un bloqueo.
Los síntomas que estás reportando suenan como una raza. Si el subproceso de la interfaz de usuario principal está bloqueado, por ejemplo, mRunningLock
, es posible que el subproceso de renderizador solo lo deje desbloqueado en una ventana muy corta. Agregar el mensaje de registro o la llamada de reposo le da al hilo principal la oportunidad de despertarse y hacer un trabajo antes de que el subproceso de render lo vuelva a atrapar.
(Esto realmente no tiene sentido para mí: parece que su código debería estar parado esperando a que se lockCanvas()
mientras se espera la actualización de la pantalla, por lo que debe ver el seguimiento del hilo en el ANR).
FWIW, no necesita sincronizar en mSurfaceHolder
. Un ejemplo temprano hizo eso, y cada ejemplo desde entonces lo ha clonado.
Una vez que haya resuelto este problema, es posible que desee leer acerca de los bucles de juego .