国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

Android中導致內存泄漏的竟然是它----Dialog

移動開發 Android
Android 中 Activity 代表一個頁面,擁有一段生命周期,生命周期結束后,Activity 對象應當在之后某個合適的時機被 VM 回收內存。出現了泄漏就意味著 Activity 生命周期結束后,VM發現 Activity 一直被持有,沒有回收這些無用的內存。

一. 內存泄漏的 Bug 猛增

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

 

 

 

 

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

插播什么是 Activity 泄漏:Android 中 Activity 代表一個頁面,擁有一段生命周期,生命周期結束后,Activity 對象應當在之后某個合適的時機被 VM 回收內存。出現了泄漏就意味著 Activity 生命周期結束后,VM發現 Activity 一直被持有,沒有回收這些無用的內存。

按照以往的經驗,大部分 Activity 泄漏的原因都是由于 Handler 內部類長時間掛在線程中導致的。而這塊我們 App 已經考慮便處理了。究竟是哪泄漏了?

二. WebView 導致內存泄漏眾所周知

帶著懷疑的心態并且為了證明清白,我一個個點進去看了,總共有三條不同的引用鏈。為了后續說明,這里取了個名字:

① AuthDialog 引用鏈

 

 

 

 

② BrowserFrame 引用鏈

 

 

 

 

③ IClipboradDataPaste 引用鏈

 

 

 

 

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

為了快速解決問題,樓主詢問了其他同事和 StackOverflow,發現這其中有三個類 CookieSyncManager, WebView, WebViewClassic 已經被很多人提起過,它們會導致內存泄漏!初步有如下的結論如下:

1.CookieSyncManager 是個全局靜態單例,操作系統內部使用了 App 的 Activity 作為 Context 構造了它的實例。我們應該在 App 啟動的時候,搶先幫系統創建這個單例,而且要用 applicationContext,讓它不會引用到 Activity。

 

 

 

 

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

 

 

 

 

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

按照以上的種種結論,我們都認定了這里面就是 WebView 引起的。

但是!我們的應用主進程 LoginActivity 根本沒有用到 WebView 啊!!!

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

根據以上的 AuthDialog 引用鏈,樓主把目標鎖定了某sdk:

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

c 跟 j 都是 SDK 中繼承于 WebView 的一個子類,k 是登錄接口的輸入參數 Activity。這里創建了 c 對象之后向上塑形賦給了 j 。

 

 

 

 

網上已經有很多例子表明,直接用 Activity 作為參數構建 WebView 就非常有可能導致 Activity 泄漏。

 

 

 

 

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

 

 

 

 

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

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

四. 心結未解,翻看WebView源碼了解根源

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

 

 

 

 

大概情況是這樣:WebView 這套結構中,有一個工廠類 WebViewFactory 提供靜態方法。

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

WebViewClassic 才是真正地實現 WebView 的各種 API。WebViewClassic 創建并維護了 WebViewCore 對象。

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

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

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

 

 

 

 

看到這里之后,樓主發現以上所說的,提前幫系統調用

CookieSyncManager.createInstance(contenxt.getApplicationContext()) 可能是沒有效果的,因為系統本來就是這么做的。手機廠商修改這里的可能性不大。

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

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

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

留下兩個孤獨的子線程還在跑,還有全局靜態的釘子戶對象。

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

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

 

 

 

 

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

五. ***的疑惑

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

 

換成 MAT 看會比較清晰:

 

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

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 的局部變量,而由于線程一直沒有結束,所以局部變量一直沒有被釋放。而這個 Message.obj 成員引用了 AuthDialog$3 對象。

這是一個內部類,樓主發現內部類混淆之后的命名規則就是:第幾個出現就命名為幾。

AuthDialog 里面有很多內部類:

 

 

 

 

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

 

 

 

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

于是,我們心中生成的一條引用鏈是這樣的:

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

 

 

 

 

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

再對比一下:

 

 

 

 

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

好吧,樓主承認研究了一個晚上沒有任何進展。。。

六. 原來是它!—Dialog

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

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

有興趣的童鞋請☞☞ 復制鏈接到瀏覽器打開 ☜☜,進行詳細閱讀!

這里簡要說明一下,作者的結論是:在 Android Lollipop 之前使用 AlertDialog 可能會導致內存泄漏!

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

如下代碼使用阻塞隊列說明問題:

 

 

 

 

子線程中調用 loop()死循環,不停地從阻塞隊列中取出一個 MyMessage 對象并且將對象的引用賦值給局部變量 message,一次 while 循環之后,虛擬機應當結束 while 花括號中的局部變量的生命周期,并且釋放對應的堆內存中的 MyMessage 對象。可是,DVM 沒有這么做!!

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

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

 

 

 

 

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

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

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

 

 

 

 

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

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

 

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

 

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

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

七. 總結一些注意點

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

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

2020-08-19 09:23:10

傳輸網絡WDM網絡技術

2024-06-17 00:04:00

JavaScriptWebRust開發

2025-06-27 02:11:00

2024-01-05 08:37:41

前端項目開發

2020-10-19 06:49:18

內存String

2015-08-24 10:31:14

Windows 10功能

2020-09-29 06:45:49

JDK

2020-12-15 08:05:40

路由器服務器網絡層

2021-07-28 06:51:08

Nacos代理模式

2015-06-18 11:04:58

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

蘋果谷歌亞馬遜

2021-04-08 22:31:20

編程語言PythonC語言

2020-10-20 17:18:00

戴爾

2022-07-07 19:44:22

Python 3.1
點贊
收藏

51CTO技術棧公眾號

亚洲欧美aⅴ...| 欧美优质美女网站| caopor在线视频| 黄色成人av网站| 免费国产一区二区| 性伦欧美刺激片在线观看| 成人免费视频97| 久久久久久夜| 99久久久无码国产精品6| 久久欧美一区二区| 中国在线观看免费国语版电影| 尤物视频一区二区| 国产精品国三级国产av| 国产偷v国产偷v亚洲高清| 好吊的妞视频这里都有| 91精品久久久久久蜜臀| 涩爱av色老久久精品偷偷鲁 | 国产日韩欧美电影在线观看| 精品美女视频| 日产精品久久久一区二区| 国产一区二区导航在线播放| 国产一级性片| 亚洲国产精品精华液网站| 日韩一级二级| 国产一区二中文字幕在线看| av在线不卡网| www.激情小说.com| 天天影视涩香欲综合网| 嗯啊主人调教在线播放视频| 欧美性猛交一区二区三区精品| 欧美视频第一| 久久久人成影片一区二区三区观看 | 国产一区二区电影在线观看| 日韩精品免费一区| 亚洲欧洲综合| 午夜啪啪福利视频| 91国产丝袜在线播放| 777午夜精品电影免费看| 日韩男女性生活视频| 久久五月婷婷丁香社区| 东北一级毛片| 欧美日韩久久久一区| 最新欧美电影| 日韩精品最新在线观看| 一区二区三区在线观看动漫| 国产精一区二区| ww国产内射精品后入国产| 中文字幕日韩在线观看| 亚洲国产精品视频一区| 精品国产一级毛片| 天天影视综合色| 欧美最近摘花xxxx摘花| 中文字幕在线观看一区二区| 9l视频自拍九色9l视频成人| 波多野结衣综合网| 久久久国产精品x99av| 国产农村妇女精品| 国产成人福利av| 中文字幕在线视频网| 91久久国产婷婷一区二区| 国产日韩欧美麻豆| 婷婷视频一区二区三区| 国产精品一区在线免费观看| 亚洲人成网站999久久久综合| 国产日韩视频在线| 91极品尤物在线播放国产| 日韩中文字幕免费视频| 综合色天天鬼久久鬼色| 黄色欧美网站| 成视频免费在线看| 国产欧美亚洲日本| 日本韩国欧美国产| 久草在在线视频| 久久综合久久鬼色| 成av人片在线观看www| 欧美爱爱视频网站| 欧美精品videossex性护士| 色综合久久九月婷婷色综合| 国内露脸中年夫妇交换精品| 日本xxxxxx| 国产精品视频网| 亚洲一级二级三级| 久久99久久久久久久久久久| 日韩欧美网址| 国产蜜臀一区二区打屁股调教| 浮妇高潮喷白浆视频| 精品国产一区二区三区久久久狼| 国内揄拍国内精品久久| 色婷婷成人网| 黄色精品免费看| 午夜视频国产| 日韩成人一级大片| 98在线视频| 欧美美乳视频网站在线观看| 精品福利一区二区三区| 亚洲免费在线观看视频| 一区二区三区四区在线看| 毛片免费不卡| 影音先锋成人资源网站| 国产日韩综合一区二区性色av| 亚洲精品一区二区三区在线观看 | 久久久综合九色合综国产精品| 伊人久久大香线蕉av超碰演员| 欧美三级美国一级| 97久久精品一区二区三区的观看方式 | 成人性片免费| 51漫画成人app入口| 色琪琪原网站亚洲香蕉| 日本男女交配视频| 日韩精品手机在线观看| 亚洲一区尤物| 色姑娘综合网| 欧美极品色图| 久久久久久国产精品mv| 久久精品国产第一区二区三区最新章节 | 99精品国产一区二区| 国产精品中文久久久久久久| 国产成人啪精品视频免费网| 亚洲欧美国产日韩天堂区| 福利一区福利二区微拍刺激| 亚洲激情一二三区| 国产精品自在在线| 成人久久久精品乱码一区二区三区| 国产成a人亚洲| 美女诱惑黄网站一区| 秋霞av亚洲一区二区三| 一本色道久久| 久久av在线| 国产aⅴ综合色| 国产区在线观看成人精品 | 中文不卡1区2区3区| 爱情岛论坛亚洲品质自拍视频网站 | 欧美日韩亚洲高清| 日韩一区二区三| 亚洲天堂第二页| 欧美激情网友自拍| 91视频网页| 在线观看视频黄色| 九七伦理97伦理| 91黄色小网站| 日本中文字幕一区二区有码在线| 狠狠热免费视频| 国产亚洲天堂网| 日本在线观看www| 999精品视频在线观看| 国产精品99一区二区三区| 伊人成人在线视频| 成人国产亚洲欧美成人综合网 | 精品国产一区二区三区四区阿崩| 搞黄网站在线看| 台湾佬中文娱乐久久久| 精灵使的剑舞无删减版在线观看| 精品一区二区三区中文字幕视频| 红桃视频亚洲| 国产欧美一区二区精品婷婷| 欧美丝袜一区二区三区| 亚洲福利视频二区| 国产精品国产福利国产秒拍| 粉嫩av一区二区三区天美传媒| 黄色免费网址大全| 91av久久| 亚洲美女啪啪| 国产精品免费视频一区| 欧洲av在线精品| 最新69国产成人精品视频免费| 日韩在线视频观看| 亚洲已满18点击进入在线看片| 少妇网站在线观看| 亚洲人成777| 99在线观看免费视频精品观看| 久久美女艺术照精彩视频福利播放 | 成人中文字幕在线播放| 久草在线在线| 成年人在线视频| 91麻豆精品一二三区在线| 国产精品女主播一区二区三区| 成人在线视频首页| 精品日韩视频在线观看| 国产欧美日韩中文| 国产四区在线观看| 99自拍视频在线观看| 欧美成人精品| 91亚洲精品乱码久久久久久蜜桃| 91伊人久久大香线蕉| 337p粉嫩大胆噜噜噜噜噜91av| 色网站国产精品| 亚洲人成自拍网站| 欧美成人三级视频网站| 亚洲区在线播放| 怡红院av亚洲一区二区三区h| 亚洲妇熟xxxx妇色黄| 99热免费精品在线观看| 精品盗摄一区二区三区| 亚洲精品二区| 2020国产在线| 久久99精品久久久久久| 精品国产在天天线2019| 日批视频在线免费看| 欧美96在线丨欧| 欧美视频三区在线播放|