Android優化

jopen 10年前發布 | 34K 次閱讀 Android Android開發 移動開發

通常我 們寫程序,都是在項目計劃的壓力下完成的,此時完成的代碼可以完成具體業務邏輯,但是性能不一定是最優化的。一般來說,優秀的程序員在寫完代碼之后都會不 斷的對代碼進行重構。重構的好處有很多,其中一點,就是對代碼進行優化,提高軟件的性能。下面我們就從幾個方面來了解Android開發過程中的代碼優 化。

基本上,優化我覺得分好幾部分:一種是JAVA語法層次通用優化的,如盡量使用局部變量(棧變量),IO緩沖等。二是應用程序內部的,如內部邏輯、數據插入及查找、數據結構的安排與組織。三是通用的Android性能優化,如各種緩存機制。

一、性能調優點

主要包括同步改異步、緩存、Layout優化、數據庫優化、算法優化、延遲執行。
1. 同步改異步

這個就不用多講了,耗時操作放在線程中執行防止占用主線程,一定程度上解決anr。
但需要注意線程和service結合(防止activity被回收后線程也被回收)以及線程的數量(Java線程池)


2. 緩存

java的對象創建需要分配資源較耗費時間,加上創建的對象越多會造成越頻繁的gc影響系統響應。主要使用單例模式、緩存(圖片緩存、線程池、View緩存、IO緩存、消息緩存、通知欄notification緩存)及其他方式減少對象創建。

單例模式:

    public class Singleton {  

        private static Object    obj      = new Object();  
        private static Singleton instance = null;  

        private Singleton(){  
        }  

        public static Singleton getInstance() {  
            // if already inited, no need to get lock everytime  
            if (instance == null) {  
                synchronized (obj) {  
                    if (instance == null) {  
                        instance = new Singleton();  
                    }  
                }  
            }  

            return instance;  
        }  
    }  

(2). 緩存
程序中用到了圖片緩存、線程池、View緩存、IO緩存、消息緩存、通知欄notification緩存等。
1. 圖片緩存:

及 時的銷毀(Activity的onDestroy時將bitmap回收,在被UI組件使用后馬上進行回收會拋 RuntimeException:Canvas:tryingtousearecycledbitmapandroid.graphics.Bitmap) 設置一定的采樣率(有開發者提供的圖片無需進行采樣,對于有用戶上傳或第三方的大小不可控圖片,可進行采樣減少圖片所占的內存),從服務端返回圖片,建議 同時反饋圖片的size巧妙的運用軟引用drawable對應resid的資源,bitmap對應其他資源任何類型的圖片,如果獲取不到(例如文件不存 在,或者讀取文件時跑OutOfMemory異常),應該有對應的默認圖片(默認圖片放在在apk中,通過resid獲取);


2. 線程池:
使用Java的Executors類,通過newCachedThreadPool、newFixedThreadPool、newSingleThreadExecutor、newScheduledThreadPool提供四種不同類型的線程池


3. View緩存:

listView的getView緩存

(1)復用convertView

通過convertView是否為null減少layout inflate次數,通過靜態的ViewHolder減少findViewById的次數,這兩個函數尤其是inflate是相當費時間的

(2)異步加載圖片

item中如果包含有webimage,那么最好異步加載

(3 快速滑動時不顯示圖片

當快速滑動列表時(SCROLL_STATE_FLING),item中的圖片或獲取需要消耗資源的view,可以不顯示出來;而處于其他兩種狀態(SCROLL_STATE_IDLE和SCROLL_STATE_TOUCH_SCROLL),則將那些view顯示出來

(4)應用開發中自定義View的時候,交互部分,千萬不要寫成線程不斷刷新界面顯示,而是根據TouchListener事件主動觸發界面的更新。

Drawable

ui組件需要用到的圖片是apk包自帶的,那么一律用setImageResource或者setBackgroundResource,而不要根據resourceid

注意:get(getResources(),R.drawable.btn_achievement_normal)該方法通過resid轉換為drawable,需要考慮回收的問題,如果drawable是對象私有對象,在對象銷毀前是肯定不會釋放內存的。

4. IO緩存:
使用具有緩存策略的輸入流,BufferedInputStream替代InputStream,BufferedReader替代Reader,BufferedReader替代BufferedInputStream.對文件、網絡IO皆適用。


5. 消息緩存:
通過 Handler 的 obtainMessage 回收 Message 對象,減少 Message 對象的創建開銷
handler.sendMessage(handler.obtainMessage(1));


6. 通知欄notification緩存:
下載中需要不斷改變通知欄進度條狀態,如果不斷新建Notification會導致通知欄很卡。這里我們可以使用最簡單的緩存
Map<String, Notification> notificationMap = new HashMap<String, Notification>();如果notificationMap中不存在,則新建notification并且put into map.


(3). 其他
能創建基類解決問題就不用具體子類:
除需要設置優先級的線程使用new Thread創建外,其余線程創建使用new Runnable。因為子類會有自己的屬性創建需要更多開銷。
控制最大并發數量:使用Java的Executors類,通過Executors.newFixedThreadPool(nThreads)控制線程池最大線程并發

使用線程池,分為核心線程池和普通線程池,下載圖片等耗時任務放置在普通線程池,避免耗時任務阻塞線程池后,導致所有異步任務都必須等待
對于http請求增加timeout

http用gzip壓縮,設置連接超時時間和響應超時時間

http請求按照業務需求,分為是否可以緩存和不可緩存,那么在無網絡的環境中,仍然通過緩存的httpresponse瀏覽部分數據,實現離線閱讀。

 

3. Layout優化
使用抽象布局標簽(include, viewstub, merge)、去除不必要的嵌套和View節點、減少不必要的infalte及其他Layout方面可調優點,順帶提及布局調優相關工具(hierarchy viewer和lint)。具體
TextView屬性優化:TextView的android:ellipsize=”marquee”跑馬燈效果極耗性能!、

1)利用系統定義的id

比如我們有一個定義ListView的xml文件,一般的,我們會寫類似下面的代碼片段。

    <ListView  
        android:id="@+id/mylist"  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent"/>  
這里我們定義了一個ListView,定義它的id是"@+id/mylist"。實際上,如果沒有特別的需求,就可以利用系統定義的id,類似下面的樣子

    <ListView  
        android:id="@android:id/list"  
        android:layout_width="fill_parent"  
        android:layout_height="fill_parent"/>  
在xml文件中引用系統的id,只需要加上“@android:”前綴即可。如果是在Java代碼中使用系統資源,和使用自己的資源基本上是一樣的。不同的是,需要使用android.R類來使用系統的資源,而不是使用應用程序指定的R類。這里如果要獲取ListView可以使用android.R.id.list來獲取。

2)利用系統的圖片資源

3)利用系統的字符串資源

4)利用系統的Style

5)利用系統的顏色定義


 

4. 數據庫優化
主要包括索引和事務及針對Sqlite的優化。保證Cursor占用的內存被及時的釋放掉,而不是等待GC來處理。并且Android明顯是傾向于編程者手動的將Cursorclose掉

5. 算法優化
這個就是個博大精深的話題了,只介紹本應用中使用的。
使用hashMap代替arrayList,時間復雜度降低一個數量級



6. 延遲執行

對于很多耗時邏輯沒必要立即執行,這時候我們可以將其延遲執行。
線程延遲執行 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
消息延遲發送 handler.sendMessageDelayed(handler.obtainMessage(0), 1000);


二、Android開發UI 優化

1、layout組件化,盡量使用merge及include復用

2、使用styles,復用樣式定義

3、軟鍵盤的彈出控制,不要讓其覆蓋輸入框

4、數字、字母和漢字混排占位問題:將數字和字母全角化。由于現在大多數情況下我們的輸入都是半角,所以字母和數字的占位無法確定,但是一旦全角化之后,數字、字母的占位就和一個漢字的占位相同了,這樣就可以避免由于占位導致的排版問題。

5、英文文檔排版:textview自動換行時要保持單詞的完整性,解決方案是計算字符串長度,然后手動設定每一行顯示多少個字母并加上‘\n‘

6、復雜布局使用RelativeLayout

7、自適應屏幕,使用dp替代pix

8、使用android:layout_weight或者TableLayout制作等分布局

9、使用animation-list制作動畫效果

三、代碼優化

1)靜態變量引起內存泄露

在代碼優化的過程中,我們需要對代碼中的靜態變量特別留意。靜態變量是類相關的變量,它的生命周期是從這個類被聲明,到這個類徹底被垃圾回收器回收才會被 銷毀。所以,一般情況下,靜態變量從所在的類被使用開始就要一直占用著內存空間,直到程序退出。如果不注意,靜態變量引用了占用大量內存的資源,造成垃圾 回收器無法對內存進行回收,就可能造成內存的浪費。

先來看一段代碼,這段代碼定義了一個Activity。

private static Resources mResources; 

@Override

protected void onCreate(Bundle state) {

super.onCreate(state);

if (mResources == null) {

    mResources = this.getResources();

    }

}

這段代碼中有一個靜態的Resources對象。代碼片段mResources = this.getResources()對Resources對象進行了初始化。這時Resources對象擁有了當前Activity對象的引 用,Activity又引用了整個頁面中所有的對象。

如果當前的Activity被重新創建(比如橫豎屏切換,默認情況下整個Activity會被重新創建),由于Resources引用了第一次創建的 Activity,就會導致第一次創建的Activity不能被垃圾回收器回收,從而導致第一次創建的Activity中的所有對象都不能被回收。這個時 候,一部分內存就浪費掉了。

在實際項目中,我們經常會把一些對象的引用加入到集合中,如果這個集合是靜態的話,就需要特別注意了。當不需要某對象時,務必及時把它的引用從集合中清理掉。或者可以為集合提供一種更新策略,及時更新整個集合,這樣可以保證集合的大小不超過某值,避免內存空間的浪費。


2)使用Application的Context

在Android中,Application Context的生命周期和應用的生命周期一樣長,而不是取決于某個Activity的生命周期。如果想保持一個長期生命的對象,并且這個對象需要一個 Context,就可以使用Application對象。可以通過調用Context.getApplicationContext()方法或者 Activity.getApplication()方法來獲得Application對象。

依然拿上面的代碼作為例子。可以將代碼修改成下面的樣子。

private static Resources mResources; 

@Override

protected void onCreate(Bundle state) {

super.onCreate(state);

if (mResources == null) {

    // mResources = this.getResources();

    mResources = this.getApplication().getResources();

    }

}

在這里將this.getResources()修改為this.getApplication().getResources()。修改以 后,Resources對象擁有的是Application對象的引用。如果Activity被重新創建,第一次創建的Activity就可以被回收了。

 

3)及時關閉資源

Cursor是Android查詢數據后得到的一個管理數 據集合的類。正常情況下,如果我們沒有關閉它,系統會在回收它時進行關閉,但是這樣的效率特別低。如果查詢得到的數據量較小時還好,如果Cursor的數 據量非常大,特別是如果里面有Blob信息時,就可能出現內存問題。所以一定要及時關閉Cursor。

下面給出一個通用的使用Cursor的代碼片段。

Cursor cursor = null;

try{

    cursor = mContext.getContentResolver().query(uri,null,null,null,null);

    if (cursor != null) {

        cursor.moveToFirst();

        // 處理數據

    }

} catch (Exception e){

    e.printStatckTrace();

} finally {

    if (cursor != null){

        cursor.close();

    }

}

 

即對異常進行捕獲,并且在finally中將cursor關閉。

同樣的,在使用文件的時候,也要及時關閉。

 

4)使用Bitmap及時調用recycle()

前面的章節講過,在不使用Bitmap對象時,需要調用recycle()釋放內存,然后將它設置為null。雖然調用recycle()并不能保證立即釋放占用的內存,但是可以加速Bitmap的內存的釋放。

在代碼優化的過程中,如果發現某個Activity用到了Bitmap對象,卻沒有顯式的調用recycle()釋放內存,則需要分析代碼邏輯,增加相關代碼,在不再使用Bitmap以后調用recycle()釋放內存。

 

5)對Adapter進行優化

下面以構造ListView的BaseAdapter為例說明如何對Adapter進行優化。

在BaseAdapter類中提供了如下方法:

public View getView(int position, View convertView, ViewGroup parent)

 

當ListView列表里的每一項顯示時,都會調用Adapter的getView方法返回一個View,

來向ListView提供所需要的View對象。

下面是一個完整的getView()方法的代碼示例。

public View getView(int position, View convertView, ViewGroup parent) {

  ViewHolder holder;

if (convertView == null) {

      convertView = mInflater.inflate(R.layout.list_item, null);

      holder = new ViewHolder();

      holder.text = (TextView) convertView.findViewById(R.id.text);

      convertView.setTag(holder);

  } else {

      holder = (ViewHolder) convertView.getTag();

  }

  holder.text.setText("line" + position);

  return convertView;

}



private class ViewHolder {

  TextView text;

}

 

當向上滾動ListView時,getView()方法會 被反復調用。getView()的第二個參數convertView是被緩存起來的List條目中的View對象。當ListView滑動的時 候,getView可能會直接返回舊的convertView。這里使用了convertView和ViewHolder,可以充分利用緩存,避免反復創 建View對象和TextView對象。

如果ListView的條目只有幾個,這種技巧并不能帶來多少性能的提升。但是如果條目有幾百甚至幾千個,使用這種技巧只會創建幾個convertView和ViewHolder(取決于當前界面能夠顯示的條目數),性能的差別就非常非常大了。

   

    6)代碼“微優化”

    當今時代已經進入了“微時代”。這里的“微優化”指的是代碼層面的細節優化,即不改動代碼整體結構,不改變程序原有的邏輯。盡管Android使用的是Dalvik虛擬機,但是傳統的Java方面的代碼優化技巧在Android開發中也都是適用的。

下面簡要列舉一部分。因為一般Java開發者都能夠理解,就不再做具體的代碼說明。

創建新的對象都需要額外的內存空間,要盡量減少創建新的對象。

將類、變量、方法等等的可見性修改為最小。

針對字符串的拼接,使用StringBuffer替代String。

不要在循環當中聲明臨時變量,不要在循環中捕獲異常。

如果對于線程安全沒有要求,盡量使用線程不安全的集合對象。

使用集合對象,如果事先知道其大小,則可以在構造方法中設置初始大小。

文件讀取操作需要使用緩存類,及時關閉文件。

慎用異常,使用異常會導致性能降低。

如果程序會頻繁創建線程,則可以考慮使用線程池。

 

代 碼的微優化有很多很多東西可以講,小到一個變量的聲明,大到一段算法。尤其在代碼Review的過程中,可能會反復審查代碼是否可以優化。不過我認為,代 碼的微優化是非常耗費時間的,沒有必要從頭到尾將所有代碼都優化一遍。開發者應該根據具體的業務邏輯去專門針對某部分代碼做優化。比如應用中可能有一些方 法會被反復調用,那么這部分代碼就值得專門做優化。其它的代碼,需要開發者在寫代碼過程中去注意。

來自:http://blog.csdn.net/a565102223/article/details/39926043

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!