ブログランキング・にほんブログ村へ


iPhone/iPad用潜水艦ゲームアプリ ソナーエコー iTunesにて公開中

2012年11月13日

OutOfMemoryがlayout.xml展開時に出る

AndroidのDalvikのメモリ制限がきつい。きつすぎる。24MBとか16MBとか正確な数字は知らないけどそんなもん。
今時内蔵カメラでいっぱつ取ったら4000x3000なんて軽くオーバー。これをRGBAで読み込んだらもう48MBでっせ。
こんなはしたメモリでどうしろと。画像だけは抽象化してDalvikの外に置くとかなんとかなんで工夫されてないのか。

で、Out of memoryとの苦闘なのはもう日常的なんでいいとして。

とうとうActivityのlayoutのxmlをリソースIDで指定して setContentViewしただけで Out of memoryを吐き出しやがりました。
しかもしょっぱなで何も処理してないトップ画面で。

調べてみると。
OutOfMemoryError: bitmap size exceeds VM budget

いつものビットマップのサイズにぶーぶー言ってる様子。
更に原因を呼び出し元へ追っていくと。

Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
at android.graphics.Bitmap.nativeCreate(Native Method)
at android.graphics.Bitmap.createBitmap(Bitmap.java:477)
at android.graphics.Bitmap.createBitmap(Bitmap.java:444)
at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:349)
ん?

at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:498)
at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:473)
at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336)
at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697)
at android.content.res.Resources.loadDrawable(Resources.java:1709)
なに?

at android.content.res.Resources.getDrawable(Resources.java:581)
at android.graphics.drawable.StateListDrawable.inflate(StateListDrawable.java:162)
at android.graphics.drawable.Drawable.createFromXmlInner(Drawable.java:787)
at android.graphics.drawable.Drawable.createFromXml(Drawable.java:728)
at android.content.res.Resources.loadDrawable(Resources.java:1694)
at android.content.res.TypedArray.getDrawable(TypedArray.java:601)

ちょっと待て〜!!

確かにxmlにImageButtonを置いた。そんでpressed selected と普通の状態を xmlで指定して styleに指定して…とやった。
Dalvikの貧相なメモリで元のビットマップを読み出して、リサイズして取って置いちゃおうって腹かい!??
てっきり描画時にリサイズしているもんだとばかり思っていたのに飛んだお間抜け実装みたいです。

しかもイメージボタンのリサイズをプログラムで制御するからと、xml上では適当にしておいたので、全画面になっていた。
するとおおざっぱに1000x1000だとして、RGBAだと1枚4MB。これにpressed selected,普通の状態と3枚で 4x3=12MB。
ボタン2つで24MB。確かにオーバーしてる!

とりあえずxml上で小さくすれば今回は逃れられるけれど、それじゃ根本的な解決にならない。
要するに、イメージボタンのメモリ効率が(特に拡大表示)非常に悪いので、イメージボタン自体があまり使わないほうが安全なものだということ。

今回は、SurfaceViewを継承したクラスを作って簡単なボタンクラスを生成し、表示するときにCanvasにdrawBitmapリアルタイムで拡大・縮小表示するようにした。面倒だが、これくらいしておかないと安心できない。

@2012.11.17 追記。

更に実験を続けていくと、実は自前で単に BitmapFactoty.decodeFileしてもcreateScaledBitmapを呼び出していることがわかった。
もちろんリサイズなどはしなくても。なので、上の場合でもリサイズが理由でcreateScaledBitmapを読み出してるかどうかはわからない。

じぶんの場合の一番の問題は、backを何度も押してアプリが終了した(ように見える)状態から再度アプリを起動した場合、メモリが2重に確保されようとすること、だったようだ。
どういうことかというと、back連打でアプリが終了したように見えてもまだアプリは完全に終わっていない。タスクマネージャーに表示されなくなっても、だ。
adb shellで入ってpsしてみると一目瞭然だがしっかりとプロセスが生き残っている。
この状態でもう一回起動しようとすると、確保したビットマップ(などのメモリ)がまだ解放されていない(gcに回収される前)に起動時のメモリ確保が行われてしまう。すると一時的に確保されるメモリが2倍になってOut of memoryになっていたと思われる。

じゃあどうすればいいかといえば、万能な解決方法はなく、自分のケースではbitmapを積極的に破棄しにいくように仕組むことで修正された。
具体的にはアプリを抜けることになる最後のActivityのonStop、できればonPauseに

bitmap.recycle();
bitmap = null;
System.gc();

を書いた。
他にも動作中にbitmapからbitmapを生成してもとのbitmapを破棄するところ(90度回転とか)でも元bitmapのほうに同様の破棄を入れた。
ぐぐってみるとこの対策は効果を上げてないことがほとんどで正直全く期待していなかったが、自分の場合はこれでOut of Memory が出なくなった。

問題点として、onPauseにメモリ破棄を入れると多くの場合Activity間の遷移で(ビットマップのリロードが常に発生するようになり)もたつくようになる。このあたりは設計次第ではあるが。

あと、一応試したこととして、onDestoryで System.exit()をするという方法。これが危険なのは百も承知だったがどうなるか知りたかった。
やってみると終了自体は問題ないのだが、2度目の起動時に必ず飛ぶようになってしまった。
これもアプリの設計次第かもしれないが。
posted by みこあいさ at 23:29| Android開発

2012年11月01日

Android layout xmlで配置したViewのサイズをプログラムから適切なサイズに制御する

Androidの何が大変ってViewをいい具合に配置すること。
多様なデバイスのどれでも綺麗に見えるように今日もlayout xmlと格闘が続く。

今回は、ImageButtonを画面の中央にビットマップの縦横比を変えずにしかもデバイスのディメンションに対するパーセンテージで表示したい、というもの。

なんとかxmlだけで解決できないものかと RelativeLayout,LinearLayout,FrameLayout総動員でぐちゃぐちゃやってまぁなんとか出来ないでもなかったのだけど、それでもちょうど30%で、かつ他のViewはそのボタンのサイズに対する相対で、とかいうともう方法がない。

仕方なくプログラムで制御することにした。
で、困ったのはxmlで配置したViewの大きさが確定するのはどのタイミングなのか、ということ。

Activity.onCreateの中では当然確定していない。view.getWidth()すると0になる。
onResumeやonStartでもやはり0。

これは
onWindowFocusChanged
でやるといい具合にとれた。

そして画面の大きさを取って、そのサイズに対する比率を設定する。

WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();

int mw = (int)(display.getWidth() * 0.7F);
int mh = ( int )( mw * 0.7F );

_button.setLayoutParams( new LinearLayout.LayoutParams( mw, mh ) );

こんな感じ。

こうやってサイズを変更したViewが親のLayoutに対してどのように振る舞うのか?というのが心配だったのだけど、
これは変更後のサイズでちゃんと親のLayoutが再配置してくれる。
例えばRelativeLayoutで、android:layout_above="@id/xxxxxx" とか指定したViewがあったとして、layout_aboveで指定したViewのほうをプログラムからサイズ変更しても、ちゃんとサイズ変更後のViewの上に乗っかってくれる。

visibilityをプログラムでGONEにしたらレイアウトをちゃんと詰めてくれるのである意味当然と言えば当然だが、Windows当たりの感覚だとこういう場合は野放しで全部自分でやれ、って感じなんである意味新鮮。
最近設計のシステムだな、と感心する。
posted by みこあいさ at 00:44| Android開発

2012年10月23日

Android EditText/TextViewに罫線を描く

AndroidのEditTextの背景にノートの罫線みたいのを引きたい場合。

ソースごと綺麗にまとまった記事があるのでリンク。

EditTextビューに罫線を引く

TextViewでも罫線引きたい!って場合は?

試してみたらTextViewでもそのまんまでOKだった。
同じようにonDrawをオーバーライト。関数の中身はEditTextと全く同じでOK。
posted by みこあいさ at 23:32| Android開発