編輯:關於Android編程
Android內存優化是我們性能優化工作中比較重要的一環,這裡其實主要包括兩方面的工作:
1、優化RAM,即降低運行時內存。這裡的目的是防止程序發生OOM異常,以及降低程序由於內存過大被LMK機制殺死的概率。另一方面,不合理的內存使用會使GC大大增多,從而導致程序變卡。
2、優化ROM,即降低程序占ROM的體積。這裡主要是為了降低程序占用的空間,防止由於ROM空間不足導致程序無法安裝。
本文的著重點為第一點,總結概述降低應用運行內存的技巧。在這裡我們不再細述PSS、USS等概念與Android應用的內存管理,如對這部分內容感興趣,可自行閱讀文末的參考文章。
內存洩露的檢測與修改
內存洩露:簡單來說對象由於編碼錯誤或系統原因,仍然存在著對其直接或間接的引用,導致系統無法進行回收。內存洩露,容易留下邏輯隱患,同時增加了應用內存峰值與發生OOM的概率。它屬於bug issue,是我們一定要修改的。
下面是造成內存洩露的一些常見原因,但是如何建立一套發現內存洩露、解決內存洩露的閉環方案,才是我們工作的重點。
一. 內存洩露的監控方案
Square的開源庫leakcanry是一個非常不錯的選擇,它通過弱引用方式偵查Activity或對象的生命周期,若發現內存洩露自動dump Hprof文件,通過HAHA庫得到洩露的最短路徑,最後通過notification展示。
內存洩露判斷與處理的流程如下圖 ,各自運行的進程空間(主進程通過idlehandler,HAHA分析使用的是單獨的進程):
微信在leakcanry推出之前已經有了自己的內存洩露監控體系,與leakcanry大致有以下的區別:
事實上,通過對leakcanry做簡單的定制,我們就可以實現以下一個內存洩露監控閉環。
二. 對系統內存洩露的Hack Fix
AndroidExcludedRefs列出了一些由於系統原因導致引用無法釋放的例子,同時對於大多數的例子,都會提供建議如何通過hack的建議去修復。在微信中,對TextLine、InputMethodManager、AudioManger、android.os.Message也采用了類似Hack的方式。三. 通過兜底回收內存
Activity洩漏會導致該Activity引用到的Bitmap、DrawingCache等無法釋放,對內存造成大的壓力,兜底回收是指對於已洩漏Activity,嘗試回收其持有的資源,洩漏的僅僅是一個Activity空殼,從而降低對內存的壓力。
做法也非常簡單,在Activity onDestory時候從view的rootview開始,遞歸釋放所有子view涉及的圖片,背景,DrawingCache,監聽器等等資源,讓Activity成為一個不占資源的空殼,洩露了也不會導致圖片資源被持有。
… … Drawable d = iv.getDrawable(); if (d != null) { d.setCallback(null); } iv.setImageDrawable(null); ... ...
總的來說,我們不是只懂得一些內存洩露解決方法就可以,更重要的是通過日常測試與監控,得到內存洩露檢測與修改的一整套閉環體系。
降低運行時內存的一些方法
當我們能確保應用中不會出現內存洩露時,我們需要一些其他的方法來降低運行時的內存。更多的時候,我們其實只希望降低應用發生OOM的概率。
Android OOM:
一. 減少bitmap占用的內存
說到內存,bitmap必然是這裡的大頭。對於bitmap內存占用,想說的有以下幾點:
1、防止bitmap占用資源多大導致OOM
Android 2.x 系統 BitmapFactory.Options 裡面隱藏的的inNativeAlloc反射打開後,申請的bitmap就不會算在external中。對於Android 4.x系統,可采用facebook的fresco庫,即可把圖片資源放於native中。
2、圖片按需加載
即圖片的大小不應該超過view的大小。在把圖片載入內存之前,我們需要先計算出一個合適的inSampleSize縮放比例,避免不必要的大圖載入。對此,我們可以重載drawable與ImageView,例如在Activity ondestroy時,檢測圖片大小與View的大小,若超過,可以上報或提示。
3、統一的bitmap加載器
Picasso、Fresco都是比較出名的加載庫,同樣微信也有自己的庫ImageLoader。加載庫的好處在於將版本差異、大小處理對使用者不感知。有了統一的bitmap加載器,我們可以在加載bitmap時,若發生OOM(try catch方式),可以通過清除cache,降低bitmap format(ARGB8888/RBG565/ARGB4444/ALPHA8)等方式,重新嘗試。
4、圖片存在像素浪費
對於.9圖,美工可能在出圖時在拉伸與非拉伸區域都有大量的像素重復。通過獲取圖片的像素ARGB值,計算連續相同的像素區域,自定義算法判定這些區域是否可以縮放。關鍵也是需要將這些工作做到系統化,可及時發現問題,解決問題。
一個好的imageLoader,可以將2.X、4.X或5.X對圖片加載的處理對使用者隱藏,同時也可以將自適應大小、質量等放於框架中。
二. 自身內存占用監控
對於系統函數onLowMemory等函數是針對整個系統而已的,對於本進程來說,其dalvik內存距離OOM的差值並沒有體現,也沒有回調函數供我們及時釋放內存。假若能有那麼一套機制,可以實時監控進程的堆內存使用率,達到設定值即關於通知相關模塊進行內存釋放,這會大大的降低OOM。
這個其實比較簡單,通過Runtime獲得maxMemory,而totalMemory-freeMemory即為當前真正使用的dalvik內存。
Runtime.getRuntime().maxMemory(); Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
我們可以定期(前台每隔3分鐘)去得到這個值,當我們這個值達到危險值時(例如80%),我們應當主要去釋放我們的各種cache資源(bitmap的cache為大頭),同時顯示的去Trim應用的memory,加速內存收集。
復制代碼 代碼如下:WindowManagerGlobal.getInstance().startTrimMemory(TRIM_MEMORY_COMPLETE);
三. 使用多進程
對於webview,圖庫等,由於存在內存系統洩露或者占用內存過多的問題,我們可以采用單獨的進程。微信當前也會把它們放在單獨的tools進程中
四. 上報OOM詳細信息
當系統發生OOM的crash時,我們應當上傳更加詳細的內存相關信息,方便我們定位當時內存的具體情況。
其他例如使用large heap、inBitmap、SparseArray、Protobuf等不再一一細述,對代碼采用優化--埋坑--優化--埋坑的方式並不推薦。我們應該著力於建立一套合理的框架與監控體系,能及時的發現諸如bitmap過大、像素浪費、內存占用過大、應用OOM等問題。
GC優化
Java擁有GC的機制,不同的系統版本GC的實現可能有比較大的差異。但是無論哪種版本,大量的GC操作則會顯著占用幀間隔時間(16ms)。如果在幀間隔時間裡面做了過多的GC操作,那麼自然其他類似計算,渲染等操作的可用時間就變得少了。
一. GC的類型
GC的類型有以下幾種,其中GC_FOR_ALLOC是同步方式進行,對應用幀率的影響最大。
當堆內存不夠的時候容易被觸發,尤其是new一個對象的時候,很容易被觸發到,所以如果要加速啟動,可以提高dalvik.vm.heapstartsize的值,這樣在啟動過程中可以減少GC_FOR_ALLOC的次數。注意這個觸發是以同步的方式進行的。如果GC後仍然沒有空間,則堆進行擴張
這個gc是被可以調用的,比如system.gc, 一般gc線程的優先級比較低,所以這個垃圾回收的過程不一定會馬上觸發, 千萬不要認為調用了system.gc,內存的情況就能有所好轉
當分配的對象大小超過384K時觸發,注意這是以異步的方式進行回收的.如果發現大量反復的Concurrent GC出現,說明系統中可能一直有大於384K的對象被分配,而這些往往是一些臨時對象,被反復觸發了。給到我們的暗示是:對象的復用不夠。
Native層的內存分配失敗了,這類GC就會被觸發。如果GPU的紋理、bitmap、或者java.nio.ByteBuffers的使用沒有釋放,這種類型的GC往往會被頻繁觸發。
二. 內存抖動現象
Memory Churn內存抖動,內存抖動是因為在短時間內大量的對象被創建又馬上被釋放。瞬間產生大量的對象會嚴重占用內存區域,當達到閥值,剩余空間不夠的時候,會觸發GC從而導致剛產生的對象又很快被回收。即使每次分配的對象占用了很少的內存,但是他們疊加在一起會增加Heap的壓力,從而觸發更多其他類型的GC。這個操作有可能會影響到幀率,並使得用戶感知到性能問題。
通過Memory Monitor,我們可以跟蹤整個app的內存變化情況。若短時間發生了多次內存的漲跌,這意味著很有可能發生了內存抖動。
三. GC優化
通過Heap Viewer,我們可以查看當前內存快照,便於對比分析哪些對象有可能發生了洩漏。更重要的工具是Allocation Tracker,追蹤內存對象的類型、堆棧、大小等。手Q有做一個統計工具,對Allocation Tracker的原始數據,按照(類型&堆棧)的組合(堆棧取棧頂的5層)統計某一種對象分配的大小、次數。同時按照次數、大小的排序,從多/大到少/小結合代碼分析,並自頂向下的逐輪進行優化。
這樣,我們就可以快速知道發生內存抖動時,是因為哪些變量的創建造成頻繁GC。一般來說我們需要注意以下幾個方面:
字符串拼接優化
減少字符串使用加號拼接,改為使用StringBuilder。減少StringBuilder.enlarge,初始化時設置capacity;這裡需要注意的是,若打開Looper中Printer回調,也會存在較多的字符串拼接。
Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); }
建立全球緩存池,對頻繁申請、釋放的對象類型重用
例如在ondraw、getview中應減少對象申請,盡量重用。更多是一些邏輯上的東西,例如循環中不斷申請局部變量等
總結
我們並不能將內存優化中用到的所有技巧都一一說明,而且隨著Android版本的更替,可能很多方法都會變的過時。我在想更重要的是我們能持續的發現問題,精細化的監控,而不是一直處於"哪個有坑填哪裡的"的窘況。在這裡給大家的建議有:
1、率先考慮采用已有的工具;中國人喜歡重復造輪子,我們更推薦花精力去優化已有工具,為廣大碼農做貢獻。生活已不易,碼農何為為難碼農!
2、不拘泥於點,更重要在於如何建立合理的框架避免發生問題,或者是能及時的發現問題。
當前微信內存監控體系中也存在一些不盡人意的地方,在未來的日子裡也同樣需要努力去優化。
以上就是本文的全部內容,希望對大家優化Android內存有所幫助。
在開發Android應用程序時,如果需要使用系統提供的服務,可以通過服務名稱調用山下文的getSystemService(String name)來獲取服務管理者,那麼該
中國移動推出的一卡多號業務可以在已有移動手機號上增加1-3個副號,不用換機、換卡。特別適合想擁有多個手機號碼的用戶或需要保護隱私的用戶服務。在不增加手機、不
今天在慕課網看了一個視頻,介紹了幾種圖像處理的方法,其中有一種就是鏡面效果,但是他是通過自定義view的方式實現的,但是算法都大同小異。他的自定義view:package
Android開放的平台,獲得高度自由度,用戶也要承受系統當中一些潛在的問題,比如後台流量的消耗。那麼怎樣才能有效控制Android的流量使用呢?下面這5個