Registro de muchos datos de los sensores de Android

Tengo una aplicación de Android que registra varios sensores de Android en aproximadamente 100Hz. Así que si estoy registrando 10 sensores, estoy escribiendo unos 3000 puntos de datos por segundo a un archivo (cada sensor normalmente tiene 3 entradas). Ahora el problema es que quiero minimizar el efecto de esta escritura en el resto de la aplicación. En concreto, no quiero que el registro de ralentizar la entrega de eventos … quiero asegurarme de que estoy recibiendo los eventos tan pronto como sucedan, y no con un retraso (sé que siempre habrá algún retraso porque Android no es En tiempo real y debido a la naturaleza "pull" del marco de eventos Sensor).

A continuación, describiré mi enfoque, que no parece estar funcionando bien. Me gustaría sugerencias para mejorar.

Mi procedimiento actual es este …

Para cada sensor, creo un hilo separado con un BlockingQueue de eventos para registrar. Dentro del hilo, tengo un bucle de tiempo que tira de la cola y hace el archivo de escritura con un escritor almacenado en búfer. Cuando el gestor de sensores entrega un nuevo SensorEvent, el evento se pone en la cola apropiada (desencadenando así el archivo IO en el otro hilo) para no retrasar el hilo principal en el que se entregan los SensorEvents.

Quiero estar recibiendo los eventos tan pronto como ocurren, por lo que es importante que no introduzca ningún retraso en el marco de Sensor. Si, por ejemplo, hice el archivo IO directamente en la devolución de llamada onEvent, entonces me preocupa que los eventos puedan comenzar a acumularse en la tubería y que luego estarían obsoletos en el momento en que finalmente se entregan. El enfoque anterior mitiga estas preocupaciones.

Pero hay otro problema …

A pesar de que el archivo IO ocurre fuera del hilo de entrega del evento del sensor, a veces la aplicación todavía se siente lenta. Es decir, a veces voy a ver eventos ocurren en sucesión rápida (por ejemplo, 5 eventos se entregan dentro de 1 ms el uno del otro). Esto indica que, aunque el IO no está ocurriendo en el hilo de entrega del sensor, el hilo de entrega sigue recibiendo retraso. Se me han sugerido algunas razones:

  1. Estoy creando demasiados hilos de IO. Quizás si empujé toda la escritura a un solo hilo aumentaría la probabilidad de que el hilo de entrega del sensor esté vivo cuando llegue un nuevo evento. En la configuración actual, podría ser que todos los subprocesos activos se estén utilizando para el archivo IO cuando entra un evento, dando como resultado los eventos de copia de seguridad hasta que uno de los eventos de escritura termina.

  2. Actualmente, estoy usando la salida plana del archivo, no una base de datos. Los beneficios de usar una base de datos para la recuperación son claros para mí. Lo que no está claro para mí es si también debería esperar una base de datos a ser más rápido si sólo estoy añadiendo datos a un archivo …. es decir, nunca necesito leer del archivo o insertar los datos en un lugar al azar, Acabo de añadir literalmente al final del archivo. Me parece que una base de datos no puede ser más rápido que el archivo estándar IO en ese caso. ¿O estoy equivocado?

  3. Otros han sugerido que el recolector de basura está probablemente interfiriendo con mis hilos y que la fuente probable del problema es golpear de memoria debido al gran número de acontecimientos que se están creando.

¿De qué ángulo debo acercarme a esto?


Editar:

La siguiente es la clase que estoy usando para escribir cadenas de archivos. Uno de ellos se crea por tipo SensorEvent.

package io.pcess.utils; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; /** * This type of File Writer internally creates a low-priority {@link Thread} * which queues data to be written to a specified file. Note that the * constructor starts the {@link Thread}. Furthermore, you can then append a * {@link String} to the end of the specified file by calling * * <pre> * fileWritingThread.append(stringToAppend); * </pre> * * Finally, you should tidy up by calling * * <pre> * fileWritingThread.close(); * </pre> * * which will force the writer to finish what it is doing and close. Note that * some {@link String}s might be left in the queue when closing, and hence will * never be written. */ public class NonblockingFileWriter { /** * --------------------------------------------- * * Private Fields * * --------------------------------------------- */ /** The {@link Thread} on which the file writing will occur. */ private Thread thread = null; /** The writer which does the actual file writing. **/ private BufferedWriter writer = null; /** A Lock for the {@link #writer} to ensure thread-safeness */ private final Object writerLock = new Object(); /** {@link BlockingQueue} of data to write **/ private final BlockingQueue<String> data = new LinkedBlockingQueue<String>(); /** Flag indicating whether the {@link Runnable} is running. **/ private volatile boolean running = false; /** * The {@link Runnable} which will do the actual file writing. This method * will keep writing until there is no more data in the list to write. Then * it will wait until more data is supplied, and continue. */ private class FileWritingRunnable implements Runnable { @Override public void run() { try { while (running) { String string = data.take(); synchronized (writerLock) { if (writer != null) { writer.write(string); } } } } catch (Exception e) { e.printStackTrace(); } finally { close(); } } }; /** * --------------------------------------------- * * Constructors * * --------------------------------------------- */ public NonblockingFileWriter(String filename) { this(new File(filename)); } public NonblockingFileWriter(File file) { writer = createWriter(file); if (writer != null) { running = true; } thread = new Thread(new FileWritingRunnable()); thread.setPriority(Thread.MIN_PRIORITY); thread.start(); } /** * --------------------------------------------- * * Public Methods * * --------------------------------------------- */ /** Append the specified string to the file. */ public void append(String string) { try { data.put(string); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } /** * Close the {@link BufferedWriter} and force the {@link Thread} to stop. */ public void close() { running = false; try { synchronized (writerLock) { if (writer != null) { writer.close(); writer = null; } } /** * This string will not be written, but ensures that this Runnable * will run to the end */ data.put("Exit"); } catch (Exception e) { e.printStackTrace(); } } /** * Create a {@link BufferedWriter} for the specified file. * * @param file * @return */ private BufferedWriter createWriter(File file) { BufferedWriter writer = null; if (!file.exists()) { try { file.getParentFile().mkdirs(); file.createNewFile(); } catch (IOException e) { e.printStackTrace(); return writer; } } if (file.canWrite()) { boolean append = true; try { synchronized (writerLock) { writer = new BufferedWriter(new FileWriter(file, append)); } } catch (IOException e) { e.printStackTrace(); } } return writer; } } 

Es toda la conjetura hasta que ate un profiler y vea qué sucede realmente. De la experiencia general diría que los puntos 1 y 3 son definitivamente válidos. Punto 2 menos; Si una base de datos es más rápido que añadir a un archivo creo que es probable que hasta la calidad de la implementación y / o el uso de C con la API nativa y usted debe ser capaz de superar la diferencia.

Con respecto a la carga de GC, eche un vistazo a Looper ( api ) y Handler ( api ). Utilizando estos, puede reemplazar su enfoque basado en BlockingQueue por uno que no genera carga GC en absoluto. Escribí una entrada en el blog explorando esto con algún detalle.

Además, si está usando alguna o todas las prácticas del Código Limpio, puede ser hora de que se utilice juiciosamente el código sucio (acceso al campo, mutabilidad) para aligerar el churn de memoria.

Respecto a los subprocesos IO: diría que sin duda se reduce a uno solo, y tratar de escribir las líneas de archivo en lotes, no uno por uno. Un Stream o un Writer almacenado en búfer haría probablemente el truco por sí solo, siempre y cuando evite llamar explícitamente a flush() .

El tuyo es un tema que apenas se puede evaluar si no se prueba, pero hago algunas sugerencias:

  • Para usar un solo hilo puedes usar un Handler y un Looper como lo propuso Barend y cuando tengas un nuevo sensor leyendo un mensaje se envía al Handler, por lo que existe la posibilidad de borrar mensajes antiguos cuando tengas varias lecturas pendientes de El mismo sensor, que añade algún tipo de protección contra el desbordamiento, si el sistema no puede procesar todas las lecturas se perderán algunas.

  • Como usted dice Android no es en tiempo real, si los sensores no se leen en horas regulares tal vez debería adjuntar una marca de tiempo a cada lectura.

  • Para evitar asignar y GC'ing variables u objetos para cada lectura de sensores, puede utilizar una agrupación de variables asignadas previamente y cada vez que tenga una lectura, utilice el siguiente índice. Esto estaría bien para el caso de enviar mensajes porque sólo tendría que enviar el índice de la nueva lectura en los argumentos del mensaje.

Espero que ayude.

https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html#toByteArray ()

El uso de arriba ahorrará lo suficiente en IO para que funcione el código del sensor. He visto androide hacer mayores tasas de bits de grabación de vídeo. No debería haber un problema al escribir un archivo. 3000 elementos por segundo Generoso 20 bytes por fila 480kbs.

Ponga su código en un servicio que seguirá funcionando cuando la aplicación va al fondo mientras el usuario comprueba instagram o va buscando pokemon o el usuario presiona el botón de inicio.

GC no GC evitar objetos finales? IDK. Tal vez me equivoque, pero sólo debe crear objetos complejos una vez. El servicio permanece en la memoria en ejecución. no es un problema.

Cinco en un ms. Esto es normal para el código asicrónico. Es la naturaleza de la multitarea. El programa tuvo cinco eventos en cola y los procesó. Usted tiene que dar prioridad a las tareas, pero no poner demasiado en su priorización o que causará problemas. Por ejemplo, si el archivo de escritura no tiene suficiente prioridad, es posible que la escritura del archivo nunca suceda. Por ahora mantenga todas las tareas en la misma prioridad hasta que entienda la salida del generador de perfiles.

Escribir en el archivo. Tal vez su pasando por encyption y en la SDCard que sería malo y tomaría todo tipo de CPU y causar aún más problemas.

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