2013-11-21 13 views
0

我正在寫一個應用程序,它將各種傳感器(一個位置)和設備特定元數據記錄到一個文件,然後將文件傳輸到服務器。它不是後臺服務 - 應用程序只需在應用程序處於活動狀態時編寫和傳輸文件(無需設置警報即可喚醒服務)。每次調用onLocationChanged()時(雖然位置數據不是唯一正在寫入的數據),或者至少以調用onLocationChanged()時的類似速率,我想在每次調用onLocationChanged()時向該文件寫入一行。 onLocationChanged()目前正在被調用一次/秒,但我們可能會以更高的速率(可能2-3x /秒)錄製數據。這似乎是相當數量的I/O內部記憶。Android如何在週期性寫入內部存儲器上的文件時避免I/O問題

我目前有一切工作(概念證明),但我需要改進我用來寫入文件的方法。每次調用onLocationChanged()時,我正在向該文件寫入一行,這可能不明智,並且似乎導致I/O堆疊。我讀過其他類似的問題,這些問題涉及各種方法(新線程,鬧鐘,計時器任務,處理程序等),但我無法找到特定於我正在嘗試做的答案。我還考慮過其他方法,例如緩存/緩存數據,並且僅以較低的頻率(每10秒?)寫入內部存儲器,或者可能寫入SQLite數據庫並稍後導出到文件。我能做些什麼來使傳感器代碼中的文件代碼最佳解耦(假設這是我需要做的),並確保及時更新文件?我還需要確保將所有數據寫入文件。

UPDATE: 我結束了使用的解決方案涉及附加到一個StringBuilder的行的一組數(每一個呼叫行onLocationChanged())。在追加了10行數據,有效緩衝後,我將StringBuilder交給AsyncTask,數據寫入文件。

回答

0

建議在單獨的線程上執行所有文件I/O。您可以設置一個生產者/消費者結構,其中生產者是生成對隊列的寫入請求的UI線程,而消費者是一個工作線程,當寫入請求排隊並喚醒文件/數據庫/其他時,喚醒該工作線程。

這將起作用,除非您對「及時更新」的定義相當嚴格。但是,由於您正在考慮排隊等待10秒,我的猜測是這不是問題。

編輯: 要處理可能備份的隊列,您應該安排消費者處理隊列中的所有內容,只要它醒來。如果I/O根本無法跟上,即使採用這種批處理方式,您可以安排隊列在達到最大尺寸時放下元素。您可能還需要降低調用onLocationChanged()的頻率。

+0

- 當然,一個線程可能是實現的一部分 - 但是沒有一個「應該嘗試做什麼」的問題得到解決 - 只要將「可用數據」觸發寫入後臺線程,對除UI響應之外的其他任何內容的影響都將最小。 –

+0

我應該澄清 - 如果可以避免這種情況,我寧願不要緩衝/排隊寫入10秒 – rsg

+0

@rsg - 如果數據生成速度比I/O處理速度快,那麼某種緩衝是需要(或者你需要願意放棄數據,或者兩者兼而有之)。大約10秒沒有什麼神奇的;據推測數據可以寫得比這更快。但是,您需要處理容量不匹配問題。我在這個答案中加入了一些想法。 –

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的提示(將其更改爲自己的dataholder對象或簡單字符串)服務器。

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(); 
} 

希望這可以讓你在這似乎並沒有回答這個問題,實際的方式

+0

謝謝@cYrixmorten這是有幫助的,類似於我想要做的。如果你不介意的話,我會關注你的代碼,但有幾個問題:你在哪裏調用'FileOutputWriter.writeData()' - 你是否在你的主要活動中調用了onLocationChanged()方法? 'DataLogger.addEntry()'同樣的問題?我可能有點困惑,因爲我沒有看到你在'onLocationChanged()'中做了什麼。 – rsg

+0

嗯,你是對的,我沒有在我的代碼中包含它。原因是我們有一些不同的目的不同的設置。但是,我認爲對於你的應用程序在onLocationChanged()中做這兩件事情是有道理的。 – cYrixmorten

+0

謝謝 - 我的主要問題與避免寫入期間的文件I/O問題有關,但示例代碼有助於思考線程上載過程。 – rsg

相關問題