Traceview簡介
TraceView 是 Android 平臺特有的數據採集和分析工具,它主要用於分析 Android 中應用程序的 hotspot。TraceView 本身只是一個數據分析工具,而數據的採集則需要使用 Android SDK 中的 Debug 類或者利用 DDMS 工具。二者的用法如下:
開發者在一些關鍵代碼段開始前調用 Android SDK 中 Debug 類的 startMethodTracing 函數,並在關鍵代碼段結束前調用 stopMethodTracing 函數。這兩個函數運行過程中將採集運行時間內該應用所有線程(注意,只能是 Java 線程)的函數執行情況,並將採集數據保存到 /mnt/sdcard/ 下的一個文件中。開發者然後需要利用 SDK 中的 TraceView 工具來分析這些數據。
藉助 Android SDK 中的 DDMS 工具。DDMS 可採集系統中某個正在運行的進程的函數調用信息。對開發者而言,此方法適用於沒有目標應用源代碼的情況。
DDMS 中 TraceView 使用示意圖如下,調試人員可以通過選擇 Devices 中的應用後點擊 按鈕 Start Method Profiling(開啓方法分析)和點擊 Stop Method Profiling(停止方法分析)
我這邊用一個簡單的demo學習一下這個工具的使用。
代碼如下所示:
package com.mingrisoft;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView;
import android.widget.TextView;
public class MainActivity extends Activity implements Runnable {
private ImageView iv; // 聲明一個顯示廣告圖片的ImageView對象
private Handler handler; // 聲明一個Handler對象
private List<Integer> list1 = new ArrayList<Integer>();
private int[] path = new int[] { R.drawable.img01, R.drawable.img02,
R.drawable.img03, R.drawable.img04, R.drawable.img05,
R.drawable.img06 }; // 保存廣告圖片的數組
private String[] title = new String[] { "編程詞典系列產品", "高效開發", "快樂分享", "用戶人羣",
"快速學習", "全方位查詢" }; // 保存顯示標題的數組
public void dynatest()
{
for(int i = 0; i < 500000; i++) {
//Log.i(TAG, "======== ");
list1.add(i);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
iv = (ImageView) findViewById(R.id.imageView1); // 獲取顯示廣告圖片的ImageView
Thread t = new Thread(this); // 創建新線程
t.start(); // 開啓線程
// 實例化一個Handler對象
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 更新UI
TextView tv = (TextView) findViewById(R.id.textView1); // 獲取TextView組件
if (msg.what == 0x101) {
dynatest();
tv.setText(msg.getData().getString("title")); // 設置標題
iv.setImageResource(path[msg.arg1]); // 設置要顯示的圖片
}
super.handleMessage(msg);
}
};
}
@Override
public void run() {
int index = 0;
while (!Thread.currentThread().isInterrupted()) {
index = new Random().nextInt(path.length); // 產生一個隨機數
Message m = handler.obtainMessage(); // 獲取一個Message
m.arg1 = index; // 保存要顯示廣告圖片的索引值
Bundle bundle = new Bundle(); // 獲取Bundle對象
m.what = 0x101; // 設置消息標識
bundle.putString("title", title[index]); // 保存標題
m.setData(bundle); // 將Bundle對象保存到Message中
handler.sendMessage(m); // 發送消息
try {
Thread.sleep(2000); // 線程休眠2秒鐘
} catch (InterruptedException e) {
e.printStackTrace(); // 輸出異常信息
}
}
}
}
這個demo主要功能是每兩秒隨機顯示一張廣告圖片。首先將三張圖片資源引用ID保存到 path 這個數組中。
private int[] path = new int[] { R.drawable.img01, R.drawable.img02,
R.drawable.img03, R.drawable.img04, R.drawable.img05,
R.drawable.img06 }; // 保存廣告圖片的數組
MainActivity 重寫 Runnable的 Run函數,每隔兩秒鐘隨機發送一個圖片ID給hander然後顯示。
TextView tv = (TextView) findViewById(R.id.textView1); // 獲取TextView組件
if (msg.what == 0x101) {
//故意增加延遲
for(int i = 0; i < 10000; i++) {
list1.add(i);
}
tv.setText(msg.getData().getString("title")); // 設置標題
iv.setImageResource(path[msg.arg1]); // 設置要顯示的圖片
}
我們在顯示廣告圖片中故意加一個循環用於延遲廣告圖片顯示。
其UI劃分爲上下兩個面板,即Timeline Panel(時間線面板)和Profile Panel(分析面板)。
左邊Pane顯示的是測試數據中所採集的線程信息。由圖1-4可知,本次測試數據採集了main線程,兩個Binder線程和其它系統輔助線程(例如GC線程等)的信息。
右邊Pane所示爲時間線,時間線上是每個線程測試時間段內所涉及的函數調用信息。這些信息包括函數名、函數執行時間等。由圖可知,main線程對應行的的內容非常豐富,而其他線程在這段時間內幹得工作則要少得多。
另外,開發者可以在時間線Pane中移動時間線縱軸。縱軸上邊將顯示當前時間點中某線程正在執行的函數信息。
同意顏色在實際軸越長說明該顏色代表的函數花費時間越長
Profile Panel 是 TraceView 的核心界面,其內涵非常豐富。它主要展示了某個線程(先在 Timeline Panel 中選擇線程)中各個函數調用的情況,包括 CPU 使用時間、調用次數等信息。而這些信息正是查找 hotspot 的關鍵依據。所以,對開發者而言,一定要了解 Profile Panel 中各列的含義。下表列出了 Profile Panel 中比較重要的列名及其描述。
另外,每一個Time列還對應有一個用時間百分比來統計的列(如Incl Cpu Time列對應還有一個列名爲Incl Cpu Time %的列,表示以時間百分比來統計的Incl Cpu Time)。
瞭解完Traceview的UI後,現在介紹如何利用Traceview來查找hotspot。
一般而言,hotspot包括兩種類型的函數:
一類是調用次數不多,但每次調用卻需要花費很長時間的函數。在示例代碼中,它就是hotspot 1。
一類是那些自身佔用時間不長,但調用卻非常頻繁的函數。在示例代碼中,它就是hotspot 2。
在我們代碼中handleMessage 函數屬於 每次調用花費很長時間的函數。因爲裏面有個耗時的dynatest循環在那一直處理。
從上圖中我們可以發現
標號 12 的 com.mingrisoft.MainActivity.dynatest這一行很特別。它的Incl Cpu Time % 是 32.1 % ,Excl Cpu Time 也是 32.1% ,並且 它的 Calls+Recur Calls/Total列顯示其調用次數爲1,即它僅僅被調用一次了。這個函數是應用程序實現的,所以極有可能是一個潛在的Hotspot。在這個函數裏面做了很多耗時的操作。
比如標號 11 的 handleMessage 雖然 它的Incl Cpu Time % 是 40.2 %,但是它的Excl Cpu Time 是 0 % 。
說明dynatest可能是手機發燙、卡頓、高 CPU 佔用率的原因所在。
我們現在把函數 dynatest() 註釋掉,再看看結果:
說明去掉dynatest這個函數後。handleMessage 主要花費時間都是在 setImageResource 函數上。而且這個函數是系統函數。
第二種情況:
就是雖然每次調用花費時間不是很多。但是調用次數很多的那種情況。
源碼中函數dynatest() 註釋掉。添加一下代碼
public void handleMessage(Message msg) {
// 更新UI
TextView tv = (TextView) findViewById(R.id.textView1); // 獲取TextView組件
if (msg.what == 0x101) {
//dynatest() ;
for(int i = 0; i < 500000; i++) {
//Log.i(TAG, "======== ");
list1.add(i);
}
tv.setText(msg.getData().getString("title")); // 設置標題
iv.setImageResource(path[msg.arg1]); // 設置要顯示的圖片
}
然後運行TraceView得到下面的圖:
發現這三個類的Incl Cpu Time 很高分別是55.6% 10.6% 和7.6%,但是你有發現他們每次的Incl Real Time大約分部是
1535 282 和 198.每次佔用cpu時間很短。但是佔用整個cpu運行週期的比例確實很高。這說明只有一種情況。這幾個函數執行頻率非常高。通過查看Call+Recur Calls/Total 發現情侶確實如我所料。
這裏發現執行次數達到49947.而且函數大部分都是一次。這說明這幾個函數運行次數太多。可以在實際開發過程中優化