Android如何在周期性地写入内部存储器上的文件时避免i / o问题

时间:2013-11-21 19:40:00

标签: java android file-io io

我正在编写一个应用程序,它将各种传感器(一个位置)和设备特定元数据记录到文件中,然后将文件传输到服务器。它不是后台服务 - 应用程序只需要在应用程序处于活动状态时编写和传输文件(无需设置警报以唤醒服务)。我想在每次调用onLocationChanged()时向文件写一行(尽管位置数据不是唯一被写入的数据) - 或者至少以与onLocationChanged()被调用的速率相似的速率。 onLocationChanged()目前被调用一次/秒,但我们最终可能会以更高的速率(可能是2-3x /秒)记录数据。这似乎是对内部存储器的大量i / o。

我目前一切正常(概念证明),但我需要改进我用来写入文件的方法。每次调用onLocationChanged()时,我都在向文件写一行,这可能不明智,似乎导致i / o堆叠。我已经阅读了其他类似的问题,涉及各种方法(新线程,警报,计时器任务,处理程序等),但我无法找到特定于我正在尝试做的答案。我还考虑过其他方法,比如缓存/缓冲数据,只是频繁地写入内部存储(每10秒钟?),或者可能写入SQLite数据库并稍后导出到文件。我可以做些什么来最好地解耦(假设我需要做的)传感器代码中的文件代码并确保及时更新文件?我还需要确保将所有数据写入文件。

更新: 我最终使用的解决方案涉及向StringBuilder附加一定数量的行(每次调用onLocationChanged()一行)。在追加10行数据,有效缓冲后,我将StringBuilder交给AsyncTask,数据写入文件。

2 个答案:

答案 0 :(得分:2)

在普及的定位课程中,情况有些类似。

以下是我们的所作所为:

<强> FileWriter的 写入文件部分对我们来说不是问题,因为我们只收集GPS位置而不是其他传感器数据。对我们来说,下面的实现就足够了。目前尚不清楚是否必须将数据写入同一文件,如果没有,那么您应该可以直接使用它。

public class FileOutputWriter {


private static String pathString = "";
    private static String sensorString = "";

public static void setPath(Context context, String path) {
    pathString = path;
    File pathFile = new File(pathString);
    pathFile.mkdirs();
}


public static void writeData(String data, String sensor, boolean append) {

    File file = new File(pathString + "/" + sensor+ ".log");

    long timeStamp = System.currentTimeMillis();

    BufferedWriter out = null;
    try {
        out = new BufferedWriter(new FileWriter(file, append));
            out.write(timeStamp + ":" + data);

        out.newLine();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
}

<强>上传 要将数据上传到服务器,我们创建一个LogEntries提示(将其更改为您自己的数据持有者对象或简单地称为String)。

public class DataLogger {

static Vector<LogEntry> log = new Vector<LogEntry>();


public static void addEnty(LogEntry entry) {
    Log.d("DEBUG", entry.getStrategy() + " added position to logger " + entry.getLocation());
    log.add(entry);
}

public static Vector<LogEntry> getLog() {
    return log;
}

public static void clear() {
    log.clear();
}
}

请注意,Vector是线程安全的。

最后我们实现了一个UploaderThread,负责定期检查DataLogger提示并上传添加的条目。

public class UploaderThread extends Thread {


public static LinkedList<String> serverLog = new LinkedList<String>();


Boolean stop = false;
Context c;



public UploaderThread(Context c) {
    this.c = c;
}

public void pleaseStop() {
    stop = true;
}

@Override
public void run() {
    while(!stop) {

        try {               
            if(DataLogger.log.size() > 0 && Device.isOnline(c)) {
                while(DataLogger.log.size() > 0) {

                    LogEntry logEntry = DataLogger.getLog().get(0);
                    String result = upload(logEntry);
                    serverLog.add("("+DataLogger.log.size()+")"+"ServerResponse: "+result);

                    if(result != null && result.equals("OK")) {
                        DataLogger.getLog().remove(0);
                    } else {
                        Thread.sleep(1000);
                    }
                }
            } else {
                serverLog.add("Queue size = ("+DataLogger.log.size()+") + deviceIsonline: "+Device.isOnline(c));
            }

            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

private String upload(LogEntry entry) {
    HttpClient httpclient = new DefaultHttpClient();
    HttpPost httppost = new HttpPost("http://yoururl/commit.php");      

    try {
        // Add your data
        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
        nameValuePairs.add(new BasicNameValuePair("tracename",sessionName +"-"+ entry.getStrategy().toString()));
        nameValuePairs.add(new BasicNameValuePair("latitude", Double.toString(entry.getLocation().getLatitude())));
        nameValuePairs.add(new BasicNameValuePair("longitude", Double.toString(entry.getLocation().getLongitude())));
        nameValuePairs.add(new BasicNameValuePair("timestamp", Long.toString(entry.getTimestamp())));

        httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));

        // Execute HTTP Post Request
        HttpResponse response = httpclient.execute(httppost);
        if(response != null) {
            InputStream in = response.getEntity().getContent();
            String responseContent = inputStreamToString(in);

            return responseContent;
        }
    } catch (ClientProtocolException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }       


    return null;
}


private String inputStreamToString(InputStream is) throws IOException {
    String line = "";
    StringBuilder total = new StringBuilder();

    // Wrap a BufferedReader around the InputStream
    BufferedReader rd = new BufferedReader(new InputStreamReader(is));

    // Read response until the end
    while ((line = rd.readLine()) != null) { 
        total.append(line); 
    }

    // Return full string
    return total.toString();
}

}

该线程只是在您应用的第一个活动中启动:

    UploaderThread ut;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    FileOutputWriter.setPath(this, Environment.getExternalStorageDirectory()
            .getAbsolutePath());

    ut = new UploaderThread(this);
    ut.start();
}

@Override
protected void onDestroy() {
    super.onDestroy();
    ut.pleaseStop();
}

希望这能让你顺利上路

答案 1 :(得分:0)

建议在单独的线程上执行所有文件I / O.您可以设置生产者/消费者结构,其中生产者是生成对队列的写入请求的UI线程,而消费者是在写入请求排队时唤醒并更新文件/数据库/任何内容的工作者线程。

除非您对“及时更新”的定义相当严格,否则这将有效。但是,由于您正在考虑排队等待10秒钟,我的猜测是这不是问题。

编辑: 要处理可能备份的队列,您应该安排使用者在唤醒时处理队列中的所有内容。如果I / O根本无法跟上,即使使用这样的批处理,您也可以安排队列在达到最大大小时删除元素。您可能还需要降低调用onLocationChanged()的频率。