0.前言
Android Studio升級到3.0以后DDMS入口不見了,不要著急,取而代之的是Layout Inspector,File Explorer以及Profiler等新工具。很多人對新工具還不是很了解,Profiler是一個分析app性能的強大工具合輯,可以分析內(nèi)存、cpu、啟動時間、網(wǎng)絡(luò)情況、功耗等各個指標(biāo),今天先來看看Profiler如何分析應(yīng)用的內(nèi)存情況吧。
1.打開Profiler
如何打開Profiler呢?點擊菜單欄上的Run -> Profiler ‘a(chǎn)pp’可以開始分析app性能。不過我更喜歡用Ctrl+Shift+a,執(zhí)行find action命令,搜索profiler,可以不用鼠標(biāo)更快地打開Profiler。

app啟動好后,profiler也會起來,主界面如下:

點擊左上角紅色的按鈕可以停止分析,想要再次分析就點左邊的+號開始新的會話。
可以看到主界面一直在跑的有四個性能指標(biāo),分別是CPU、MEMORY、NETWORK、ENERGY,今天我們是來看內(nèi)存分析的,所以點擊MEMORY看看。
2.進入Memory分析界面

可以看到,這里以四種顏色分別表示四種內(nèi)存占用情況,其中我們比較關(guān)心的Native和Java堆內(nèi)存在最下面,把鼠標(biāo)放上去可以看到具體的數(shù)值。

點擊左上的垃圾桶可以強制進行g(shù)c垃圾回收,在定位內(nèi)存泄漏的時候很有用。
3.查看Java堆內(nèi)存和具體Java實例
點擊任意一個時間點可以看到當(dāng)前的java堆內(nèi)存情況,下面來做個實驗,在當(dāng)前Activity添加幾個自定義對象Person并保存到一個成員變量集合中:
public class MainActivity extends AppCompatActivity {
List<Person> persons = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
persons.add(new Person("zhangsan",23));
persons.add(new Person("lisi",24));
persons.add(new Person("wangwu",25));
}
private class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
}
按道理來說,被成員變量persons集合持有后,這三個實例肯定不會被回收,我們來看下是不是這樣,點擊啟動后的任意一個時間點,下方會列出所有的java對象,然后我們點擊右側(cè)的搜索,搜索到Person

可以看到Person對象的totalsize是3,點擊后右側(cè)顯示的正好是三個實例?,F(xiàn)在我們明白了如何去查詢某個時間點內(nèi)存對象的數(shù)量了。
當(dāng)然你的模擬器或測試機可能不支持這種隨時點擊任意時間點就能看到Java堆的feature(看官網(wǎng)寫的好像是要8.0以上),沒關(guān)系點擊垃圾桶右邊的Dump Java Heap按鈕一樣可以打印Java堆的情況。打印下來的文件可以在左側(cè)另存為hprof文件,可以用MAT等內(nèi)存分析工具進行分析。
4.內(nèi)存泄漏分析
Android內(nèi)存優(yōu)化最常見的問題就是發(fā)現(xiàn)并解決內(nèi)存泄漏了,其中又以Activity泄漏最為常見。下面我們來手寫一個會發(fā)生泄漏的Activity(沒用問題制造問題也要上- -),看看怎么用Profiler定位泄漏問題:
public void openLeakActivity(View view) {
startActivity(new Intent(this, LeakActivity.class));
}
創(chuàng)建一個新的LeakActivity,并在MainActivity添加一個啟動LeakActivity的按鈕。然后我們來給LeakActivity制造泄漏。
public class LeakActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak);
mHandler.sendEmptyMessageDelayed(0, 24 * 60 * 60);
}
}
Activity泄漏的經(jīng)典案例:Handler持有造成的泄漏。handler持有Activity對象,讓Handler延時發(fā)送消息這樣Handler在消息發(fā)完之前就不會回收,Activity也跟著無法銷毀。其實這段代碼寫出來就會被Android Studio瘋狂diss,用大大的黃塊告訴你Handler應(yīng)當(dāng)用static修飾,否則可能會發(fā)生泄漏。但是現(xiàn)在我們要的就是泄漏。打開應(yīng)用,開啟Profiler,不斷反復(fù)打開、關(guān)閉LeakActivity,我這里打開關(guān)閉了六次,看下內(nèi)存的波動情況:

我這里圖沒截好,不過也能看出來趨勢了,每次打開LeakActivity內(nèi)存都會上漲一點點。這里我忘記點force gc了,當(dāng)然由于handler無法銷毀,gc自然也是沒用的。
現(xiàn)在我們可以確認LeakActivity存在泄漏了,那如何定位泄漏原因呢?我們先Dump一下Java堆,來查找一下LeakActivity:

果不其然,打開的六個LeakActivity一個也沒有銷毀,右側(cè)點擊任意一個LeakActivity,我們來看看引用情況。
很明顯可以看到,前面的八個this$0引用都是值得那一個Handler匿名內(nèi)部類,這樣我們就輕松定位到了泄漏原因。原因找到了,再根據(jù)內(nèi)存引用的知識去修改就容易了。這里我們只要根據(jù)提示把匿名內(nèi)部類Handler改為靜態(tài)內(nèi)部類就可以,如過引用了成員變量無法用static修飾,就使用弱引用去獲取對象。
好了,使用Profiler去分析內(nèi)存是不是輕松加愉快呢?不要再怕定位內(nèi)存泄漏啦!