Android 性能優化 之 TraceView工具的使用

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() 註釋掉,再看看結果:
這是註釋前handleMessage 各個函數時間比

這是註釋後的結果
說明去掉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.而且函數大部分都是一次。這說明這幾個函數運行次數太多。可以在實際開發過程中優化

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章