(匿名)内部クラスを使用するのは正確にいつリーク安全ですか?


324

私はAndroidでのメモリリークに関する記事をいくつか読んでおり、この件に関するGoogle I / Oからのこの興味深いビデオを見ました

それでも、概念を完全には理解していません。特に、Activity内の内部クラスをユーザーが安全または危険にさらす場合はそうです。

これは私が理解したことです:

内部クラスのインスタンスが外部クラス(アクティビティ)よりも長く存続すると、メモリリークが発生します。-> どのような状況でこれが起こりますか?

この例では、拡張OnClickListenerする匿名クラスがアクティビティよりも長く存続する方法がないため、リークのリスクはないと思います。

    final Dialog dialog = new Dialog(this);
    dialog.setContentView(R.layout.dialog_generic);
    Button okButton = (Button) dialog.findViewById(R.id.dialog_button_ok);
    TextView titleTv = (TextView) dialog.findViewById(R.id.dialog_generic_title);

    // *** Handle button click
    okButton.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            dialog.dismiss();
        }
    });

    titleTv.setText("dialog title");
    dialog.show();

さて、この例は危険ですか、なぜですか?

// We are still inside an Activity
_handlerToDelayDroidMove = new Handler();
_handlerToDelayDroidMove.postDelayed(_droidPlayRunnable, 10000);

private Runnable _droidPlayRunnable = new Runnable() { 
    public void run() {
        _someFieldOfTheActivity.performLongCalculation();
    }
};

このトピックを理解することは、アクティビティが破棄されて再作成されたときに保持される内容を詳細に理解することと関係があるという事実に疑問があります。

それは...ですか?

デバイスの向きを変更したとしましょう(リークの最も一般的な原因です)。ときsuper.onCreate(savedInstanceState)、私の中で呼び出されますonCreate()(彼らは姿勢変更前あったように)、これはフィールドの値を復元しますか?これは内部クラスの状態も復元しますか?

私の質問はそれほど正確ではないことに気づきましたが、物事をより明確にすることができるどんな説明にも本当に感謝します。


14
このブログ投稿このブログ投稿には、メモリリークと内部クラスに関するいくつかの良い情報があります。:)
Alex Lockwood 2014年

回答:


651

あなたが求めているのはかなり難しい質問です。たった1つの質問だと思うかもしれませんが、実際には一度に複数の質問をしていることになります。私はそれをカバーしなければならないという知識で最善を尽くします、そしてうまくいけば、私が逃したかもしれないものをカバーするために他の何人かが参加します。

ネストされたクラス:はじめに

JavaでのOOPがどれほど快適かわからないので、これはいくつかの基本事項に当てはまります。ネストされたクラスとは、クラス定義が別のクラス内に含まれている場合です。基本的に2つのタイプがあります:静的なネストされたクラスと内部クラスです。これらの実際の違いは次のとおりです。

  • 静的ネストクラス:
    • 「トップレベル」と見なされます。
    • 包含クラスのインスタンスを作成する必要はありません。
    • 明示的な参照がないと、含まれているクラスメンバーを参照できません。
    • 自分の寿命を持っています。
  • 内部入れ子クラス:
    • 常に包含クラスのインスタンスを構築する必要があります。
    • 自動的に、含まれているインスタンスへの暗黙の参照があります。
    • 参照なしでコンテナのクラスメンバーにアクセスできます。
    • 寿命は、コンテナの寿命よりも長くないと想定されています。

ガベージコレクションと内部クラス

ガベージコレクションは自動ですが、使用されていると見なされるかどうかに基づいてオブジェクトを削除しようとします。ガベージコレクターはかなりスマートですが、完璧ではありません。オブジェクトへのアクティブな参照があるかどうかによってのみ、何かが使用されているかどうかを判断できます。

ここでの実際の問題は、内部クラスがそのコンテナーよりも長く存続している場合です。これは、包含クラスへの暗黙的な参照が原因です。これが発生する可能性がある唯一の方法は、包含クラスの外側のオブジェクトが、包含オブジェクトに関係なく、内部オブジェクトへの参照を保持している場合です。

これにより、内部オブジェクトは(参照​​を介して)存続しているが、それを含むオブジェクトへの参照は他のすべてのオブジェクトからすでに削除されているという状況につながる可能性があります。したがって、内部オブジェクトは常に包含オブジェクトを存続させます。参照してさせます。これに関する問題は、プログラムされていない限り、それが生きているかどうかを確認するために、包含オブジェクトに戻る方法がないことです。

この実現の最も重要な側面は、アクティビティ内にあるかドローアブルであるかに関係なく、違いがないことです。内部クラスを使用するときは常に系統立てて、それらがコンテナーのオブジェクトより長く存続しないようにする必要があります。幸いなことに、それがコードのコアオブジェクトでない場合、リークは比較的小さい可能性があります。残念ながら、これらのリークは見つけるのが最も難しいものの一部です。それらの多くは、リークが発生するまで気付かれない可能性があるためです。

ソリューション:内部クラス

  • 含まれているオブジェクトから一時的な参照を取得します。
  • 含まれているオブジェクトが、内部オブジェクトへの長期間有効な参照を保持する唯一のオブジェクトになるようにします。
  • Factoryなどの確立されたパターンを使用します。
  • 内部クラスが包含クラスのメンバーへのアクセスを必要としない場合は、それを静的クラスに変換することを検討してください。
  • アクティビティ内かどうかに関係なく、注意して使用してください。

アクティビティとビュー:はじめに

アクティビティには、実行および表示できるようにするための多くの情報が含まれています。アクティビティは、ビューを持つ必要があるという特性によって定義されます。また、特定の自動ハンドラーもあります。指定するかどうかに関係なく、アクティビティには含まれるビューへの暗黙的な参照があります。

ビューを作成するには、ビューを作成できる場所と、ビューを表示できるように子があるかどうかを知る必要があります。これは、すべてのビューが(を介してgetContext())アクティビティへの参照を持つことを意味します。さらに、すべてのビューはその子(つまりgetChildAt())への参照を保持します。最後に、各ビューは、その表示を表すレンダリングされたビットマップへの参照を保持します。

アクティビティ(またはアクティビティコンテキスト)への参照がある場合は常に、レイアウト階層全体でチェーン全体をたどることができます。これが、アクティビティまたはビューに関するメモリリークが非常に大きな原因である理由です。大量のメモリが一度にリークされる可能性があります。

アクティビティ、ビュー、内部クラス

上記の内部クラスに関する情報を考慮すると、これらは最も一般的なメモリリークですが、最も一般的には回避されています。内部クラスがアクティビティクラスのメンバーに直接アクセスできるようにするのが望ましいですが、潜在的な問題を回避するために、多くの人はそれらを静的にしたいと思っています。アクティビティとビューの問題は、それよりもさらに深くなります。

漏洩したアクティビティ、ビュー、アクティビティコンテキスト

すべては、コンテキストとライフサイクルに起因します。アクティビティコンテキストを強制終了する特定のイベント(向きなど)があります。非常に多くのクラスとメソッドがコンテキストを必要とするため、開発者は、コンテキストへの参照を取得して保持することにより、コードを保存しようとする場合があります。たまたま、アクティビティを実行するために作成する必要があるオブジェクトの多くは、アクティビティが必要なことを実行できるようにするために、アクティビティライフサイクルの外部に存在する必要があります。オブジェクトが破棄されたときに、アクティビティ、そのコンテキスト、またはビューのいずれかへの参照が発生している場合、そのアクティビティとそのビューツリー全体がリークされています。

ソリューション:アクティビティとビュー

  • ビューまたはアクティビティへの静的参照を作成することは、絶対に避けてください。
  • アクティビティコンテキストへのすべての参照は、有効期間が短い必要があります(関数の期間)
  • 長期間有効なコンテキストが必要な場合は、アプリケーションコンテキスト(getBaseContext()またはgetApplicationContext())を使用します。これらは暗黙的に参照を保持しません。
  • または、構成の変更を上書きして、アクティビティの破棄を制限することもできます。ただし、これによって他の潜在的なイベントがアクティビティを破壊するのを防ぐことはできません。これ行うことはできます、上記のプラクティスを参照することもできます。

Runnables:はじめに

ランナブルは実際にはそれほど悪くありません。私が意味する、彼らは可能性があることが、実際に我々はすでに危険区域のほとんどをヒットしました。Runnableは、作成されたスレッドに依存しないタスクを実行する非同期操作です。ほとんどの実行可能ファイルは、UIスレッドからインスタンス化されます。本質的に、Runnableを使用すると、もう少し管理された別のスレッドが作成されます。標準クラスのようにRunnableをクラス化し、上記のガイドラインに従っている場合、いくつかの問題が発生するはずです。多くの開発者がこれを行わないのが現実です。

使いやすさ、読みやすさ、論理的なプログラムフローから、多くの開発者は匿名インナークラスを使用して、上記で作成した例のように、ランナブルを定義します。これにより、上記で入力したような例が表示されます。匿名内部クラスは、基本的には個別の内部クラスです。まったく新しい定義を作成する必要はなく、単に適切なメソッドをオーバーライドするだけです。他のすべての点では、それは内部クラスです。つまり、コンテナへの暗黙的な参照を保持します。

ランナブルとアクティビティ/ビュー

わーい!このセクションは短い場合があります!Runnableは現在のスレッドの外部で実行されるため、非同期操作の実行時間が長くなるという危険があります。ランナブルがアクティビティまたはビューで匿名の内部クラスまたはネストされた内部クラスとして定義されている場合、いくつかの非常に深刻な危険があります。これは、前述のように、コンテナが誰であるかを知る必要があるためです。向きの変更(またはシステムの強制終了)を入力します。ここで、前のセクションに戻って、何が起こったのかを理解してください。はい、あなたの例は非常に危険です。

ソリューション:Runnables

  • コードのロジックを壊さない場合は、Runnableを拡張してみてください。
  • ネストされたクラスでなければならない場合は、拡張Runnableを静的にするために最善を尽くしてください。
  • 匿名ランナブルを使用する必要がある場合は、使用中のアクティビティまたはビューへの長期間有効な参照を持つオブジェクトでそれらを作成しないでください。
  • 多くのRunnableは、AsyncTasksと同じくらい簡単にできました。AsyncTaskを使用することを検討してください。これらはデフォルトでVM管理されるためです。

最後の質問 への回答ここで、この投稿の他のセクションでは直接取り上げられなかった質問に回答ます。「内部クラスのオブジェクトが外部クラスよりも長く存続できるのはいつですか?」これを説明する前に、もう一度強調しておきます。アクティビティでこれを心配するのは正しいことですが、どこにでもリークが発生する可能性があります。簡単に説明するために、アクティビティを使用しない簡単な例を示します。

以下は、基本的なファクトリの一般的な例です(コードがありません)。

public class LeakFactory
{//Just so that we have some data to leak
    int myID = 0;
// Necessary because our Leak class is an Inner class
    public Leak createLeak()
    {
        return new Leak();
    }

// Mass Manufactured Leak class
    public class Leak
    {//Again for a little data.
       int size = 1;
    }
}

これは一般的な例ではありませんが、十分に簡単に説明できます。ここでの鍵はコンストラクタです...

public class SwissCheese
{//Can't have swiss cheese without some holes
    public Leak[] myHoles;

    public SwissCheese()
    {//Gotta have a Factory to make my holes
        LeakFactory _holeDriller = new LeakFactory()
    // Now, let's get the holes and store them.
        myHoles = new Leak[1000];

        for (int i = 0; i++; i<1000)
        {//Store them in the class member
            myHoles[i] = _holeDriller.createLeak();
        }

    // Yay! We're done! 

    // Buh-bye LeakFactory. I don't need you anymore...
    }
}

現在、リークはありますが、ファクトリはありません。ファクトリーをリリースしても、すべてのリークはそれを参照しているため、メモリに残ります。外部クラスにデータがなくても問題ありません。これは、考えられるよりはるかに頻繁に発生します。クリエーターは必要ありません。クリエーションだけが必要です。そのため、一時的に作成しますが、無期限に使用します。

コンストラクタを少しだけ変更するとどうなるか想像してみてください。

public class SwissCheese
{//Can't have swiss cheese without some holes
    public Leak[] myHoles;

    public SwissCheese()
    {//Now, let's get the holes and store them.
        myHoles = new Leak[1000];

        for (int i = 0; i++; i<1000)
        {//WOW! I don't even have to create a Factory... 
        // This is SOOOO much prettier....
            myHoles[i] = new LeakFactory().createLeak();
        }
    }
}

現在、これらの新しいLeakFactoriesのすべてがリークされています。あなたはそれをどう思いますか?これらは、内部クラスがどのタイプの外部クラスよりも長生きすることができるかについての2つの非常に一般的な例です。その外部クラスがアクティビティであった場合、それがどれほど悪化したかを想像してください。

結論

これらは、これらのオブジェクトを不適切に使用することの主な既知の危険性を示しています。一般的に、この投稿はあなたの質問のほとんどをカバーしているはずですが、私はそれが大げさな投稿であったことを理解しています。上記の慣例に従っている限り、漏洩の心配はほとんどありません。


3
この明確で詳細な回答をありがとうございました。私はちょうどあなたが「多くの開発者が自分のランナブルを定義するためにクロージャを利用」によって何を意味するかを取得しない
セバスチャン・

1
Javaのクロージャーは、あなたが説明するRunnableのように、匿名の内部クラスです。Runnableを拡張する定義済みのClassを作成せずに、クラスを利用する(ほとんど拡張する)方法です。これは、実際の包含オブジェクト内に独自の閉じたメモリ空間を持つという点で「閉じたクラス定義」であるため、クロージャと呼ばれます。
Fuzzical Logic

26
啓発記事!用語に関する1つの注釈:Javaには静的内部クラスなどはありません。(ドキュメント)。ネストされたクラスはstaticまたはinnerのいずれかですが、同時に両方にすることはできません。
jenzz 2013年

2
技術的には正しいのですが、Javaでは静的クラス内に静的クラスを定義できます。この用語は私の利益のためではなく、技術的な意味論を理解していない他の人のためのものです。これが、それらが「トップレベル」であると最初に言及された理由です。Android開発者のドキュメントでもこの用語が使用されており、これはAndroid開発を検討している人を対象としているため、一貫性を保つことをお勧めします。
Fuzzical Logic

13
StackOverflowで最高の1つであるesp for Androidです。
StackOverflowed 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.