(匿名の)内部クラスを使用するとリークが安全になるのはいつですか? 質問する

(匿名の)内部クラスを使用するとリークが安全になるのはいつですか? 質問する

私は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()、フィールドの値は (向きの変更前の状態) 復元されますか? これにより、内部クラスの状態も復元されますか?

私の質問があまり正確ではないことは承知していますが、物事をより明確にする説明があれば本当にありがたいです。

ベストアンサー1

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

ネストされたクラス: 概要

Java の OOP にどの程度慣れているかはわかりませんので、ここではいくつかの基本事項について説明します。ネストされたクラスとは、クラス定義が別のクラス内に含まれているクラスです。基本的に、静的ネストされたクラスと内部クラスの 2 つの種類があります。これらの実際の違いは次のとおりです。

  • 静的ネストクラス:
    • 「トップレベル」とみなされます。
    • 包含クラスのインスタンスの構築を必要としません。
    • 明示的な参照なしに含まれているクラス メンバーを参照することはできません。
    • それぞれの生涯を持ちます。
  • 内部にネストされたクラス:
    • 常に、包含クラスのインスタンスを構築する必要があります。
    • 包含インスタンスへの暗黙的な参照を自動的に保持します。
    • 参照なしでコンテナのクラス メンバーにアクセスできます。
    • 寿命はコンテナの寿命よりも長くならないはずです。

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

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

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

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

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

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

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

活動と見解: はじめに

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

View を作成するには、View をどこに作成するか、また、View に表示できる子があるかどうかを知る必要があります。つまり、すべての View には Activity への参照 ( 経由getContext()) があります。さらに、すべての View にはその子への参照 ( などgetChildAt()) が保持されます。最後に、各 View には、その表示を表すレンダリングされた Bitmap への参照が保持されます。

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

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

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

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

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

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

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

ランナブルズ: はじめに

Runnable は実際にはそれほど悪くありません。つまり、そうである可能性はありますが、実際にはすでにほとんどの危険領域に達しています。Runnable は、作成されたスレッドから独立してタスクを実行する非同期操作です。ほとんどの Runnable は UI スレッドからインスタンス化されます。本質的に、Runnable を使用することは、少し管理が進んだ別のスレッドを作成することです。Runnable を標準クラスのようにクラス化し、上記のガイドラインに従えば、問題はほとんど発生しないはずです。現実には、多くの開発者がこれを実行していません。

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

実行可能項目とアクティビティ/ビュー

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

ソリューション: 実行可能

  • コードのロジックが壊れない場合は、Runnable を拡張してみてください。
  • 拡張された Runnable をネストされたクラスにする必要がある場合は、できるだけ静的にしてください。
  • Anonymous Runnable を使用する必要がある場合は、使用中の Activity または View への長期参照を持つオブジェクト内で Anonymous 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...
    }
}

これで、Leak はありますが、Factory はありません。Factory をリリースしたにもかかわらず、すべての Leak が Factory を参照しているため、Factory はメモリ内に残ります。外部クラスにデータがないことは問題ではありません。これは、想像するよりもはるかに頻繁に発生します。必要なのは作成者ではなく、その作成物だけです。そのため、一時的に作成しますが、作成したものは無期限に使用します。

コンストラクターを少しだけ変更すると何が起こるか想像してみてください。

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 つの例です。その外部クラスが Activity であった場合、どれほどひどい状況になっていたか想像してみてください。

結論

これらは、これらのオブジェクトを不適切に使用した場合に主に知られている危険性を列挙したものです。一般的に、この投稿はほとんどの質問に回答しているはずですが、非常に長い投稿だったことは承知していますので、説明が必要な場合はお知らせください。上記の方法に従っている限り、漏洩の心配はほとんどありません。

おすすめ記事