インタビューでひどく尋ねられたハンドラー、あなたは理解していますか?

序文

みなさん、こんにちは。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番目の例は、ネットワークのリクエストなど、子スレッドで作業し、リクエストが成功した後にHandlerUI更新する、よく使用するものです。

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と同じ内部クラス変更する必要があります。このメソッドは外部クラスへの参照を保持するため、メモリリークを考慮する必要があります。

メモリリークを解決する

そうは言っても、どうすれば内存泄漏問題を解決できますか?実際、メモリリークのすべての解決策は、主に次のように類似しています。

  • せてはいけません长生命周期对象開催された短生命周期对象参照を、しかし使用长生命周期对象手持ち长生命周期对象の参照を。

たとえばGlideActivity使用時に渡された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

 

おすすめ

転載: blog.csdn.net/qq_39477770/article/details/111639305