2012年12月11日火曜日

【Android】bindView内でHandlerを使うとposition:0のbindViewが大量に呼び出される

bindView内のHandlerでUI処理を行うとpositionが0のViewをバインドさせるような処理が呼ばれてしまう.
positionが0というのはListViewなら一番上のリスト,GridViewなら一番左上のリストである.
この現象はbindViewのHandler内でUI処理を行わない場合は起きない.
…が,そもそもUI処理を行わないのにHandlerなんて使わない.

それがどのくらい大量に呼び出されるかというと,コードにも依るかもしれないが
読み込んだリストの倍の数だけposition:0のbindViewが呼び出される.
1列に3個Viewが並んだGridViewだとすると通常のViewがリストの分,3つ呼び出されて,
その後にposition:0のViewが6個呼び出される.

Handler内でUI処理を行なう場合でも
ImageView.setImageResource()やTextView.setText()だけを行った場合はこの現象が起きない.
View.setVisibility()はViewが変更されれば現象が起きるが変更されなければ起きない.

謎だ.

いろいろ調べてると何箇所かでViewの高さをwrap_contentにしてると重複して呼び出されるよって書いてあるけど
fill_parentでもなる.
dp指定でも起こるうえに設定したheightが見事に無視された.

bindView内でHandlerを使うということはそれなりに重い処理(画像処理,ネット接続等)を別スレッドで行なって
処理完了後にUIに反映させるんだろうが,
こうやってムダなbindViewが呼び出されるとその分スレッドが作られてリソースを圧迫することになる.
ユーザーがファストスクロールしまくると
メモリ圧迫⇒オーバーフロー死
することになる.

fastscrollをオフにしたら問題が起こるほどではないんだろうけど
グリッドにバインドするアイテムが万単位にもなるとfastscrollオフは完璧に地獄になる.

これを抑えることができればスレッド数も1/3に抑えられてメモリリークのリスクも圧倒的に減るのだが.
最低でも強制終了はよろしくないので


対応その1(対策ではない)
設置スレッド数の上限を決めてしまう方法

このようなかんじでスレッド開始前にスレッド数確認して乱立していたらスレッドを追加しない
という方法は採ることはできるが…
スクロールを止めた画面は描画されないことに成りかねない.
それに機種によって限界は違ってくるし,バックグラウンドの状況によってもどうなるかは分からない.
強制終了よりはマシというレベル.

既にキューに並んだスレッドを間引くことができればまだ何とかできそうだが
Thread.stop()などが非推奨になっているためそれもできず.
要らないスレッド殺してえ…

対応その2
UIとの整合性が保たれていないスレッド
つまりバインドすべきViewはとっくに通り過ぎた場合なんかは
スレッド内の重い処理に到達するまでにreturnしてやる.
そしてさっさとスレッドを消化してやる.

スレッドの乱立を防止するわけではなく,立ってしまったスレッドに対しての後手策なので
ほんとうに足掻き程度だけど.

根本原因のbindView乱立が起きなければ問題は緩和されるのに.
全然わからん.

こういうbindViewでスレッドを設置して処理させるという作り方をしたのがそもそも間違いなのだろう.
bindView内の処理を大幅に見直すことにする.
が,こういう現象があるよという知見程度に.

0 件のコメント:

コメントを投稿