這次接到的任務是下載一個壓縮過的xml檔案,並灌入db裡面。
檔案下載之前已經寫好含db+cache管理功能的模組,zip也只要套個zipinputstream並預先分析zipentry是不是目標檔。
XML parser之前也已經用DOM格式寫好可以任意取text / attributes值的tool。
一切都是如此美好。
但是在遇到2MB的xml之後,完全變了調。
初次的完整執行,等了3分鐘開始不耐煩,以為跑進了無限迴圈。
開始加一些debug print,發現一直有在動,只好耐心等待。
等了5分鐘,真的等不下去,分析一下debug print,資料的重複性似乎很高,
有點擔心是不是會跑不出來。轉而步進分析,看看到底是慢在哪。
一開始步進,傻了...
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db; try { db = dbf.newDocumentBuilder(); InputSource ii = new InputSource(); ii.setCharacterStream(is); Document doc = db.parse(ii); doc.getDocumentElement().normalize(); return doc; } catch (ParserConfigurationException e) { // Auto-generated catch block e.printStackTrace(); }
以上這幾行就要1分鐘(debug狀態)。 Orz
常用的doc.getElementsByTagName(...); 一call就是幾十秒在算。
心想這不是辦法。趕快找資料求救。咕狗:「android xml dom parse too slow」
直到看到了這一篇:
才知道DOM parser之慢... Orz (上面那篇的圖表必見。)
看來把parser改寫成SAX格式是在所難免。還好之前有寫過並不會覺得太難,只是SAX的循序走查方式,對付xml階層很深的格式的xml很難寫。
目標的XML只有兩層,資料都用attributes帶入,就不需要考慮這樣的問題。
(開始覺得設計目標資料格式的人一定相當有經驗。)
改寫成sax格式之後再跑,果然是快多了。一口氣從10分鐘降為3分鐘。
不過還是在需要做task管理的範圍。結果又花了點時間寫暫停跟續傳。
但是,想要加速的想法並沒有這樣就停止。
看起來問題應該不是xml了。那剩下的就是在db的存取上面。
繼續查資料... 咕狗:「android db insert too slow」
又看到一篇:
http://stackoverflow.com/questions/3501516/android-sqlite-database-slow-insertion
介紹只要把db.insert(...)
用下面的方法寫
db.beginTransaction(); db.insert(...); db.setTransactionSuccessful(); db.endTransaction();
就可以爆速。於是加了那三行。3分鐘變成30秒。 (抖)
30秒已經是可以不需要暫停背景執行的射程目標。
接下來就是繼續努力看看是不是可以再縮。
因為目前都還是使用單筆插入,於是把焦點放到bulkinsert(...)上面。
得一次全抓完資料再寫入,需要比較大的記憶體,為了速度這時也不在意了,
全部放到ArayList<ContentValues[]>去。
bulkinsert一樣加上上面三行,改成bulkinsert之後,降為10秒!
繼續努力。上面那篇文中還提到一個技巧:
private void insertTestData() { String sql = "insert into producttable (name, description, price, stock_available) values (?, ?, ?, ?);"; dbHandler.getWritableDatabase(); database.beginTransaction(); SQLiteStatement stmt = database.compileStatement(sql); for (int i = 0; i < NUMBER_OF_ROWS; i++) { //generate some values stmt.bindString(1, randomName); stmt.bindString(2, randomDescription); stmt.bindDouble(3, randomPrice); stmt.bindLong(4, randomNumber); long entryID = stmt.executeInsert(); stmt.clearBindings(); } database.setTransactionSuccessful(); database.endTransaction(); dbHandler.close(); }
這是使用db的compile sql方式(加速處理,不需要每次都sql command parse)然後綁參數的強力技巧。但是因為這方法需要照欄位的資料型態綁參數,加上insert所指定的欄位,每一個欄位都必需要有資料,其實有點麻煩。不過...
之前寫過一篇關於db架構的文。
用這篇提到的方法架構的db,稍微小改寫一下,要取得欄位名稱跟欄位的資料型態是輕而易舉。
改寫成此法之後... 降為6秒。 應該沒招了。到此為止。
備註:
- 經由這次教訓之後,決心以後設計xml格式的時候,帶的參數一律用attributes帶。
- 日期建議直接以字串存取。在不考慮時區的處理,yyyy-MM-dd HH:mm:ss格式是最好用的。要比對最小單位為秒的時間也很簡單,直接使用大小於比對就好。
轉成Calendar / date等等日期格式再parse出來存將會拖累速度。反正android的sqlite,所有資料都是string存放(連數字都一樣)...