說起Android的性能優化,其實是一個很大的范疇,說到深入的就是架構師級別的。但是對很對初中級的Android開發工程師們經常遇到——
用戶say,什么狗屎,刷這么久都沒反應,取關卸載算了。又或者是老板拍板了,施壓給CTO,然后CTO又來找你:Y的今天必須給我想辦法優化了,不然不準回家。
參考40 Developer Tips for Android Optimization
那為什么從UI的表象上看,App又卡又慢而且還錯亂。基于這些,leo就給大家整理出一些能輕易掌握一些應用開發中用到的性能優化技巧,從而輕松應對這些情況。
一、UI層
首先要明白的是UI的繪制流程:measure-layout-draw,measure與layout都需要for loop所有的子控件,匯集起來才能完成繪制,布局。所以子控件越多,所消耗的時間越長(inflate,layout_weight,relative,多層嵌套等),減少不必要的子控件或層級,是相當有必要的。你可以通過merge,viewstub這些標簽來減少層級嵌套。如果你的空間觀念沒那么好,可以用HierarchyViewer工具來檢查。
復用item,會更輕松。對于Listview或者GridView這種多item的組件來說,復用item可以減少inflate次數,通過setTag,getTag的ViewHolder方式實現復用,這里要注意的是,holder中的控件最好reset后再賦值,避免圖片,文字錯亂。
讓空白空間大于圖像空間,讓圖像空間大于按鈕的大小。如果將按鈕,多選框,切換控件放大后是很丑陋的。一個100dip(0.63")大小的按鈕是不想在平板上顯示為原來兩倍寬度200dip(1.25")的.原因是屏幕變大了,這不是說平板是給巨人用的。我們可以這樣做,在按鈕增加的空間和圖片擴展的空間里添加空白。
pager最好不要預加載。對于ViewPager第一次顯示時卡頓以及左右滑動卡頓,有以下幾種優化方式。
同時緩存page數最好為最小值3,如果過多,那么第一次顯示時,ViewPager所初始化的pager就會很多,這樣pager累積渲染耗時就會增多,看起來就卡。
每個pager應該只在顯示時才加載網絡或數據庫(UserVisibleHint=true),最好不要預加載數據,以免造成浪費。
-
用GraphicalLayout工具快速預覽。GraphicalLayout是WYSIWG XML編輯器。我喜歡直接編寫元素-而不是拖,丟棄的可見編程方式,但在添加一些元素之后,可以在GraphicalLayout的下拉選擇菜單里選擇不同屏幕尺寸進行測試。
Paste_Image.png Selectors是創建buttons的利器。我們在上面提到了如何在XML里定義button的背景,但是你將如何創建一個當按下去會改變的button呢?很簡單:像下面那樣在xml文件里定義背景。該xml文件將接收到button當前狀態并且在外觀上做出相應的改變。
在Honeycomb之前的版本里時不存在ActionBar跟很多animation樣式的,所以可以使用ActionBarSherlock 跟NineOldAndroids來代替。Jake Wharton寫的Android開源 組件都是往下兼容的精心杰作。更為驚喜的是,ABS 擁有強大的功能用來定義ActionBar。
二、圖片
圖片顯示不出來或者加載時間太長,怎么辦?分兩部分,加載速度,下載速度。
對于加載速度,我們要知道一點,雖然下載的圖片可能只有幾百K,但是decode成bitmap所占用的內存可是成倍的,盡可能的減小圖片size是根本因素,讓服務端提供不同分辨率的圖片才是最好的解決方案,內存總有耗盡的時刻,別老想著大分辨率會更清晰,實際就只有150&150的空間,非給弄張1000&1000的圖片是不恰當的。另外論加載速度:內存>硬盤>網絡,合理的使用內存緩存也是關鍵。假如自己寫不好,沒關系,有那么多開源的圖片緩存框架,不用自己操心。
對于下載,要控制好同時下載的最大任務數(平均速度慢),同時給InputStream再包一層緩沖流會更快(如BufferedInputStream)。
不要把所有的圖片都縮放了。用布局文件來適應不同屏幕尺寸的方法只是成功的一半,布局里的元素(如:圖片)也要能在高分辨率的屏幕下良好工作。在概念上比較簡單的方式就是創建一套完整的圖片目錄并將它們與很多drawable目錄匹配起來。
drawable-sw600dp-ldpi drawable-sw600dp-mdpi drawable-sw600dp-hdpi drawable-sw600dp-xhdpi drawable-sw600dp-xxhdpi ...其它的類似。 但是實際就不要太盡信書了。 一般來說有drawble-ldpi, drawable-hdpi等目錄就足夠了,不需要將所有的情況都加上。避免使用位圖(jpg,png)。對于一些圖標來說,用位圖是個不錯的選擇,因為它們使用簡單。但是如果可以避免使用位圖,你可以節省很多空間。但用不同的方法也可以達到很好的結果。
用XML繪圖。位圖都可以用XML繪圖來代替的。XML繪圖不是萬能的,但是它的方便性還是使我感到驚訝。你可以在布局文件的任何地方來引用,而且它可以適應于任何屏幕。用它可以做出理想的按鈕。如果必須,就用位圖。
通過覆蓋onDraw()創建自定義views. 有些事情XML并不十分在行,我們在OpenSignal和WeatherSignal中畫過許多圖像,為此有許多的庫,但是我們要為自定義圖像自己編寫代碼。這很有趣。或許你永遠也不需要做這個,但為了使圖像高度動態并自定義,這經常是唯一可行的辦法。
在不能使用XML的地方使用SVG. 有時候覆蓋onDraw()并勤勤懇懇的為自定義view編寫代碼畫出需要的線條與弧線是過于技術化了。畢竟有一種矢量圖像語言,它稱作…Scalable Vector Graphics(可擴展矢量圖形)。它也是史上最酷的Android應用之一—Androidify的動力來源。事實上他們創建這個庫就是為了那款應用,他們將它發布在這里:SVG for Android 。
對SVG文件GZip壓縮。將它們變得更小它們就會處理的更快。 但是SVG庫并不是支持一切。在一些特定的alpha通道中似乎不能正常工作,你甚至不得不在代碼中將它們剔除。
如果要用PNG,最好優化一下(用PNGCrush或ImageOptim)
三、運行速度
在運行慢的手機上測試。你將在運行慢的手機上發現很多問題,同時它讓你抓狂,沒人會喜歡運行慢的程序。盡量精簡你的代碼,你寫的層級與解析越多,就意味著系統需要花更多的時間。
盡量減少XML布局層次。更多的層次意味著系統將為解析你的代碼付出更多的工作,這將會讓圖像渲染的更慢。用<merge>可以幫助你減少視圖層次結構。這是一種簡單的方式來去除多余的層次。好的文章都對此有所解釋,而且在 Android Developer中它也顯得與眾不同。
用Android Lint。在工程目錄上右鍵選擇Eclipse>Android Tools>Run Lint。它將會得到程序的一些信息,并能提高程序的運行速度,或者它能讓你得代碼更加清爽。它可以給你的代碼提供很詳細的信息,并在你出錯之前就可以給做出提示。
用HierarchyViewer可以直觀的看到你布局的層次。這個智能的工具可以顯示布局中有多少層次,而且可以提示出那些可以讓程序變慢。
-
如果可以盡量用RelativeLayout。AbsoluteLayout已經過期了,就不要用了。你經常會遇到在RelativeLayout和LinearLayout中做出選擇的情況,那就直接用RelativeLayouot吧,因為它可以讓你減少視圖層次。
第二個表單比第一個難看的多。事實上,我們已經介紹過一個完整的新元素了。但是假如我們要給每個盒子里加入一個圖片,一般的我們將這樣做:盒子 A 在屏幕左半邊 圖片|盒子 B在屏幕右半邊 圖片用第一中方法,你得創建一個有兩個層次的LinearLayout,如果用第二種方法,你可以直接在同一個RelativeLayout中加入圖片,比如要指定第一個圖片必須在“dummy_center”的左邊,而且一個TextView A必須也在其左側。那么你就得用7個元素3個視圖層次了(LinearLayout 方式),而(RelativeLayout方式)只用6個元素2個層次,這樣所有的工作添加完成。
Paste_Image.png 用一些擴展工具如DDMS。這可以幫助你發現一些不必要的網絡調用、查看電池使用量、垃圾回收信息,狀態變化(例子:當回調onStop和onDestroy時)等。LittleEye是我目前比較喜歡的工具。
用AsyncTasks。Anroid工程團隊受夠了人們經常在UI線程里面實現網絡調用(譯注:耗時操作,容易阻塞UI刷新),所以他們實現了一些可產生編譯級錯誤信息的API。但是仍然在很多app中的一些工作會拖垮UI線程,我們要考慮到UI布局要快以及提高UI的響應性。
四、緩存與網絡
有很多種緩存方式,也不用leo列舉了,我們要說的是搭配使用。優化可不是一個人的事,實現一個功能簡單,但是想優化重構,那是很不容易的事。需要多方面的預判與聯調。合理的假設與實踐是優化最重要的手段。
軟引用、弱引用。 比方說,以前我們一直在用強引用,HashMap,后來我們發現占內存,我們就用軟引用,弱引用來及時回收,再后來因為回收機制不可控,所以又有了lrucache,disklrucache通過算法來平衡內存與硬盤緩存。隨著android版本的推進與演化,我們也應該擁抱變化。如果你的App里還有軟引用,弱引用的地方,不妨再check下。
網絡+數據庫。網絡我們一般都是去主動獲取,而非被動接受。那如果說數據是重復的或者未更改的呢?那我們去取一次網絡數據有什么意義呢?我的解決方案是給每個activity或fragment或每個組件設置一個最大請求間隔,比如一個listview,第一次請求數據時,保存一份到數據庫,并記下時間戳,當下次重新初始化時,判斷是否超過最大時間間隔(如5分鐘),如果沒有,只加載數據庫數據,不需要再做網絡請求。當然,還有一些隱式的http請求框架會緩存服務器數據,在一定時間內不再請求網絡,或者當服務器返回304時將之前緩存的數據直接返回。
合并多個請求。現在有很多現成HTTP框架供我們使用,我們幾乎只用寫配置就可以搞定一個url請求,但是這里有很多需要服務端配合的,比如:json數據格式,WebP代替jpg,支持斷點續傳,多個請求合并成一個,盡量不做重定向,服務器緩存以及負載均衡等。
控制請求并發量。對客戶端本身,除了上述的實現,我們還需要合理的緩存,控制最大請求并發量,及時取消已失效的請求,過濾重復請求,timeout時間設置,請求優先級設置等。
五、代碼規范
接著leo給大家 補充些代碼規范。
你要知道for loop中不要聲明臨時變量,不到萬不得已不要在里面寫try catch。
明白垃圾回收機制,避免頻繁GC,內存泄漏,OOM。(有機會專門說)
合理使用數據類型,比如StringBuilder代替String,(筆試題最常見的是str+=”str”中有幾個對象) ,少用枚舉enum,少用父類聲明。(List,Map)
如果你有頻繁的new線程,那最好通過線程池去execute它們,減少線程創建開銷。
你要知道單例的好處,并正確的使用它。
多用常量,少用顯式的”action_key”,并維護一個常量類,別重復聲明這些常量。
如果可以,至少要弄懂設計模式中的策略模式,組合模式,裝飾模式,工廠模式,觀察者模式,這些能幫助你合理的解耦,即使需求頻繁變更,你也不用害怕牽一發而動全身。需求變更不可怕,可怕的是沒有在寫代碼之前做合理的設計。
優化當然還有很多很多,leo所說的也只是一個大的輪廓,還是需要自己不斷的嘗試。會開發寫代碼跟會做產品的區別還是蠻大的,僅僅是態度就能刷死80%的碼農了。當你碰到一些需要優化的地方,耐心的去分析,時間的累積會讓你成為真正的工程師。
另外優化也沒有絕對的完美,每一次優化都是基于當前的業務來做的,要明白溝通是最好的優化,不盲從,不隨便,三思而后行。整理出來的,以上就這些了,希望對你們有幫助,有什么問題,歡迎聯系,一起提高技術知識!