Mono for Androidとメモリリークのまとめ
Mono for Androidはそのフレームワーク内のほぼ全てのクラスがIDisposableを持っている。よって.NETの基本的ルールとして生成したインスタンスは明示的にDisposeするべきである。しかしDisposeしてもなおメモリリークするパターンも存在している。それをまとめてみた。
- この記事を書いている時点でのMono for Androidのバージョンはv1.9.2(2.0 Beta3)である。
- メモリリークと書いているが正確には各クラスが内包しているJNIのハンドルがグローバルリファレンスから削除されない現象である。良い表現が思いつかなかったのでメモリリークとした。
基本
代入した時
メソッドの引数に使用したオブジェクトをDisposeしなければならない時がある。代入した物をDisposeすると代入した先で問題が起きる気がするが、実際はそうではない。
たとえばImageViewのSetImageBitmapを使うとき
Bitmap bm = ...; imageView.SetImageBitmap(bm); bm.Dispose (); // もしくは using (Bitmap bm = ...) { imageView.SetImageBitmap(bm); }
とするべきである。このあとImageViewが必要無くなればImageViewをDisposeすること。
参照を取得した時
メソッドの戻り値
メソッドの戻り値をDisposeしなければならない時がある。
例えば下記の場合、取得したTextViewへの参照は未解放になる。
TextView v = context.FindViewById<TextView>(Resource.Id.textView); ...
これは
TextView v = context.FindViewById<TextView>(Resource.Id.textView); ... v.Dispose (); // もしくは using (TextView v = context.FindViewById<TextView>(Resource.Id.textView)){ .... }
とするべきである。
プロパティの参照
プロパティを参照するときにgetterの内部でインスタンス化しているものがある。
例えば下記の場合ResourcesとDisplayMetricsが未解放となる。
float d = context.Resources.DisplayMetrics.DensityDpi;
これは面倒くさくても参照した物を一つ一つDisposeしなければならない。
Android.Content.Res.Resources res = context.Resources; AndroiAndroid.Util.DisplayMetrics m = res.DisplayMetrics; float d = m.DensityDpi; m.Dispose (); res.Dispose (); //もしくは using (Android.Content.Res.Resources res = context.Resources) { using (AndroiAndroid.Util.DisplayMetrics m = res.DisplayMetrics) { float d = m.DensityDpi; } }
とするべきである。
回避不能
強制GCで回避可能?
イベントハンドラは全般的にadder内部でなにかしらのクラスをインスタンス化している。これはDisposeでは解放されない。例えばAndroid.Views.View.Clickイベントハンドラは内部でAndroid.Views.View.IOnClickListenerImplementorクラスをインスタンス化している。
for (int i = 0; i < 2000; i++) { TextView v = new TextView (context); v.Text = i.ToString (); v.Click += HandleVClick; // <- 内部でIOnClickListenerImplementorがインスタンス化される v.Dispose (); // <- IOnClickListenerImplementorが解放されずにリークする }
強制GCにてIOnClickListenerImplementorも解放されるようだが、コードが複雑になると解放されないことがあった。
どうやってもメモリリークするパターン
普通のやり方では回避する手段を見つけることができなかった。リフレクションで無理矢理には可能。
class MyClass{} ////////// { JavaList<MyClass> list = new JavaList<MyClass>(); for (int i = 0; i < 500; i++) { list.Add(new MyClass()); // <- 内部でJavaObjectがインスタンス化されている } ArrayAdapter<MyClass> ad = new ArrayAdaptor<MyClass> (context, 0 ,list); list.Dispose (); ad.Dispose (); // この時点で500個のJavaObjectが未解放のまま }
※Android.Widget.ArrayAdapterの場合はAndroid.Widget.BaseAdapterを継承した独自クラスを作成することで回避可能。