[素材資源] Android開發性能優化簡介(很是不錯的)

轉自(http://www.starming.com/index.php?action=plugin&v=wave&tpl=union&ac=viewgrouppost&gid=74&tid=20713&pg=1賀小令 )

 

隨着技術的發展,智能手機硬件配置愈來愈高,但是它和如今的PC相比,其運算能力,續航能力,存儲空間等都仍是受到很大的限制,同時用戶對手機的體驗要 求遠遠高於PC的桌面應用程序。以上理由,足以須要開發人員更加專心去實現和優化你的代碼了。選擇合適的算法和數據結構永遠是開發人員最早應該考慮的事 情。同時,咱們應該時刻牢記,寫出高效代碼的兩條基本的原則:(1)不要作沒必要要的事;(2)不要分配沒必要要的內存。

我從去年開始接觸Android開發,如下結合本身的一點項目經驗,同時參考了Google的優化文檔和網上的諸多技術大牛給出的意見,整理出這份文檔。



1.      內存優化

Android系統對每一個軟件所能使用的RAM空間進行了限制(如:Nexus one 對每一個軟件的內存限制是24M),同時Java語言自己比較消耗內存,dalvik虛擬機也要佔用必定的內存空間,因此合理使用內存,彰顯出一個程序員的素質和技能。



1)       瞭解JIT

即時編譯(Just-in-time Compilation,JIT),又稱動態轉譯(Dynamic Translation),是一種經過在運行時將字節碼翻譯爲機器碼,從而改善字節碼編譯語言性能的技術。即時編譯前期的兩個運行時理論是字節碼編譯和動 態編譯。Android原來Dalvik虛擬機是做爲一種解釋器實現,新版(Android2.2+)將換成JIT編譯器實現。性能測試顯示,在多項測試 中新版本比舊版本提高了大約6倍。

詳細請參考 http://hi.baidu.com/cool_parkour/blog/item/2802b01586e22cd8a6ef3f6b.html



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

在源生語言像C++中,一般作法是用Getters(i=getCount())代替直接字段訪問(i=mCount)。這是C++中一個好的習慣,由於編譯器會內聯這些訪問,而且若是須要約束或者調試這些域的訪問,你能夠在任什麼時候間添加代碼。

而在Android中,這不是一個好的作法。虛方法調用的代價比直接字段訪問高昂許多。一般根據面嚮對象語言的實踐,在公共接口中使用Getters和Setters是有道理的,但在一個字段常常被訪問的類中宜採用直接訪問。

無JIT時,直接字段訪問大約比調用getter訪問快3倍。有JIT時(直接訪問字段開銷等同於局部變量訪問),要快7倍。



5)       將成員緩存到本地

訪問成員變量比訪問本地變量慢得多,下面一段代碼:
for(int i =0; i <this.mCount; i++)  {
dumpItem(this.mItems);
}



最好改爲這樣:
int count = this.mCount;
Item[] items = this.mItems;
for(int i =0; i < count; i++)  {
       dumpItems(items);
}



另外一個類似的原則是:永遠不要在for的第二個條件中調用任何方法。以下面方法所示,在每次循環的時候都會調用getCount()方法,這樣作比你在一個int先把結果保存起來開銷大不少。
for(int i =0; i < this.getCount(); i++) {
dumpItems(this.getItem(i));
}




一樣若是你要屢次訪問一個變量,也最好先爲它創建一個本地變量,例如:
protected void drawHorizontalScrollBar(Canvas canvas, int width, int height) {
if(isHorizontalScrollBarEnabled()) {
intsize = mScrollBar.getSize(false);
if(size <=0) {
       size = mScrollBarSize;
}
mScrollBar.setBounds(0, height - size, width, height);
mScrollBar.setParams(computeHorizontalScrollRange(), computeHorizontalScrollOffset(), computeHorizontalScrollExtent(),false);
mScrollBar.draw(canvas);
}
}


這裏有4次訪問成員變量mScrollBar,若是將它緩存到本地,4次成員變量訪問就會變成4次效率更高的棧變量訪問。

另外就是方法的參數與本地變量的效率相同。



1)       對常量使用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」只能使本地變量看起來更清晰些(可是也有些時候這是必須的,好比在使用匿名內部類的時候)。



2)       使用改進的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.mSplat;
        }
    }

    public void one() {
        int sum = 0;
        Foo[] localArray = mArray;
        int len = localArray.length;

        for (int i = 0; i < len; ++i) {
            sum += localArray.mSplat;
        }
    }

    public void two() {
        int sum = 0;
        for (Foo a : mArray) {
            sum += a.mSplat;
        }
}
}


在zero()中,每次循環都會訪問兩次靜態成員變量,取得一次數組的長度。

在one()中,將全部成員變量存儲到本地變量。

two()使用了在java1.5中引入的foreach語法。編譯器會將對數組的引用和數組的長度保存到本地變量中,這對訪問數組元素很是好。 可是編譯器還會在每次循環中產生一個額外的對本地變量的存儲操做(對變量a的存取)這樣會比one()多出4個字節,速度要稍微慢一些。



3)       避免使用浮點數

一般的經驗是,在Android設備中,浮點數會比整型慢兩倍,在缺乏FPU和JIT的G1上對比有FPU和JIT的Nexus One中確實如此(兩種設備間算術運算的絕對速度差大約是10倍)

從速度方面說,在現代硬件上,float和double之間沒有任何不一樣。更普遍的講,double大2倍。在臺式機上,因爲不存在空間問題,double的優先級高於float。

但即便是整型,有的芯片擁有硬件乘法,卻缺乏除法。這種狀況下,整型除法和求模運算是經過軟件實現的,就像當你設計Hash表,或是作大量的算術那樣,例如a/2能夠換成a*0.5。



4)       瞭解並使用類庫

選擇Library中的代碼而非本身重寫,除了一般的那些緣由外,考慮到系統空閒時會用匯編代碼調用來替代library方法,這可能比JIT中生成的等價的最好的Java代碼還要好。

           i.    當你在處理字串的時候,不要吝惜使用String.indexOf(),String.lastIndexOf()等特殊實現的方法。這些方法都是使用C/C++實現的,比起Java循環快10到100倍。



           ii.    System.arraycopy方法在有JIT的Nexus One上,自行編碼的循環快9倍。



           iii.    android.text.format包下的Formatter類,提供了IP地址轉換、文件大小轉換等方法;DateFormat類,提供了各類時間轉換,都是很是高效的方法。

詳細請參考 http://developer.android.com/reference/android/text/format/package-summary.html



           iv.    TextUtils類

對於字符串處理Android爲咱們提供了一個簡單實用的TextUtils類,若是處理比較簡單的內容不用去思考正則表達式不妨試試這個在android.text.TextUtils的類,詳細請參考 http://developer.android.com/reference/android/text/TextUtils.html



            v.    高性能MemoryFile類。

不少人抱怨Android處理底層I/O性能不是很理想,若是不想使用NDK則能夠經過MemoryFile類實現高性能的文件讀寫操做。

MemoryFile適用於哪些地方呢?對於I/O須要頻繁操做的,主要是和外部存儲相關的I/O操做,MemoryFile經過將 NAND或SD卡上的文件,分段映射到內存中進行修改處理,這樣就用高速的RAM代替了ROM或SD卡,性能天然提升很多,對於Android手機而言同 時還減小了電量消耗。該類實現的功能不是不少,直接從Object上繼承,經過JNI的方式直接在C底層執行。

詳細請參考 http://developer.android.com/reference/android/os/MemoryFile.html

在此,只簡單列舉幾個經常使用的類和方法,更多的是要靠平時的積累和發現。多閱讀Google給的幫助文檔時頗有益的。



5)       合理利用本地方法

本地方法並非必定比Java高效。最起碼,Java和native之間過渡的關聯是有消耗的,而JIT並不能對此進行優化。當你分配本地資源時 (本地堆上的內存,文件說明符等),每每很難實時的回收這些資源。同時你也須要在各類結構中編譯你的代碼(而非依賴JIT)。甚至可能須要針對相同的架構 來編譯出不一樣的版本:針對ARM處理器的GI編譯的本地代碼,並不能充分利用Nexus One上的ARM,而針對Nexus One上ARM編譯的本地代碼不能在G1的ARM上運行。

當你想部署程序到存在本地代碼庫的Android平臺上時,本地代碼才顯得尤其有用,而並不是爲了Java應用程序的提速。



6)       複雜算法儘可能用C完成

複雜算法儘可能用C或者C++完成,而後用JNI調用。可是若是是算法比較單間,沒必要這麼麻煩,畢竟JNI調用也會花必定的時間。請權衡。



7)       減小沒必要要的全局變量

儘可能避免static成員變量引用資源耗費過多的實例,好比Context。Android提供了很健全的消息傳遞機制(Intent)和任務模型(Handler),能夠經過傳遞或事件的方式,防止一些沒必要要的全局變量。



8)       不要過多期望gc

Java的gc使用的是一個有向圖,判斷一個對象是否有效看的是其餘的對象能到達這個對象的頂點,有向圖的相對於鏈表、二叉樹來講開銷是可想而知。因此不要過多期望gc。將不用的對象能夠把它指向NULL,並注意代碼質量。同時,顯示讓系統gc回收,例如圖片處理時,

if(bitmap.isRecycled()==false) { //若是沒有回收
     bitmap.recycle();
}




9)       瞭解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

             

10)     使用實體類比接口好

假設你有一個HashMap對象,你能夠將它聲明爲HashMap或者Map:
Map map1 = new HashMap();
HashMap map2 = new HashMap();


哪一個更好呢?

按照傳統的觀點Map會更好些,由於這樣你能夠改變他的具體實現類,只要這個類繼承自Map接口。傳統的觀點對於傳統的程序是正確的,可是它並不 適合嵌入式系統。調用一個接口的引用會比調用實體類的引用多花費一倍的時間。若是HashMap徹底適合你的程序,那麼使用Map就沒有什麼價值。若是有 些地方你不能肯定,先避免使用 Map,剩下的交給IDE提供的重構功能好了。(固然公共API是一個例外:一個好的API經常會犧牲一些性能)



11)     避免使用枚舉

枚舉變量很是方便,但不幸的是它會犧牲執行的速度和並大幅增長文件體積。例如:
public class Foo {
       public enum Shrubbery { GROUND, CRAWLING, HANGING }
}


會產生一個900字節的.class文件(Foo$Shubbery.class)。在它被首次調用時,這個類會調用初始化方法來準備每一個枚舉變量。每一個 枚舉項都會被聲明成一個靜態變量,並被賦值。而後將這些靜態變量放在一個名爲」$VALUES」的靜態數組變量中。而這麼一大堆代碼,僅僅是爲了使用三個 整數。

這樣:Shrubbery shrub =Shrubbery.GROUND;會引發一個對靜態變量的引用,若是這個靜態變量是final int,那麼編譯器會直接內聯這個常數。

一方面說,使用枚舉變量可讓你的API更出色,並能提供編譯時的檢查。因此在一般的時候你毫無疑問應該爲公共API選擇枚舉變量。可是當性能方面有所限制的時候,你就應該避免這種作法了。

有些狀況下,使用ordinal()方法獲取枚舉變量的整數值會更好一些,舉例來講:
for(int n =0; n < list.size(); n++) {
       if(list.items[n].e == MyEnum.VAL_X) {
              // do something
       } else if(list.items[n].e == MyEnum.VAL_Y) {
              // do something
       }
}


替換爲:
int valX = MyEnum.VAL_X.ordinal();
int valY = MyEnum.VAL_Y.ordinal();
int count = list.size();
MyItem items = list.items();
for(int n =0; n < count; n++) {
       intvalItem = items[n].e.ordinal();
       if(valItem == valX) {
              // do something
       } else if(valItem == valY) {
              // do something
       }
}



會使性能獲得一些改善,但這並非最終的解決之道。



12)     在私有內部內中,考慮用包訪問權限替代私有訪問權限
public class Foo {
           public class Inner {
                public 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:"+value);
           }
}


須要注意的關鍵是:咱們定義的一個私有內部類(Foo$Inner),直接訪問外部類中的一個私有方法和私有變量。這是合法的,代碼也會打印出預期的「Value is 27」。

但問題是,虛擬機認爲從Foo$Inner中直接訪問Foo的私有成員是非法的,由於他們是兩個不一樣的類,儘管Java語言容許內部類訪問外部類的私有成員,可是經過編譯器生成幾個綜合方法來橋接這些間隙的。
/*package*/static int Foo.access$100(Foo foo) {
return foo.mValue;
}
/*package*/static void Foo.access%200(Foo foo,int value) {
       foo.duStuff(value);
}


內部類會在外部類中任何須要訪問mValue字段或調用doStuff方法的地方調用這些靜態方法。這意味着這些代碼將直接存取成員變量表現爲經過存取器方法訪問。以前提到過存取器訪問如何比直接訪問慢,這例子說明,某些語言約會定致使不可見的性能問題。

若是你在高性能的Hotspot中使用這些代碼,能夠經過聲明被內部類訪問的字段和成員爲包訪問權限,而非私有。但這也意味着這些字段會被其餘處於同一個包中的類訪問,所以在公共API中不宜採用。



13)     將與內部類一同使用的變量聲明在包範圍內

請看下面的類定義:
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),它須要訪問外部類的私有域變量和函數。這是合法的,而且會打印出咱們但願的結果Value is 27。

問題是在技術上來說(在幕後)Foo$Inner是一個徹底獨立的類,它要直接訪問Foo的私有成員是非法的。要跨越這個鴻溝,編譯器須要生成一組方法:
/*package*/ static int Foo.access$100(Foo foo) {
    return foo.mValue;
}
/*package*/ static void Foo.access$200(Foo foo, int value) {
    foo.doStuff(value);
}



內部類在每次訪問mValueg和gdoStuffg方法時,都會調用這些靜態方法。就是說,上面的代碼說明了一個問題,你是在經過接口方法訪問 這些成員變量和函數而不是直接調用它們。在前面咱們已經說過,使用接口方法(getter、setter)比直接訪問速度要慢。因此這個例子就是在特定語 法下面產生的一個「隱性的」性能障礙。

經過將內部類訪問的變量和函數聲明由私有範圍改成包範圍,咱們能夠避免這個問題。這樣作可讓代碼運行更快,而且避免產生額外的靜態方法。(遺憾 的是,這些域和方法能夠被同一個包內的其餘類直接訪問,這與經典的OO原則相違背。所以當你設計公共API的時候應該謹慎使用這條優化原則)。



14)     緩存

適量使用緩存,不要過量使用,由於內存有限,能保存路徑地址的就不要存放圖片數據,不常用的儘可能不要緩存,不用時就清空。



15)     關閉資源對象

對SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O操做等都應該記得顯示關閉。

             

2.      視圖優化

1)       View優化

                       i.    減小沒必要要的View以及View的嵌套層次。

好比實現一個listview中經常使用的layout,可使用RelativeLayout減小嵌套,要知道每一個View的對象會耗費1~2k內存,嵌套層次過多會引發頻繁的gc,形成ANR。

                     ii.    經過HierarchyViewer查看佈局結構

利用HierarchyViewer來查看View的結構:~/tools/hierarchyviewer,能很清楚地看到RelativeLayout下面的扁平結構,這樣能加快dom的渲染速度。

詳細請參考 http://developer.android.com/guide/developing/tools/hierarchy-viewer.html

                   iii.    經過Layoutopt優化佈局

經過android sdk中tools目錄下的layoutopt 命令查看你的佈局是否須要優化。詳細請參考 http://apps.hi.baidu.com/share/detail/34247942

             

2)       多線程解決複雜計算

佔用CPU較多的數據操做盡量放在一個單獨的線程中進行,經過handler等方式把執行的結果交於UI線程顯示。特別是針對的網絡訪問,數據庫查詢,和複雜的算法。目前Android提供了AsyncTask,Hanlder、Message和Thread的組合。

對於多線程的處理,若是併發的線程不少,同時有頻繁的建立和釋放,能夠經過concurrent類的線程池解決線程建立的效率瓶頸。

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



3)       佈局用Java完成比XML快

通常狀況下對於Android程序佈局每每使用XML文件來編寫,這樣能夠提升開發效率,可是考慮到代碼的安全性以及執行效率,能夠經過Java 代碼執行建立,雖然Android編譯過的XML是二進制的,可是加載XML解析器的效率對於資源佔用仍是比較大的,Java處理效率比XML快得多,但 是對於一個複雜界面的編寫,可能須要一些套嵌考慮,若是你思惟靈活的話,使用Java代碼來佈局你的Android應用程序是一個更好的方法。



4)       對大型圖片進行縮放

圖片讀取是OOM(Out of Memory)的常客,當在Android手機上直接讀取4M的圖片時,死神通常都會降臨,因此致使每每本身手機拍攝的照片都不能直接讀取。對大型圖片進 行縮放有,處理圖片時咱們常常會用到BitmapFactory類,android系統中讀取位圖Bitmap時分給虛擬機中圖片的堆棧大小隻有8M。用 BitmapFactory解碼一張圖片時,有時也會遇到該錯誤。這每每是因爲圖片過大形成的。這時咱們須要分配更少的內存空間來存儲。 BitmapFactory.Options.inSampleSize設置恰當的inSampleSize可使BitmapFactory分配更少的 空間以消除該錯誤。Android提供了一種動態計算的,以下:



讀取圖片以前先查看其大小:
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);


使用獲得的圖片原始寬高計算適合本身的smaplesize

BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 4 ;// 4就表明容量變爲之前容量的1/4
Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);


                    

對於過期的Bitmap對象必定要及時recycle,而且把此對象賦值爲null。

bitmap.recycle();
bitmap = null;


             

5)       合理使用ViewStub

ViewStub 是一個隱藏的,不佔用內存空間的視圖對象,它能夠在運行時延遲加載佈局資源文件。當ViewStub可見,或者調用 inflate()函數時,纔會加載這個佈局資源文件。 該ViewStub在加載視圖時在父容器中替換它自己。所以,ViewStub會一直存在於視圖中,直到調用setVisibility(int) 或者inflate()爲止。ViewStub的佈局參數會隨着加載的視圖數一同被添加到ViewStub父容器。一樣,你也能夠經過使用 inflatedId屬性來定義或重命名要加載的視圖對象的Id值。因此咱們可使用ViewStub延遲加載某些比較複雜的layout,動態加載 View,採用ViewStub 避免一些不常常的視圖長期握住引用。

詳細請參考http://developer.android.com/reference/android/view/ViewStub.html



6)       針對ListView的性能優化

                    i.    複用convertView。



                    ii.    在getItemView中,判斷convertView是否爲空,若是不爲空,可複用。若是couvertview中的view須要添加 listerner,代碼必定要在if(convertView==null){}以外。



                   iii.    異步加載圖片,item中若是包含有web image,那麼最好異步加載。



                    iv.    快速滑動時不顯示圖片

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



                      v.    item儘量的減小使用的控件和佈局的層次;背景色與cacheColorHint設置相同顏色;ListView中item的佈局相當重要,必須儘可 能的減小使用的控件,佈局。RelativeLayout是絕對的利器,經過它能夠減小布局的層次。同時要儘量的複用控件,這樣能夠減小 ListView的內存使用,減小滑動時gc次數。ListView的背景色與cacheColorHint設置相同顏色,能夠提升滑動時的渲染性能。



                      vi.    getView優化

ListView中getView是性能是關鍵,這裏要儘量的優化。getView方法中要重用view;getView方法中不能作複雜的邏輯計算,特別是數據庫和網絡訪問操做,不然會嚴重影響滑動時的性能。優化以下所示:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
       Log.d("MyAdapter", "Position:" + position + "---" + String.valueOf(System.currentTimeMillis()));
       final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
       View v = inflater.inflate(R.layout.list_item_icon_text, null);
       ((ImageView) v.findViewById(R.id.icon)).setImageResource(R.drawable.icon);
       ((TextView) v.findViewById(R.id.text)).setText(mData[position]);
       return v;
}



建議改成:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
       Log.d("Adapter", "Position:" + position + " : " + String.valueOf(System.currentTimeMillis()));
       ViewHolder holder;
       if (convertView == null) {
              final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
              convertView = inflater.inflate(R.layout.list_item_icon_text, null);
              holder = new ViewHolder();
             holder.icon = (ImageView) convertView.findViewById(R.id.icon);
             holder.text = (TextView) convertView.findViewById(R.id.text);
             convertView.setTag(holder);
       } else {
             holder = (ViewHolder) convertView.getTag();
       }
              holder.icon.setImageResource(R.drawable.icon);
              holder.text.setText(mData[position]);
              return convertView;
       }

       static class ViewHolder {
               ImageView icon;
               TextView text;
       }
}



以上是Google IO大會上給出的優化建議,通過嘗試ListView確實流暢了許多。使用1000條記錄,經測試第一種方式耗時:25211ms,第二種方式耗時:16528ms。



7)       其餘

                   i.    分辨率適配

-ldpi,-mdpi, -hdpi配置不一樣精度資源,系統會根據設備自適應,包括drawable, layout,style等不一樣資源。



                   ii.    儘可能使用dp(density independent pixel)開發,不用px(pixel)。



                   iii.    多用wrap_content, fill_parent



                   iv.    拋棄AbsoluteLayout



                   v.    使用9patch(經過~/tools/draw9patch.bat啓動應用程序),png格式



                   vi.    採用<merge> 優化佈局層數;採用<include >來共享佈局。



                   vii.    將Acitivity中的Window的背景圖設置爲空。getWindow().setBackgroundDrawable(null);android的默認背景是否是爲空。



                   viii.    View中設置緩存屬性.setDrawingCache爲true。



3.      網絡優化

1)       避免頻繁網絡請求

訪問server端時,創建鏈接自己比傳輸須要跟多的時間,如非必要,不要將一交互能夠作的事情分紅屢次交互(這須要與Server端協調好)。 有效管理Service 後臺服務就至關於一個持續運行的Acitivity,若是開發的程序後臺都會一個service不停的去服務器上更新數據,在不更新數據的時候就讓它 sleep,這種方式是很是耗電的,一般狀況下,可使用AlarmManager來定時啓動服務。以下所示,第30分鐘執行一次。
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, MyService.class);
PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
long interval = DateUtils.MINUTE_IN_MILLIS * 30;
long firstWake = System.currentTimeMillis() + interval;
am.setRepeating(AlarmManager.RTC,firstWake,  interval,  pendingIntent);


2)       數據壓縮

傳輸數據通過壓縮 目前大部門網站都支持GZIP壓縮,因此在進行大數據量下載時,儘可能使用GZIP方式下載,能夠減小網絡流量,通常是壓縮前數據大小的30%左右。

HttpGet request = new HttpGet("http://example.com/gzipcontent");
HttpResponse resp = new DefaultHttpClient().execute(request);
HttpEntity entity = response.getEntity();
InputStream compressed = entity.getContent();
InputStream rawData = new GZIPInputStream(compressed);




3)       使用線程池

線程池,分爲核心線程池和普通線程池,下載圖片等耗時任務放置在普通線程池,避免耗時任務阻塞線程池後,致使全部異步任務都必須等待。



4)       選擇合適的數據格式傳輸形式



    



其中Tree Parse 是DOM解析 Event/Stream是SAX方式解析。

很明顯,使用流的方式解析效率要高一些,由於DOM解析是在對整個文檔讀取完後,再根據節點層次等再組織起來。而流的方式是邊讀取數據邊解析,數 據讀取完後,解析也就完畢了。在數據格式方面,JSON和Protobuf效率明顯比XML好不少,XML和JSON你們都很熟悉。

從上面的圖中能夠得出結論就是儘可能使用SAX等邊讀取邊解析的方式來解析數據,針對移動設備,最好能使用JSON之類的輕量級數據格式爲佳。



1)       其餘

設置鏈接超時時間和響應超時時間。Http請求按照業務需求,分爲是否能夠緩存和不可緩存,那麼在無網絡的環境中,仍然經過緩存的HttpResponse瀏覽部分數據,實現離線閱讀。



2.      數據庫相關

1)       相對於封裝過的ContentProvider而言,使用原始SQL語句執行效率高,好比使用方法rawQuery、execSQL的執行效率比較高。

2)       對於須要一次性修改多個數據時,能夠考慮使用SQLite的事務方式批量處理。

3)       批量插入多行數據使用InsertHelper或者bulkInsert方法

4)       有些能用文件操做的,儘可能採用文件操做,文件操做的速度比數據庫的操做要快10倍左右。



3.      性能測試

對於Android平臺上軟件的性能測試能夠經過如下幾種方法來分析效率瓶頸,目前Google在android軟件開發過程當中已經引入了多種測試工具包,好比Unit測試工程,調試類,還有模擬器的Dev Tools均可以直接反應執行性能。

1)       在模擬器上的Dev Tools能夠激活屏幕顯示當前的FPS,CPU使用率,能夠幫助咱們測試一些3D圖形界面的性能。

2)       通常涉及到網絡應用的程序,在效率上和網速有不少關係,這裏須要屢次的調試才能實際瞭解。

3)       對於邏輯算法的效率執行,咱們使用Android上最廣泛的,計算執行時間來查看:


long start = System.currentTimeMillis();
// do something
long duration = System.currentTimeMillis() - start;


最終duration保存着實際處理該方法須要的毫秒數。

4)       gc效率跟蹤,若是你執行的應用比較簡單,能夠在DDMS中查看下Logcat的VM釋放內存狀況,大概模擬下那些地方能夠緩存數據或改進算法的。

5)       線程的使用和同步,Android平臺上給咱們提供了豐富的多任務同步方法,但在深層上並無過多的好比自旋鎖等高級應用,不 過對於Service和 appWidget而言,他們實際的產品中都應該以多線程的方式處理,以釋放CPU時間,對於線程和堆內存的查看這些均可以在DDMS中看到。

6)       利用traceview和monkey等工具測試應用。

7)       利用layoutopt和ninepatch等工具優化視圖。



4.      結束語

本文給出了一些Android 移動開發中常見的優化方法,多數狀況下利用這些優化方法優化後的代碼,執行效率有明顯的提升,內存溢出狀況也有所改善。在實際應用中對程序的優化必定要權 衡是不是必須的,由於優化可能會帶來諸如增長BUG,下降代碼的可讀性,下降代碼的移植性等不良效果。

但願不要盲目優化,請先肯定存在問題,再進行優化。而且你知道當前系統的性能,不然沒法衡量你進行嘗試所獲得的性能提高。
php

相關文章
相關標籤/搜索