編輯:關於Android編程
Android開發中時常會遇到內存洩漏的問題,而Android系統對單個App又有一定的內存限制,此值可以通過一下方式獲取:
ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();
上述代碼中momeryClass的值可以當做每個App的內存限制。這個值根據不同的設備廠商都是不一樣的,比如我的模擬器的值是32M,如果在我的模擬器上運行的一個App,分配的內存空間超過32M,則會報OOM(內存溢出)!而內存洩漏也是一個導致內存溢出的隱患,因此必須掌握解決內存溢出的方法。
本章主要講解使用Android Studio查看是否有內存洩漏問題,然後使用MAT(Memory Analyzer Tool)來分析並解決內存洩漏問題。
Android Studio分析是否有內存洩漏
打開Android Studio中的Android Monitor中的Memory面板,可以看到有一個實時變化的堆內存曲線圖,如下圖所示
上圖中重點列出了3部分內容:
<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxjb2RlPrG7suLK1LXE1tW2y8no1sOjrMjnzbzL+cq+ztLKx9TaxKPE4sb3TmV4dXNfU8nP1/ay4srUILG7suLK1LXEvfizzKOsteO797/J0aHU8cbky/tBcHBsaWNhdGlvbrvy1d+9+LPMILWxx7Cxu7LiytS1xL34s8zW0MTatOa31sXkx+m/9jxiciAvPg0KQWxsb2NhdGVktPqx7dLRt9bF5LXEv9W85CBGcmVltPqx7b/J08PKo9Pgv9W85CBBbGxvY2F0ZWQgKyBGcmVlsrvE3LOsQXBwxNq05s/e1sYoMzJNKSA8L2NvZGU+DQo8cD48Y29kZT7E2rTmt9bO9rXEuaS+38C4o6y008nPz/LPwtK7ubI0uPawtMWlo6zSwLTOysejujxiciAvPg0KPGltZyBhbHQ9"Enable" src="/uploadfile/Collfiles/20160811/20160811100327967.png" title="\" /> 終止檢測的開關,沒什麼實質性的作用
就是手動調用GC,我們在抓內存前,一定要手動點擊 Initiate GC按鈕手動觸發GC,這樣抓到的內存使用情況就是不包括Unreachable對象的(Unreachable指的是可以被垃圾回收器回收的對象,但是由於沒有GC發生,所以沒有釋放,這時抓的內存使用中的Unreachable就是這些對象)
獲取hprof文件(hprof文件是我們使用MAT工具分析內存時使用的文件),但這裡直接產生的文件MAT還不能直接使用,需用轉換成標准的hprof文件。可以使用AndroidStudio轉換或者用hprof-conv命令轉化,網上可以查到
開始分配追蹤,第一次點擊可以指定追蹤內存的開始位置,第二次點擊可以結束追蹤的位置。這樣我們截取了一段要分析的內存,等待幾秒鐘AndroidStudio會給我們打開一個Allocation視圖(感覺和MAT工具差不多,不過MAT工具更加強大,我們也可以獲取hprof文件,使用MAT來分析)
寫一段代碼動態演示一下:
xml布局文件如下,定義一個Button,並設置onClick屬性
Activity代碼如下:聲明Button被點擊回調的click方法
public void click(View view) {
for (int i = 0; i < 10000; i++) {
ImageView imageView = new ImageView(this);
list.add(imageView);
}
}
通過上面代碼,可以預見,每次點擊Button時,都會動態生成10000個ImageView並添加到List中保存起來,Memory的效果圖如下:
可以看到,剛開始系統分配了2M左右的內存,當點擊一次Button之後,內存增加到8M,再次點擊內存增加到24M左右。
上述情況下,當我們按下返回退出Activity時,然後點擊Init GC按鈕執行垃圾回收操作,進程中的內存會重新回到2M,如下圖:
這種情況下,代碼是安全穩定的代碼,但是如果Activity中有內存洩漏會是何種情況呢,接下來我們先把之前的代碼修改一下,認為構造一個內存洩漏的場景,如下代碼所示:
public class MainActivity extends AppCompatActivity {
private List list = new ArrayList<>();
static MemoryLeak memoryLeak;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (memoryLeak == null) {
memoryLeak = new MemoryLeak();
}
}
public void click(View view) {
for (int i = 0; i < 10000; i++) {
ImageView imageView = new ImageView(this);
list.add(imageView);
}
}
class MemoryLeak {
void doSomeThing() {
System.out.println("Wheee!!!");
}
}
可以看到,在MainActivity中,添加了一個非靜態內存類MemoryLeak,然後聲明了一個靜態MemoryLeak引用。
運行上述代碼,然後再次執行點擊Button的操作,可以看到內存同樣會上升到8M左右,再次點擊上升到16M左右,但是此時按下返回按鈕並執行垃圾回收操作之後,Allocated + Free的總空間並沒有重新回到2M左右,而是一直徘徊於8M左右 說明存在內存洩漏!!! 但是為什麼會是8M呢??
Android Studio生成內存字節文件
剛才在介紹Studio的Memory面板時,有提到一個工具欄Dump Java Heap,通過點擊此按鈕就可以導出一個hprof文件,此過程會比較慢,需要耐心等待,當下圖中心的圓圈停止轉動之後hprof文件也就導出成功
導出完成後將自動打開這個文件,如下圖所示:
點擊Analyzer Tasks右邊的綠色運行箭頭,Android Studio會自動的根據此hprof文件分析有哪些類是有內存洩漏的,如下圖所示:
確實有一個MainActivity存在內存洩漏的情況,但是跟我之前預想的有一點出入,本來以為向網上很多人說的那樣,每次打開一個MainActivity時都會造成內存洩漏,但是現在事實就擺在眼前。仔細想了一下也恍然大悟了,MemoryLeak在第一個MainActivity中被聲明是static靜態的,當第二個被打開的MainActivity並不會再重新初始化MemoryLeak對象了,因此static MemoryLeak對象在內存中只是持有了第一個MainActivity的對象的引用,因此當我們調用多次GC操作之後,實際上只有第一個MainActivity不會被GC回收掉!!
如果再將Activity的代碼修改一下
package material.danny_jiang.com.adbmemoryanalyze;
import android.app.ActivityManager;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private List list = new ArrayList<>();
//private static MemoryLeak memoryLeak;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//memoryLeak = new MemoryLeak();
}
public void click(View view) {
for (int i = 0; i < 10000; i++) {
ImageView imageView = new ImageView(this);
list.add(imageView);
}
new Thread() {
@Override
public void run() {
super.run();
while (true) {
try {
System.out.println("Thread running!!");
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
class MemoryLeak {
void doSomeThing() {
System.out.println("Wheee!!!");
}
}
}
可以看到,我講造成內存洩漏的場景由內部類改成了內部線程類,並且在線程中無限循環打印log。
再次執行進入MainActivity–返回鍵–進入MainActivity–返回鍵的操作
然後再生成hprof文件並打開,並執行Analyzer Tasks,可以看到如下圖片的信息:
上圖可以看出打開的每一個MainActivity都會造成內存洩漏。 擦嘞!!為什麼這會兒又是這種情況呢???這個問題就牽涉到Java中線程的問題了—Java中的Thread有一個特點就是她們都是直接被GC Root所引用,也就是說Dalvik虛擬機對所有被激活狀態的線程都是持有強引用,導致GC永遠都無法回收掉這些線程對象,除非線程被手動停止並置為null或者用戶直接kill進程操作。看到這相信你應該也是心中有答案了吧 : 我在每一個MainActivity中都創建了一個線程,此線程會持有MainActivity的引用,即使退出Activity當前線程因為是直接被GC Root引用所以不會被回收掉,導致MainActivity也無法被GC回收。所以當使用線程時,一定要考慮在Activity退出時,及時將線程也停止並釋放掉
MAT內存分析工具
正常來講,根據上面我講的使用Studio來分析簡單的內存洩漏已經足夠了,但是在Studio之前有一款更加強大的內存分析工具MAT,谷歌工程師稱它更加的Powerful!!。接下來就看一下如何使用MAT來分析內存問題
1 首先在eclipse官網下載MAT工具
https://www.eclipse.org/mat/
2 下載完MAT並安裝好之後,需要先生成hprof文件。
這兩我還是使用之前線程造成內存洩漏的案例來演示,
首先第一次打開MainActivity時,點擊dump heap生成一個hprof文件 其次進行一系列的操作, 比如點擊Button,按下返回鍵,再次進入MainActivity等等,這裡我重復了4遍如上操作,然後再點擊dump heap生成hprof文件
3 點擊Studio的Captures欄,顯示剛才生成的hprof文件,如下圖所示:
這兩個文件我們需要使用MAT去打開並對比分析,但是MAT不能直接打開這兩個文件,需要將它轉換成MAT能夠識別的文件,Captures欄中,右鍵點擊每一個hprof文件,然後選擇Export to standard .hprof並保存到電腦目錄中,如下圖:
4 使用MAT打開轉換後的hprof文件,顯示如下圖
可以看到有兩個dump的面板,其中每一個都顯示了一個內存的餅狀圖。其中用的最多的功能是左下角的Histogram, 點擊 Actions下的 Histogram項將得到 Histogram結果:
它按類名將所有的實例對象列出來,可以點擊表頭進行排序,在表的第一行可以輸入正則表達式來匹配結果 :
在Histogram中,可以右鍵某一想查看的對象,然後選中List Objects來查看此對象的所有實例,如下圖
選中之後,會跳出所有實例對象面板,在此面板中可以可以繼續某一特定實例在內存中的Path To GC Root(從GC開始的強引用)。在之前的案例操作中,我重復的進入MainActivity4次,並依次點擊Button運行線程,因此正常來說MainActivity應該有4個實例在內存當中,如下圖
exclude all phantom/weak/soft的意思是講所有的虛引用/軟引用/弱引用都排除掉,因為只有強引用才會造成內存洩漏!點擊之後顯示下圖信息:
可以看到,MainActivity最終都是被一個叫做MainActivity
如何發現內存洩漏
上面分別介紹了使用Android studio和MAT分析內存的方法。Android studio自帶的內存分析工具直觀方便,但其功能卻不如MAT強大,特別是沒有有效的搜索、排序等功能。遇到一些棘手的問題,可能還是要借助MAT來分析內存。
上面的例子是我們人為制造了一個內存洩漏,然後有意用工具檢測他。但實際開發中,我們如何發現內存洩漏呢?我想可以首先使用studio自帶或DDMS中的heap分析工具,觀察在反復執行某個操作時(例如打開某個頁面、點擊某個按鈕、加載某個資源等等)時,內存在執行GC後能始終維持在穩定的值附近。如果內存呈線性增長的趨勢,那一定是發生了內存洩漏。此時,就要dump出內存鏡像,然後使用工具分析了。
在分析內存時,第一是可以使用工具自帶的洩漏檢查器幫助定位。另外,可以在執行操作(懷疑造成內存洩漏的操作)前後,分別dump出一份內存鏡像,然後使用MAT的Compare Basket對比兩個文件的內存情況,這樣可以幫助定位到是哪個對象發生了洩漏。然後再找到這個對象的GC Roots,這樣就可以進一步定位到具體的代碼了。
由於公司項目的需要,要實現在項目中使用第三方授權登錄以及分享文字和圖片等這樣的效果,幾經波折,查閱了一番資料,做了一個Demo。實現起來的效果還是不錯的,不敢獨享,決定寫
1、概述之前寫了一個Android 高仿 QQ5.0 側滑菜單效果 自定義控件來襲 ,恰逢QQ5.2又加了一個右側菜單,剛好看了下DrawerLayout,一方面官方的東
本文是以源碼中development/tools/idegen/README作為指導文檔.環境: Ubuntu 14.10,openJdk 1.7,Android Stu
引導頁面相信大家都不會陌生,安裝了一個新的App後第一次打開,都會有類似下圖,相當於說明文檔 實現效果 程序目錄結構 在主layout裡main.