1. 程式人生 > 其它 >iOS APP記憶體優化記錄

iOS APP記憶體優化記錄

 本文來源:碼農網 本文連結:https://www.codercto.com/a/79722.html

內容簡介:APP在執行過程中,如果記憶體佔用過高則會引起以下幾個問題:①被作業系統的守護程序給殺掉,無論是前臺還是後臺;②耗電增大,手機發熱;

APP在執行過程中,如果記憶體佔用過高則會引起以下幾個問題:

①被作業系統的守護程序給殺掉,無論是前臺還是後臺;

②耗電增大,手機發熱;

③系統可能會執行卡頓(不是換入換出到磁碟,而是解壓縮和重壓縮記憶體);

業務背景

因為我們APP的直播間要玩網頁版的小遊戲,比較耗記憶體,除了JS遊戲本身降低記憶體消耗之外,native也需要釋放更多的記憶體以提供給遊戲使用,避免因為記憶體佔用過大而被作業系統殺掉程序。

優化native本身的記憶體佔用大體分為兩部分:優化長期的記憶體佔用和優化峰值記憶體佔用。

理論上的技術方案

優化長期的記憶體佔用

1.記憶體洩漏問題;

要做記憶體優化,先要分析當前佔用記憶體較大的頁面或功能。尤其是直播間。

①開instruments來跟課,檢視直播間的記憶體洩漏;

——需要高階一點的機器,比如iPad才能跑起來,低端機器跑不了instruments。

②整合MLeaksFinder庫到開發的target,檢測記憶體洩漏;

——不過這個庫只能檢查UIViewcontroller和其對應的UIView是否有洩漏,可以方便開發的時候發現問題。

③整合 FBMemoryProfiler

 視覺化工具,直接嵌入到 App 中,可以起到在 App 中直接檢視記憶體使用情況,並篩選潛在洩漏OC物件的作用; 

網上說instruments的leaks工具無法檢測迴圈引用導致的記憶體洩漏,驗證下是否屬實;

——是真的,下圖這種block和self形式的迴圈引用,instruments檢測不出來

2.不必要的單例;是否可以改造成懶載入,用的時候才去初始化,用完可以釋放掉;這個主要是去排查程式碼邏輯看是否合理;

3.RN頁面的記憶體佔用問題見這裡 

①列表的cell是否有複用;

②圖片是否用了大圖,能不能改成小圖;

③進入直播間時回收掉所有RN頁面,在退出直播間後重新初始化RN頁面。——已採用,效果顯著,每個RN頁面大概佔用20-40M的記憶體。

4.大圖片的使用方式,包括動畫所用圖片的初始化方式,UIImage的imageWithContentWithFile可以避免使用快取,用完即釋放,但是需要把圖片放在bundle的目錄下,而不是XCAssets裡;

——對於降低峰值記憶體有一定效果,但是imageNamed方式圖片的快取,在APP退到後臺或者受到記憶體警告時,如果未被使用則會被系統自動清理。所以該方式可以採用,但是不要有太高的心理預期。

5.圖片下采樣。大一點的圖片(比如做動畫用的圖片),改用ImageIO的api,而不是直接imageWithNamed的方式來建立,詳情見 這裡 和 這裡 ;另實際顯示的尺寸小於圖片實際尺寸時可以降低取樣率(下采樣); 

——下采樣的方式僅針對圖片尺寸比實際顯示的尺寸大的情況下才會降低記憶體消耗。由於效果並不明顯,專案中並未使用該種方式。

對一張1500 × 2668的圖片做測試發現

①使用普通的ImageWithNamed方式,APP退到後臺再拉回前臺,記憶體會被清理;

②使用ImageWithContentWithFile的方式,每次使用後記憶體被釋放;——直播間可以採用

這兩種方式的記憶體變化見下圖:

③使用下采樣的方式,峰值記憶體降低了,但是丟棄圖片後,整體佔用的記憶體並未下降。暫不知該圖片快取釋放的時機。

④使用按比例縮放UIGraphicsBeginImageContext(),效果跟下采樣類似,圖片快取在APP退到後臺依然沒有釋放

6.APP監聽到記憶體警告或APP退到後臺的通知時,釋放一些可重建的非必要物件。

優化峰值記憶體佔用

1.在合適的地方新增AutoreleasePool來及時釋放記憶體,比如全域性IM訊息的接收和解析,視訊回放的訊息過濾等;

2.快取的使用,比如系統的UIImage的imageWithNamed函式建立的物件,以及YYMemoryCache和SDImageCache來共享快取,多個業務使用同一份記憶體快取。

3.快取釋放時機,YYMemoryCache和SDImageCache以及系統的UIImage的imageWithNamed建立的圖片物件,都會在APP退到後臺時,或者收到記憶體警告時清理全部的記憶體快取。

4.網路圖片單張圖片size過大監控,以及網路圖片總記憶體大小限制;

5. OOM捕捉

——最大的用處是用來分析短期記憶體增長過快的原因,同時可以獲取C和C++的記憶體分配。需要權衡下是否引入。

OOM暫未支援bugly整合,獲取的堆疊日誌暫時沒地方存放。且因為都用了fishhook,會跟 FBRetainCycleDetector 衝突,暫不引入。 

6.瞭解mmap,測試其在圖片對映的記憶體降低資料;

SDWebImage使用NSData的dataWithContentOfFile的方式來讀取圖片到記憶體;

mmap主要是可以省略從核心空間拷貝到使用者空間的這一步操作,其他省不掉。FastImage額外省略了圖片解碼,但是卻是通過把解碼後的圖片寫磁碟來實現的(解碼後圖片增大,讀取的IO耗時也會增大)。另FastImage可以使圖片位元組對齊,避免CoreAnimation在渲染時做一次額外的拷貝操作。

7.vmmap分析Xcode抓到的memgraph記憶體資訊,看看有什麼收穫;

——沒必要,原理跟 FBMemoryProfiler 類似,還不如直接用 FBMemoryProfiler 。 

ps:Facebook三件套 FBRetainCycleDetector , FBAllocationTracker 和 FBMemoryProfiler 。 

命令列有不少 工具 可以用來配合分析記憶體,比如vmmap,leaks和heap,見 這裡 。 

在我們專案中的實際應用

記憶體洩漏

1.Instruments 的部分捕獲結果

①AFNetworking的session

②JSBridge的記憶體洩漏

③第三方庫的洩漏

原因見下圖,是第三方的庫裡邊,create出來的CF物件沒有release掉:

2.MLeaksFinder發現的部分記憶體問題

①巡堂和動畫的view記憶體洩漏,block互相持有導致,已fixed。

②IAP的Header記憶體洩漏,動畫的delegate強引用導致,在開始做動畫的時候設定delegate,在動畫結束後的回撥裡把delegate置空可以解決這個問題。

記憶體峰值優化

1.視訊回放的業務,拖動進度條會有大量的string被分配記憶體,佔據了快100M

解決方案:在拖曳進度條時會過濾訊息,在過濾訊息的函式里加自動釋放池,及時釋放區域性變數的記憶體。

2.另一塊是由於IM群組的設計,很多使用者不需要知道的別的小班的訊息也會一起發過來,資料量會很大,在子執行緒進行高度計算時會產生大量的臨時變數,此處也加一個自動釋放池用來降低記憶體峰值。同時之前在另一篇文章裡曾經針對訊息資料的過濾做過優化 

之後的instruments記憶體監控如下:

第一張圖裡紅圈的string記憶體佔用已經不再出現。

3.SSZPicMemoryTool優化網路圖片的記憶體佔用問題(通過SD下載的,包括native和RN),

——主要是通過SSZPicMemoryTool接管了native通過SDWebImage使用的網路圖片,以及RN頁面使用的網路圖片。所以可以及時在使用時監控到記憶體超過閾值的單張圖片。只需要不同的業務頁面走查一遍即可發現。除了單張圖片的最大size超出監控之外,還直接把YYCache裡的LRU淘汰演算法拿來用,用於避免網路圖片佔用的峰值記憶體過大。

原理見下圖,SSZPicMemoryTool接管了圖片記憶體快取的管理,包括讀取,寫入和淘汰。

業務邏輯優化和時間空間置換策略

直播間玩H5的小遊戲會比較吃記憶體,所以可以採用一些策略,在使用者玩遊戲的時候,先釋放掉一些記憶體出來供遊戲使用。我們採用的兩個方案,一個是使用者玩遊戲時,把直播的視訊畫面給移除掉,僅播放音訊,因為此時本身視訊畫面也是被遊戲畫面擋住的,所以沒有太大必要。另一個就是由於RN頁面消耗的記憶體比較大,採用進入直播間就釋放掉外圍的RN頁面,等退出直播間再恢復。

H5遊戲本身也做了一輪記憶體的優化,優化前後降低了一半左右的記憶體消耗,效果還是很明顯的。

1.遊戲時關閉視訊,以及進入直播間後回收RN的記憶體優化效果

測試環境:iPhone8P + 無其他課堂互動(訊息等) + 進入直播後穩定後採集資料,直接跳轉到直播間,不經過試講和課程頁

遊戲中剔除視訊可以優化部分記憶體:剔除視訊渲染後直播間整體記憶體可以降低25~30M(佔進入直播間後增加的 40~50%),接收解碼等操作因為SDK沒有遮蔽介面,可能需要obs端做改造,但剩餘可優化空間不足了,預計最多可優化10~20M。

直播間外部的記憶體有優化空間:剔除RN後,進入首屏和進入直播間的記憶體降低了約 50M,RN佔用的記憶體比較大,首屏39M也有待細化,這兩部分可以跟進優化。

——進入APP後會佔用約40M的記憶體,經過測試,這部分無法繼續降低了,一個空的工程也需要消耗掉這麼多的記憶體。

回收RN頁面

重新載入RN頁面

2.直播間遊戲自身的記憶體佔用優化(主要是JS做的)

WKWebView 是執行在另一個程序裡,Network Loading 以及 UI Rendering 在其它程序中執行。在 WKWebView 的程序裡當總體的記憶體佔用比較大的時候,WebContent Process 會 crash,從而出現白屏現象。

WebContent Process 因為整機記憶體過大被系統kill掉的時候不可控,所以一般會在crash白屏的回撥加reload的邏輯,故而出現遊戲不斷reload問題。

Webview的CPU和記憶體優化交給前端自己做,只需要關注native本身的優化即可。WebKit程序的記憶體消耗可以用Instruments的Activity Monitor元件看到。

Instrument Activity Monitor + Allocations ,忽略IM和互動其他情況,只在音視訊+測評、遊戲的情況分析:

關於記憶體統計

APP使用記憶體統計可以直接如下的函式,參考來自 這裡

+ (NSInteger)getResidentMemory
{
    task_vm_info_data_t vmInfo;
    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
    kern_return_t result = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
    if (result == KERN_SUCCESS) {
        return (NSInteger)vmInfo.phys_footprint; ///< 應用使用的實體記憶體大小
    } else {
        return -1;
    }
}

shared memory

共享記憶體可以提供跨程序訪問的能力,不過如果你的App使用了別的程序建立的共享記憶體,那麼Debug Navigator是不會將它計入你自己的記憶體總量的,不過vmmap會將它加入TOTAL中,所以可能會導致vmmap計算的記憶體量會大於Debug Navigator統計記憶體量。

Debug Navigator其實就是統計了當前程序的所有虛擬記憶體的Dirty Size + Swapped Size,當然還要剔除掉對第三方共享記憶體的使用量,當我們發現Debug Navigator的記憶體量飆高時,不僅僅要去關注Heap上的記憶體用量,更要關注VM Tracker中那些大Dirty Size的VM Region,這樣才能更透徹的瞭解你的App究竟是怎樣使用記憶體的。

其他

記憶體洩漏工具的使用

1.介紹

在iOS的Dev的包裡會整合騰訊的MLeaksFinder庫和Facebook的FBMemoryProfiler三件套的庫,作用都是用來檢測記憶體洩漏的。

不同點在於,MLeaksFinder只用於檢查頁面和頁面相關的view的記憶體洩漏,當發現有記憶體洩漏時,會用一個彈窗來做出提示。比如最常見的,你退出一個頁面,2秒後如果該頁面的記憶體還未釋放,則會提示記憶體洩漏。

FBMemoryProfiler則可以檢測所有型別的記憶體洩漏,原理是hook了系統的alloc和dealloc函式,跟instruments的功能類似,只不過更加輕量化,可以在APP執行時實時看到記憶體分配的情況,如果有物件記憶體洩漏,則會標紅表示。

2.MLeaksFinder使用指南

很簡單,在退出一個頁面後,如果有彈出下圖的彈窗,則說明該頁面有記憶體洩漏,並且列出了具體的記憶體洩漏的物件。

備註:這個列表是一個記憶體洩漏的連結串列指標,不一定每個物件都會洩漏,但至少有一個是發生了記憶體洩漏的,通常可能是最後一個,也可能是多個,比如上圖其實XunTangTipView和LOTAnimationView都有記憶體洩漏。

另外就是預設的判斷時間是2秒,實際測試發現有時候會有誤傷,所以可以把時間間隔調大到3秒或者更大一些。

3.FBMemoryProfiler使用指南

在Dev的pod裡整合該庫後,Dev的包,頂部會有一個小浮窗顯示當前的總記憶體,點選後可以展開大圖檢視更多資訊,如下圖所示。

當你認為某個時刻,可能有記憶體洩漏,或者想看看是否有記憶體洩漏時,可以點選下圖的Mark Gen按鈕,此時會生成一個當前記憶體狀態相比前一個generation時的快照,點選Expand按鈕,會展開一個列表,把所有的物件都列出來,紅色的一欄則表示該物件有記憶體洩漏。

左上方有一個輸入款,可以用來進行過濾。

點選紅色的那一欄,就能看到具體的記憶體洩漏的原因,如下圖,nats庫的例項跟timer互相強引用導致。

本站部分資源來源於網路,本站轉載出於傳遞更多資訊之目的,版權歸原作者或者來源機構所有,如轉載稿涉及版權問題,請聯絡我們


本文來源:碼農網
本文連結:https://www.codercto.com/a/79722.html