日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

Handler引發的泄露(通常發生在Activity、Fragment等容器)、crash是Android開發中常見的問題,也是面試時非常容易被問到的技術點。關于Handler為何會引起容器泄露,網上有很多的文章,這里就簡單提一下引用鏈:

Thread->ThreadLocal->Looper->MessageQueue->Message->Handler->Activity

Handler產生的泄露一般是暫時的,當消息成功調度后,從消息隊列中移除,上面的引用鏈也便就不存在了,在下次gc時,Activity便可以正常釋放。因此大多情況下,Handler引起的泄露問題并不可怕(極端情況另說),可怕的是引起crash。下面重點討論下Handler如何引起crash,看個偽代碼:

class TestFragment: Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Toast.makeText(activity, "AAA", Toast.LENGTH_SHORT).show()
        Handler().postDelayed({
            Toast.makeText(activity, "BBB", Toast.LENGTH_SHORT).show()
        }, 5000)
        parentFragmentManager.beginTransaction().run {
            remove(this@TestFragment)
            commitAllowingStateLoss()
        }
    }
}

啟動TestFragment后,會先看到一條"AAA"的吐司,然后在logcat中看到如下crash日志:

JAVA.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference

at android.widget.Toast.<init>(Toast.java:121)

at android.widget.Toast.makeText(Toast.java:286)

at android.widget.Toast.makeText(Toast.java:276)

at com.ada.test_App.TestFragment.onCreate$lambda-0(MainActivity.kt:136)

at com.ada.test_app.TestFragment.$r8$lambda$ZvBB3ieIi-neYI0Ok2qP--pCPEg(Unknown Source:0)

at com.ada.test_app.TestFragment$$ExternalSyntheticLambda0.run(Unknown Source:2)

at android.os.Handler.handleCallback(Handler.java:883)

at android.os.Handler.dispatchMessage(Handler.java:100)

at android.os.Looper.loop(Looper.java:238)

at android.app.ActivityThread.main(ActivityThread.java:7798)

at java.lang.reflect.Method.invoke(Native Method)

at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)

at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:995)

原因是當Fragment生命周期結束時,會將activity對象置為null,等消息延遲調度時,取得的便是一個空的activity,因此出現了空指針異常。解決的辦法很簡單,加個空判斷就好了。然而現實中的場景會復雜很多,而且開發人員素質參差不齊,沒法保證所有場景都正確處理了,我們希望能有一套通用的解決方案。以下是筆者寫的SafeHandler,在實際項目中已經廣泛使用,是一個小而美的組件:

class SafeHandler(owner: LifecycleOwner, looper: Looper = Looper.getMainLooper()): Handler(looper), LifecycleObserver {
    private val host = WeakReference(owner)

    init {
        owner.lifecycle.addObserver(this)
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    fun onDestroy() {
        removeCallbacksAndMessages(null)
    }

    override fun dispatchMessage(msg: Message) {
        val owner = host.get()
        if (owner != null && owner.lifecycle.currentState != Lifecycle.State.DESTROYED) {
            super.dispatchMessage(msg)
        }
    }
}

fun handlerOf(owner: LifecycleOwner, looper: Looper = Looper.getMainLooper()): Handler {
    return SafeHandler(owner, looper)
}

fun LifecycleOwner.newHandler(looper: Looper = Looper.getMainLooper()): Handler {
    return handlerOf(this, looper)
}

分析下代碼:

  • 這里looper默認為主線程looper,而構建系統的Handler在沒有設置looper時,默認是獲取當前線程looper。從Handler的通用性來說這樣設計沒有問題,但從業務的角度來說,我們使用的Handler絕大多數是位于主線程中,因此這樣設計會更安全一些,避免一些開發者因為對Handler的機制不夠了解而使用默認構建方法構建出了錯誤的Handler。
  • LifecycleOwner使用弱引用存儲,SafeHandler本身就是為了解決內存泄露及crash,當然不能因為自身的缺陷導致另外的泄露了。
  • 監聽LifecycleOwner的銷毀,在銷毀時清除所有消息。
  • 在調度消息時判斷LifecycleOwner的狀態,如果已經銷毀,就不允許執行。既然已經在onDestroy時清空消息了,為什么還要做這步操作呢?這是因為外部有可能在onDestroy后依然使用Handler去發送消息。

分享到:
標簽:Android
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定