Android 性能優化小技巧

Android 性能優化小技巧

說明:本文翻譯來源谷歌官網
非完全翻譯,按理解來的,水平有限,勿噴!

原文地址

本文主要包括能夠細微改善所有APP性能,通過這些改進不能造成性能的翻天覆地變化。選擇正確的算法和數據結構纔是的優先考慮的事,但是這已經超過了本文檔的範圍。你應該使用這些小技巧培養你的編碼習慣,來創造高效的代碼。

這裏有兩條基本的規則去寫高效的代碼:

  • 不要做不需要做的事;

  • 不要分配你可以避免分配的內存;

    最複雜的問題之一是當進行細微優化的你需要確保你的APP能夠運行在多種硬件上。運行在不同處理器上不同版本的虛擬機運行的速度不一樣。這不是一個簡單的問題你說“設備X比設備Y運行的快或慢”,需要測量你的結果在不同的設備。特殊的情況,在模擬器上的測量告訴你很少的性能差異在任何設備上。是否使用JIT即時有巨大的區別:有JIT的設備最好的代碼不一定是沒有JIT的設備上最好的代碼。

確保你的APP在不多種設備上運行良好,確保你的代碼是高效在各種層級,積極的優化你的APP性能。

避免創建不需要的對象


對象的創建從來都不是免費。垃圾回收和爲分配臨時對象的每個線程池使得分配代價更小,但是分配內存總是會比不分配內存代價更加的高。

當你分配對象在你的APP,你將強制進行週期性的垃圾回收,會造成卡頓的用戶體驗,併發的垃圾回收器在Android 2.3引入,但是不需要的工作還是應該被避免。

因此,你應該避避免創建對象你不需要的。下面有一些有用的例子:

  • 假如你有個返回String,你知道這個結果總是被附加到一個StringBuffer上面,改變你的函數簽名和實現,讓函數直接附加而不是創建一個短生命週期的臨時對象。

  • 當獲得額外的Strings從一個輸入數據集,努力的返回一個原始數據的子字符串,而不是產生一個複製。 你將創建一個新的字符串對象,但是它共享char[]字符(博主注:這裏是說明是字符保存在字符數組中)。

有一些更加徹底的想法就是拆分多維數組到對應的一維數組。

  • 一個int類型的數組要比一個Integer對象的數組要更好,進一步的事實是兩個平行的數組更加的高效對比一個擁有(int,int)的數組。其它的基本數據類型也是類似的道理。

  • 假如你需要實現一個容器用來存儲(Foo,Bar)元組對象,嘗試去記住平行的Foo[]和Bar[]數組是更好的。

通常來說,只要可以就應該避免創建短生命週期的臨時對象。更好的創建對象就意味着更少的頻次的垃圾回收,這對用戶體驗有直接的影響。

選擇Static而不是Virtual


加入你不要或者對象的成員域,使你的方法爲Static.調用將快15%-20%。這也是一個好的習慣,因爲你能通過方法簽名來告訴這個方法不能修改對象狀態。

使用Static Final 給常量 (博主注:很有用)


思考下面的聲明在頂級類。

static int intVal = 42;
static String strVal = "hello world!";

編譯器產生一個類的初始化方法叫做,這個方法將被執行,當類第一次被使用。這個方法存儲數值42到intVal,以及提取一個引用從類的字符串常量表給strVal。當這些數值之後被引用,它們能夠通過域查找來獲取。

我們能夠改善這個問題通過”final”關鍵字

static final int intVal = 42;
static final String strVal = "Hello, world!";

這個類不再需要方法,因爲這個常量進入在Dex文件中的靜態域初始化器。代碼引用intVal將直接使用整數42,並且獲取strVal使用相對消耗較低的“字符串常量”結果而不是域查找。

避免內部的Getters/Setters


在native語言像C++,使用(i==getCount())而不是(i = mCount)是一個好習慣。它是一個優秀的習慣對C++來說,也是慣例對C#和java來說。因爲編譯器能偶內聯這種獲取,假如你需要重構或者Debug域的獲取,您能夠在任何時候添加代碼。

但是,這是一個壞主意在Android上。虛方法的調用是昂貴的,比實例域的查找更昂貴。公共接口擁有getters和setters是合理的,但是在類的內部應該直接進行調用。

沒有JIT,直接域的獲取比調用一個不重要的getter快三倍。如果是擁有JIT,則快了7倍。

使用增強過的循環語法


static class Foo {
    int mSplat;
}

Foo[] mArray = ...

public void zero() {
    int sum = 0;
    for (int i = 0; i < mArray.length; ++i) {
        sum += mArray[i].mSplat;
    }
}

public void one() {
    int sum = 0;
    Foo[] localArray = mArray;
    int len = localArray.length;

    for (int i = 0; i < len; ++i) {
        sum += localArray[i].mSplat;
    }
}

public void two() {
    int sum = 0;
    for (Foo a : mArray) {
        sum += a.mSplat;
    }
}

zero() 是最慢的
one() 快一點
two() 是最快的的

針對私有內部類,使用包可見權限而不是私用權限


考慮下面的一種定義:

public class Foo {
    private class Inner {
        void stuff() {
            Foo.this.doStuff(Foo.this.mValue);
        }
    }

    private int mValue;

    public void run() {
        Inner in = new Inner();
        mValue = 27;
        in.stuff();
    }

    private void doStuff(int value) {
        System.out.println("Value is " + value);
    }
}

這種寫法是合理的,結果是預期的。
問題是虛擬機認爲直接獲取從FooInnerFooFooFoo Inner是不合法的,即使Java語言允許使用內部類獲取外部類的私有成員。爲了銜接這個裂縫,編譯器產生了一對合成的方法:

/*package*/ static int Foo.access$100(Foo foo) {
    return foo.mValue;
}
/*package*/ static void Foo.access$200(Foo foo, int value) {
    foo.doStuff(value);
}

不管什麼時候,只要內部類獲取外部類mValue或者調用方法外部類的doStuff(),內部方法將調用以上靜態方法。這意味着就本質而言,上述方式中你獲取成員變量是通過getter方法。更早之前我們談論過,爲什麼getter要直接獲取域要慢。

避免使用浮點類型


根據經驗來說,在安卓設備上,浮點類型要比整數類型慢兩倍。

在速度方面,float和double沒有區別在大多數的現代硬件。控件方面,double要大兩倍。對桌面機器而言,空間不是問題,你應該選擇double而不是float.

瞭解並使用Libraries


使用更多的Library代碼,像 String.indexOf(),System.arraycopy().

謹慎的使用natvie 方法


使用已有的natvie代碼來對接Android,native是有用的,不要爲了速度優化而是用native

性能謬見


沒有JIT的設備(Android2.2版本提供了JIT機制提升性能),具體的類型比接口類型高效。
但是JIT的設備上兩者沒有太大的差距
擁有JIT的設備上會緩存域的獲取。擁有JIT的設備對域變量和本地變量獲取擁有相同的耗費,所以這不值得優化,除非是爲了代碼的可讀性。

持續的測量


在你開始優化之前,確保你有需要解決的問題。確保你能夠精確的測量你已經存在的程序的性能,否則你沒法測量使用常識方案後性能改善。

使用TraceView工具是有用的。根據TraceView的數據建議改善代碼後,不使用TraceView ,確認代碼的性能有所改善。

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