私は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()
で呼び出されると、これによりフィールドの値が復元されます(方向が変更される前のように)。これにより、内部クラスの状態も復元されますか?
私の質問はあまり正確ではないことは承知していますが、物事を明確にする説明があれば本当に感謝しています。
あなたが求めているのはかなり難しい質問です。あなたはそれがただ一つの質問だと思うかもしれませんが、実際には一度にいくつかの質問をしています。私はそれをカバーしなければならないという知識で最善を尽くし、できれば、他の人が私が見逃しているものをカバーするために参加することを願っています。
ネストされたクラス:はじめに
JavaのOOPにどれだけ慣れているかわからないので、これはいくつかの基本事項にぶつかります。ネストされたクラスは、クラス定義が別のクラス内に含まれている場合です。基本的に、静的ネストクラスと内部クラスの2つのタイプがあります。これらの本当の違いは次のとおりです。
ガベージコレクションと内部クラス
ガベージコレクションは自動的に行われますが、オブジェクトが使用されていると思われるかどうかに基づいてオブジェクトを削除しようとします。ガベージコレクターは非常にスマートですが、完璧ではありません。オブジェクトへのアクティブな参照があるかどうかによってのみ、何かが使用されているかどうかを判断できます。
ここでの本当の問題は、内部クラスがコンテナよりも長く存続している場合です。これは、包含クラスへの暗黙的な参照が原因です。これが発生する唯一の方法は、包含クラスの外側のオブジェクトが、包含オブジェクトに関係なく、内部オブジェクトへの参照を保持する場合です。
これにより、内部オブジェクトは(参照を介して)生きているが、それを含むオブジェクトへの参照が他のすべてのオブジェクトから既に削除されている状況につながる可能性があります。したがって、内部オブジェクトは、alwaysへの参照を持つため、包含オブジェクトを存続させます。これに関する問題は、プログラムされていない限り、包含オブジェクトに戻って、それが生きているかどうかをチェックする方法がないことです。
この実現の最も重要な側面は、アクティビティ内にあるかドローアブルであるかにかかわらず、違いがないことです。 alwaysは、内部クラスを使用するときは整然としていて、コンテナのオブジェクトよりも長く存続しないようにする必要があります。幸いなことに、それがコードのコアオブジェクトでない場合、リークは比較的少ないかもしれません。残念ながら、これらのリークは、それらの多くがリークするまで気付かれない可能性があるため、見つけるのが最も難しいリークの一部です。
解決策:内部クラス
アクティビティとビュー:はじめに
アクティビティには、実行および表示できるようにするための多くの情報が含まれています。アクティビティは、ビューが必要な特性によって定義されます。また、特定の自動ハンドラーもあります。指定するかどうかに関係なく、アクティビティには含まれるビューへの暗黙的な参照があります。
ビューを作成するには、ビューを作成する場所と、表示できるように子があるかどうかを知る必要があります。これは、すべてのビューがアクティビティへの参照を持っていることを意味します(getContext()
経由)。さらに、すべてのビューはその子(つまりgetChildAt()
)への参照を保持します。最後に、各ビューは、表示を表すレンダリングされたビットマップへの参照を保持します。
アクティビティ(またはアクティビティコンテキスト)への参照があるときはいつでも、これはレイアウト階層全体でチェーン全体をたどることができることを意味します。これが、アクティビティまたはビューに関するメモリリークが非常に大きい理由です。 tonのメモリがすべて同時にリークされている可能性があります。
アクティビティ、ビュー、および内部クラス
内部クラスに関する上記の情報を考えると、これらは最も一般的なメモリリークですが、最も一般的に回避されます。内部クラスにActivitiesクラスメンバへの直接アクセスを持たせることが望ましいが、多くの場合、潜在的な問題を回避するために静的クラスにするだけです。アクティビティとビューの問題は、それよりもはるかに深刻です。
リークされたアクティビティ、ビュー、アクティビティコンテキスト
それはすべてコンテキストとライフサイクルに帰着します。アクティビティコンテキストを強制終了する特定のイベント(方向など)があります。非常に多くのクラスとメソッドがコンテキストを必要とするため、開発者はコンテキストへの参照を取得して保持することでコードを保存しようとすることがあります。アクティビティを実行するために作成しなければならないオブジェクトの多くは、アクティビティが必要なことを実行できるようにするために、アクティビティライフサイクルの外側に存在する必要があります。オブジェクトが破壊されたときにアクティビティ、コンテキスト、またはビューのいずれかを参照している場合、そのアクティビティとそのビューツリー全体がリークされています。
ソリューション:アクティビティとビュー
getBaseContext()
またはgetApplicationContext()
)を使用します。これらは暗黙的に参照を保持しません。実行可能ファイル:はじめに
Runnableは実際にはそれほど悪くはありません。つまり、彼らはcouldでしたが、実際にはすでにほとんどの危険地帯を襲っています。 Runnableは、作成されたスレッドから独立したタスクを実行する非同期操作です。ほとんどの実行可能ファイルはUIスレッドからインスタンス化されます。本質的に、Runnableを使用すると、もう少し管理された別のスレッドが作成されます。標準クラスのようなRunnableをクラス化し、上記のガイドラインに従う場合、いくつかの問題が発生するはずです。現実には、多くの開発者はこれを行いません。
使いやすさ、読みやすさ、論理的なプログラムフローから、多くの開発者は匿名内部クラスを利用して、上記で作成した例のようにRunnableを定義します。これにより、上記で入力したような例が表示されます。匿名内部クラスは、基本的に個別の内部クラスです。まったく新しい定義を作成する必要はなく、適切なメソッドをオーバーライドするだけです。他のすべての点では、内部クラスです。つまり、コンテナへの暗黙的な参照を保持します。
実行可能ファイルとアクティビティ/ビュー
わーい!このセクションは短くすることができます! Runnablesは現在のスレッドの外部で実行されるという事実により、これらの危険性は、長時間実行される非同期操作になります。実行可能ファイルがアクティビティまたはビューで匿名の内部クラスORネストされた内部クラスとして定義されている場合、いくつかの非常に重大な危険があります。これは、前述のように、コンテナが誰であるかを知るためにhasであるためです。方向の変更(またはシステムの強制終了)を入力します。さて、前のセクションに戻って、何が起こったのかを理解してください。はい、あなたの例は非常に危険です。
ソリューション:Runnables
最終質問への回答ではない質問に答えます直接この投稿の他のセクションで対処しました。 「内側のクラスのオブジェクトは、外側のクラスよりも長く生き残ることができるのはいつですか?」これに着手する前に、強調しておきましょう。アクティビティでこれを心配するのは正しいことですが、どこでもリークを引き起こす可能性があります。デモのために、単純な例を(アクティビティを使用せずに)提供します。
以下は、基本的なファクトリの一般的な例です(コードがありません)。
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つの非常に一般的な例です。その外側のクラスがアクティビティであった場合、それがどれほど悪化していたかを想像してください。
結論
これらは、これらのオブジェクトを不適切に使用することの主な既知の危険性をリストしています。一般的に、この投稿はあなたの質問のほとんどをカバーしているはずですが、それが長すぎる投稿であると理解しているので、説明が必要な場合はお知らせください。上記の慣行に従う限り、漏れの心配はほとんどありません。