成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Android中導(dǎo)致內(nèi)存泄漏的竟然是它----Dialog

移動開發(fā) Android
Android 中 Activity 代表一個頁面,擁有一段生命周期,生命周期結(jié)束后,Activity 對象應(yīng)當(dāng)在之后某個合適的時機(jī)被 VM 回收內(nèi)存。出現(xiàn)了泄漏就意味著 Activity 生命周期結(jié)束后,VM發(fā)現(xiàn) Activity 一直被持有,沒有回收這些無用的內(nèi)存。

一. 內(nèi)存泄漏的 Bug 猛增

最近在 App 進(jìn)行 mokey 測試的時候檢測到一些內(nèi)存泄漏問題。在前天的測試中,樓主一瞬間收到了4個這樣的 Bug 單,瞬間心理無比糾結(jié),真有千萬只羊駝向我奔來。

 

 

 

 

登錄頁面出現(xiàn)內(nèi)存泄漏??!!樓主的代碼是如此的***而無懈可擊,這么可能出現(xiàn)這么多泄漏的問題?

插播什么是 Activity 泄漏:Android 中 Activity 代表一個頁面,擁有一段生命周期,生命周期結(jié)束后,Activity 對象應(yīng)當(dāng)在之后某個合適的時機(jī)被 VM 回收內(nèi)存。出現(xiàn)了泄漏就意味著 Activity 生命周期結(jié)束后,VM發(fā)現(xiàn) Activity 一直被持有,沒有回收這些無用的內(nèi)存。

按照以往的經(jīng)驗(yàn),大部分 Activity 泄漏的原因都是由于 Handler 內(nèi)部類長時間掛在線程中導(dǎo)致的。而這塊我們 App 已經(jīng)考慮便處理了。究竟是哪泄漏了?

二. WebView 導(dǎo)致內(nèi)存泄漏眾所周知

帶著懷疑的心態(tài)并且為了證明清白,我一個個點(diǎn)進(jìn)去看了,總共有三條不同的引用鏈。為了后續(xù)說明,這里取了個名字:

① AuthDialog 引用鏈

 

 

 

 

② BrowserFrame 引用鏈

 

 

 

 

③ IClipboradDataPaste 引用鏈

 

 

 

 

看來這次情況有點(diǎn)不同!由于 Monkey 測試的機(jī)型比較少,這里所有的 Bug 都來自一部三星 GT-I9300@android+4.3 手機(jī)。

為了快速解決問題,樓主詢問了其他同事和 StackOverflow,發(fā)現(xiàn)這其中有三個類 CookieSyncManager, WebView, WebViewClassic 已經(jīng)被很多人提起過,它們會導(dǎo)致內(nèi)存泄漏!初步有如下的結(jié)論如下:

1.CookieSyncManager 是個全局靜態(tài)單例,操作系統(tǒng)內(nèi)部使用了 App 的 Activity 作為 Context 構(gòu)造了它的實(shí)例。我們應(yīng)該在 App 啟動的時候,搶先幫系統(tǒng)創(chuàng)建這個單例,而且要用 applicationContext,讓它不會引用到 Activity。

 

 

 

 

2.使用 WebView 的頁面(Activity),在生命周期結(jié)束頁面退出(onDestory)的時候,需要主動調(diào)用 WebView.onPause()以及 WebView.destory()以便讓系統(tǒng)釋放 WebView 相關(guān)資源。

 

 

 

 

4.WebView 內(nèi)存泄漏是眾所周知的,建議另外啟動一個進(jìn)程專門運(yùn)行 WebView。不要9998,不要9999,我們要100%!WebView 用完之后就把進(jìn)程殺死,即使泄漏了也無礙。

按照以上的種種結(jié)論,我們都認(rèn)定了這里面就是 WebView 引起的。

但是!我們的應(yīng)用主進(jìn)程 LoginActivity 根本沒有用到 WebView 啊!!!

三. 第三方 jar 包使用 WebView 這可如何是好

根據(jù)以上的 AuthDialog 引用鏈,樓主把目標(biāo)鎖定了某sdk:

翻了一陣子惡心的混淆后的代碼,找到下面這么一段。SDK 確實(shí)創(chuàng)建了 WebView 實(shí)例,并且用的是客戶程序的 Activity 對象作為 WebView 的 Context 如下:

c 跟 j 都是 SDK 中繼承于 WebView 的一個子類,k 是登錄接口的輸入?yún)?shù) Activity。這里創(chuàng)建了 c 對象之后向上塑形賦給了 j 。

 

 

 

 

網(wǎng)上已經(jīng)有很多例子表明,直接用 Activity 作為參數(shù)構(gòu)建 WebView 就非常有可能導(dǎo)致 Activity 泄漏。

 

 

 

 

不過也看到了代碼中,有調(diào)用了 WebView 的 destory()方法釋放資源。但是這里似乎無法保證 dismiss()一定會被執(zhí)行。

 

 

 

 

問題到這里發(fā)現(xiàn)比較麻煩了,SDK 對我們來說是第三方包,我們沒法讓第三方包不用 WebView,或者讓第三方包把 WebView 放在另外一個進(jìn)程中運(yùn)行啊!于是,在 App 上面做規(guī)避暫時不好實(shí)現(xiàn)。于是找了 SDK 的童鞋一起分析了。

最終,大家都有了一個初步的共識,在 Android4.3 以下的舊版本,使用 Activity 對象創(chuàng)建 WebView,確實(shí)有可能導(dǎo)致內(nèi)存泄漏。非常高興能得到 SDK 童鞋的大力支持,一起分析,問題到這里有了初步的進(jìn)展。

四. 心結(jié)未解,翻看WebView源碼了解根源

不過,問題到這里樓主心理還是有個很嚴(yán)重的疑惑沒有解開(是什么疑惑呢?)。于是拿了 Android4.3 的源碼又翻了一遍希望找尋這里頭的根本原因,做了一點(diǎn)記錄,針對 WebView 在 Java 層的結(jié)構(gòu)畫了一個不嚴(yán)謹(jǐn)?shù)念悎D:源碼來源:http://androidxref.com/4.3_r2.1/ (請復(fù)制以上鏈接到瀏覽器打開)

 

 

 

 

大概情況是這樣:WebView 這套結(jié)構(gòu)中,有一個工廠類 WebViewFactory 提供靜態(tài)方法。

Android4.3(JellyBean) 版本通過 WebViewFactory 工廠類創(chuàng)建了一個全局單例對象 WebViewClassic$Factory,然后使用這個 Factory 創(chuàng)建了一整套實(shí)現(xiàn)的代碼(XXXClassic):WebViewClassic, CookieManagserClassic, WebViewDatabaseClassic。

WebViewClassic 才是真正地實(shí)現(xiàn) WebView 的各種 API。WebViewClassic 創(chuàng)建并維護(hù)了 WebViewCore 對象。

WebViewCore 創(chuàng)建了一個子線程“WebViewCoreThread”,這里是一個全局的單例的而且一旦啟動就不會停止的 Thread!WebViewCore 會在這個子線程中創(chuàng)建維護(hù)并調(diào)用 BrowserFrame 的方法。

BrowserFrame 本身是一個屬于“WebViewCoreThread”線程的 Handler 子類。BrowserFrame 會被 native(c++) 層調(diào)用,然后將這些調(diào)用切換到“WebViewCoreThread”線程中去執(zhí)行,比如刷新進(jìn)度或者處理屏幕旋轉(zhuǎn)事件等等。

BrowserFrame 還會調(diào)用 CookieSyncManager.createIntance(),這也是系統(tǒng)框架中唯一一處調(diào)用的地方!

 

 

 

 

看到這里之后,樓主發(fā)現(xiàn)以上所說的,提前幫系統(tǒng)調(diào)用

CookieSyncManager.createInstance(contenxt.getApplicationContext()) 可能是沒有效果的,因?yàn)橄到y(tǒng)本來就是這么做的。手機(jī)廠商修改這里的可能性不大。

CookieSyncManager 又是什么東西?同樣的,它自己也創(chuàng)建一個子線程,線程名就叫“CookieSyncManager”,又是全局單例不會停!這個線程每過5分鐘就會把緩存在內(nèi)存中的 Cookie 進(jìn)行持久化 syncFromRamToFlash()。

這里我們比較關(guān)心為什么 Activity 會泄漏,所以關(guān)鍵看看哪些類對象中持有了 Activity(Context) 引用:WebViewClassic, WebViewCore, BrowserFrame。

這套結(jié)構(gòu)中有很多靜態(tài)單例,還有子線程,想想也挺惡心的。而且三個關(guān)鍵的類都持有 Activity 引用。不過我們發(fā)現(xiàn),其實(shí) WebViewClassic, WebViewCore 這兩個個對象跟 WebView 對象的生命周期是一致的,Activity 銷毀于是 WebView 銷毀了,WebView 銷毀了另外兩個對象也跟著銷毀。煙消云散。。。

留下兩個孤獨(dú)的子線程還在跑,還有全局靜態(tài)的釘子戶對象。

但是!BrowserFrame 本身是 Handler,假如它因?yàn)?native 層的調(diào)用往”WebViewCoreThread”掛了一個消息,那么便可以建立一條引用鏈:

Thread->MessageQueue->Message->Handler(BrowserFrame)->Activity

 

 

 

 

好了,樓主的疑惑是什么?

五. ***的疑惑

我們再來看看 AuthDialog 的引用鏈。

 

換成 MAT 看會比較清晰:

 

樓主發(fā)現(xiàn),這里 CookieSyncManager 線程,居然直接引用了 Message 對象!這是什么鬼?一般情況下,HandlerThread 持有一個 MessageQueue 對象,MessageQueue 才持有 Message 隊(duì)列。

Java Local : A local variable. For example, input parameters, or locally created objects of methods that are still in the stack of a thread. Native stack.

Input or output parameters in native code, for example user-defined JNI code or JVM internal code. Many methods have native parts, and the objects that are handled as method parameters become garbage collection roots. For example, parameters used for file, network, I/O, or reflection operations.

這里表明,CookieSyncManager 線程中存在某個 Message 的局部變量,而由于線程一直沒有結(jié)束,所以局部變量一直沒有被釋放。而這個 Message.obj 成員引用了 AuthDialog$3 對象。

這是一個內(nèi)部類,樓主發(fā)現(xiàn)內(nèi)部類混淆之后的命名規(guī)則就是:第幾個出現(xiàn)就命名為幾。

AuthDialog 里面有很多內(nèi)部類:

 

 

 

 

如上圖,MAT 中的引用鏈中的 AuthDialog$3 指的就是這里的 OnDismissListener 匿名內(nèi)部類!接著我們來看看 Dialog.setOnDismissListener 里面做了什么勾搭:

 

 

 

納尼!OnDismissListener 居然被賦給了 Message.obj 成員!

于是,我們心中生成的一條引用鏈?zhǔn)沁@樣的:

Thread(main) -> MessageQueue->Message -> obj(OnDismissListener) -> AuthDialog -> Activity

 

 

 

 

可是不對啊,我們所能找到的引用鏈跟 CookieSyncManager 子線程一點(diǎn)關(guān)系都沒有!

再對比一下:

 

 

 

 

子線程 CookieSyncManager 拿到了主線程的 Message!! Oh no !! 這是什么情況???這個 Message 被某處地方錯誤引用了?子線程通過 JNI 在 native 中拿到 Java 層的對象?

好吧,樓主承認(rèn)研究了一個晚上沒有任何進(jìn)展。。。

六. 原來是它!—Dialog

注:以下的分析感悟來自Github上面的一篇文章:《一個內(nèi)存泄漏引發(fā)的血案》

https://github.com/bboyfeiyu/android-tech-frontier/blob/master/issue-25/%E4%B8%80%E4%B8%AA%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E5%BC%95%E5%8F%91%E7%9A%84%E8%A1%80%E6%A1%88-Square.md

有興趣的童鞋請☞☞ 復(fù)制鏈接到瀏覽器打開 ☜☜,進(jìn)行詳細(xì)閱讀!

這里簡要說明一下,作者的結(jié)論是:在 Android Lollipop 之前使用 AlertDialog 可能會導(dǎo)致內(nèi)存泄漏!

作者發(fā)現(xiàn),局部變量的生命周期在 Dalvik VM 跟 ART/JVM 中有區(qū)別。在 DVM 中,假如線程死循環(huán)或者阻塞,那么線程棧幀中的局部變量假如沒有被置為 null,那么就不會被回收。

如下代碼使用阻塞隊(duì)列說明問題:

 

 

 

 

子線程中調(diào)用 loop()死循環(huán),不停地從阻塞隊(duì)列中取出一個 MyMessage 對象并且將對象的引用賦值給局部變量 message,一次 while 循環(huán)之后,虛擬機(jī)應(yīng)當(dāng)結(jié)束 while 花括號中的局部變量的生命周期,并且釋放對應(yīng)的堆內(nèi)存中的 MyMessage 對象??墒?,DVM 沒有這么做!!

在 VM 中,每一個棧幀都是本地變量的集合,而垃圾回收器是保守的:只要存在一個存活的引用,就不會回收它。在每次循環(huán)結(jié)束后,本地變量不再可訪問,然而本地變量仍持有對 Message 的引用,interpreter/JIT 理論上應(yīng)該在本地變量不可訪問時將其引用置為 null,然而它們并沒有這樣做,引用仍然存活,而且不會被置為 null,使得它不會被回收!!

這種場景不就是 Android Handler 消息機(jī)制的處理方式么?!

 

 

 

 

Looper 不停地從阻塞隊(duì)列 MessageQueue 中取出下一條消息 Message 并將引用賦給本地變量 msg。一旦一次循環(huán)結(jié)束了,msg 沒有被置為 null,對應(yīng)的 Message 對象沒有被回收,于是就泄漏了。

不過,Message 是自帶回收機(jī)制的,而且任何線程共用,從上面源碼可以看到,每個 Message 被 Handler 處理完之后都會 recycle(),置空所有的成員變量,并且放到回收池中。

好了,被 CookieSyncManager 子線程的 Looper 輪過一次的 Message 對象也跟其他人一樣,被回收并放在了回收池中。這個時候,剛好遇到了 Dialog!!

 

 

 

 

這家伙剛剛好通過 obtainMessage()從回收池中拿到了這個 Message(被 CookieSyncManager 線程的本地變量引用住了),而且Message.obj 變量就是 OnDismissListener。

拿到之后,Dialog 居然據(jù)為己有!!作為一個成員寵愛著!

 

Dialog 自從擁有了 mDismissMessage 對象之后就不會讓它掛到消息隊(duì)列中了,每次要用都是拷貝一份而已。Message.obtain(mDismissMessage),所以這個 Message 再也不會回到回收池中,直到 Dialog 被銷毀,mDismissMessage 變量也被置為 null 了。

 

但是,這個 Message 依然占據(jù)著堆內(nèi)存,而且被一個“游離”著的子線程局部變量 msg 引用著!!于是有了這條引用鏈:

Thread(CookieSyncManager) -> Message -> AuthDialog$3(OnDismissListener) -> AuthDialog -> Activity

七. 總結(jié)一些注意點(diǎn)

針對 Android4.3 及以下版本,或者使用 DVM 的 Android 版本

  1. 使用 WebView 的時候,需要注意確保調(diào)用 destroy()
  2. 考慮是否使用 applicationContext()來構(gòu)建 WebView 實(shí)例
  3. 調(diào)用 Dialog 設(shè)置 OnShowListener、OnDismissListener、OnCancelListener 的時候,注意內(nèi)部類是否泄漏 Activity 對象
  4. 盡量不要自己持有 Message 對象.
責(zé)任編輯:龐桂玉 來源: 騰訊Bugly
相關(guān)推薦

2020-08-19 09:23:10

傳輸網(wǎng)絡(luò)WDM網(wǎng)絡(luò)技術(shù)

2025-06-27 02:11:00

2024-06-17 00:04:00

JavaScriptWebRust開發(fā)

2024-01-05 08:37:41

前端項(xiàng)目開發(fā)

2020-10-19 06:49:18

內(nèi)存String

2015-08-24 10:31:14

Windows 10功能

2020-09-29 06:45:49

JDK

2015-06-18 11:04:58

2020-12-15 08:05:40

路由器服務(wù)器網(wǎng)絡(luò)層

2021-07-28 06:51:08

Nacos代理模式

2024-08-05 01:28:26

2024-09-27 11:38:49

2021-10-18 13:42:52

加密貨幣金融工具

2018-07-06 00:09:47

2023-03-13 08:09:03

Protobuffeature分割

2016-09-25 14:34:10

蘋果谷歌亞馬遜

2020-10-20 17:18:00

戴爾

2022-07-07 19:44:22

Python 3.1

2021-08-28 10:15:26

項(xiàng)目結(jié)構(gòu)Flask
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 欧美综合网| 天天操综合网站 | 精品欧美一区二区三区久久久 | 色网站视频 | 精品久久久久久亚洲精品 | 天堂精品 | 国产偷录叫床高潮录音 | 欧美一级特黄aaa大片在线观看 | 特级丰满少妇一级aaaa爱毛片 | 国产精品视频久久久久久 | 亚洲一区二区三区视频 | 久久精品久久久久久 | 特黄视频| 日韩激情在线 | 亚洲网在线 | 91传媒在线观看 | 国产精品久久久久久久毛片 | 久久久精 | a级大片免费观看 | 最新日韩av | 高清久久久 | 日韩乱码一二三 | 欧美1—12sexvideos | 国产一区在线看 | 91久久北条麻妃一区二区三区 | 黄色三级毛片 | 中文字幕日本一区二区 | h片在线免费看 | 三级黄片毛片 | 狠狠干影院 | 欧美色综合网 | 日本欧美国产在线观看 | 亚洲网站在线观看 | 日韩在线免费视频 | 亚洲欧美日韩精品 | 91成人精品 | 国产免费视频 | 欧美一二区 | 中文字幕在线视频免费观看 | 日本高清aⅴ毛片免费 | 亚洲av毛片成人精品 |