web-dev-qa-db-ja.com

メモリ/リソースリークを見つけるのに最適なAndroidツールとメソッドは何ですか?

Androidアプリを開発しましたが、電話アプリ開発の段階にあり、すべてがうまく機能しているようで、勝利と出荷を宣言したいのですが、ただそれだけでいいのです。メモリとリソースのリークがそこにあります。 Androidにはわずか16MBのヒープしかなく、Androidアプリでリークするのは驚くほど簡単です。

私は周りを見回してきましたが、これまでのところ「hprof」と「traceview」に関する情報を掘り下げることができましたが、どちらも多くの好評を得ていません。

OSプロジェクトで共有したいツールや方法はありますか?

151
jottos

Androidアプリの開発で見つかった最も一般的なエラーの1つは、「Java.lang.OutOfMemoryError:ビットマップサイズがVM Budget」を超えていることです。向きを変更した後、多くのビットマップを使用するアクティビティでこのエラーを頻繁に発見しました。アクティビティは破棄され、再度作成され、ビットマップに使用可能なVMメモリを消費するXMLからレイアウトが「膨張」します。

前のアクティビティレイアウトのビットマップは、アクティビティへの相互参照を持っているため、ガベージコレクタによって適切に割り当て解除されません。多くの実験の後、私はこの問題の非常に良い解決策を見つけました。

最初に、XMLレイアウトの親ビューで「id」属性を設定します。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
     Android:layout_width="fill_parent"
     Android:layout_height="fill_parent"
     Android:id="@+id/RootView"
     >
     ...

次に、ActivityのonDestroy()メソッドで、参照を親Viewに渡してunbindDrawables()メソッドを呼び出し、System.gc()を実行します。

@Override
protected void onDestroy() {
    super.onDestroy();

    unbindDrawables(findViewById(R.id.RootView));
    System.gc();
}


private void unbindDrawables(View view) {

    if (view.getBackground() != null) {
        view.getBackground().setCallback(null);
    }

    if (view instanceof ViewGroup) {
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            unbindDrawables(((ViewGroup) view).getChildAt(i));
        }

        ((ViewGroup) view).removeAllViews();
    }
}

このunbindDrawables()メソッドは、ビューツリーを再帰的に探索し、次のことを行います。

  1. すべての背景ドロアブルのコールバックを削除します
  2. すべてのビューグループの子を削除します
90
hp.android
29
kohlerm

主に将来のGoogle旅行者向け:

ほとんどのJavaツールは、JVMヒープのみを分析するため、残念ながらこのタスクには適していません。ただし、すべてのAndroidアプリケーションにもネイティブヒープがあり、これも最大16 MBの制限内に収まる必要があります。通常、たとえばビットマップデータに使用されます。したがって、多くのドロウアブルを使用すると、JVMヒープが3 MB前後であっても、メモリ不足エラーに非常に簡単に遭遇する可能性があります。

28
Timo Ohr

@ hp.Androidからの回答は、ビットマップの背景を操作しているだけでうまく機能しますが、私の場合、BaseAdapterImageViewsのセットを提供するGridViewがありました。アドバイスとしてunbindDrawables()メソッドを修正し、条件が次のようになるようにしました。

if (view instanceof ViewGroup && !(view instanceof AdapterView)) {
  ...
}

しかし、問題は再帰メソッドがAdapterViewの子を処理しないことです。これに対処するために、代わりに次のことを行いました。

if (view instanceof ViewGroup) {
  ViewGroup viewGroup = (ViewGroup) view;
  for (int i = 0; i < viewGroup.getChildCount(); i++)
    unbindDrawables(viewGroup.getChildAt(i));

  if (!(view instanceof AdapterView))
    viewGroup.removeAllViews();
}

AdapterViewの子がまだ処理されるように、メソッドはすべての子を削除しようとしません(サポートされていません)。

ただし、ImageViewsはバックグラウンドではないビットマップを管理するため、これで問題は解決しません。したがって、次を追加しました。それは理想的ではありませんが、動作します:

if (view instanceof ImageView) {
  ImageView imageView = (ImageView) view;
  imageView.setImageBitmap(null);
}

全体的にunbindDrawables()メソッドは次のとおりです。

private void unbindDrawables(View view) {
  if (view.getBackground() != null)
    view.getBackground().setCallback(null);

  if (view instanceof ImageView) {
    ImageView imageView = (ImageView) view;
    imageView.setImageBitmap(null);
  } else if (view instanceof ViewGroup) {
    ViewGroup viewGroup = (ViewGroup) view;
    for (int i = 0; i < viewGroup.getChildCount(); i++)
    unbindDrawables(viewGroup.getChildAt(i));

    if (!(view instanceof AdapterView))
      viewGroup.removeAllViews();
  }
}

このようなリソースを解放するためのより原則的なアプローチがあることを望んでいます。

20
darrenp

Androidのメモリ管理に関するGoogle I/Oトーク(2011年)、およびメモリプロファイリングのツール+テクニックの詳細:
http://www.youtube.com/watch?v=_CruQY55HOk

12
greg7gkb

ValgrindはAndroid(Mozillaがスポンサー)に移植されました。 AndroidでのValgrind —現在のステータス および ARMでのAndroidのValgrindの実行のサポート (コメント67)を参照してください。

4
jww

まあ、それらはAndroidが使用するユニークなフォーマットにフックするツールです。あなたが満足していないのは、使用中の基礎となるテストコードフレームワークです。

Android Mock Frameworkを使用してコードの領域を模擬テストしてみましたか?

1
Fred Grott