序文
みなさん、こんにちは。Tencentの「簡単な」インタビューの質問をしたいと思います。
Handler
メモリリークの原因は何ですか?
どのように答えますか?
そんな面接の質問を見ると、多くの友達が安堵のため息をつくと思います。
単に、それが「ない内部クラスは、外クラスを参照保持 Hanlder
手持ち Activity
リサイクルされることが不可能参照を、。」
しかし、あなたの答えが要点に答えなかったか、ある程度間違っていたことをお伝えしたいと思います。
なんでそんなこと言うの?
実際、Handler
メモリリークの原因を理解したい ですか?まず、メモリリークとは何かを理解する必要がありますか?その理由については、この記事の後半で説明します。これはばかげたパズルです。
メモリーリーク
Java
到達可能性分析のアルゴリズムは、オブジェクトをリサイクルできるかどうかを判断するために仮想マシンで使用されます。つまり、GCRoot
オブジェクトを開始点として、通過したパス(参照チェーン)を検索し、オブジェクトまたはオブジェクトのグループに到達できないことが判明した場合は、リサイクルされます。
これ内存泄漏
は、役に立たない一部のオブジェクト(短期間オブジェクト)を参照しますが、他の有用なクラス(長期間オブジェクト)によって参照されるため、役に立たないオブジェクトがメモリスペースを占有し、メモリリークを形成します。
したがって、誰が内部类持有了外部类的引用
内部クラスを参照するかを指定せずに上記の質問に答えるだけでは、内部クラスと外部クラスは役に立たないオブジェクトであり、使用できるため、メモリリークは論理的に発生しません正常回收
。
したがって、この質問の鍵は、内部クラスが谁
参照されているということですか?つまり、ハンドラーを参照しているのは誰ですか?
一緒に練習して勉強しましょう〜
ハンドラメモリリーク
1.遅延メッセージを送信する
最初のケースはhandler
、遅延メッセージを送信することです。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_handler)
btn.setOnClickListener {
//跳转到HandlerActivity
startActivity(Intent(this, HandlerActivity::class.java))
}
}
}
class HandlerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_handler2)
//发送延迟消息
mHandler.sendEmptyMessageDelayed(0, 20000)
btn2.setOnClickListener {
finish()
}
}
val mHandler = object : Handler() {
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
btn2.setText("2222")
}
}
}
私たちは、あるHandlerActivity
メッセージ遅れて20代を送ることに。開封HandlerActivity
後、すぐに終了してください。メモリリークがあるかどうかを確認します。
メモリリークを表示して分析する
これで、メモリリークをチェックするのに非常に便利です。AndroidStudio
ヒープダンプファイルの分析が付属しており、メモリリークポイントが明確にマークされます。
プロジェクトを実行し、をクリックProfiler——Memory
すると、実行中のメモリのリアルタイム画像である次の画像が表示されます。
写真でマークしたボタンが2つあることがわかります。
捕获堆转储文件按钮
、つまり、hprofファイルを生成します。このファイルには、Javaヒープの使用状況が表示されます。このボタンをクリックすると、AndroidStudioがこのヒープダンプファイルの生成と分析に役立ちます。GC按钮
、通常、ヒープダンプファイルをキャプチャする前に、GCをクリックして弱参照を再利用し、分析への干渉を防ぐことができます。
したがってHandlerActivity
、開いた後すぐfinish
に、[GC]ボタンをクリックしてから、[ヒープダンプファイルのキャプチャ]ボタンをクリックします。AndroidStudio
次のインターフェイスに自動的にジャンプします。
左上隅に1つあることがわかりますLeaks
。これがメモリリークのポイントです。クリックしてメモリリーククラスを確認してください。右下隅は、メモリリーククラスの参照パスです。
この図からHandlerActivity
、メモリリークが発生していることがわかります。参照パスから、匿名内部クラスのインスタンスmHandler
によって参照されHandler
、参照がMessage
保持され、Message
参照がMessageQueue
保持されます。
学習したハンドラーの知識とこの参照パス分析を組み合わせると、このメモリリークの完全な参照チェーンは次のようになります。
主線程—>スレッドローカル—>ルーパー—>メッセージキュー—>メッセージ—>ハンドラー—>アクティビティ
したがって、今回の引用で头头
は主线程
、メインスレッドは运行中的线程
、JVMによって静态变量
特別に処理されるのと同じように、JVMによってリサイクルされない限り、絶対にリサイクルされません。
今回のメモリリークの原因が明らかになりました。もちろん、Handler
メモリリークはこれに限定されません。2番目の状況を見てみましょう。
2.子スレッドが終了しない
2番目の例は、ネットワークのリクエストなど、子スレッドで作業し、リクエストが成功した後にHandler
UIを更新する、よく使用するものです。
class HandlerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_handler2)
//运行中的子线程
thread {
Thread.sleep(20000)
mHandler.sendEmptyMessage(0)
}
btn2.setOnClickListener {
finish()
}
}
val mHandler = object : Handler() {
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
btn2.setText("2222")
}
}
}
同じ操作後のメモリリークを確認します。
ここでのメモリリークの主な理由は、运行中的子线程
子スレッド匿名内部类
が外部クラスへの参照を保持し、子スレッド自体が常に実行されているためであることがわかります。現在、実行中のスレッドはリサイクルされません。したがって、ここでのメモリリーク引用链
は次のようになります。
子スレッドの実行—>アクティビティ
もちろん、ここでの参照Handler
も保持されActivity
ますが、メモリリークの主な原因は、子スレッドが使用されていない場合でも、子スレッド自体です。Handler
呼び出されたActivity
他の変数またはメソッドは、メモリをリークします。
したがって、この状況はHandler
メモリリークの原因とは言えないと思います。根本的な原因は子スレッドです。Activity
子スレッドが破壊されたときに停止するなど、子スレッドのメモリリークが解決されれば、それがActivity
正常にリサイクルすることができる。そして、何もありませんHandler
問題。
拡張質問1:内部クラスが外部クラスへの参照を保持するのはなぜですか
これは、内部クラスと外部クラスは同じファイルに書き込まれclass
ますが、コンパイル後に異なるファイルが生成されるためです。内部クラスのコンストラクターは外部クラスのインスタンスを渡しthis$0
、のメンバーにアクセスできるようになります。外側のクラス。
実際、外部クラスのメソッドと変数は内部クラスで呼び出すことができるため、非常に理解しやすく、外部クラスの参照を確実に保持します。
コンパイル後に内部クラスJD-GUI
を表示するためのclass
コードを投稿してください。理解しやすいかもしれません。
//原代码
class InnerClassOutClass{
class InnerUser {
private int age = 20;
}
}
//class代码
class InnerClassOutClass$InnerUser {
private int age;
InnerClassOutClass$InnerUser(InnerClassOutClass var1) {
this.this$0 = var1;
this.age = 20;
}
}
拡張質問2:KotlinとJavaの内部クラスに違いはありますか?
実際、上記のコードで、私が文を追加したことがわかります
btn2.setText("2222")
これは、kotlin
そこ匿名内部类
に2つの状況は次のとおりです。
在Kotlin中
、匿名内部クラスが外部クラスのオブジェクト参照を使用しない場合、外部クラスのオブジェクト参照を保持しません。現時点では、匿名内部クラスは実際には1つ静态匿名内部类
であり、メモリリークは発生しません。在Kotlin中
、匿名の内部クラスが、私が使用したように外部クラスへの参照を使用する場合、btn2
この時点で外部クラスへの参照を保持するため、考慮する必要のある内存泄漏
問題があります。
そこで这一句
、意図的に追加しました。メモリリークの問題を再現するために、匿名の内部クラスに外部クラスの参照を保持させます。
同じことkotlin
が内部クラスでも異なりますJava
:
- Kotlinのすべての内部クラスはデフォルトで静的です。つまり、です
静态内部类
。 - 外部オブジェクトメソッドを呼び出す必要がある
inner
場合は、それを変更してJavaと同じ内部クラスに変更する必要があります。このメソッドは外部クラスへの参照を保持するため、メモリリークを考慮する必要があります。
メモリリークを解決する
そうは言っても、どうすれば内存泄漏
問題を解決できますか?実際、メモリリークのすべての解決策は、主に次のように類似しています。
- せてはいけません
长生命周期对象
開催された短生命周期对象
参照を、しかし使用长生命周期对象
手持ち长生命周期对象
の参照を。
たとえばGlide
、Activity
使用時に渡されたApplication
コンテキストは使用せず、代わりにコンテキストを使用します。Activity
コンテキストを渡さないシングルトンモードもあります。
- オブジェクトの強い参照をに変更します
弱引用
强引用
オブジェクトが強く参照された後は、とにかくリサイクルされません。弱引用
つまり、ガベージコレクション中に、オブジェクトが弱い参照のみに関連付けられている場合(強い参照が関連付けられていない場合)、オブジェクトはリサイクルされます。软引用
つまり、システムがメモリをオーバーフローすると、システムはリサイクルされます。虚引用
寿命にまったく影響を与えないオブジェクトであり、あまり使用されていない仮想参照を介してオブジェクトインスタンスを取得することもできません。
そのため、オブジェクトを弱参照に変更して、次のようにHandler
渡されActivity
た弱参照インスタンスなど、ガベージコレクション中に正常にリサイクルされるようにします。
MyHandler(WeakReference(this)).sendEmptyMessageDelayed(0, 20000)
//kotlin中内部类默认为静态内部类
class MyHandler(var mActivity: WeakReference<HandlerActivity>):Handler(){
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
mActivity.get()?.changeBtn()
}
}
- 内部クラスは静的クラスまたは外部クラスとして記述されます
上記のHanlder
状況と同様に、内部クラスが不適切に使用され、メモリリークが発生しやすい場合があります。解決策は、外部クラスまたは静的内部クラスを作成することです。
- 短いサイクルの終わりに発生する可能性のあるメモリリークを削除します
たとえばHandler
、メッセージの遅延、リソースが閉じられていない、コレクションがクリーンアップされていないなどの原因によるメモリリークはActivity
、シャットダウン時に排除できます。
@Override
protected void onDestroy() {
//移除handler所有消息
if(mHanlder != null){
mHandler.removeCallbacksAndMessages(null)
}
super.onDestroy();
}
総括する
ハンドラメモリリークの原因は何ですか?
Handler
メモリリークは通常、遅延メッセージが送信されたときに発生します。Activity
閉じられたとき、遅延メッセージは送信されていません。メインスレッドMessageQueue
はメッセージの参照を保持し、このメッセージは保持されたHandler
参照でありhandler
、匿名の内部としてclassActivity
参照を保持するため、次の参照チェーンがあります。
主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity
これ根本原因
は、この参照チェーンの先頭、つまりが主线程
リサイクルされないため、アクティビティをリサイクルできず、メモリリークが発生し、ハンドラーはヒューズとしか見なされないためです。
また、通常、Handler
更新UIを介して子スレッドを使用します。その理由は、実行中の子スレッドが回復されず、子スレッドがActiivty
参照を保持しているため(そうでない場合は呼び出さActivity
れないHandler
ため)、メモリリークが発生しますが、これの主な理由は、子スレッド自体です。
したがって、2つの状況を組み合わせると、メモリリークの場合、Handler
どちらも原因とは見なされません。原因(根本原因)はその主なスレッドです。
私について
私は6年の開発経験を持つハンサムなAndroid包囲ライオンです。読んだ後は、それを気に入って習慣を身に付けることを忘れないでください。WeChat検索「ProgrammingApe Development Center」は、乾物を書くのが好きなこのプログラマーに注目しています。
また、Androidファーストラインインタビューの完全なテストサイトのPDFを整理して収集するのに2年かかりました。情報[フルバージョン]が私の[Github]で更新されました。インタビューが必要な友達はそれを参照できます。それはあなたを助けます、あなたは星をクリックすることができます!
インターネット企業のAndroidインタビューの知識ポイントの概要:https://github.com/733gh/xiongfan