¿Qué pasó con mi aplicación de Android de subclase? | Pablo Baxter | Agosto 2021
Nota: Los nombres de paquetes y clases personalizados se han cambiado para que sean más genéricos.
De alguna manera, mi subclase Android Application
Provoca el siguiente bloqueo …
Caused by: java.lang.ClassCastException: android.app.Application cannot be cast to com.example.app.MyApp
No tiene ningún sentido.En la clase MyApp
Fue declarado en AndroidManifest.xml
. Una generaciónTonelada Se ejecuta cada vez que la aplicación se inicia en todos mis dispositivos, se ejecuta para más del 95% de los usuarios y getApplication()
Utilizado en fundición. El bloqueo no apareció en Crashlytics, las métricas mostraron que no hubo ningún problema y nadie pudo reproducirlo … Pero es cierto: hay miles de bloqueos por usuario por día en Google Play Console, y el usuario revisiones verifican el accidente. Este bloqueo ha existido por un tiempo, y nadie se dio cuenta hasta que verificamos el bloqueo en Google Play Console …
Si esto no es lo suficientemente malo, más tarde descubrí que este error tiene un gemelo malvado … NullPointerException
Para cualquier llamado MyApp.getInstance()
Debería haber vuelto MyApp
. Sí, estoy creando solteros en onCreate()
Funcion de MyApp
. Sin embargo, de alguna manera, MyApp
Sin inicialización, no sé por qué.
Como hace cualquier ingeniero de software cuando se encuentra con un error completamente sin sentido, he realizado una verificación de integridad de las herramientas que utilizo. Lo primero que hice fue verificar para asegurarme de que no cometí un error en la secuencia de arranque de Android.Entrar Application
Documento, vi …
Crear()
Se llama cuando se inicia la aplicación, antes de crear cualquier actividad, servicio u objeto receptor (sin incluir proveedores de contenido).
Suena bastante bien.
Crashlytics se está inicializando en ContentProvider; la biblioteca de métricas, la instancia de contexto de la aplicación y el gráfico de daga en la aplicación; la expectativa de operación del servicio / receptor está configurada. asi que…
seriamente… ¿que demonios? !
Ahora, he encontrado un error que va en contra del contenido grabado. ¿Es este un error de la plataforma Android o algún error extraño en la aplicación que estoy desarrollando? Bueno, a menos que este error haya existido desde Android 6.0 y afecte a todas las versiones anteriores a Android 10 (aproximadamente 2020), llegaré a la conclusión lógica de que el error se ha resuelto.
Estúpido yo.
Entonces comencé con el método simple: agregar registro.
Sigo moviendo todas las inicializaciones a ContentProvider (porque MyApp
No funciona por alguna razón), agregue algunas métricas adicionales para rastrear más información y espere a que se complete el ciclo de lanzamiento. resultado: Accidente, sin registros.
OK…
Luego comencé a agregar registros en ContentProvider para averiguar por qué no obtuve los registros del servicio / receptor y esperé otro ciclo de publicación. resultado: Crash de nuevo, sin registros.
Extraño…
En este punto, comencé a sentir que era un desarrollador de Android incompetente, así que les pedí a algunos colegas que emparejaran la programación conmigo para asegurarme de que no me perdía nada. Profundizamos un poco más y agregamos algo de lógica de registro personalizada. Le pedimos a otros miembros del equipo que probaran el código y QA probó todo el código. Ahora, estoy esperando otro ciclo de lanzamiento. resultado: Se bloquea, pero no hay registro.
Esto se está poniendo ridículo…
En este momento, han pasado 2 meses y mi salud mental ha comenzado a deteriorarse. Me obsesioné con este error y ahora estoy cuestionando la plataforma Android. Estoy desesperado. Hice un cambio final en ContentProviders … moví todo lo que necesitaba a un nuevo contenido, me aseguré de que se ejecutara con la máxima prioridad, agregué registros y controladores de excepciones no detectados … cualquier cosa Obtenga una pista sobre por qué ocurrió este accidente.El ciclo de liberación de estos cambios se siente como una dilación para siempre. resultado: Accidente, sin registros.
Le dije a mi gerente de ingeniería que iba a ser negro. Me estoy sumergiendo en el proyecto de código abierto de Android en la papelera y no sé cuándo saldré. Me deseó suerte.
Comencé desde donde nació la aplicación: ActivityManagerService. Estoy tratando de entender todas las 20K + líneas de código. Pasé unos días espiando esto y las otras clases que tocaba. Me enteré de que ActivityManagerService está fuera del entorno limitado de la aplicación y envía los comandos necesarios para iniciar la aplicación, las actividades, los servicios y los receptores. Aquí es donde Zygote se bifurca. Este curso me llevó a otra área de la plataforma Android … ActivityThread.
Pasé unos días más entendiendo lo que sucedió con la clase ActivityThread, buscando pistas sobre cómo se inició el servicio o el receptor. adelante Una aplicación (y posiblemente un proveedor). Finalmente encontré algo que me hizo hacer una pregunta muy aguda …
¿Qué es el modo restringido?
¿Encontré algo que evita que el proveedor de contenido se inicialice? Desplazándome rápidamente desde esa parte del código, encontré lo siguiente:
Tengo una pista … ¡Por fin! Pero, ¿qué es el modo «restringido» y qué tiene que ver con la copia de seguridad? ¿Qué tipo de respaldo? Decidí resurgir del bote de basura del proyecto de código abierto de Android y volver directamente al documento, esta vez centrándome en la «copia de seguridad» y el «modo restringido». Esta investigación me llevó al documento de Copia de seguridad automática de Android. En la parte inferior del documento de copia de seguridad, encontré lo siguiente:
Debes estar tomándome el pelo …
Así que ahora tengo un líder confiable en qué área posible Está sucediendo. Pero, ¿cómo pruebo cosas que no ejecutan mi aplicación, mi proveedor o no inician mi actividad? Fue entonces cuando de repente me di cuenta de que no hay nada en este documento sobre el servicio o el destinatario. Desarrollé una prueba y ahora es el momento de implementarla. Creé una aplicación con los siguientes requisitos:
- Se utilizará la subclase de aplicación
- Un receptor externo, solo imprime el nombre de la clase del objeto devuelto por la llamada
getApplicationContext()
- La lista debe incluir
android:allowBackup=true
Realicé una verificación de cordura para asegurarme de que el destinatario imprimiera el nombre de la aplicación de subcategoría, y así fue.
I/System.out: Hi, my name is BlahApp
Luego comencé la copia de seguridad a través de ADB para ejecutar la prueba real. Cuando la copia de seguridad estaba en progreso, activé el receptor para que se ejecutara, y fue realmente … sorprendente.
I/System.out: Hi, my name is Application
Bien, ahora tengo algo para explicar cómo cada usuario puede fallar una o dos veces al día.Pero, ¿qué activa el receptor o el servicio al hacer una copia de seguridad de la aplicación y por qué falla? mil ¿Cuántas veces al día muchos usuarios?
La primera parte de la pregunta es un poco difícil de responder (y probar). Sin embargo, creo que las notificaciones push, alarmas, trabajos y otros intentos externos pueden desencadenarse en cualquier momento, incluso si la aplicación está muerto. Dada la evidencia, tiene sentido que también se activen al realizar copias de seguridad de las aplicaciones.
Básicamente, me topé con la respuesta a la segunda parte de la pregunta. Debido a las copias de seguridad automáticas, quiero que la aplicación se bloquee al menos una vez, solo para restaurar mi cordura.Así que modifiqué la prueba anterior para convertir el resultado getApplicationContext()
llegar BlahApp
Cuando se activa el receptor.Cuando se activó la ejecución de la copia de seguridad, la aplicación se bloqueó ClassCastException
Muchos usuarios de aplicaciones están experimentando.
Por alguna razón (no recuerdo por qué), decidí volver a abrir la aplicación después del bloqueo, eché un vistazo a Logcat y descubrí que faltaba el registro en la subclase de la aplicación.La aplicación funciona bien, pero solía ser Solo una actividad simple con un botón. Puedo escribirlo como algo así como «registro despejado», pero mi cordura está amenazada. Entonces decidí modificar la actividad para imprimir el nombre de la aplicación en la vista, y lo hizo: BlahApp
La aplicación de inicio de sesión también se imprime normalmente. Así que volví a ejecutar la prueba rota: activé la copia de seguridad, activé el receptor y vi que la aplicación fallaba. Luego volví a abrir la aplicación y la vista activa mostró: Application
.
¿Esperar lo?
Cuando se abrió la actividad, activé el receptor nuevamente: ClassCastException
.
Reabrí la aplicación y verifiqué la vista de actividad: Application
.
Modifiqué el evento para emitir getApplication()
llegar BlahApp
Y vuelva a ejecutar la prueba: active la copia de seguridad, active el receptor y vea cómo se bloquea la aplicación. Reabrí la aplicación: ClassCastException
. Reabrí: ClassCastException
Es posible que haya estado sentado allí durante unos 5 minutos, haciendo clic en el icono de la aplicación y luego viendo a Logcat imprimir el mismo rastro de pila una y otra vez.
Si la aplicación falla en modo restringido, se reiniciará en modo restringido … ¡incluso si el usuario inicia la aplicación!
Una vez que se calmó el impacto que descubrí, comencé a relacionar lo que había presenciado con otros problemas. Inmediatamente volví a los comentarios de la aplicación, las viejas entradas de Jira, etc. Estoy buscando algo sobre el cierre forzado, «La aplicación no se puede abrir», «No pasa nada cuando se hace clic en el icono de la aplicación» o básicamente cualquier cosa que no se refiera a fallos. Descubrí que hay tantos votos sobre estos problemas como tickets sobre bloqueos cuando se inicia la aplicación (o cuando la aplicación está en segundo plano).
Vuelvo a la base del código para ver dónde lanzamos la aplicación o usamos un contexto estático … En todas partes! Esta pregunta comienza a explicar todos los bloqueos que encontramos en la clase Aplicación. Le conté a mi gerente sobre este conocimiento e inmediatamente comenzamos a difundir nuestro conocimiento sobre este tema. Este problema tiene mayor prioridad. Una investigación más profunda muestra que este problema en realidad explica otros problemas que nos han afectado a muchos de nosotros en el pasado. Las copias de seguridad causan más problemas de los que creemos.
Finalmente me di cuenta de lo que pensé que podría resolver este problema:
android:allowBackup=false
Ahora hemos estado esperando otro ciclo de lanzamiento, y parece que ha llevado mucho tiempo … de nuevo. resultado: sin chocarse. Ninguno de ellos se debe a ClassCastException
Causado por la aplicación.Esta NullPointerException
El choque también desapareció. También desapareció la denuncia por el cierre forzoso de la aplicación.
La etiqueta AndroidManifest, la predeterminada es true
, Me tomó más de 3 meses investigar, lo que generó muchas preguntas a los usuarios de la aplicación. P̶r̶o̶b̶a̶b̶l̶y definitivamente me hizo sentir culpable.