您好,登錄后才能下訂單哦!
本篇內容介紹了“如何理解Handler內存泄露”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
有的朋友看到這個題表示,就這?太簡單了吧。
"內部類持有了外部類的引用,也就是Hanlder
持有了Activity
的引用,從而導致無法被回收唄。"
其實這樣回答是錯誤的,或者說沒回答到點子上。
Java
虛擬機中使用可達性分析的算法來決定對象是否可以被回收。即通過GCRoot
對象為起始點,向下搜索走過的路徑(引用鏈),如果發現某個對象或者對象組為不可達狀態,則將其進行回收。
而內存泄漏
指的就是有些對象(短周期對象)沒有用了,但是卻被其他有用的類(長周期對象)所引用,從而導致無用對象占據了內存空間,形成內存泄漏。
所以上面的問題,如果僅僅回答內部類持有了外部類的引用
,沒有指出內部類被誰所引用,那么按道理來說是不會發生內存泄漏的,因為內部類和外部類都是無用對象了,是可以被正常回收
的。
所以這一題的關鍵在于,內部類被誰
引用了?也就是Handler被誰引用了?
一起通過實踐研究下吧~
第一種情況,是通過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
中,發送一個延遲20s的消息。然后打開HandlerActivity
后,馬上finish。看看會不會內存泄漏。
現在查看內存泄漏還是蠻方便的了,AndroidStudio
自帶對堆轉儲(Heap Dump)文件進行分析,并且會把內存泄漏點明確標出來。
我們運行項目,點擊Profiler——Memory
,就能看到以下圖片了,一個正在運行的內存情況實時圖:
可以看到圖片中有兩個按鈕我標出來了:
捕獲堆轉儲文件按鈕
,也就是生成hprof文件,這個文件會展示Java堆的使用情況,點擊這個按鈕后,AndroidStudio會幫我們生成這個堆轉儲文件并且進行分析。GC按鈕
,一般我們在我們捕獲堆轉儲文件之前,點一下GC,就能把一些弱引用給回收,防止給我們分析帶來干擾。所以我們打開HandlerActivity
后,馬上finish
,然后點擊GC按鈕,再點擊捕獲堆轉儲文件按鈕。AndroidStudio
會自動跳轉到以下界面:
可以看到左上角有一個Leaks
,這就是你內存泄漏的點,點擊就能看到內存泄漏的類了。右下角就是內存泄漏類的引用路徑。
從這張圖可以看到,我們的HandlerActivity
發生了內存泄漏,從引用路徑來看,是被匿名內部類的實例mHandler
持有引用了,而Handler
的引用是被Message
持有了,Message
引用是被MessageQueue
持有了...
結合我們所學的Handler知識和這次引用路徑分析,這次內存泄漏完整的引用鏈應該是:
主線程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity
所以這次引用的頭頭
就是主線程
,主線程肯定是不會被回收的,只要是運行中的線程
都不會被JVM回收,跟靜態變量
一樣被JVM特殊照顧。
這次內存泄漏的原因算是搞清楚了,當然Handler
內存泄漏的情況不光這一種,看看第二種情況:
第二個實例,是我們常用到的,在子線程中工作,比如請求網絡,然后請求成功后通過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")
}
}
}
同樣運行后看看內存泄漏情況:
可以發現,這里的內存泄漏主要的原因是因為這個運行中的子線程
,由于子線程這個匿名內部類
持有了外部類的引用,而子線程本身是一直在運行的,剛才說過運行中的線程是不會被回收的,所以這里內存泄漏的引用鏈
應該是:
運行中的子線程 —> Activity
當然,這里的Handler
也是持有了Activity
的引用的,但主要引起內存泄漏的原因還是在于子線程本身,就算子線程中不用Handler
,而是調用Activity
的其他變量或者方法還是會發生內存泄漏。
所以這種情況我覺得不能看作Handler
引起內存泄漏的情況,其根本原因是因為子線程引起的,如果解決了子線程的內存泄漏,比如在Activity
銷毀的時候停止子線程,那么Activity
就能正常被回收,那么也不存在Handler
的問題了。
這是因為內部類雖然和外部類寫在同一個文件中,但是編譯后還是會生成不同的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;
}
}
其實可以看到,在上述的代碼中,我都加了一句
btn2.setText("2222")
這是因為在kotlin
中的匿名內部類
分為兩種情況:
在Kotlin中
,匿名內部類如果沒有使用到外部類的對象引用時候,是不會持有外部類的對象引用的,此時的匿名內部類其實就是個
靜態匿名內部類
,也就不會發生內存泄漏。在Kotlin中
,匿名內部類如果使用了對外部類的引用,像我剛才使用了
btn2
,這時候就會持有外部類的引用了,就會需要考慮
內存泄漏
的問題。所以我特意加了這一句
,讓匿名內部類持有外部類的引用,復現內存泄漏問題。
同樣kotlin
中對于內部類也是和Java
有區別的:
靜態內部類
。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內存泄露”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。