Mono for Androidとメモリリークのまとめ

Mono for Androidはそのフレームワーク内のほぼ全てのクラスがIDisposableを持っている。よって.NETの基本的ルールとして生成したインスタンスは明示的にDisposeするべきである。しかしDisposeしてもなおメモリリークするパターンも存在している。それをまとめてみた。

  • この記事を書いている時点でのMono for Androidのバージョンはv1.9.2(2.0 Beta3)である。
  • メモリリークと書いているが正確には各クラスが内包しているJNIのハンドルがグローバルリファレンスから削除されない現象である。良い表現が思いつかなかったのでメモリリークとした。

基本

インスタンス化したもの

Java.Lang.Objectを継承したクラスは使い終わった時にかならずDisposeする。

代入した時

メソッドの引数に使用したオブジェクトを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を継承した独自クラスを作成することで回避可能。

その他

Mono for Androidが内部でstaticとして保持しているオブジェクト

例えばAndroid.Graphics.PorterDuff.Modeクラスはstaticコンストラクタ内でAndroid.Graphics.PorterDuff.Modeのインスタンスを16個作成する。これは解放出来ない。
このようなクラスが幾つか存在している。