android 記憶體和效能最佳化匯總
1、即時編譯(Just-in-time Compilation,JIT),又稱動態轉譯(Dynamic Translation),是一種通過在運行時將位元組碼翻譯為機器碼,從而改善位元組碼編譯語言效能的技術。即時編譯前期的兩個運行時理論是位元組碼編譯和動態編譯。Android原來Dalvik虛擬機器是作為一種解譯器實現,新版(Android2.2+)將換成JIT編譯器實現。效能測試顯示,在多項測試中新版本比舊版本提升了大約6倍。
2、
就像世界上沒有免費的午餐,世界上也沒有免費的對象。雖然gc為每個線程都建立了臨時對象池,可以使建立對象的代價變得小一些,但是分配記憶體永遠都比不分配記憶體的代價大。如果你在使用者介面迴圈中指派至記憶體,就會引發周期性的記憶體回收,使用者就會覺得介面像打嗝一樣一頓一頓的。所以,除非必要,應盡量避免儘力對象的執行個體。下面的例子將協助你理解這條原則:當你從使用者輸入的資料中截取一段字串時,盡量使用substring函數取得未經處理資料的一個子串,而不是為子串另外建立一份拷貝。這樣你就有一 個新的String對象,它與未經處理資料共用一個char數組。 如果你有一個函數返回一個String對象,而你確切的知道這個字串會被附加到一個StringBuffer,那麼,請改變這個函數的參數和實現方式, 直接把結果附加到StringBuffer中,而不要再建立一個短命的臨時對象。
一個更極端的例子是,把多維陣列分成多個一維數組:
int數組比Integer數組好,這也概括了一個基本事實,兩個平行的int數組比 (int,int)對象數組效能要好很多。同理,這試用於所有基本類型的組合。如果你想用一種容器儲存(Foo,Bar)元組,嘗試使用兩個單獨的 Foo[]數組和Bar[]數組,一定比(Foo,Bar)數組效率更高。(也有例外的情況,就是當你建立一個API,讓別人調用它的時候。這時候你要注重對API介面的設計而犧牲一點兒速度。當然在API的內部,你仍要儘可能的提高代碼的效率)
總體來說,就是避免建立短命的臨時對象。減少對象的建立就能減少垃圾收集,進而減少對使用者體驗的影響。
3、 靜態方法代替虛擬方法
如果不需要訪問某對象的欄位,將方法設定為靜態,調用會加速15%到20%。這也是一種好的做法,因為你可以從方法聲明中看出調用該方法不需要更新此對象的狀態。
4、避免內部Getters/Setters
5、 將成員緩衝到本地
訪問成員變數比訪問本地變數慢得多,下面一段代碼:
for(int i =0; i
最好改成這樣:
int count = this.mCount;Item[] items = this.mItems;for(int i =0; i < count; i++) { dumpItems(items);}
另一個相似的原則是:永遠不要在for的第二個條件中調用任何方法。如下面方法所示,在每次迴圈的時候都會調用getCount()方法,這樣做比你在一個int先把結果儲存起來開銷大很多。
同樣如果你要多次訪問一個變數,也最好先為它建立一個本地變數,例如:
這裡有4次訪問成員變數mScrollBar,如果將它緩衝到本地,4次成員變數訪問就會變成4次效率更高的棧變數訪問。
另外就是方法的參數與本地變數的效率相同。
6、對常量使用static final修飾符讓我們來看看這兩段在類前面的聲明:
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方法,因為在成員變數初始化的時候,會將常量直接儲存到類檔案中。用到intVal的代碼被直接替換成42,而使用strVal的會指向一個字串常量,而不是使用成員變數。
將一個方法或類聲明為final不會帶來效能的提升,但是會協助編譯器最佳化代碼。舉例說,如果編譯器知道一個getter方法不會被重載,那麼編譯器會對其採用內聯調用。
你也可以將本地變數聲明為final,同樣,這也不會帶來效能的提升。使用“final”只能使本地變數看起來更清晰些(但是也有些時候這是必須的,比如在使用匿名內部類的時候)。
7、
使用改進的For迴圈文法
改進for迴圈(有時被稱為for-each迴圈)能夠用於實現了iterable介面的集合類及數組中。在集合類中,迭代器讓介面調用 hasNext()和next()方法。在ArrayList中,手寫的計數迴圈迭代要快3倍(無論有沒有JIT),但其他集合類中,改進的for迴圈語 法和迭代器具有相同的效率。下面展示集中訪問數組的方法:
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()使用了在java1.5中引入的foreach文法。編譯器會將對數組的引用和數組的長度儲存到本地變數中,這對訪問數組元素非常好。 但是編譯器還會在每次迴圈中產生一個額外的對本地變數的儲存操作(對變數a的存取)這樣會比one()多出4個位元組,速度要稍微慢一些。
8、
避免使用浮點數
通常的經驗是,在Android裝置中,浮點數會比整型慢兩倍,在缺少FPU和JIT的G1上對比有FPU和JIT的Nexus One中確實如此(兩種裝置間算術運算的絕對速度差大約是10倍)從速度方面說,在現代硬體上,float和double之間沒有任何不同。更廣泛的講,double大2倍。在台式機上,由於不存在空間問題,double的優先順序高於float。但即使是整型,有的晶片擁有硬體乘法,卻缺少除法。這種情況下,整型除法和求模運算是通過軟體實現的,就像當你設計Hash表,或是做大量的算術那樣,例如a/2可以換成a*0.5。
9、
減少不必要的全域變數
盡量避免static成員變數引用資源耗費過多的執行個體,比如Context。Android提供了很健全的訊息傳遞機制(Intent)和任務模型(Handler),可以通過傳遞或事件的方式,防止一些不必要的全域變數
10、
瞭解Java四種引用方式
JDK 1.2版本開始,把對象的引用分為4種層級,從而使程式能更加靈活地控制對象的生命週期。這4種層級由高到低依次為:強引用、軟引用、弱引用和虛引用。
i. 強引用(StrongReference)
強引用是使用最普遍的引用。如果一個對象具有強引用,那記憶體回收行程絕不會回收它。當記憶體空間不足,Java虛擬機器寧願拋出OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的對象來解決記憶體不足的問題。
ii. 軟引用(SoftReference)
如果一個對象只具有軟引用,則記憶體空間足夠,記憶體回收行程就不會回收它;如果記憶體空間不足了,就會回收這些對象的記憶體。只要記憶體回收行程沒有回收它,該對象就可以被程式使用。軟引用可用來實現記憶體敏感的快取。
iii. 弱引用(WeakReference)
在記憶體回收行程線程掃描它所管轄的記憶體地區的過程中,一旦發現了只具有弱引用的對象,不管當前記憶體空間足夠與否,都會回收它的記憶體。不過,由於記憶體回收行程是一個優先順序很低的線程,因此不一定會很快發現那些只具有弱引用的對象。
iv. 虛引用(PhantomReference)
顧名思義,就是形同虛設。與其他幾種引用都不同,虛引用並不會決定對象的生命週期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被記憶體回收行程回收。瞭解並熟練掌握這4中引用方式,選擇合適的對象應用方式,對記憶體的回收是很有協助的。
詳細請參考 http://blog.csdn.net/feng88724/article/details/6590064
Android中常使用緩衝:
a. 線程池
b. Android圖片緩衝,Android圖片Sdcard緩衝,資料預取緩衝
c. 訊息緩衝
通過handler.obtainMessage複用之前的message,如下:
| 1 |
handler.sendMessage(handler.obtainMessage(0,object)); |
d. ListView緩衝
e. 網路緩衝
資料庫緩衝http response,根據http頭資訊中的Cache-Control域確定緩衝到期時間。
f. 檔案IO緩衝
使用具有緩衝策略的輸入資料流,BufferedInputStream替代InputStream,BufferedReader替代Reader,BufferedReader替代BufferedInputStream.對檔案、網路IO皆適用。
g. layout緩衝
h. 其他需要頻繁訪問或訪問一次消耗較大的資料緩衝
(2). 資料存放區最佳化
包括資料類型、資料結構的選擇。
a. 資料類型選擇
字串拼接用StringBuilder代替String,在非並發情況下用StringBuilder代替StringBuffer。如果你對字串的長度有大致瞭解,如100字元左右,可以直接new StringBuilder(128)指定初始大小,減少空間不夠時的再次分配。
64位類型如long double的處理比32位如int慢
使用SoftReference、WeakReference相對正常的強應用來說更有利於系統記憶體回收
final類型儲存在常量區中讀取效率更高
LocalBroadcastManager代替普通BroadcastReceiver,效率和安全性都更高
b. 資料結構選擇
常見的資料結構選擇如:
ArrayList和LinkedList的選擇,ArrayList根據index取值更快,LinkedList更占記憶體、隨機插入刪除更快速、擴容效率更高。一般推薦ArrayList。
ArrayList、HashMap、LinkedHashMap、HashSet的選擇,hash系列資料結構查詢速度更優,ArrayList儲存有序元素,HashMap為鍵值對資料結構,LinkedHashMap可以記住加入次序的hashMap,HashSet不允許重複元素。
HashMap、WeakHashMap選擇,WeakHashMap中元素可在適當時候被系統記憶體回收行程自動回收,所以適合在記憶體緊張型中使用。
Collections.synchronizedMap和ConcurrentHashMap的選擇,ConcurrentHashMap為細分鎖,鎖粒度更小,並發效能更優。Collections.synchronizedMap為對象鎖,自己添加函數進行鎖控制更方便。
Android也提供了一些效能更優的資料類型,如SparseArray、SparseBooleanArray、SparseIntArray、Pair。
Sparse系列的資料結構是為key為int情況的特殊處理,採用二分尋找及簡單的數組儲存,加上不需要泛型轉換的開銷,相對Map來說效能更優。不過我不太明白為啥預設的容量大小是10,是做過資料統計嗎,還是說現在的記憶體最佳化不需要考慮這些東西,寫16會死嗎,還是建議大家根據自己可能的容量設定初始值。
(3). 演算法最佳化
這個主題比較大,需要具體問題具體分析,盡量不用O(n*n)時間複雜度以上的演算法,必要時候可用空間換時間。
查詢考慮hash和二分,盡量不用遞迴。可以從結構之法 演算法之道或微軟、Google等面試題學習。
(4). JNI
Android應用程式大都通過Java開發,需要Dalvik的JIT編譯器將Java位元組碼轉換成本地代碼運行,而本地代碼可以直接由裝置管理員直接執行,節省了中間步驟,所以執行速度更快。不過需要注意從Java空間切換到本地空間需要開銷,同時JIT編譯器也能產生最佳化的本地代碼,所以糟糕的本地代碼不一定效能更優。
這個最佳化點會在後面單獨用一片部落格介紹。
(5). 邏輯最佳化
這個不同於演算法,主要是理清程式邏輯,減少不必要的操作。
(6). 需求最佳化
這個就不說了,對於sb的需求可能帶來的效能問題,只能說做為一個合格的程式員不能只是執行者,要學會說NO。不過不能拿這種介面敷衍產品經理哦。
2、非同步,利用多線程提高TPS
充分利用多核Cpu優勢,利用線程解決密集型計算、IO、網路等操作。
關於多線程可參考:
在Android應用程式中由於系統ANR的限制,將可能造成主線程逾時操作放入另外的背景工作執行緒中。在背景工作執行緒中可以通過handler和主線程互動。
3、提前或延遲操作,錯開時間段提高TPS
(1) 延遲操作
不在Activity、Service、BroadcastReceiver的生命週期等對回應時間敏感函數中執行耗時操作,可適當delay。
Java中延遲操作可使用ScheduledExecutorService,不推薦使用Timer.schedule;
Android中除了支援ScheduledExecutorService之外,還有一些delay操作,如
handler.postDelayed,handler.postAtTime,handler.sendMessageDelayed,View.postDelayed,AlarmManager定時等。
(2) 提前操作
對於第一次調用較耗時操作,可統一放到初始化中,將耗時提前。如得到壁紙wallpaperManager.getDrawable();
4、網路最佳化
以下是網路最佳化中一些用戶端和伺服器端需要盡量遵守的準則:
a. 圖片必須緩衝,最好根據機型做圖片做圖片適配
b. 所有http請求必須添加httptimeout
c. api介面資料以json格式返回,而不是xml或html
d. 根據http頭資訊中的Cache-Control域確定是否緩衝請求結果。
e. 減少網路請求次數,伺服器端適當做請求合并。
f. 減少重新導向次數
g. api介面伺服器端回應時間不超過100ms
google正在做將移動端網頁速度降至1秒的項目,關注中https://developers.google.com/speed/docs/insights/mobile
Android 效能最佳化方法
http://mobile.51cto.com/abased-410785.htm
對於一些Android項目,影響效能瓶頸的主要是Android自己記憶體管理機制問題,目前手機廠商對RAM都比較吝嗇,對於軟體的流暢性來說RAM對效能的影響十分敏感,除了 最佳化Dalvik虛擬機器的堆記憶體配置外,我們還可以強制定義自己軟體的對記憶體大小,我們使用Dalvik提供的 dalvik.system.VMRuntime類來設定最小堆記憶體為例:
- private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
- VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);
//設定最小heap記憶體為6MB大小。當然對於記憶體吃緊來說還可以通過手動幹涉GC去處理
bitmap 設定圖片尺寸,避免 記憶體溢出 OutOfMemoryError的最佳化方法
★android 中用bitmap 時很容易記憶體溢出,報如下錯誤:Java.lang.OutOfMemoryError : bitmap size exceeds VM budget
● 主要是加上這段:
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = 2;
● eg1:(通過Uri取圖片)
- private ImageView preview;
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = 2;//圖片寬高都為原來的二分之一,即圖片為原來的四分之一
- Bitmap bitmap = BitmapFactory.decodeStream(cr
- .openInputStream(uri), null, options);
- preview.setImageBitmap(bitmap);
以上代碼可以最佳化記憶體溢出,但它只是改變圖片大小,並不能徹底解決記憶體溢出。
● eg2:(通過路徑去圖片)
- private ImageView preview;
- private String fileName= "/sdcard/DCIM/Camera/2010-05-14 16.01.44.jpg";
- BitmapFactory.Options options = new BitmapFactory.Options();
- options.inSampleSize = 2;//圖片寬高都為原來的二分之一,即圖片為原來的四分之一
- Bitmap b = BitmapFactory.decodeFile(fileName, options);
- preview.setImageBitmap(b);
- filePath.setText(fileName);
★Android 還有一些效能最佳化的方法:
● 首先記憶體方面,可以參考 Android堆記憶體也可自己定義大小 和 最佳化Dalvik虛擬機器的堆記憶體配置
● 基礎類型上,因為Java沒有實際的指標,在敏感運算方面還是要藉助NDK來完成。這點比較有意思的是Google 推出NDK可能是協助遊戲開發人員,比如OpenGL ES的支援有明顯的改觀,本地代碼操作圖形介面是很必要的。
● 繪圖物件最佳化,這裡要說的是Android上的Bitmap對象銷毀,可以藉助recycle()方法顯示讓GC回收一個Bitmap對象,通常對一個不用的Bitmap可以使用下面的方式,如
- if(bitmapObject.isRecycled()==false) //如果沒有回收
- bitmapObject.recycle();
● 目前系統對動畫支援比較弱智對於常規應用的補間過渡效果可以,但是對於遊戲而言一般的美工可能習慣了GIF方式的統一處理,目前Android系統僅能預覽GIF的第一幀,可以藉助J2ME中通過線程和自己寫解析器的方式來讀取GIF89格式的資源。
● 對於大多數Android手機沒有過多的物理按鍵可能我們需要想象下了做好手勢識別 GestureDetector 和重力感應來實現操控。通常我們還要考慮誤操作問題的降噪處理。
Android堆記憶體也可自己定義大小
對於一些大型Android項目或遊戲來說在演算法處理上沒有問題外,影響效能瓶頸的主要是Android自己記憶體管理機制問題,目前手機廠商對RAM都比較吝嗇,對於軟體的流暢性來說RAM對效能的影響十分敏感,除了上次Android開發網提到的最佳化Dalvik虛擬機器的堆記憶體配置外,我們還可以強制定義自己軟體的對記憶體大小,我們使用Dalvik提供的 dalvik.system.VMRuntime類來設定最小堆記憶體為例:
- private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
- VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);
//設定最小heap記憶體為6MB大小。當然對於記憶體吃緊來說還可以通過手動幹涉GC去處理,我們將在下次提到具體應用。
最佳化Dalvik虛擬機器的堆記憶體配置
對於Android平台來說,其託管層使用的Dalvik JavaVM從目前的表現來看還有很多地方可以最佳化處理,比如我們在開發一些大型遊戲或耗資源的應用中可能考慮手動幹涉GC處理,使用 dalvik.system.VMRuntime類提供的setTargetHeapUtilization方法可以增強程式堆記憶體的處理效率。當然具體原理我們可以參考開源工程,這裡我們僅說下使用方法: private final static floatTARGET_HEAP_UTILIZATION = 0.75f; 在程式onCreate時就可以調用 VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); 即可。
【編輯精選】
Android 效能最佳化的一些方法
http://blog.csdn.net/yugui865/article/details/10211595
1.採用硬體加速,在androidmanifest.xml中application添加 android:hardwareAccelerated="true"。不過這個需要在android 3.0才可以使用。
2. View 中設定緩衝屬性. setDrawingCache為true.
3. 最佳化你的布局。通過Android sdk中tools目錄下的layoutopt 命令查看你的布局是否需要最佳化。
4. 動態載入View. 採用ViewStub 避免一些不經常的視圖長期握住引用.
5.將Acitivity 中的Window 的背景圖設定為空白。getWindow().setBackgroundDrawable(null); android的預設背景是不是為空白。
6. 採用 最佳化布局層數。 採用來共用布局。
7. 查看Heap 的大小
8. 利用TraceView查看跟蹤函數調用。有的放矢的最佳化。
9. cursor 的使用。不過要注意管理好cursor,不要每次開啟關閉cursor.因為開啟關閉Cursor非常耗時。Cursor.require用於重新整理cursor.
10.採用環形Buffer(可以採用鏈表資料結構實現)。可以設定一個鏈表長度的上限,根據手勢的變化來不斷地更新環形Buffer的內容。
11.採用SurfaceView在子線程重新整理UI, 避免手勢的處理和繪製在同一UI線程(普通View都這樣做)。
12.採用JNI,將耗時間的處理放到c/c++層來處理。
13.有些能用檔案操作的,盡量採用檔案操作,檔案操作的速度比資料庫的操作要快10倍左右。
14. 懶載入和緩衝機制。訪問網路的耗時操作啟動一個新線程來做,而不要再UI線程來做。
集合中對象沒清理造成的記憶體泄露
我們通常把一些對象的引用加入到了集合中,當我們不需要該對象時,並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。
.static關鍵字
當類的成員變數聲明成static後,它是屬於類的而不是屬於對象的,如果我們將很大的資來源物件(Bitmap,context等)聲明成static,那麼這些資源不會隨著對象的回收而回收,
會一直存在,所以在使用static關鍵字定義成員變數的時候要謹慎。
使用保守的Service
如果你的應用需要使用 service 在後台執行業務功能, 除非是一直在進行活動的工作(比如每隔幾秒向伺服器端請求資料之類)否則不要讓它一直保持在後台運行. 並且, 當你的 service 執行完成但是停止失敗時要小心 service 導致的記憶體泄露問題.
當你啟動 service 時, 系統總是優先保持服務的運行. 這會導致記憶體應用效率非常低, 因為被該服務使用的記憶體不能做其它事情. 也會減少系統一直保持的LRU緩衝處理數目, 使不同的app切換效率降低. 當前所有 service 的運行會導致記憶體不夠不能維持正常系統的運行時, 系統會發生卡頓的現象嚴重時能導致系統不斷重啟.
最好的方式是使用 IntentSevice 控制 service 的生命週期, 當使用 intent 開始任務後, 該 service 執行完所有的工作時會自動停止.
在android應用中當不需要使用常駐 service 執行業務功能而去使用一個常駐 service 是最糟糕的記憶體管理方式之一. 所以不要貪婪的使用 service 使你的應用一直運行狀態. 這樣不僅使你因為記憶體的限制提高了應用啟動並執行風險, 也會導致使用者發現這些異常行為後而卸載應用.
當視圖變為隱藏狀態後釋放記憶體
當使用者跳轉到不同的應用並且你的視圖不再顯示時, 你應該釋放應用視圖所佔的資源. 這時釋放所佔用的資源能顯著的提高系統的緩衝處理容量, 並且對使用者的體驗品質有直接的影響.
當實現當前 Activity 類的 onTrimMemory() 回調方法後, 使用者離開視圖時會得到通知. 使用該方法可以監聽 TRIM_MEMORY_UI_HIDDEN 層級, 當你的視圖元素從父視圖中處於隱藏狀態時釋放視圖所佔用的資源.
注意只有當你應用的所有視圖元素變為隱藏狀態時你的應用才能收到 onTrimMemory() 回調方法的 TRIM_MEMORY_UI_HIDDEN . 這個和 onStop() 回調方法不同, 該方法只有當 Activity 的執行個體變為隱藏狀態, 或者有使用者移動到應用中的另外的 activity 才會引發. 所以說你雖然實現了 onStop() 去釋放 activity 的資源例如網路連接或者未註冊的廣播接收者, 但是應該直到你收到 onTrimMemory(TRIM_MEMORY_UI_HIDDEN)才去釋放視圖資源否則不應該釋放視圖所佔用的資源. 這裡可以確定的是如果使用者通過後退鍵從另外的 activity 進入到你的應用中, 視圖資源會一直處於可用的狀態可以用來快速的恢複 activity.
記憶體資源緊張時釋放記憶體
在應用生命週期的任何階段 onTrimMemory() 回調方法都可以告訴你裝置的記憶體越來越低的情況, 你可以根據該方法推送的記憶體緊張層級來釋放資源.