2013年12月17日火曜日

【Android】BundleにでかいStringをあげたら死んだ

Activityをまたいである程度の大きさのリストを共有したい.
そこで,一番手っ取り早そうな方法を使った.
具体的には

====ActivityA
ArrayList<Hoge> bigList;
String json = Json.encode(bigList);
Intent intent = new Intent (this, ActivityB.class);
intent.putExtra ("BIGLIST", json);
startActivityForResult (intent, 666);

つまりActivityAクラスで大きなリストをJSON文字列化して
それをインテントに乗せてActivityBクラスへ渡し,
ActivityBクラス内でJSONからリストに戻していた.
このリストが(1件のデータはさほどの大きさではないが)500件ほどになると
以下のようなエラーを吐いて爆死した.

12-17 17:41:54.349: E/JavaBinder(282): !!! FAILED BINDER TRANSACTION !!!
12-17 17:41:54.349: W/ActivityManager(282): Exception when starting activity jp.mau.dummy/.activity.ActivityB
12-17 17:41:54.349: W/ActivityManager(282): android.os.TransactionTooLargeException
12-17 17:41:54.349: W/ActivityManager(282): at android.os.BinderProxy.transact(Native Method)
12-17 17:41:54.349: W/ActivityManager(282): at android.app.ApplicationThreadProxy.scheduleLaunchActivity(ApplicationThreadNative.java:705)
12-17 17:41:54.349: W/ActivityManager(282): at com.android.server.am.ActivityStack.realStartActivityLocked(ActivityStack.java:702)
12-17 17:41:54.349: W/ActivityManager(282): at com.android.server.am.ActivityStack.startSpecificActivityLocked(ActivityStack.java:811)
12-17 17:41:54.349: W/ActivityManager(282): at com.android.server.am.ActivityStack.resumeTopActivityLocked(ActivityStack.java:1759)
12-17 17:41:54.349: W/ActivityManager(282): at com.android.server.am.ActivityStack.resumeTopActivityLocked(ActivityStack.java:1393)
12-17 17:41:54.349: W/ActivityManager(282): at com.android.server.am.ActivityStack.completePauseLocked(ActivityStack.java:1141)
12-17 17:41:54.349: W/ActivityManager(282): at com.android.server.am.ActivityStack.activityPaused(ActivityStack.java:1039)
12-17 17:41:54.349: W/ActivityManager(282): at com.android.server.am.ActivityManagerService.activityPaused(ActivityManagerService.java:4309)
12-17 17:41:54.349: W/ActivityManager(282): at android.app.ActivityManagerNative.onTransact(ActivityManagerNative.java:381)
12-17 17:41:54.349: W/ActivityManager(282): at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:1616)
12-17 17:41:54.349: W/ActivityManager(282): at android.os.Binder.execTransact(Binder.java:367)
12-17 17:41:54.349: W/ActivityManager(282): at dalvik.system.NativeStart.run(Native Method)

この現象自体は前々から把握しており,
そろそろ対策するかと何度も再現検証して初めて出たログである.
リストを大きくすると死ぬから原因自体は容易に想像できるが,
このTransactionTooLargeExceptionという例外クラス名を見れたのは大きい.

対策はまだしていないが,以下のような方法が考えられる.

1.リストが絶対に100件程度で収まる場合
無対策でそのままJson化して受け手側で展開すればOK.
しかし受け手側で展開したリストと送り手が持っていたリストは別物であり
メモリ上に重複しているということを覚えておく必要がある.
巨大なデータを2つも持ちたくはないし,
受け手側でした変更を送り手側に反映するためには
受け手側で再度JSON化して送り手側に返す必要がある.

2.リストがまぁそれなりの大きさで済んでくれる場合
メモリ上にリストを全部展開しても生き残れる程度に確実に収まる場合は
調べたところではSerializableを継承したクラスにリストを配置し,
それを受け渡すことで回避できるらしい.
ただ,やってることは1.とさほど変わらず,
Stringというプリミティブオブジェクトを渡すのか,
Serializeされたストリームを渡すのかの違いで
受け手側で展開して多重化されるのは変わらないらしい.

3.リストが青天井の場合
ArrayListなんてつかわずにおとなしくDB化しよう.
インテントもDBのURL渡すだけでいい.
ArrayAdapterとか使ってたら改造が大変だけど,
やる実は大きいと思う.
DBは使わなくなったら棄てるようにしよう.


…と今日考えたこと.
まだ(改造がめんどくさくて)実装していないがこれからDB化していく.
はぁ…

0 件のコメント:

コメントを投稿