『壹』 android性能優化總結
常用的Android性能優化方法:
一、布局優化:
1)盡量減少布局文件的層級。
層級少了,繪制的工作量也就少了,性能自然提高。
2)布局重用 <include標簽>
3)按需載入:使用ViewStub,它繼承自View,一種輕量級控制項,本身不參與任何的布局和繪制過程。他的layout參數里添加一個替換的布局文件,當它通過setVisibility或者inflate方法載入後,它就會被內部布局替換掉。
二、繪制優化:
基於onDraw會被調用多次,該方法內要避免兩類操作:
1)創建新的局部對象,導致大量垃圾對象的產生,從而導致頻繁的gc,降低程序的執行效率。
2)不要做耗時操作,搶CPU時間片,造成繪制很卡不流暢。
三、內存泄漏優化:
1)靜態變數導致內存泄漏 比較明顯
2)單例模式導致的內存泄漏 單例無法被垃圾回收,它持有的任何對象的引用都會導致該對象不會被gc。
3)屬性動畫導致內存泄漏 無限循環動畫,在activity中播放,但是onDestroy時沒有停止的話,動畫會一直播放下去,view被動畫持有,activity又被view持有,導致activity無法被回收。
四、響應速度優化:
1)避免在主線程做耗時操作 包括四大組件,因為四大組件都是運行在主線程的。
2)把一些創建大量對象等的初始化工作放在頁面回到前台之後,而不應該放到創建的時候。
五、ListView的優化:
1)使用convertView,走listView子View回收的一套:RecycleBin 機制
主要是維護了兩個數組,一個是mActiveViews,當前可見的view,一個是mScrapViews,當前不可見的view。當觸摸ListView並向上滑動時,ListView上部的一些OnScreen的View位置上移,並移除了ListView的屏幕范圍,此時這些OnScreen的View就變得不可見了,不可見的View叫做OffScreen的View,即這些View已經不在屏幕可見范圍內了,也可以叫做ScrapView,Scrap表示廢棄的意思,ScrapView的意思是這些OffScreen的View不再處於可以交互的Active狀態了。ListView會把那些ScrapView(即OffScreen的View)刪除,這樣就不用繪制這些本來就不可見的View了,同時,ListView會把這些刪除的ScrapView放入到RecycleBin中存起來,就像把暫時無用的資源放到回收站一樣。
當ListView的底部需要顯示新的View的時候,會從RecycleBin中取出一個ScrapView,將其作為convertView參數傳遞給Adapter的getView方法,從而達到View復用的目的,這樣就不必在Adapter的getView方法中執行LayoutInflater.inflate()方法了。
RecycleBin中有兩個重要的View數組,分別是mActiveViews和mScrapViews。這兩個數組中所存儲的View都是用來復用的,只不過mActiveViews中存儲的是OnScreen的View,這些View很有可能被直接復用;而mScrapViews中存儲的是OffScreen的View,這些View主要是用來間接復用的。
2)使用ViewHolder避免重復地findViewById
3)快速滑動不適合做大量非同步任務,結合滑動監聽,等滑動結束之後載入當前顯示在屏幕范圍的內容。
4)getView中避免做耗時操作,主要針對圖片:ImageLoader來處理(原理:三級緩存)
5)對於一個列表,如果刷新數據只是某一個item的數據,可以使用局部刷新,在列表數據量比較大的情況下,節省不少性能開銷。
六、Bitmap優化:
1)減少內存開支:圖片過大,超過控制項需要的大小的情況下,不要直接載入原圖,而是對圖片進行尺寸壓縮,方式是BitmapFactroy.Options 采樣,inSampleSize 轉成需要的尺寸的圖片。
2)減少流量開銷:對圖片進行質量壓縮,再上傳伺服器。圖片有三種存在形式:硬碟上時是file,網路傳輸時是stream,內存中是stream或bitmap,所謂的質量壓縮,它其實只能實現對file的影響,你可以把一個file轉成bitmap再轉成file,或者直接將一個bitmap轉成file時,這個最終的file是被壓縮過的,但是中間的bitmap並沒有被壓縮。bitmap.compress(Bitmap.CompressFormat.PNG,100,bos);
七、線程優化:
使用線程池。為什麼要用線程池?
1、從「為每個任務分配一個線程」轉換到「在線程池中執行任務」
2、通過重用現有的線程而不是創建新線程,可以處理多個請求在創建銷毀過程中產生的巨大開銷
3、當使用線程池時,在請求到來時間 ,不用等待系統重新創建新的線程,而是直接復用線程池中的線程,這樣可以提高響應性。
4、通過和適當調整線程池的大小 ,可以創建足夠多的線程以使處理器能夠保持忙碌狀態,同時還可以防止過多線程相互競爭資源而使應用程序耗盡內存或者失敗。
5、一個App裡面所有的任務都放在線程池中執行後,可以統一管理 ,當應用退出時,可以把程序中所有的線程統一關閉,避免了內存和CPU的消耗。
6、如果這個任務是一個循環調度任務,你則必須在這個界面onDetach方法把這個任務給cancel掉,如果是一個普通任務則可cancel,可不cancel,但是最好cancel
7、整個APP的總開關會在應用退出的時間把整個線程池全部關閉。
八、一些性能優化建議:
1)避免創建過多對象,造成頻繁的gc
2)不要過多使用枚舉,枚舉佔用的空間比整型大很多
3)字元串的拼接使用StringBuffer、StringBuilder來替代直接使用String,因為使用String會創建多個String對象,參考第一條。
4)適當使用軟引用,(弱引用就不太推薦了)
5)使用內存緩存和磁碟緩存。
『貳』 Android App內存優化
內存優化就是對內存問題的一個預防和解決,做內存優化能讓應用掛得少、活得好和活得久。
掛的少:
「掛」指的是 Crash,內存問題導致 Crash 的具體表現就是內存溢出異常 OOM。
活得好:
活得好指的是使用流暢,Android 中造成界面卡頓的原因有很多種,其中一種就是由內存問題引起的。內存問題之所以會影響到界面流暢度,是因為垃圾回收(GC,Garbage Collection),在 GC 時,所有線程都要停止,包括主線程,當 GC 和繪制界面的操作同時觸發時,繪制的執行就會被擱置,導致掉幀,也就是界面卡頓。
活得久:
活得久指的是我們的應用在後台運行時不會被幹掉。Android 會按照特定的機制清理進程,清理進程時優先會考慮清理後台進程。清理進程的機制就是LowMemoryKiller。在 Android 中不同的進程有著不同的優先順序,當兩個進程的優先順序相同時,低殺會優先考慮幹掉消耗內存更多的進程。也就是如果我們應用佔用的內存比其他應用少,並且處於後台時,我們的應用能在後台活下來,這也是內存優化為我們應用帶來競爭力的一個直接體現。
內存佔用是否越少越好?
當系統 內存充足 的時候,我們可以多用 一些獲得更好的性能。當系統 內存不足 的時候,我們希望可以做到 」用時分配,及時釋放「。內存優化並不能一刀切。
我們都知道,應用程序的內存分配和垃圾回收都是由Android虛擬機完成的,在Android 5.0以下,使用的是Dalvik虛擬機,5.0及以上,則使用的是ART虛擬機。
Android虛擬機Dalvik和ART
1、內存區域劃分
詳細請看以下兩篇文章(建議全看):
java內存四大區_JVM內存區域劃分
Android 內存機制
2、內存回收
垃圾收集的標記演算法(找到垃圾):
垃圾收集演算法(回收垃圾):
引用類型:強引用、軟引用、弱引用、虛引用
對象的有效性=可達性+引用類型
JAVA垃圾回收機制-史上最容易理解看這一篇就夠了
Android:玩轉垃圾回收機制與分代回收策略
android中還存在低殺機制,這種情況屬於系統整機內存不足,直接把應用進程殺掉的情況。
Android後台殺死系列:LowMemoryKiller原理
1、內存溢出
系統會給每個App分配內存空間也就是heap size值,當app佔用的內存加上申請的內存超過這個系統分配的內存限額,最終導致OOM(OutOfMemory)使程序崩潰。
通過命令 getprop |grep dalvik.vm.heapsize 可以獲取系統允許的最大
注意:在設置了heapgrowthlimit的狀況下,單個進程可用最大內存為heapgrowthlimit值。在android開發中,若是要使用大堆,須要在manifest中指定android:largeHeap為true,這樣dvm heap最大可達heapsize。
關於heapsize & heapgrowthlimit
2、內存泄漏
Android系統虛擬機的垃圾回收是通過虛擬機GC機制來實現的。GC會選擇一些還存活的對象作為內存遍歷的根節點GC Roots,通過對GC Roots的可達性來判斷是否需要回收。內存泄漏就是 在當前應用周期內不再使用的對象被GC Roots引用,造成該對象無法被系統回收,以致該對象在堆中所佔用的內存單元無法被釋放而造成內存空間浪費,使實際可使用內存變小。簡言之,就是 對象被持有導致無法釋放或不能按照對象正常的生命周期進行釋放。
Android常見內存泄漏匯總
3、內存抖動
指的是在短時間內大量的新對象被實例化,運行時可能無法承載這樣的內存分配,在這種情況下就會導致垃圾回收事件被大量調用,影響到應用程序的UI和整體性能,最終可能導致卡頓和OOM。
常見情況:在一些被頻繁調用的方法內不斷地創建對象。例如在View 的onDraw方法內new 一些新的對象。
注意內存抖動也會導致 OOM,主要原因有如下兩點:
1、Android Studio Profiler
作用
優點
內存抖動問題處理實戰
理解內存抖動的概念的話,我們就能明白只要能找到抖動過程中所產生的對象及其調用棧,我們就能解決問題,剛好Android Studio 的Porfiler裡面的Memory工具就能幫我們記錄下我們操作過程中或靜止界面所產生的新對象,並且能清晰看到這些對象的調用棧。
選擇Profile 中 的Memory ,選擇 Record Java/Kotlin allocations,再點擊Record開始記錄, Record Java/Kotlin allocations 選項會記錄下新增的對象。
操作完成之後,點擊如圖所示的紅腦按鈕,停止記錄。
停止記錄後,我們就可以排序(點擊 Allocations可以排序)看看哪些對象或基本類型在短時間被頻繁創建多個,點擊這些新增的對象就可以看到它的完成的調用鏈了,進而就找找到導致內存抖動的地方在哪裡了。
2、利用DDMS 和 MAT(Memory Analyzer tool)來分析內存泄漏
我們利用工具進行內存泄漏分析主要是用對比法:
a.先打開正常界面,不做任何操作,先抓取一開始的堆文件。
b.一頓胡亂操作,回到原來操作前的界面。主動觸發一兩次GC,過10秒再抓取第二次堆文件。
c.通過工具對比,獲取胡亂操作後新增的對象,然後分析這些新增的對象。
DDMS作用:抓取堆文件,主動觸發GC。(其實也是可以用Android Studio 的Profile裡面的Memory工具來抓取堆文件的,但是我這邊在利用Profile 主動觸發gc 的時候會導致程序奔潰,也不知道是不是手機的問題,所以沒用Android Studio的Profiler)
MAT作用:對堆文件進行對比,找到多出的對象,找到對象的強引用調用鏈。
以下是詳細的過程:
步驟1.打開DDMS,選擇需要調試的應用,打開初始界面,點擊下圖的圖標(Dump Hprof File)先獲取一次堆文件。
步驟2.對應用隨便操作後,回到一開始的界面,先多觸發幾次GC ,點擊下圖的圖標(Cause Gc)來主動觸發GC,然後再次點擊 Dump Hprof File 圖標來獲取堆文件。
步驟3.通過Android Studio Profile 或者 DDMS mp 的堆文件無法在MAT 打開,需要藉助android sdk包下的一個工具hprof-conv.exe來轉換。
格式為 hprof-conv 舊文件路徑名 要轉換的名稱;
例如:hprof-conv 2022-04-13_17-54-40_827.hprof change.hprof
步驟4.把兩份堆文件導入MAT,然後選擇其中第二次獲取的堆文件,點擊 如圖所示的 Histogram查看。
步驟5.點擊下圖圖標,Compare To Another Heap Dump ,選擇另一份堆文件。
6.會得出下圖所示的 Hitogram 展示,我們主要看Objects 這一列。 如下圖所示 「+ 2」 則代表前面兩份堆文件對比,這個對象多了兩個,我們主要就是要分析這些多了出來,沒有被回收的對象。
7.加入我們從增加的對象中,看到了MainActivity ,則需要從一開始打開的Hitogram 展示裡面找到這個對象的調用棧。如下圖所示,搜索MainActivity
8.看到下圖所示解僱,然後滑鼠右鍵點擊下圖紅色圈圈著的MainActivity ,選擇 Merger Shortest Paths to Gc Roots ,再選擇 exclude all phantom/weak/soft etc.references ,就可以看到這個MainActivity 對象的強引用鏈,至此我們就可以找到MainActivity對象是被什麼引用導致無法回收了。
3、內存泄露檢測神器之LeakCanary(線下集成)
自行學習了解,接入簡單,使用簡單,基本可以解決大部分內存泄漏問題。
github地址 : https://github.com/square/leakcanary/
學習地址 : https://square.github.io/leakcanary/changelog/#version-22-2020-02-05
針對內存抖動的建議:
針對內存泄漏問題的建議:
針對內存溢出問題的建議(主要就是要減少內存佔用):
建議參考:
深入探索 Android 內存優化(煉獄級別)
對於 優化的大方向,我們應該優先去做見效快的地方,主要有以下三部分:內存泄漏、內存抖動、Bitmap。完善監控機制也是我們的重點,能幫助我們對內存問題快速分析和處理。
參考:
深入探索 Android 內存優化(煉獄級別)
『叄』 Android內存優化三:內存泄漏檢測與監控
Android內存優化一:java垃圾回收機制
Android內存優化二:內存泄漏
Android內存優化三:內存泄漏檢測與監控
Android內存優化四:OOM
Android內存優化五:Bitmap優化
Memory Profiler 是 Profiler 中的其中一個版塊,Profiler 是 Android Studio 為我們提供的性能分析工具,使用 Profiler 能分析應用的 CPU、內存、網路以及電量的使用情況。
進入了 Memory Profiler 界面。
點擊 Record 按鈕後,Profiler 會為我們記錄一段時間內的內存分配情況。
在內存分配面板中,通過拖動時間線來查看一段時間內的內存分配情況
通過搜索類或者報名的方式查看對象的使用情況
使用Memory Profiler 分析內存可以查看官網: 使用內存性能分析器查看應用的內存使用情況
對於內存泄漏問題,Memory Profiler 只能提供一個簡單的分析,不能夠確認具體發生問題的地方。
而 MAT 就可以幫我們做到這一點,它是一款功能強大的 Java 堆內存分析工具,可以用於查找內存泄漏以及查看內存消耗情況。
as 生成hprof文件無法被mat識別,需要進行轉換
使用hprof-conv進行轉換,hprof-conv位於sdkplatform-tools
ps:as導出hprof前最好先gc幾次,可排除一些干擾
Histogram 可以列出內存中的對象,對象的個數以及大小; Dominator Tree 可以列出那個線程,以及線程下面的那些對象佔用的空間; Top consumers 通過圖形列出最大的object; Leak Suspects 通過MA自動分析泄漏的原因。
Shallow Heap就是對象本身佔用內存的大小,不包含其引用的對象內存,實際分析中作用不大。常規對象(非數組)的ShallowSize由其成員變數的數量和類型決定。數組的shallow size有數組元素的類型(對象類型、基本類型)和數組長度決定。對象成員都是些引用,真正的內存都在堆上,看起來是一堆原生的byte[], char[], int[],對象本身的內存都很小。
Retained Heap值的計算方式是將Retained Set(當該對象被回收時那些將被GC回收的對象集合)中的所有對象大小疊加。或者說,因為X被釋放,導致其它所有被釋放對象(包括被遞歸釋放的)所佔的heap大小。
Path To GC Roots -> exclude all phantim/weak/soft etc. references:查看這個對象的GC Root,不包含虛、弱引用、軟引用,剩下的就是強引用。從GC上說,除了強引用外,其他的引用在JVM需要的情況下是都可以 被GC掉的,如果一個對象始終無法被GC,就是因為強引用的存在,從而導致在GC的過程中一直得不到回收,因此就內存泄漏了。
List objects -> with incoming references:查看這個對象持有的外部對象引用
List objects -> with outcoming references:查看這個對象被哪些外部對象引用
使用對象查詢語言可以快速定位發生泄漏的Activity及Fragment
使用 MAT 來分析內存問題,效率比較低,為了能迅速發現內存泄漏,Square 公司基於 MAT 開源了 LeakCanary ,LeakCanary 是一個內存泄漏檢測框架。
集成LeakCanary後,可以在桌面看到 LeakCanary 用於分析內存泄漏的應用。
當發生泄漏,會為我們生成一個泄漏信息概覽頁,可以看到泄漏引用鏈的詳情。
LeakCanary 會解析 hprof 文件,並且找出導致 GC 無法回收實例的引用鏈,這也就是泄漏蹤跡(Leak Trace)。
泄漏蹤跡也叫最短強引用路徑,這個路徑是 GC Roots 到實例的路徑。
LeakCanary 存在幾個問題,不同用於線上監控功能
線上監控需要做的,就是解決以上幾個問題。
各大廠都有開發線上監控方案,比如快手的 KOOM ,美團的 Probe ,位元組的 Liko
快手自研OOM解決方案KOOM今日宣布開源
總結一下幾點:
通過無性能損耗的 內存閾值監控 來觸發鏡像採集。將對象是否泄漏的判斷延遲到了解析時
利用系統內核COW( Copy-on-write ,寫時復制)機制,每次mp內存鏡像前先暫停虛擬機,然後fork子進程來執行mp操作,父進程在fork成功後立刻恢復虛擬機運行,整個過程對於父進程來講總耗時只有幾毫秒,對用戶完全沒有影響。