Android設備作為一種移動設備,不管是內存還是CPU的性能都受到了一定的限制,無法做到像PC設備那樣具有超大的內存和高性能的CPU,這也意味著Android程序不可能無限制地使用內存和CPU資源,過多地使用內存會導致程序內存溢出,即OOM。而過多地使用CPU資源,一般指做大量的耗時任務,會導致手機變得卡頓甚至出現無法響應的情況,即ANR。
Android的性能優化方法
1、布局優化
布局優化的思想很簡單,就是盡量減少布局文件的層級,布局中的層級少了,這就意味著Android繪制時的工作量少了,那么程序的性能自然就高了。
那么如何進行布局優化呢?有以下兩點:
? 首先刪除布局中無用的看控件和層級,其次有選擇地使用性能較低的ViewGroup,比如RelativeLayout。
? 可以采用標簽、標簽、ViewStub。標簽主要用于布局重用,標簽一般配合標簽使用,它可以降低減少布局的層級,而ViewStub則提供了按需加載的功能。
2、繪制優化
繪制優化是指View的onDraw方法要避免執行大量的操作,主要體現在兩個方面:
? onDraw中不要創建新的局部對象,這是因為onDraw方法可能會被頻繁調用,這樣就會在一瞬間產生大量的臨時對象,這不僅占用了過多的內存而且還會導致系統會更頻繁gc,降低程序的執行效率。
? onDraw方法中不要做耗時的任務,也不能執行成千上萬次的循環操作,盡管每次循環都很輕量級,但是大量的循環仍然十分搶占CPU的時間片,這會造成View的繪制過程很不流暢。
3、內存優化
內存泄露在開發過程中是一個需要重視的問題,內存優化分為兩個方面,一方面是在開發過程中避免寫出有內存泄露的代碼,另一方面是通過一些分析工具比如MAT來找出潛在的內存泄露繼而解決。
場景1:靜態變量導致內存泄露
比如下面這段代碼:
public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private static Context sContext; private static View sView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); sContext = this; sView = new View(this); } }
MainActivity無法正常銷毀,因為靜態變量sContext引用了它。同樣,sView是一個靜態變量,他內部持有了當前Activity,所以Activity仍然無法釋放。
場景2:單例模式導致內存泄露
靜態變量導致的內存泄露都太過明顯了,但單例模式所帶來的內存泄露是我們容易忽視的。比如下面這段代碼:
public class TestManager { private List mOnDataArrivedListeners = new ArrayList(); private static class SingletonHolder { public static final TestManager INSTANCE = new TestManager(); } private TestManager() { } public static TestManager getInstance() { return SingletonHolder.INSTANCE; } public synchronized void registerListener(OnDataArrivedListener listener) { if (!mOnDataArrivedListeners.contains(listener)) { mOnDataArrivedListeners.add(listener); } } public synchronized void unregisterListener(OnDataArrivedListener listener) { mOnDataArrivedListeners.remove(listener); } public interface OnDataArrivedListener { public void onDataArrived(Object data); } }
首先提供一個單例模式的TestManager,TestManager可以接收外部的注冊并將外部的監聽器存儲起來。然后用Activity實現OnDataArrivedListener接口并向TestManager注冊監聽,但是如果缺少解注冊的操作,會引起內存泄露。比如下面這段代碼:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TestManager.getInstance().registerListener(this); }
Activity的對象被單例模式的TestManager所持有,而單例模式的特點是其生命周期和Application保持一致,因此Activity對象無法被及時釋放。
場景3:屬性動畫導致的內存泄露
從Android3.0開始,Google提供了屬性動畫,屬性動畫中有這么一類無限循環的動畫,如果在Activity中播放此類動畫且沒有在onDestroy中停止動畫,那么動畫就會一直播放下去,盡管已經無法在界面上看到動畫效果,但這個時候Activity的View會被動畫持有,而View又持有了Activity,最終Activity無法釋放。
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "rotation",0, 360).setDuration(2000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.start(); //animator.cancel(); }
4、響應速度優化和ANR日志分析
響應速度優化的核心思想是避免在主線程中做耗時操作,但是有時候的確有很多耗時操作,怎么辦呢?可以將這些耗時操作放在線程中去執行,即采用異步的方式執行耗時操作。響應速度過慢更多地體現在Activity的啟動速度上面,如果在主線程中做太多的事情,會導致Activity啟動出現黑屏現象,甚至出現ANR。Android規定,Activity如果5秒鐘之內無法響應屏幕觸摸事件或者鍵盤輸入事件就會出現ANR,而BroadcastReceiver如果10秒之內還未執行完操作也會出現ANR,那么在實際開發過程中遇到ANR,怎么定位問題呢?其實當一個進程發生ANR了以后,系統會在/data/anr/目錄下創建一個文件traces.txt,通過分析這個文件就能定位出ANR的原因。比如下面代碼在Activity的onCreate中休眠30s,程序運行持續點擊屏幕,應用一定會出現ANR:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SystemClock.sleep(30 * 1000); }
5、ListView和Bitmap優化
ListView優化三個方面:
? 采用ViewHolder并避免在getView中執行耗時操作
? 根據列表的滑動狀態來控制任務的執行頻率,比如當列表快速滑動時顯然是不太適合開啟大量異步任務的。
? 嘗試開啟硬件加速來使ListView的滑動更加流暢。
Bitmap優化,主要是通過BitmapFactory.Options來根據需要對圖片進行采樣,采樣過程中主要用到了BitmapFactory.Option的inSampleSize參數。
6、線程優化
線程優化的思想是采用線程池,避免在程序中存在大量的Thread。線程池可以重用內部的線程,從而避免了現場的創建和銷毀所帶來的性能開銷,同時線程池還能有效地控制線程池的最大并發數,避免大量的線程因互相搶占系統資源從而導致阻塞現象發生。
7、其他性能優化建議
還有一些其他性能優化的小建議,通過它們可以在一定程度上提高性能:
? 避免創建過多的對象
? 不要過多使用枚舉,枚舉占用的內存空間要比整型大
? 常量請用static final來修飾
? 使用一些Android特有的數據結構,比如SpareArray和Pair等,它們都具有更好的性能
? 適當使用軟引用和弱引用
? 采用內存緩存和磁盤緩存
? 盡量采用靜態內部類,這樣可以避免在的由于內部類而導致的內存泄露
內存泄露分析之MAT工具
MAT的全稱是Eclipse Memory Analyzer,他是一款強大的內存泄露分析工具。MAT提供了很多功能,但是最常用的只有Histogram和Dominator Tree,通過Histogram可以直觀看出內存中不同類型的buffer的數量和占用的內存大小,而Dominator Tree則把內存中的對象按照從大到小的順序進行排序,并且可以分析對象之間的引用關系,內存泄露分析就是通過Dominator Tree來完成。在Dominator Tree中內存泄露的原因一般不會直接顯示出來,這個時候需要按照從大到小的順序去排查一遍。
提高程序的可維護性
主要是提高代碼的可維護性和可擴展性,而程序的可維護性本質上也包含可擴展性。
? 命名要規范,要能正確地傳達出變量或者方法的含義,少用縮寫,關于變量的前綴可以參考Android源碼的命名方式,比如私有方式以m開頭,靜態成員以s開頭,常量則全部用大寫字母表示,等等。
? 代碼的排版上需要留出合理的空白區分不同的代碼塊,其中同類變量的聲明要放在一組,兩類變量之間要留出一行空白作為區分。
? 僅為非常關鍵的代碼添加注釋,其他地方不寫注釋,這就對變量和方法的命名風格提出了很高的要求。
? 代碼的層次性指代碼要有分層的概念,對于一段業務邏輯,不要試圖在一個方法或者一個類中去全部實現,它可以分成幾個子邏輯,然后每個子邏輯做自己的事情。單一職責是和層次性相關聯,代碼分成以后,每一層僅僅關注少量的邏輯,這樣就做到了單一職責。
評論
查看更多