web-dev-qa-db-ja.com

(匿名の)内部クラスを使用するのが正確に漏れるのはいつですか?

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

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

300
Sébastien

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

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

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

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

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

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

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

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

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

解決策:内部クラス

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

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

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

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

アクティビティ(またはアクティビティコンテキスト)への参照があるときはいつでも、これはレイアウト階層全体でチェーン全体をたどることができることを意味します。これが、アクティビティまたはビューに関するメモリリークが非常に大きい理由です。 tonのメモリがすべて同時にリークされている可能性があります。

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

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

リークされたアクティビティ、ビュー、アクティビティコンテキスト

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

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

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

実行可能ファイル:はじめに

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

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

実行可能ファイルとアクティビティ/ビュー

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

ソリューション:Runnables

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

最終質問への回答ではない質問に答えます直接この投稿の他のセクションで対処しました。 「内側のクラスのオブジェクトは、外側のクラスよりも長く生き残ることができるのはいつですか?」これに着手する前に、強調しておきましょう。アクティビティでこれを心配するのは正しいことですが、どこでもリークを引き起こす可能性があります。デモのために、単純な例を(アクティビティを使用せずに)提供します。

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

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...
    }
}

リークがありますが、ファクトリはありません。 Factoryをリリースしましたが、すべてのLeakがそれを参照しているため、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つの非常に一般的な例です。その外側のクラスがアクティビティであった場合、それがどれほど悪化していたかを想像してください。

結論

これらは、これらのオブジェクトを不適切に使用することの主な既知の危険性をリストしています。一般的に、この投稿はあなたの質問のほとんどをカバーしているはずですが、それが長すぎる投稿であると理解しているので、説明が必要な場合はお知らせください。上記の慣行に従う限り、漏れの心配はほとんどありません。

613
Fuzzical Logic