編輯:關於Android編程
原文地址:http://android.xsoftlab.net/training/articles/perf-tips.html
本篇文章主要介紹那些可以提升整體性能的微小優化點。它與那些能突然改觀性能效果的優化手段並不屬於同一類。選擇正確的算法與數據結構必然是我們的第一總則,但是這不是我們這篇文章要介紹的。你應該將這篇文章所提及的知識點作為編碼的日常習慣,這可以提升常規代碼的執行效率。
下面是書寫代碼的基本准則:
絕不要做你不需要的工作。 如果可以不申請內存就不要申請,要合理復用已有的對象。另一個較復雜的問題就是被優化過的APP肯定是要運行在各種類型的硬件平台上。不同版本的虛擬機運行在不同的處理器上肯定會有不同的運行速度。需要特別說明的是,在模擬器上測試很少會得知其它設備的性能。在不同設備上還有一個很大的不同點就是有沒有JIT(JIT的意思是即時編譯器):在JIT設備上運行的最優代碼並不總在沒有JIT設備上有效。
為了確保APP可以在各類設備上運行良好,要確保代碼在各個版本的平台上都是高效的。
創建對象絕不是沒有成本的。雖然分代垃圾收集器可以使臨時對象的分配成本變得很低,但是內存分配的成本總是遠高於非內存分配的成本。
隨著更多對象的生成,你可能就開始關注垃圾收集器了。雖然Android 2.3中出現的並發收集器可能會幫到你,但是不必要的工作總是應該避免的。
因此,要避免創建不需要的對象。下面的示例可能會幫到你:
如果你有個返回字符串的方法,該方法所返回的字符串總是被接在一個StringBuffer對象後面。那麼就可以更改此方法的實現方式:讓該字符串直接跟在StringBuffer的後面返回。這樣就可以避免創建那些臨時性的變量。 當從字符串中提取子串時,應該嘗試返回原始數據的子串,而不是創建一個副本。子串將會創建一個新的String對象,但是它與char[]共用的是同一數據。采用這種方式的唯一不足就是:雖然使用了其中的一部分數據,但是剩余的數據還都保留在內存中。一條更為先進的法則就是,將多維數組轉換為平行數組使用:
int數組的效率要比Integer數組的效率高的多。 如果你需要實現一個用於存儲(Foo,Bar)對象的數組,要記得使用兩個平行的Foo[],Bar[]數組,這要比單一的(Foo,Bar)數組效率好太多。通常來說,要盡量避免創建那些生命周期很短的臨時變量。更少的對象創建意味著更低頻率的垃圾回收,這會直接反應到用戶體驗上。
如果不需要訪問對象的屬性,那麼就可以將方法設置為靜態方法。這樣調用將會增加15%-20%的速度。這還是一個好的習慣,因為這樣可以告訴其它方法一個信號:它們更改不了對象的狀態。
請先考慮以下聲明:
static int intVal = 42; static String strVal = "Hello, world!";
編譯器會產生出一個類的實例化方法,名為< clinit>,它會在類首次被用到的時候執行。該方法會將值42存到intVal中,並將字符串常量表中的引用賦給strVal。當這些值被引用之後,其它屬性才可以訪問它們。
我們可以使用”final”關鍵字來改進一下:
static final int intVal = 42; static final String strVal = "Hello, world!";
這樣的話,類就不需要再調用< clinit>方法,因為常量的初始化工作被移入了dex文件中。代碼可以直接引用intVal為42的值,並且訪問strVal也會直接得到字符串”string constant” ,這樣可以省去了查找字符串的過程。
Note: 這樣優化手段僅僅適用於基本數據類型以及字符串常量,不要作用其它類型。
像C++這種本地語言通常都會使用get方法來訪問屬性。這對C++來說是一個非常好的習慣,並且C#、Java等面向對象語言也廣泛使用這種方式,因為編譯器通常會進行內聯訪問,並且如果你需要限制訪問或者調試屬性的話,只需要添加代碼就可以。
不過,這在Android上並不是個好習慣。方法調用的開銷是非常大的。雖然為了遵循面向對象語言提供get、set方法是合理的,但是在Android中最好是可以直接訪問對象的字段。
在沒有JIT的設備中,直接訪問對象字段的速度要比通過get方法訪問的速度快3倍。在含有JIT的設備中,這個效率會達到7倍之多。
注意:如果你使用了ProGuard,那麼就有了一個兩全其美的結果,因為ProGuard會直接為你進行內聯訪問。
增強for循環可用於實現了Iterable接口的集合或數組。在集合內部,迭代器需要實現接口方法:hasNext()以及next()。
有以下幾種訪問數組的方式:
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()方法是最慢的,因為JIT不能夠對每次訪問數組長度的開銷進行優化。
one()方法是稍快點的。它將一切元素放入了本地變量,這樣避免了每一次的查詢。只有數組的長度提供了明顯的性能提升。
two()方法是最快的。它使用了增強for循環。
所以應當在默認情況下使用增強for循環。
Tip: 也可以查看Josh Bloch 的 Effective Java,第46條。
請先思考以下類定義:
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); } }
上面的代碼定義了一個內部類,它可以直接訪問外部類的私有成員以及私有方法。這是正確的,這段代碼將會打印出我們所期望的”Value is 27”。
這裡的問題是:VM會認為Foo$Inner直接訪問Foo對象的私有成員是非法的,因為Foo和Foo$Inner是兩個不同的類,雖然Java語言允許內部類可以直接訪問外部類的私有成員(PS:虛擬機與語言是兩種互不干擾的存在)。為了彌補這種差異,編譯器專門為此生成了一組方法:
/*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()方法時會調用上面這些靜態方法。上面的代碼歸結為你所訪問的成員屬性都是通過訪問器方法訪問的。早期我們說通過訪問器訪問要比直接訪問慢很多,所以這是一段特定語言形成的隱性性能開銷示例。
一般來說,在Android設備上浮點型要比整型慢大概2倍的速度。
在速度方面,float與double並沒有什麼區別。在空間方面,double是float的兩倍大。所以在桌面級設備上,假設空間不是問題,那麼我們應當首選double,而不是float。
還有,在對待整型方面,某些處理器擅長乘法,不擅長除法。在這種情況下,整型的除法與取模運算都是在軟件中進行的,如果你正在設計一個哈希表或者做其它大量的數學運算的話,這些東西應該考慮到。
使用本地代碼開發的APP並不一定比Java語言編寫的APP高效多少。首先,它會花費在Java-本地代碼的轉換過程中,並且JIT也不能優化到這些邊界。如果你正在申請本地資源,那麼對於這些資源的收集能明顯的感覺到困難。除此之外,你還需要對每一種CPU架構進行單獨編譯。你可能甚至還需要為同一個CPU架構編譯多個不同的版本:為G1的ARM處理器編譯的代碼不能運行在Nexus One的ARM處理上,為Nexus One的ARM處理器編譯的代碼也同樣不能運行在G1的ARM處理器上。
本地代碼在這種情況下適宜采用:當你有一個已經存在的本地代碼庫,你希望將它移植到Android上時,不要為了改善Java語言所編寫的代碼速度而去使用本地代碼。
如果你需要使用本地代碼,那麼應該讀一讀JNI Tips.
Tip: 相關信息也可以查看Josh Bloch 的 Effective Java,第54條。
在沒有JIT的設備中,通過具體類型的變量調用方法要比抽象接口的調用要高效,這是事實。舉個例子,通過HashMap map調用方法要比Map map調用方法要高效的多,開銷也少,雖然這兩個實現都是HashMap。事實上速度並不會慢2倍這麼多;真實的不同大概有6%的減緩。進一步講,JIT會使兩者的差別進一步縮小。
在沒有JIT的設備上,通過緩存訪問屬性要比反復訪問屬性要快將近20%的速度。在JIT的設備中,屬性訪問的花銷與本地訪問的花銷基本一致,所以這不是一項有多少價值的優化手段,除非你覺得這樣做的話代碼更易讀(這對static,final,常量同樣適用)。
在開始優化之前,要確保你有個問題需要解決:要確保你可以精准測量現有的性能,否則將不能觀察到優化所帶來的提升。
基准點由Caliper的微型基准點框架創建。基准點很難正確獲得,所以Caliper將這份很難處理的工作做了,甚至是在你沒有在測量那些你想測量的地方的時候它也在工作。我們強烈的推薦你使用Caliper來創建自己的微型基准點。
你可能還發現Traceview非常有助於提升性能,不過你應該意識到Traceview工作的時候JIT並沒有開啟。這會錯誤的認為JIT會將損失掉的時間彌補回來。這尤其重要:根據Traceview所更改的結果會使實際代碼運行的更快。
有關更多提升APP性能的工具及方法,請參見以下文檔:
Profiling with Traceview and dmtracedump Analyzing UI Performance with Systrace一、背景最近做項目需要用到選擇圖片上傳,類似於微信、微博那樣的圖片選擇器,ContentResolver讀取本地圖片資源並用RecyclerView+Glide加載圖片顯
什麼是root,安卓用戶想要對手機進行刪除系統自帶軟件、修改系統某些設置等高權限操作,就要先進行獲取ROOT權限的操作,當然我們要知道,這個操作可能會失敗的
復制代碼 代碼如下://刪除全部else if(id==R.id.btnDelet){new AlertDialog.Builder(this).setTitle(刪除提
一般我們在寫android Activity的時候總是會在onCreate方法中加上setContentView方法來加載layout,通過findViewById來實現