JSONのパースにJacksonを使う
以前LDRFullFeedのJSONを扱うときに、1.5MBくらいのJSONを扱うとOut of Memoryになって苦労したけど、こういうのは Jackson を使うとよさげ。
Androidの標準のJSONパーサーは String しか渡せないけど、Jackson だと InputStream で渡せる。Stream で処理するのはちょっと面倒だけど、相当なメリットがありますな。
LDRMateのフィード一覧を読む部分をJacksonに変えた図。
JsonFactory factory = new JsonFactory(); JsonParser jp = factory.createJsonParser(is); if (jp.nextToken() == JsonToken.START_ARRAY) { while (jp.nextToken() != JsonToken.END_ARRAY) { if (jp.getCurrentToken() == JsonToken.START_OBJECT) { long subscribeId = 0; String folder = ""; String icon = ""; String link = ""; long modifiedOn = 0; int rate = 0; String title = ""; int unreadCount = 0; while (jp.nextToken() != JsonToken.END_OBJECT) { String name = jp.getCurrentName(); jp.nextToken(); if ("subscribe_id".equals(name)) subscribeId = jp.getLongValue(); else if ("icon".equals(name)) icon = jp.getText(); else if ("folder".equals(name)) folder = jp.getText(); else if ("link".equals(name)) link = jp.getText(); else if ("modified_on".equals(name)) modifiedOn = jp.getLongValue(); else if ("rate".equals(name)) rate = jp.getIntValue(); else if ("title".equals(name)) title = jp.getText(); else if ("unread_count".equals(name)) unreadCount = jp.getIntValue(); else jp.skipChildren(); } Feed feed = new Feed(subscribeId, folder, icon, link, modifiedOn, rate, title, unreadCount); feeds.add(feed); } else { jp.skipChildren(); } } }
Jacksonを使い始める前に簡単なベンチマークをとってみたけど、だいたい標準のJSONParserに比べて4倍くらいの速さですな。標準のJSONパーサーではStringに変換するために大きなメモリが必要になったり、いろんなところでオブジェクトが大量に生成されたりするけど、JacksonのStreamで処理すればそういう無駄がほとんど無くなるのも嬉しいですな。
LDReader で使われている org.json.simple も試したけどこれは800KB程度のJSONを読ませただで Out of Memory になる。標準のorg.jsonのパーサーでも処理できるサイズが処理できないのは不思議だけど、Jackson の結果がよかったので気にしないことにする...。
でもちょっと気になってることが。
{ items: [ ... ], subscribe_id: 1 }
こういうデータを処理するとき、items を処理する段階で subscribe_id が必要だった場合。
Streamだと先にitemsを処理しなくちゃいけなくて、そのときは subscribe_id の情報がないから困ったことになる。
実際に今これで困ってるわけではないけど、Javascriptのオブジェクトの中身は順番が保証されないのに、読み出す側は特定の順番を期待して処理を書くのはまずいよなぁ...。