天天看點

Android 性能優化小技巧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);
    }
}
           

這種寫法是合理的,結果是預期的。

問題是虛拟機認為直接擷取從Foo Inner擷取Foo的的私有成員變量是不合法的,因為Foo和Foo 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 ,确認代碼的性能有所改善。