我有一个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;
}
}
答案 0 :(得分:2)
在你附上一个分析器并查看真正发生的事情之前,这一切都是猜测。根据一般经验,我说第1点和第3点绝对有效。点2不那么;如果数据库比附加到文件更快,我认为这可能取决于实现质量和/或使用本机API的C,你应该能够克服这些差异。
关于GC加载,请查看Looper
(api)和Handler
(api)。使用这些方法,您可以将基于BlockingQueue
的方法替换为根本不产生GC负载的方法。我写了一篇blog post来详细探讨这一点。
此外,如果您正在使用任何/所有清洁代码实践,可能是时候明智地使用脏代码(字段访问,可变性)来减轻内存流失。
关于IO线程:我说肯定会缩小到单个线程,并尝试将这些行分批写入文件,而不是逐个写入。只要您避免显式调用flush()
,缓冲的Stream或Writer就可以自行完成这个技巧。
答案 1 :(得分:0)
如果未经测试,您的主题很难评估,但我提出了一些建议:
要使用单个主题,您可以使用 Barend 建议的处理程序和循环,当您有新主题时读取消息的传感器发送到此处理程序,因此当您有多个相同传感器的待处理读数时,可以删除旧消息,如果系统无法处理所有读数,则会添加某种防溢保护有些人会迷路。
如你所说Android不是实时的,如果传感器没有定时读取,也许你应该为每次阅读附上一个时间戳。
避免分配&amp;读取每个传感器的GC变量或对象可以使用先前分配的变量池(数组),每次读取时都使用以下索引。这对于发送消息的情况很好,因为它只需要在消息参数中发送新读数的索引。
我希望它有所帮助。
答案 2 :(得分:0)
https://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html#toByteArray()
使用上面的内容可以节省足够的IO以使您的传感器代码正常工作。我见过android做更高的比特率录制视频。编写文件不应该是一个问题。每秒3000个元素每行大约20个字节480kbs。
将您的代码放入服务中,当用户检查Instagram或寻找口袋妖怪或用户按下主页按钮时应用程序进入后台时它将保持运行。
GC不会GC避免最终对象吗? IDK。也许我错了,但你应该只创建一次复杂的对象。该服务保持在内存中运行。不是问题。五分之一毫秒。这对于asycronous代码来说是正常的。这是多任务处理的本质。程序有五个排队的事件并处理它们。您必须优先处理任务,但不要过多地考虑优先级,否则会导致问题。例如,如果写入文件没有足够的优先级,则文件写入可能永远不会发生。现在让所有任务保持相同的优先级,直到你理解了探查器输出。
写入文件。也许它会通过加密进入SDCard,这会很糟糕,会占用各种CPU并导致更多问题。