Volleyでjsonに日本語が含まれると文字化けする・・・?
今回の問題
Volleyで日本語込のjsonを取得すると何故か文字化けする・・・という。
環境
サーバーというかjsonは、Amazon S3に配置したjsonを取得している。
S3の設定で、Metadataには以下を設定している。
Content-Type application/json;charset=utf-8
しかし、文字化け
{"gameList":[{"homePageUrl":"http:\/\/atelier-ps3.jp\/rorona\/new\/#home","sub":"3Dã¢ãã«ããã£ã¼ã«ããããã¨ãã£ãã°ã©ãã£ãã¯ã¯ãã¡ããã調åã»ããã«ã·ã¹ãã ã¨ãã£ãå種è¦ç´ ããã¡ã«ã«ã®ã¢ããªã¨ãããã¼ã¹ã«ã²ã¼ã ã·ã¹ãã ãåæ§ç¯ããã¾ã£ããæ°ããã¿ã¤ãã«!!","title":"æ°ã»ãããã®ã¢ããªã¨ ã¯ãã¾ãã®ç©èªãã¢ã¼ã©ã³ãã®é¬éè¡å£«ã[PS3] [PSVita]","releaseDate":"2013\/11\/21","imageUrl":"http:\/\/atelier-ps3.jp\/rorona\/new\/img\/home_biglinkbanner01.png"},{"homePageUrl":"http:\/\/aquaplus.jp\/ttt2\/","sub":"å2ä½åæ§ããã¡ã³ã¿ã¸ã¼ä¸çãèå°ã¨ãå£ã¨éæ³ãé§ä½¿ãã¹ãã¼ãªã¼ãé²ãããã·ãã¥ã¬ã¼ã·ã§ã³RPGãã","title":"ãã£ã¢ã¼ãºã»ãã¥ã»ãã£ã¢ã©â¡ [PS3]","releaseDate":"2013\/10\/31","imageUrl":"http:\/\/aquaplus.jp\/ttt2\/bn\/rnd.php?type=650x120"},{"homePageUrl":"http:\/\/www.compileheart.com\/neptune\/re-birth1\/","sub":"ã²ã¤ã ã®ã§ã¦çãæãããã女ç¥ãã¡ãPlayStation®Vitaã§åèµ·åï¼","title":"è¶æ¬¡æ¬¡åã²ã¤ã ãããã¥ã¼ãRe;Birth1 [PSVita]","releaseDate":"2013\/10\/31","imageUrl":"http:\/\/sce.scene7.com\/is\/image\/playstation\/vljm35058_banner?$bnr$"}],"version":2}
なぜかCacheClear後に取得するとうまく取れる。
12-01 23:12:24.462: D/Volley(9295): [1] DiskBasedCache.clear: Cache cleared.
{"gameList":[{"homePageUrl":"http:\/\/atelier-ps3.jp\/rorona\/new\/#home","sub":"3Dモデル、フィールドマップといったグラフィックはもちろん、調合・バトルシステムといった各種要素を「メルルのアトリエ」をベースにゲームシステムも再構築したまったく新しいタイトル!!","title":"新・ロロナのアトリエ はじまりの物語〜アーランドの錬金術士〜[PS3] [PSVita]","releaseDate":"2013\/11\/21","imageUrl":"http:\/\/atelier-ps3.jp\/rorona\/new\/img\/home_biglinkbanner01.png"},{"homePageUrl":"http:\/\/aquaplus.jp\/ttt2\/","sub":"前2作同様、ファンタジー世界を舞台とし剣と魔法を駆使しストーリーを進める「シミュレーションRPG」。","title":"ティアーズ・トゥ・ティアラⅡ [PS3]","releaseDate":"2013\/10\/31","imageUrl":"http:\/\/aquaplus.jp\/ttt2\/bn\/rnd.php?type=650x120"},{"homePageUrl":"http:\/\/www.compileheart.com\/neptune\/re-birth1\/","sub":"ゲイムギョウ界を救うため、女神たちがPlayStation®Vitaで再起動!","title":"超次次元ゲイムネプテューヌRe;Birth1 [PSVita]","releaseDate":"2013\/10\/31","imageUrl":"http:\/\/sce.scene7.com\/is\/image\/playstation\/vljm35058_banner?$bnr$"}],"version":2}
ふむ・・・
gzipに圧縮して送信してみる
contentTypeすら取れない・・・なんじゃこれ
12-02 00:55:12.860: D/CustomJsonObjectRequest(12034): contentType = response = ��XR��[OQÇßý°òÝ^CbH|ñ¡è!d'íâv»înABHzÎÒJn0!¥KKË- 12-02 00:55:12.860: D/CustomJsonObjectRequest(12034): øQ¶'¿çF¹¢Éî¦ÝýÏÌï?»Ò(2-5ªK~o§R"è©jÙàÎd«¶$¿T[Þú è8Þ��ÞòhhH¹ ðàUÀßðX=µÕ¨ä ¾&Nn=b¡fz»9³Ð(,9U0è÷ ¶±ësÕV¥NÉDR,ԫؼ¬×íñÉìõ°²[¾^ Lt({ÍÙ5ª¼��Í¥@W��OÞ��¼d_tÐʤ@öx»��.��É��Yb:Ê3#αª@|��¿¬æÌOÕË»M\ÿ²ñ4Ш^½Fâ@ßñöf¢ÕüqBD¦Vï1@+¼aƱÕ9ãàùÍ) g@>´zkog8ÔBÏL1 Û¶áeÆLSÙeX>×!Q3ª+²Æd5ÃÑ 12-02 00:55:12.860: D/CustomJsonObjectRequest(12034): ª!MÕ_]G¦Ûã2ôâ¡Àÿ«ÝåùÒdç¥ýà£0º-»ÉW2 ¤(~ü mý.´ ËoqÚ-û.vRïùé3®Vÿx]àÄQ³JçÇËõlAn$sÎܾ°iÅI1tåæÎÛÚ!Ë_lç§g!ü+¢ËÅfü��2ËMdÐu ¬ãmeK¬V_à ÷1Nn&þ*¦ZÌâlÛöÊA]6õ.#lôØãzt¯ÛýÚãußÊøúóW©6>O׶ìp¦)ßîh/8ߢ&Ý>VM;ìù×tï Ùå³ÍËZvpB¼7QwõbN,cp: )ãý¶b³ÏÅ]^poãÓ3ýuoÄd #;utß5Ȫ%Ù`bVKLÕF"¾nw÷¡Örötu³ã6pccc\ÊP5Fi]vLG²ºÃ98yç7_+0ðà����
やはりCacheClearするとちゃんと取れる。
12-02 00:55:44.383: D/CustomJsonObjectRequest(12128): contentType = application/gzip;charset=utf-8 response = {"version":2,"gameList":[ 12-02 00:55:44.383: D/CustomJsonObjectRequest(12128): {"title":"新・ロロナのアトリエ はじまりの物語〜アーランドの錬金術士〜[PS3] [PSVita]","releaseDate":"2013/11/21","sub":"3Dモデル、フィールドマップといったグラフィックはもちろん、調合・バトルシステムといった各種要素を「メルルのアトリエ」をベースにゲームシステムも再構築したまったく新しいタイトル!!","imageUrl":"http://atelier-ps3.jp/rorona/new/img/home_biglinkbanner01.png","homePageUrl":"http://atelier-ps3.jp/rorona/new/#home"}, 12-02 00:55:44.383: D/CustomJsonObjectRequest(12128): {"title":"ティアーズ・トゥ・ティアラⅡ [PS3]","releaseDate":"2013/10/31","sub":"前2作同様、ファンタジー世界を舞台とし剣と魔法を駆使しストーリーを進める「シミュレーションRPG」。","imageUrl":"http://aquaplus.jp/ttt2/bn/rnd.php?type=650x120","homePageUrl":"http://aquaplus.jp/ttt2/"}, 12-02 00:55:44.383: D/CustomJsonObjectRequest(12128): {"title":"超次次元ゲイムネプテューヌRe;Birth1 [PSVita]","releaseDate":"2013/10/31","sub":"ゲイムギョウ界を救うため、女神たちがPlayStation®Vitaで再起動!","imageUrl":"http://sce.scene7.com/is/image/playstation/vljm35058_banner?$bnr$","homePageUrl":"http://www.compileheart.com/neptune/re-birth1/"}]} 12-02 00:55:44.508: D/AppListFragment(12128): {"gameList":[{"homePageUrl":"http:\/\/atelier-ps3.jp\/rorona\/new\/#home","sub":"3Dモデル、フィールドマップといったグラフィックはもちろん、調合・バトルシステムといった各種要素を「メルルのアトリエ」をベースにゲームシステムも再構築したまったく新しいタイトル!!","title":"新・ロロナのアトリエ はじまりの物語〜アーランドの錬金術士〜[PS3] [PSVita]","releaseDate":"2013\/11\/21","imageUrl":"http:\/\/atelier-ps3.jp\/rorona\/new\/img\/home_biglinkbanner01.png"},{"homePageUrl":"http:\/\/aquaplus.jp\/ttt2\/","sub":"前2作同様、ファンタジー世界を舞台とし剣と魔法を駆使しストーリーを進める「シミュレーションRPG」。","title":"ティアーズ・トゥ・ティアラⅡ [PS3]","releaseDate":"2013\/10\/31","imageUrl":"http:\/\/aquaplus.jp\/ttt2\/bn\/rnd.php?type=650x120"},{"homePageUrl":"http:\/\/www.compileheart.com\/neptune\/re-birth1\/","sub":"ゲイムギョウ界を救うため、女神たちがPlayStation®Vitaで再起動!","title":"超次次元ゲイムネプテューヌRe;Birth1 [PSVita]","releaseDate":"2013\/10\/31","imageUrl":"http:\/\/sce.scene7.com\/is\/image\/playstation\/vljm35058_banner?$bnr$"}],"version":2}
調査してみる
CacheClearで何かやってるのかな?と思ったが面倒なので、デバッグでVolleyのソースでhttpのreponseを受け取ったところを追ってみたらいきなりビンゴ!
「/Volley/src/com/android/volley/toolbox/BasicNetwork.java」の99行目付近でHttpStatusCodeが304(変更なし)が帰ってきた時にCacheを使っているところに問題があるっぽい?
サーバーが304の時に返すheaderがほとんどnullってるため、JsonRequestクラスでresponseをparseするときに参照しているheaderのContentTypeが「application/json;charset=utf-8」ではなくnullなので、jsonパースできず、それで文字化け状態になってるかと。サーバーのheader情報がVolleyの仕様と違うのかな・・?
とりあえずキャッシュしてるresponseHeaderで返すようにしたら無事に動くようになった。
以下対応コード。
/Volley/src/com/android/volley/toolbox/BasicNetwork.java
if (statusCode == HttpStatus.SC_NOT_MODIFIED) { // 2013/12/02 edit start // return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, // request.getCacheEntry().data, responseHeaders, true); return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, request.getCacheEntry().data, request.getCacheEntry().responseHeaders, true); // 2013/12/02 edit end }
Volleyのバグなのか仕様なのかちょっとわからない。。。。
304を返すと時は一般的にheader情報だけはちゃんと作って返すものなのかな? そっちの知識は疎いので勉強しておきます。。。
2013/12/07追記
304についてちょっと調べました。
どうやら、304返す時はレスポンスボディだけじゃなくてレスポンスヘッダーも含めないで返すのが仕様みたい。 キャッシュしてるやつとヘッダーが変わったとき困るしね・・・
というわけでVolleyのバグくさいので早く直らないかなー( ^ω^)
以下、抜粋。
10.3.5 304 Not Modified クライアントが条件付き GET リクエストを実行し、アクセスは許可されたがその文書は更新されていなかった場合、 サーバはこのステータスコードもって応答すべきである。 304 レスポンスはレスポンスボディを含んではならないので、いつもヘッダフィールドの後の最初の空行で終了する。 強いキャッシュバリディタ (section 13.3.3 参照) を使う場合、 レスポンスは他のエンティティヘッダを含めるべきではない。 そうでない (例えば条件付き GET が弱いバリディタを使う) 場合、 レスポンスは他のエンティティヘッダを含めてはならない。 これは、キャッシュされたエンティティボディと更新されたエンティティヘッダとの不一致を避ける為である。
参考: http://www.studyinghttp.net/cgi-bin/rfc.cgi?2616#Sec10.3.5