最近一直想着本身之後的路如何走,Android的坑位愈來愈少,對於能力的要求也愈來愈高。曾想着換一個方向,可是最終都放棄了,畢竟這是本身喜歡的東西。因此,繼續下去,不斷的在Android方向發展吧。機會是給準備的人的,不斷的充實本身,時刻準備着~html
進入正題。優化工做是一個開發工程師進階必備的一種能力。包括內存優化,電量優化,網絡優化等等。這些優化所須要的能力,實際上是對於各類知識的一種綜合運用處理能力。java
內存優化,是對於應用程序的內存使用、空間佔用進行必定的優化處理。android
經過內存優化,可以有效的避免內存泄漏、內存溢出、內存佔用過大等問題,從而最大程度上保證應用的流暢性、穩定性、以及存活機率。算法
內存的管理,主要是對於進程、對象以及變量的分配以及回收。數據庫
Android將內存分爲了:堆區、方法區、棧區。其中堆區是內存優化的主要區域。c#
對象的生命週期以及大小都是有區別的。爲了可以更合理的利用堆區,將其又按照生命週期的長短區分爲了年輕代、老年代和持久代。緩存
Android應用在啓動時,會經過Zygote進行來孵化應用所須要的進程。當進程建立以後,會交由Framework層來進行託管。性能優化
而對於對象和變量的內存分配,採用一種動態內存分配策略,可是並不會說能夠無限的增加,會有一個上限的限制。而這個最大值則跟具體的設備有關。畢竟隨着手機性能的增長,手機的處理能力更強了,從原來的512M到如今的6G內存,單個應用的可處理能力也在增長。網絡
對於應用中建立的不一樣對象的具體分配策略,則以下圖所示ide
這裏強調一下靜態分配區域中的靜態常量,這種常量會一直存活於程序的整個運行期間。後面咱們會講到這種常量致使的問題。
對於Android系統,依賴gc來自動的執行對於內存的回收工做。而回收工做主要依賴於各類回收算法。在Android的ART虛擬機,用到的算法主要有4種:
ART虛擬機會自動的根據實際的狀況,自動的選擇回收算法來進行內存的回收工做。好比說,當咱們的應用處於前臺的時候,顯示速度對於咱們來講是最重要的,這時候ART虛擬機會選擇簡單的標記清除算法。當應用處於後臺的時候,對於速度要求就低一些,這時候可能會採用標記整理算法來進行垃圾的回收工做。
ART虛擬機還具有對於內存的整理能力,從而減小內存空洞的問題
應用進程建立之後,ActivityManagerService就會根據進程的狀態計算一個其對應的OomAdj值,而後將這個值傳遞給Kernel中,kernel存在一個低內存回收機制(LMK)。當內存達到必定閾值時,觸發清理OomAdj較高的進程。
對於一些後臺佔用過大的程序,其回收以後的效益最大,其OomAdj對應的值高一些,回收的機率更大。
經過LMK機制可以保證資源的合理利用,防止過大的後臺應用影響到前臺程序的正常使用。
在Java中,對象的引用主要分爲四種。
強引用沒法回收,當對象用完之後,不移除對應的引用關係,就會致使對象沒法回收,而發生內存泄漏等狀況。因此在Android中要注意這種問題。
對於對象的使用不當的話,可能會致使3種內存問題:內存抖動、內存泄漏,內存溢出。
內存抖動是指內存的頻繁分配和回收會致使內存不穩定。在內存上一般呈現一種鋸齒狀,頻繁進行GC。
內存抖動一般會致使應用的頁面卡頓,長時間的內存抖動可能會致使應用的OOM。
實戰解決
對於內存抖動的問題,通常可使用Memory Profiler來進行排查處理便可處理。
測試案例:
@SuppressLint("HandlerLeak")
private static Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
for (int i = 0; i < 100; i++) {
String[] strings = new String[100000];
}
handler.sendEmptyMessageDelayed(0, 100);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
handler.sendEmptyMessage(0);
}
複製代碼
這裏咱們模擬了一建立大對象的操做。當程序運行起來之後,咱們點擊Android Studio中的Profile按鈕,就能夠看到咱們的內存狀況。這裏看到內存呈現了鋸齒狀,也就是所謂的內存抖動。
當遇到這種狀況的時候,就能夠知道發生了內存的抖動,那麼就須要查找哪些佔用內存比較高。
如上圖,經過順序操做之後,在4這個位置咱們發現了建立大批量對象位置是在SplashActivity的handlerMessage的方法中。經過雙擊就能夠跳轉到咱們上面寫的那一部分代碼了。
對於內存抖動,應該重點關注:循環或者頻繁調用的地方。由於這兩個地方很容易形成對象的頻繁分配會回收。
有一些常見的內存抖動的案例,在咱們進行代碼編寫的時候,應該儘可能避免的。
虛擬機進行GC時,會選擇一些還存活的對象做爲GC Roots,而後經過可達性分析來判斷對象是否回收。而內存泄漏是指一些再也不使用的對象被長期持有,致使內存沒法被釋放,其本質是由於再也不使用的對象被其餘生命週期更長的對象所持有。而在內存上的表現則是應用的內存逐漸上升,可用內存逐漸變小。
發生內存泄漏會致使咱們的內存不足,頻繁GC,若是泄漏狀況嚴重,會致使OOM。
實戰解決
對於內存泄漏一般會使用Memory Profile+MAT來進行內存泄漏的分析。
這裏咱們寫一個簡單的內存泄漏的案例,而後經過案例排查。
public class CallBackManager {
private static List<Activity> list=new ArrayList<Activity>();
public static void addCallBack(Activity activity){
list.add(activity);
}
}
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sedsd);
CallBackManager.addCallBack(this);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
findViewById(R.id.iv).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(SecondActivity.class);
}
});
}
複製代碼
這裏咱們準備了兩個頁面,第二個頁面註冊回調管理。咱們看一下內存的狀況。
當咱們進入第二個頁面,而後退出。通過循環屢次的點擊以後,內存慢慢的上升了~~~這種狀況基本就是內存泄漏致使的。
按照上圖的步驟點開之後,會看到內存中存在着18個對應的SecondActivity對象。也就是SecondActivity發生了內存泄漏。可是對於如何發生了泄漏,如今什麼實例發生泄漏,在這裏是沒法看出來的,須要經過MAT來幫助咱們定位問題。
在AndroidStudio中,將dump的文件導出。而後經過Android SDK的自帶轉換工具hprof-conv.exe,將文件轉化爲可以被MAT識別的hprof文件。轉化語句爲:
./hprof-conv file.hprof converted.hprof
複製代碼
而後經過MAT打開轉化後的.hprof文件。
gif可能效果不太好,這裏只是演示一下如何去使用MAT,咱們看一下最後找到的結果。
這裏左邊有小圓點的是咱們的gcRoot,也就是持有SecondActivity的實例,致使其沒法回收的根源。能夠看到持有實例的是CallBackManager的list對象
這時候咱們就能夠回到項目中去找這個對象,而後進行一個處理了。
集合類
集合類添加元素後,會持有集合對象的引用信息,致使集合對象沒法回收而內存泄漏。在程序退出,或者集合再也不使用的時候,先clear,再置爲空。
List<Object> objectList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Object o = new Object();
objectList.add(o);
o = null;
}
// 釋放objectList
objectList.clear();
objectList=null;
複製代碼
Static修飾的成員變量
被Static修飾的成員變量的週期=應用的生命週期。若是static修飾的成員變量引用耗費資源的實例(例如Context),那麼當實例結束生命週期的時候,由於靜態變量的持有而沒法被回收,從而出現內存泄漏。
解決方案:
典型:單例中的靜態變量
對於單例中的,可能第二種弱引用並非很合適,這時候只能使用第一種方案。
非靜態內部類/匿名內部類
非靜態內部類或者匿名類都會持有外部類的引用。而靜態的內部類則不會。
因此若是非靜態內部類的實例若是沒法回收的話,好比說用Static修飾,也會致使外部類沒法釋放而內存泄漏。
解決方案:
若是是Runnable,Thread等匿名類,若是內部耗時工做,那麼工做線程實例持有外部類引用,也會形成實例沒法回收的問題。
Handler臨時性的內存泄漏
Handler在發送出去Message以後,會保存在MessageQueue中。因爲Message中會保存着Handler的引用,因此若是是延遲處理的消息,那麼極可能致使Handler沒法回收,若是再使用了匿名內部類,那麼所對應的的Activity實例也就沒法回收,從而致使內存泄漏。
解決方案:
資源型文件未關閉
對於資源的使用(廣播,文件流,數據庫遊標,圖片資源BitMap等),若是不關閉或者註銷,那麼資源就不會回收,從而致使內存泄漏。應該在資源對象再也不使用的時候,調用其close()方法將其關閉,而後再置爲null。
WebView
WebView因爲其特性問題,只要在應用中使用一次,那麼內存就不會進行釋放。能夠爲WebView建立單獨的進程,經過AIDL方式和主進程進行通訊,當WebView須要銷燬時,直接銷燬其進程來達到內存釋放的目的。
Adapter
在快速滑動ListView或者RecyclerView的時候,會頻繁的建立大量對象,不只浪費資源和時間,內存也會愈來愈大。可使用緩存的convertView和ViewHolder來進行緩存。
當內存使用過大的時候,會致使內存的溢出,也就是OOM。這種狀況不必定發生在相對應的代碼處。內存抖動和內存泄漏都有可能會致使最後的內存溢出。對於內存溢出的狀況,除了要考慮內存抖動和內存泄漏的問題,還應該儘可能使用合適的類型來優化代碼
使用Android優化事後的SparseArray,SparseInt等集合類。代替HashMap。
使用IntDef、StringDef等,來替代枚舉。
經過LruCache方法來實現對大資源的緩存功能
BitMap優化。位圖使用RGB_565或者ARGB_4444。
重寫 onTrimMemory/onLowMemory 方法去釋放掉圖片緩存、靜態緩存來自保
RecyclView和ListView不可見時,釋放掉對圖片的
ListView的3級緩存:在ImageView的DetachFromWindows時釋放掉
RecyclerView的5級緩存:在放入到mRecyclerPool時回收(重寫Adapter的onViewRecycled方法)
其餘
使用基本數據類型
使用For加強
使用軟引用和弱引用
採用內存緩存或者磁盤緩存
對於建立複雜的使用池技術
儘可能使用靜態內部類
哪怕對於常見的內存泄漏案例瞭如指掌,可是確定仍是會不免出現內存泄漏現象。這時候咱們就須要借用工具來檢測內存的泄漏狀況了。最經常使用的工具分別
對於前兩種工具,咱們在以前的案例中進行了一部分的功能展現,這裏咱們作一些總結的信息。
實時的內存使用狀況,方便直觀
識別內存抖動、泄漏等,
提供堆轉儲、強制GC以及跟蹤內存分配的能力
線下平時使用
強大的Java Heap 分析工具,查找內存泄漏以及內存佔用
生成總體的報告,分析問題等等
線下的深刻使用工具
能夠和Memory Profiler結合使用,一個定位問題,一個深刻分析
LeakCanary是一種自動內存泄漏檢測的神器,僅推薦使用於線下集成。可是其缺點也是比較明顯的。
這篇文章對於內存的常見問題以及處理方案進行了總結。
可是內存優化,若是深挖,會涉及到不少不少的知識點。
一個最須要優化的Bitmap,其內存佔是如何計算的等等,爲何就佔用了那麼多的內存;對於LMK機制,咱們如何保證程序在後臺運行的時候可以保證最大程度不被殺掉;線上如何造成一套APM的機制,內存出現問題之後上報等等都是值得研究的地方。
Android性能優化:手把手帶你全面瞭解 內存泄露 & 解決方案
本文由 開了肯 發佈!
同步公衆號[開了肯]