1. ホーム
  2. java

[解決済み] 匿名)内部クラスを使用することがリークセーフとなるのは、具体的にどのような場合ですか?

2022-03-17 17:43:11

質問

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つの質問だと思うかもしれませんが、実際にはいくつかの質問を一度に投げかけているのです。私が持っている知識で最善を尽くし、できれば他の人も加わって、私が見逃していることをカバーしたいと思います。

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

JavaでのOOPにどれだけ慣れているかは分かりませんが、いくつかの基本的なことを説明します。ネストされたクラスとは、クラス定義が他のクラスの中に含まれていることです。基本的に2つのタイプがあります。静的入れ子クラスと内部クラスです。これらの本当の違いは

  • 静的なネストされたクラス。
    • トップレベルとみなされます。
    • 含むクラスのインスタンスを構築する必要がない。
    • 明示的な参照なしに、包含するクラスのメンバを参照してはならない。
    • 独自のライフタイムを持つ。
  • 内側のネストされたクラス。
    • 常に、包含するクラスのインスタンスが構築されることを要求します。
    • 自動的に内包するインスタンスへの暗黙の参照を持つ。
    • コンテナのクラスメンバに参照なしでアクセスすることができる。
    • ライフタイムは と思われる は、コンテナの長さよりも長くはありません。

ガベージコレクションとインナークラス

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

ここで本当に問題になるのは、内部クラスがそのコンテナよりも長く生き続けている場合です。これは、内包するクラスが暗黙のうちに参照されているためです。このようなことが起こるのは、コンテナクラスの外のオブジェクトが、コンテナオブジェクトに関係なく、インナーオブジェクトへの参照を保持する場合だけです。

この場合、内側のオブジェクトは(参照を介して)生きていますが、他のすべてのオブジェクトから包含オブジェクトへの参照がすでに削除されているという状況が起こり得ます。したがって、内側のオブジェクトは、包含オブジェクトを生存させ続けることになります。 常に への参照を持っています。この問題は、プログラムされていない限り、包含オブジェクトに戻り、それが生きているかどうかをチェックする方法がないことです。

この実現に最も重要な点は、Activityの中にあっても、drawableであっても違いはない、ということです。あなたは 常に インナークラスを使用する場合は、コンテナのオブジェクトよりも長持ちするように注意しなければなりません。幸いなことに、あなたのコードの中核となるオブジェクトでなければ、リークも小さく済むかもしれません。残念ながら、これらのリークは、多くのリークが発生するまで気づかれない可能性が高いため、見つけるのが最も難しいリークの一つです。

解決策 内部クラス

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

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

アクティビティには、実行と表示ができるようにするために多くの情報が含まれています。アクティビティは、ビューを持たなければならないという特性で定義されています。また、特定の自動ハンドラも持っています。指定してもしなくても、アクティビティは、それが含むViewへの暗黙の参照を持っています。

Viewを作成するためには、表示できるように、作成する場所と、子オブジェクトがあるかどうかを知っている必要があります。つまり、すべてのViewはActivityへの参照を持つことになります(via. getContext() ). さらに、すべてのViewは、その子への参照を保持しています(すなわち getChildAt() ). 最後に、各 View は、その表示を表すレンダリングされた Bitmap への参照を保持します。

Activity(またはActivity Context)への参照がある場合はいつでも、レイアウト階層の下にあるENTIREチェーンをたどることができることを意味します。このため、ActivityやViewに関するメモリーリークは、非常に大きな問題となります。となることがあります。 トン のメモリが一度にリークしてしまうのです。

アクティビティ、ビュー、インナークラス

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

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

すべてはコンテキストとライフサイクルに帰結します。Activity Contextを消滅させるイベント(オリエンテーションなど)があります。多くのクラスやメソッドがContextを必要とするため、開発者はContextへの参照を取得し、それを保持することによってコードを節約しようとすることがあります。しかし、Activityを実行するために作成しなければならない多くのオブジェクトは、Activityが必要なことを実行するために、Activity LifeCycleの外側に存在しなければならないことがあります。もし、ActivityやそのContext、Viewを参照しているオブジェクトが破壊された場合、そのActivityとViewツリー全体が流出したことになります。

解決策 アクティビティとビュー

  • ViewやActivityをStaticに参照することは、絶対に避けてください。
  • アクティビティコンテキストへの参照はすべて短命であるべきです(関数の持続時間)。
  • 長寿命の Context が必要な場合は、アプリケーション Context ( getBaseContext() または getApplicationContext() ). これらは暗黙のうちに参照を保持しない。
  • また、Configuration Changesをオーバーライドすることで、Activityの破壊を制限することができます。しかし、これは他の潜在的なイベントによるActivityの破壊を止めるものではありません。ただし できる このような場合にも、上記のプラクティスを参考にしてください。

ランナブル。はじめに

Runnablesは、実はそれほど悪いものではありません。つまり、それらは かもしれない しかし、私たちはすでにほとんどの危険地帯に到達しています。Runnableは、作成されたスレッドとは無関係にタスクを実行する非同期オペレーションである。ほとんどのRunnableは、UIスレッドからインスタンス化されます。要するに、Runnableを使うということは、もう一つのスレッドを作るということです。Runnableを標準的なクラスのように分類し、上記のガイドラインに従えば、ほとんど問題は発生しないはずです。しかし、現実には多くの開発者がこのようなことをしていません。

多くの開発者は、読みやすさと論理的なプログラムの流れから、上の例のようにAnonymous Inner Classを使用してRunnableを定義しています。その結果、上記のような例になってしまうのです。匿名インナークラスは、基本的に離散的なインナークラスです。ただ、全く新しい定義を作る必要はなく、単に適切なメソッドをオーバーライドするだけです。他のすべての点ではInner Classであり、そのコンテナへの暗黙の参照を保持することを意味します。

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

やったー! このセクションは短くてもいいんだ! Runnablesは現在のスレッドの外側で実行されるという事実のために、これらの危険は長い間実行される非同期操作に来る。もしRunnableがActivityやViewでAnonymous Inner Classやnested Inner Classとして定義されている場合、非常に深刻な危険があります。というのも、先に述べたように、それは があります。 を使えば、その入れ物が誰であるかを知ることができます。オリエンテーションの変更(またはシステムキル)を入力します。今、何が起こったのか理解するために、前のセクションを参照してください。そう、あなたの例はかなり危険なのです。

解決策 実行可能ファイル

  • コードのロジックを壊さないのであれば、Runnableを拡張してみてください。
  • ネストされたクラスでなければならない場合は、拡張 Runnables を static にするよう最善を尽くしてください。
  • 匿名Runnableを使用する必要がある場合、その作成は 任意の オブジェクトで、使用中のアクティビティまたはビューへの長期的な参照を持つもの。
  • 多くの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...
    }
}

さて、Leaksはありますが、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だったら、どんなにひどいことになっていたか想像してみてください。

結論

以上が、これらのオブジェクトを不適切に使用した場合の主な既知の危険性のリストです。一般的に、この投稿はあなたの質問のほとんどをカバーしているはずですが、長い投稿だったことを理解しています。上記のプラクティスに従う限り、漏洩の心配はほとんどないでしょう。