Android 效能優化 - 徹底解決記憶體抖動

svenWang_發表於2018-02-11

起源

記憶體抖動是由於短時間內有大量物件進出新生區導致的,它伴隨著頻繁的GC。 gc會大量佔用ui執行緒和cpu資源,會導致app整體卡頓

android profile 效果圖如下圖

Memory 中

這裡寫圖片描述

我們可以看到 上面的一溜白色垃圾桶。說明在大量的執行gc操作。用了一會兒 手機就開始卡了

學習內容

  • android studio 3.0 編譯器 檢視記憶體抖動
  • 使用工具來快速定位 引起記憶體抖動的程式碼。
  • 學習 到什麼樣的 錯誤操作會導致記憶體都懂,如何避免。

快速定位記憶體抖動

快速定位 還得使用ddms。莫慌 as裡面自帶了 Tools->Android->Android Device Monitor 然後進行如下操作

這裡寫圖片描述

然後我們看如下圖片。

這裡寫圖片描述

不要慌。

中間紅框的就是我們要分析的內容,看他參差不齊的就是 記憶體抖動造成的。

然後我們把紅框 內容放大。滑鼠點住 然後往右拖動,就會變大,點選 紅框上面的數字就會變小。

這裡寫圖片描述

我們將 抖動的地方 放大後。隨便點選會出現下圖樣式

這裡寫圖片描述

可以看到這個粉色的拱門的 圖案。從它的左邊到右邊 代表 一個函式 消耗的時間。

我們接下來 就快速定位有問題的程式碼在哪裡

這裡寫圖片描述

我就隨便的滑動了一下,然後 隨便的選中了一個, 然後下邊就展示了 我所選中的 函式方法。

這裡有一個細節

這裡寫圖片描述

  • onClick 最前面 的序號是 9
  • Parent 下的方法 序號為8
  • children 下的方法序號為10

說明 onClick 的序號 大於onClick 呼叫的方法 的序號。小於 onClick 被呼叫的方法的序號。

如果我們一直點選Parent 下的方法就會找到 序號為1 的方法

如下圖所示。

這裡寫圖片描述

我們找到了錯誤程式碼在哪。那麼我們就看一下 原始碼的樣子


public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                imPrettySureSortingIsFree();

            }
        });
    }

    /**
     *  排序後列印二維陣列,一行行列印
     */
    public void imPrettySureSortingIsFree() {
        int dimension = 300;
        int[][] lotsOfInts = new int[dimension][dimension];
        Random randomGenerator = new Random();
        for (int i = 0; i < lotsOfInts.length; i++) {
            for (int j = 0; j < lotsOfInts[i].length; j++) {
                lotsOfInts[i][j] = randomGenerator.nextInt();
            }
        }

        for (int i = 0; i < lotsOfInts.length; i++) {
            String rowAsStr = "";
            //排序
            int[] sorted = getSorted(lotsOfInts[i]);
            //拼接列印
            for (int j = 0; j < lotsOfInts[i].length; j++) {
                rowAsStr += sorted[j];
                if (j < (lotsOfInts[i].length - 1)) {
                    rowAsStr += ", ";
                }
            }
        }


    }

    public int[] getSorted(int[] input) {
        int[] clone = input.clone();
        Arrays.sort(clone);
        return clone;
    }

}

複製程式碼

發現 rowAsStr 物件在被不斷地建立。 我們可以把它優化一下


public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                imPrettySureSortingIsFree();

            }
        });
    }

    /**
     * &emsp;排序後列印二維陣列,一行行列印
     */
    public void imPrettySureSortingIsFree() {
        int dimension = 300;
        int[][] lotsOfInts = new int[dimension][dimension];
        Random randomGenerator = new Random();
        for (int i = 0; i < lotsOfInts.length; i++) {
            for (int j = 0; j < lotsOfInts[i].length; j++) {
                lotsOfInts[i][j] = randomGenerator.nextInt();
            }
        }

        //優化以後
        StringBuilder sb = new StringBuilder();
        String rowAsStr = "";
        for(int i = 0; i < lotsOfInts.length; i++) {
            //清除上一行
            sb.delete(0,rowAsStr.length());
            //排序
            int[] sorted = getSorted(lotsOfInts[i]);
            //拼接列印
            for (int j = 0; j < lotsOfInts[i].length; j++) {
//                rowAsStr += sorted[j];
                sb.append(sorted[j]);
                if(j < (lotsOfInts[i].length - 1)){
//                    rowAsStr += ", ";
                    sb.append(", ");
                }
            }
            rowAsStr = sb.toString();
            Log.i("ricky", "Row " + i + ": " + rowAsStr);
        }

    }

    public int[] getSorted(int[] input) {
        int[] clone = input.clone();
        Arrays.sort(clone);
        return clone;
    }

}

複製程式碼

這樣就不會記憶體抖動了

能找到問題,這個問題就基本解決了。

下面是避免發生記憶體抖動的幾點建議:

  • 儘量避免在迴圈體內建立物件,應該把物件建立移到迴圈體外。
  • 注意自定義View的onDraw()方法會被頻繁呼叫,所以在這裡面不應該頻繁的建立物件。
  • 當需要大量使用Bitmap的時候,試著把它們快取在陣列中實現複用。
  • 對於能夠複用的物件,同理可以使用物件池將它們快取起來。

其他

分析皮膚 皮膚列名含義如下:

Name 方法的詳細資訊,包括包名和引數資訊
col 3 is right-aligned
col 2 is centered
zebra stripes are neat
Incl Cpu Time Cpu執行該方法該方法及其子方法所花費的時間
Incl Cpu Time % Cpu執行該方法該方法及其子方法所花費佔Cpu總執行時間的百分比
Excl Cpu Time Cpu執行該方法所話費的時間
Excl Cpu Time % Cpu執行該方法所話費的時間佔Cpu總時間的百分比
Incl Real Time 該方法及其子方法執行所話費的實際時間,從執行該方法到結束一共花了多少時間
Incl Real Time % 上述時間佔總的執行時間的百分比
Excl Real Time % 該方法自身的實際允許時間
Excl Real Time 上述時間佔總的允許時間的百分比
Calls+Recur 呼叫次數+遞迴次數,只在方法中顯示,在子展開後的父類和子類方法這一欄被下面的資料代替
Calls/Total 呼叫次數和總次數的佔比
Cpu Time/Call Cpu執行時間和呼叫次數的百分比,代表該函式消耗cpu的平均時間
Real Time/Call 實際時間於呼叫次數的百分比,該表該函式平均執行時間

參考

https://www.youtube.com/watch?v=McAvq5SkeTk

相關文章