2017-04-26 73 views
4

我有一個Android應用程序,它以大約100Hz的頻率記錄幾個Android傳感器。因此,如果我記錄10個傳感器,我每秒鐘向文件寫入約3000個數據點(每個傳感器通常有3個條目)。現在的問題是,我想盡量減少這種寫作對應用程序其餘部分的影響。具體來說,我不希望日誌記錄會減慢事件發送速度......我想確保我在事件發生後立即獲取事件,而不是延遲(我知道總會有一些延遲,因爲Android不是實時以及由於傳感器事件框架的「拉」性質)。記錄大量的Android傳感器數據

接下來,我將概述我的方法,該方法看起來不太好。我想提出如何改進的建議。

我現在的程序是這樣的......

對於每一個傳感器,我創建一個事件日誌的BlockingQueue的一個單獨的線程。在線程內部,我有一個從隊列中拉出來的while循環,並使用緩衝寫入器寫文件。當傳感器管理器傳遞一個新的SensorEvent時,該事件將放入適當的隊列中(從而觸發另一個線程上的文件IO),以便不延遲SensorEvents傳遞的主線程。

我希望在事件發生後立即獲取事件,所以重要的是我不會在傳感器框架中引入任何延遲。例如,如果我在onEvent回調中直接執行了文件IO,那麼我擔心事件可能開始堆積在流水線中,並且他們在最終交付時會過期。上述方法緩解了這些擔憂。

但是還有另外一個問題...

儘管IO時關閉傳感器事件傳遞線程的文件,有時應用程序仍然感覺呆滯。也就是說,有時我會看到事件快速連續發生(例如5個事件在彼此的1ms內傳遞)。這表明雖然傳感器傳送線程上沒有發生IO,但傳送線程仍然延遲。有幾個原因給我建議:

  1. 我創建了太多的IO線程。也許如果我把所有的寫入都推到一個單獨的線程中,我會在新的事件進入時增加傳感器傳送線程處於活動狀態的可能性。在當前的設置中,可能是所有活動線程都被用於文件IO當事件進入時,導致事件備份,直到其中一個寫事件結束。

  2. 目前,我使用的是平面文件輸出,而不是數據庫。使用數據庫進行檢索的好處對我來說很明顯。我不清楚的是,如果我只將數據附加到文件中,我是否也應該期望數據庫速度更快......也就是說,我從不需要從文件讀取數據或將數據插入隨機地點,我只是從字面上追加到文件的末尾。在我看來,在這種情況下,數據庫不能比標準文件IO更快。或者我錯了?

  3. 其他人建議垃圾收集器可能會干擾我的線程,並且問題的可能來源是由於正在創建的大量事件導致的內存抖動。

從哪個角度我應該接近這個?


編輯:

以下是我使用的字符串寫入到文件中的類。其中一個是根據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

你正在寫入單個文件還是多個? – Pavlus

+0

我正在寫入多個文件。我的想法是,如果我試圖將所有內容轉儲到同一個文件,我會創建一個瓶頸。或者那是錯的? –

+1

使用更少的文件和更少的線程通常更高效,請嘗試使用輪詢線程寫入來自'ArrayBlockingQueue'的單個緩衝流並使用數據持有者的池,onEvent - 將數據從池中複製到持有者並將其放入隊列在輪詢線程中寫入持有者的數據並將其返回給池。採用這種方法,我已經設法在雙核設備上每秒接收4k 1442字節的數據包,並且具有足夠的性能 – Pavlus

回答

2

這是所有的猜測,直到你附上一個探查器,看看真的發生了什麼。從一般經驗來看,我認爲第1點和第3點絕對有效。第2點不如此;如果數據庫比追加文件快,我認爲這可能歸結爲執行質量和/或將C與本地API結合使用,您應該能夠克服差異。

關於GC負荷,請看Looperapi)和Handlerapi)。使用這些,您可以將基於BlockingQueue的方法替換爲完全不產生GC加載的方法。我寫了一個blog post詳細探討了這一點。此外,如果您使用任何/所有Clean Code實踐,那麼可能需要一些明智地使用髒代碼(字段訪問,可變性)來減輕內存流失。

關於IO線程:我會說肯定縮小到一個單一的,並嘗試寫入行批量文件,而不是一個接一個。緩衝的流或寫入器可能會自己動手,只要你避免明確地調用flush()

+0

關於GC負載,存在誤解。我並不擔心由於創建消息而造成的垃圾。問題是SensorEvents是可變的。因此,爲了絕對安全,應在將它們放入隊列中進行日誌記錄之前將其複製。否則,我們無法保證可以稍後修改。我認爲問題是這些副本的生成可能會打亂記憶。也許我應該爲此執行一個池... –

0

你的是如果沒有測試的也很難被評估的對象,但我提出一些建議:

  • 要使用一個單獨的線程,你可以使用一個處理器尺蠖爲建議由Barend和當你有一個新的傳感器讀取消息發送到這個處理程序,所以有可能刪除舊的消息,當你有幾個待定讀數的同一個傳感器,添加某種反溢出保護,如果系統無法處理所有的讀數,有些將會丟失。

  • 正如你所說的Android不是實時的,如果傳感器沒有定期讀取,也許你應該給每個閱讀附加一個時間戳。

  • 爲避免爲每個傳感器讀數分配& GC'ing變量或對象,可以使用之前分配的變量的池(數組),並且每次讀取數據時都使用以下索引。這對於發送消息的情況來說很好,因爲它只需要在消息參數中發送新讀取的索引。

我希望它有幫助。

0

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

以上使用將節省IO足以讓您的傳感器代碼的工作。我已經看到android做更高比特率錄製視頻。編寫文件應該沒有問題。每秒3000個元素每行大容量20個字節480kbs。

將您的代碼放入一個服務中,當應用程序轉到背景時用戶檢查instagram或去尋找寵物小精靈或用戶按Home鍵時,它將保持運行。

GC GC不會避免最終對象? IDK。也許我錯了,但你應該只創建一次複雜的對象。該服務保持在內存中運行。不是問題。

五合一毫秒。這是非常規代碼的正常情況。這是多任務的本質。計劃有五個事件排隊等候處理。您必須優先考慮任務,但不要過多地考慮優先級,否則會導致問題。例如,如果寫入文件沒有足夠的優先級,則文件寫入可能永遠不會發生。現在將所有任務保持在相同的優先級,直到您瞭解分析器輸出爲止。

寫入文件。也許它會通過加密和SD卡,這將是不好的,並會採取各種CPU,並導致更多的問題。

+0

代碼已經在服務中運行。我沒有看到BigInteger.toByteArray()與這個討論有什麼關係。 –

+0

對不起自動更正的文本。發佈我們的代碼,我會解決它。應用程序無法處理480kbs比特率沒有任何理由。它必須是你的代碼。 – danny117

+0

我發佈了代碼。 –