记录大量Android传感器数据

我有一个 Android应用程序,它以大约100Hz的速度记录了几个Android传感器.因此,如果我正在记录10个传感器,我每秒向文件写入大约3000个数据点(每个传感器通常有3个条目).现在的问题是,我希望尽量减少这种写作对应用程序其余部分的影响.具体来说,我不希望日志记录减慢事件传递…我想确保我一旦发生事件就会收到事件,而不是延迟(我知道总会有一些延迟,因为Android不是实时和因为传感器事件框架的“拉”性质.

接下来,我将概述我的方法,该方法看起来效果不佳.我想提出如何改进的建议.

我目前的程序是……

对于每个传感器,我创建一个单独的线程,其中包含要记录的BlockingQueue事件.在线程内部,我有一个从队列中拉出来的while循环,并使用缓冲的writer编写文件.当传感器管理器传递新的SensorEvent时,事件将被放入适当的队列中(从而触发另一个线程上的文件IO),以免延迟传递SensorEvent的主线程.

我想在事件发生时立即获取事件,因此重要的是我不要在Sensor框架中引入任何延迟.例如,如果我直接在onEvent回调中执行了文件IO,那么我担心事件可能会开始堆积在管道中,并且它们在最终交付时会过时.上述方法减轻了这些担忧.

但还有另一个问题……

尽管文件IO发生在传感器事件传递线程之外,但有时应用程序仍然感觉迟钝.也就是说,有时我会看到事件快速连续发生(例如,在彼此的1毫秒内传递5个事件).这表明尽管传感器传递线程上没有发生IO,但传递线程仍然会延迟.有人向我提出了一些理由:

>我创建了太多的IO线程.也许如果我把所有的写入都推到一个线程中,我会增加新事件进入时传感器传递线程处于活动状态的可能性.在当前设置中,可能是所有活动线程都用于文件当事件进入时IO,导致事件备份直到其中一个写入事件结束.
>目前,我使用的是平面文件输出,而不是数据库.使用数据库进行检索的好处对我来说很清楚.我不清楚的是,如果我只是将数据附加到文件中,我是否还应该期望数据库更快……也就是说,我永远不需要从文件中读取数据或将数据插入到随机位置,我只是附加到文件的末尾.在我看来,在这种情况下,数据库不能比标准文件IO快.还是我错了?
>其他人建议垃圾收集器可能会干扰我的线程,问题的可能来源是内存抖动,因为正在创建大量事件.

我应该从哪个角度来看待这个?

编辑:

以下是我用来将字符串写入文件的类.其中一个是按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;
    }

}

最佳答案 在你附上一个分析器并看看究竟发生了什么之前,这一切都是猜测.根据一般经验,我会说第1点和第3点绝对有效.点2不那么;如果数据库比附加到文件更快,我认为这可能取决于实现质量和/或使用本机API的C,你应该能够克服这些差异.

关于GC负载,请查看Looper(api)和Handler(api).使用这些可以将基于BlockingQueue的方法替换为根本不产生GC负载的方法.我写了一篇blog post,详细探讨了这一点.

此外,如果您正在使用任何/所有清洁代码实践,可能是时候明智地使用脏代码(字段访问,可变性)来减轻内存流失.

关于IO线程:我会说明确缩小到单个线程,并尝试将这些行分批写入文件,而不是一个接一个地写入.只要你避免显式调用flush(),缓冲的Stream或Writer就可以独自完成这个技巧.

点赞