En una aplicación React Native JavaScript, ¿por qué cambiaría el comportamiento de Android GC si creaba una variable temporal en lugar de devolver un valor directamente?

Me siento como que podría haber un error en algún lugar en el Android GC, JavaScriptCore, o tal vez Genymotion. Estoy probando una aplicación React Native en Android, por lo que todo mi código está escrito en JavaScript (no en Java). Estoy tratando de averiguar un accidente que he reproducido en los siguientes emuladores de Android:

  • Samsung Galaxy S6 – 6.0.0 – API 23
  • Google Nexus 5X – 7.1.0 – API 25

Me di cuenta de que mi aplicación siempre se estrellaba después de usarlo durante unos 5 minutos. Después de mirar los registros con adb logcat , me di cuenta de que siempre se estrellaba inmediatamente después de una ejecución de GC. Sólo se estrelló en Android, y el choque nunca ha ocurrido en iOS. También estaba ocurriendo cuando "Debug JS Remotely" estaba apagado, y me di cuenta de por qué. Es porque cuando "Debug JS Remotely" está habilitado, todo el JS se está ejecutando dentro de Chrome en mi computadora portátil, en el motor V8 . (Que hizo esto muy difícil de depurar!)

Estoy utilizando la biblioteca reselect , pero estoy usando una rama con un "cacheSize" configurable para almacenar resultados múltiples, en vez de poner en caché un solo resultado. Puedes ver mi nueva función defaultMemoize aquí . Mi primer pensamiento es que he hecho algo mal aquí, pero no estoy usando un WeakMap o algo así. Sólo estoy almacenando los resultados almacenados en caché en una matriz normal, y siempre estoy sosteniendo una referencia al selector, por lo que no creo que la GC debe borrar la memoria.

Voy a describir algunos detalles más sobre mi aplicación. Tengo una clase LookupTable , que hace referencia a una clase Matrix . La tabla de búsqueda pre-calcula algunas cosas para realizar búsquedas más rápidas.

Mi código de la tabla de búsqueda se parece a esto:

 // @flow import autobind from 'autobind-decorator' export default class LookupTable { matrix: Matrix constructor(state) { this.generateLookupTable(state) } @autobind valueAt(x: number, y: number) { this.matrix.get(x, y) } @autobind generateLookupTable() { // generates the Matrix at this.matrix } } 

Mi código de selector original era así:

 import Immutable from 'immutable' import { createSelectorCreator, defaultMemoize } from 'reselect' const createImmutableSelector = (cacheSize = 1, ...args) => createSelectorCreator(defaultMemoize, Immutable.is, cacheSize)(...args) export const lookupTableSelector = createImmutableSelector(3, firstSelector, secondSelector, thirdSelector, fourthSelector, (one, two, three, four) => new LookupTable(one, two, three, four)) 

Si no estás familiarizado con reselect o immutable-js , sólo estoy haciendo una nota de algunos cálculos (similar a memoize de memoize ), y estoy usando Immutable.is para comprobar la igualdad de argumentos.

Finalmente, yo llamaría al selector de esta manera:

 const lookupTable = lookupTableSelector(state) const value = lookupTable.valueAt(x, y) 

Después de que se ejecute la GC de Android, valueAt estaba causando un bloqueo, porque this.matrix estaba indefinido. Curiosamente, la función lookupTable.generateLookupTable también fue indefinida. De hecho, todo en la instancia era indefinido. Es como si el selector estuviera devolviendo un shell de una instancia de LookupTable , donde todos los datos y métodos habían desaparecido.

Así que aquí está mi intento de una solución:

 export const lookupTableSelectorWithoutGCFix = createImmutableSelector(3, firstSelector, secondSelector, thirdSelector, fourthSelector, (one, two, three, four) => new LookupTable(one, two, three, four)) export const lookupTableSelector = (gameState: Map) => { const lookupTable = lookupTableSelectorWithoutGCFix(gameState) if (lookupTable.matrix == null) { console.warn('LookupTable selector returned an instance with an undefined matrix.' + 'This might be a GC bug on Android. Will clear the cache and generate a new instance.') lookupTableSelectorWithoutGCFix.clearCache() return lookupTableSelectorWithoutGCFix(gameState) } return lookupTable } 

Ahora lo extraño es que este código nunca se llama realmente. Nunca veo la advertencia de la consola en adb logcat , y nunca aparece como un cuadro amarillo en el emulador. Antes de esto, el accidente estaba ocurriendo después de cada ejecución del GC. Pero después de este cambio, probé la aplicación a través de 5 GC, y todo funcionó perfectamente. Así que lo que hice aquí, supongo que cambió el comportamiento de la GC de Android, y ya no es borrar la memoria de mis instancias LookupTable .

¿Puedes ver una razón por la cual esto estaría sucediendo? ¿O es esto probablemente un error de conteo de referencia en el GC de Android?

FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.