メモリ/リソースリークを見つけるのに最適なAndroidツールと方法は何ですか?[閉まっている]


152

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

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

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


3
voteСТȢѸ́ФХѾЦЧШЩЪЫЬѢѤЮѦѪѨѬѠѺѮѰѲѴが彼の名前を人間が読めるものに変更するよう投票できますか?
JPM 2015

1
質問のタイトルから「Android Tools」という単語を削除した場合、この質問を再度開いてもいいですか?答えはここにあり、私のメモリリークの問題解決するのは非常に便利だった
K3B

わかりましたので、私はユーザーにアクティビティを常に切り替えています。つまり、20秒で15のアクティビティを切り替えた可能性があります。それがメモリ不足エラーの原因ですか?修正するにはどうすればよいですか?ありがとう!
Ruchir Baronia

1
質問は締め切られているため、これを回答として提供することはできませんが、Leak Canaryを確認することをお勧めします。アプリを使用して、アクティビティを開閉し、ライブラリに任せてください。リークが発生した場所についても教えてくれます。リークが発生してから、リークアナライザーが動作するまでしばらく時間をかけてください。リークの原因が見つかるまで、通常2分以上かかります。その後、アプリ内できれいに表示されます。追加のツールは必要ありません!
ubuntudroid 2017年

回答:


90

私がAndroidアプリの開発で見つけた最も一般的なエラーの1つは、「java.lang.OutOfMemoryError:Bitmap Size Exceeds 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"
     >
     ...

次に、アクティビティのonDestroy()メソッドで、親バインドに参照を渡して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. すべてのビューグループの子を削除します

3
一般的な問題に対する適切な解決策。
While-E

9
これは、AdapterViewのサブクラス(ListView、GridViewなど)では機能しません。
Arjun

@Arjunはい..itはAdapterViewサブクラスでは機能しません。そのためには、例外を処理する必要があります。残りはうまくいきます。それは私のコードで使用するものであり、正常に動作します。お役に立てれば。
hp.android

@ hp.android「例外でこれを処理する」ということで、それがどのように見えるかの例はありますか?私は私の中に画像のページがPageAdapterあり、私はこのエラーに
対処し

4
@Jacksonは条件を変更するだけです:if(view instanceof ViewGroup &&!(view instanceof AdapterView))これは、アダプターに対して取得している例外を取り除きます
hp.android


28

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

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


6
Android 3.0(ハニカム)以降、ドローアブルはヒープに格納されます
Gu1234

@Timoでは、ネイティブヒープのリークを検出するために何を使用しますか?
sydd

1
テスト、たくさんのテスト。問題は、メモリ共有やその他の最適化手法のために、アプリが使用しているメモリの量を実際に知ることさえできないということです。通常のシェルコマンドを使用してメモリの読み取りを取得できますが、これらは非常に大まかな見積もりです。
Timo Ohr、2011

20

@ hp.androidからの回答は、ビットマップの背景で作業している場合にうまく機能しますが、私の場合、にBaseAdapterのセットのを提供していImageViewましたGridViewunbindDrawables()アドバイスに従ってメソッドを変更したので、条件は次のようになります。

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();
  }
}

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


12

Androidのメモリ管理に関するGoogle I / Oの優れた講演(2011)、およびメモリプロファイリングのツールと手法の詳細:http : //www.youtube.com/watch? v
=_CruQY55HOk


4
または対応するブログ記事:android-developers.blogspot.com/2011/03/...
greg7gkb

1
わかりましたので、私はユーザーにアクティビティを常に切り替えています。つまり、20秒で15のアクティビティを切り替えた可能性があります。それがメモリ不足エラーの原因ですか?修正するにはどうすればよいですか?ありがとう!」
Ruchir Baronia


1

まあ、それらはAndroidが使用する独自の形式にフックするツールです。私はあなたが満足していないかもしれないものは、使用中の基になるテストコードフレームワークだと思います。

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


1
それほど多くはありませんが、その性質のテストは、アプリケーションの実行中に実際に何が起こっているかを記録することほど問題ではありません。私が本当に必要なのは、リソース/メモリリークプロファイリングツールです
jottos
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.