App性能优化浅谈
前言
前段時(shí)間給公司的小伙伴們進(jìn)行了關(guān)于app性能優(yōu)化的技術(shù)分享,這里我稍微整理一下也給大家分享一下,關(guān)于性能優(yōu)化這個(gè)話題很大,涉及面可以很廣,也可以很深入,本人能力有限,不會(huì)給大家講特別難懂,特別底層的東西,都是我們開發(fā)能著手去做的點(diǎn),大家都在講性能優(yōu)化,但對(duì)于項(xiàng)目經(jīng)驗(yàn)不夠豐富的朋友很難有一個(gè)概念,做優(yōu)化的時(shí)候也會(huì)比較茫然,這里我就給大家指明方向。
從何講起?
筆者在做產(chǎn)品開發(fā)的時(shí)候,也遇到性能瓶頸,測(cè)試工程師反饋了一些比較明顯的問題,比如UI界面的過度繪制,列表滑動(dòng)有明顯卡頓,比較耗內(nèi)存等等,但以往的都沒有針對(duì)性的去做相應(yīng)的優(yōu)化,所以借著保證產(chǎn)品質(zhì)量的出發(fā)點(diǎn),自己定了相關(guān)的性能優(yōu)化方案,可能不太成熟,不過可以逐步完善,并找到最適合自己產(chǎn)品的優(yōu)化方案。
這里我定了四個(gè)方向:
- 響應(yīng)時(shí)間(Response Time)
- 界面卡頓(ANR)
- 耗內(nèi)存(Memory)
- 內(nèi)存泄露(Out of memory)
響應(yīng)時(shí)間
這里指的是客戶端與服務(wù)端交互,拿到數(shù)據(jù)、解析、再到顯示到界面整個(gè)過程耗費(fèi)的時(shí)間。
這個(gè)部分涉及客戶端的優(yōu)化,也涉及服務(wù)端的優(yōu)化,這里只討論客戶端。
HTTP請(qǐng)求方式
我們的app一般離不開網(wǎng)絡(luò),請(qǐng)求接口是最平常的操作了,如何請(qǐng)求,請(qǐng)求什么我們?cè)陂_發(fā)初期就要定好,服務(wù)端給我的提供的接口,大致可以通過GET、POST、HEAD、PUT、DELETE這幾種請(qǐng)求方式,不同的請(qǐng)求方式有不同應(yīng)用場(chǎng)景,比如GET請(qǐng)求,應(yīng)當(dāng)用來請(qǐng)求返回結(jié)果,參數(shù)是作為url的一部分;POST請(qǐng)求,用于請(qǐng)求會(huì)更改服務(wù)端數(shù)據(jù)或狀態(tài);HEAD請(qǐng)求跟GET一樣,只是服務(wù)器不能在響應(yīng)里返回消息主體;PUT請(qǐng)求,用于將網(wǎng)頁放置正確的地方;DELETE請(qǐng)求用于刪除服務(wù)器指定文檔。
使用優(yōu)秀的開源Http框架是我們比較好的選擇,它的優(yōu)點(diǎn)是經(jīng)過市場(chǎng)的驗(yàn)證,很多坑都被填過,缺點(diǎn)也是我們需要去深究它才能對(duì)其進(jìn)行擴(kuò)展,遇到坑也不一定能填。
如果自己造輪子的話,還需要我們花時(shí)間去驗(yàn)證去適應(yīng)我們的業(yè)務(wù)需求,但好處是我們可以自己去擴(kuò)展可把控,不過這很考量開發(fā)者的素質(zhì)。
數(shù)據(jù)解析
實(shí)際開發(fā)當(dāng)中服務(wù)端的返回?cái)?shù)據(jù)格式無非就兩種:
- JSON
- XML
這兩種格式數(shù)據(jù)格式各有優(yōu)劣,從可讀性來看,xml略微好一點(diǎn),不過JSON也有規(guī)范的標(biāo)簽,從解析難度和速度來看,大家都比較傾向使用JSON,目前JSON也是主流的數(shù)據(jù)格式。
在Android中均可以使用優(yōu)秀的解析庫來加快我們的解析速度,XML中有dom4j,JSON有Jackson、Gson,我們通過這些庫實(shí)現(xiàn)我們更快的完成數(shù)據(jù)解析,提高我們的開發(fā)效率。
數(shù)據(jù)存儲(chǔ)
上一節(jié)講的是數(shù)據(jù)解析,我們解析完后的數(shù)據(jù),可能就需要將數(shù)據(jù)存儲(chǔ)在某個(gè)地方,Android的五種存儲(chǔ)方式:
- Content Provider(主要用來向其他應(yīng)用程序共享數(shù)據(jù))
- SQLite(存儲(chǔ)數(shù)據(jù)到數(shù)據(jù)庫中)
- File(本地文件保存)
- SharedPreference(主要用來保存簡(jiǎn)單的配置信息)
- 網(wǎng)絡(luò)存儲(chǔ)(WebService返回的數(shù)據(jù)或是解析HTTP協(xié)議實(shí)現(xiàn)網(wǎng)絡(luò)數(shù)據(jù)交互)
為了提高應(yīng)用程序的響應(yīng)時(shí)間,數(shù)據(jù)緩存是一個(gè)比較好的方式,我們可以預(yù)處理服務(wù)器返回的數(shù)據(jù),對(duì)數(shù)據(jù)進(jìn)行緩存刷新。
優(yōu)化點(diǎn):
- 異步請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù)
- 預(yù)處理服務(wù)器返回?cái)?shù)據(jù)
- 異步進(jìn)行數(shù)據(jù)存儲(chǔ)操作
- 數(shù)據(jù)緩存刷新
- Timeout超時(shí)重試
- 在主線程中操作UI
界面卡頓
ANR表示”應(yīng)用程序無響應(yīng)”,這個(gè)是需要我們避免發(fā)生的事情,出現(xiàn)這個(gè)異常的原因:
- 主線程 (“事件處理線程” / “UI線程”) 在5秒內(nèi)沒有響應(yīng)輸入事件
- BroadcastReceiver在10秒內(nèi)沒有執(zhí)行完畢
導(dǎo)致ANR的原因有很多,一般情況就是在UI線程做了耗時(shí)的操作,例如”網(wǎng)絡(luò)請(qǐng)求”、數(shù)據(jù)庫操作。
那么如何避免?
- UI線程只做界面刷新,不做任何耗時(shí)操作,耗時(shí)操作放在子線程來做
- 可以使用Thread+handle或者AsyncTask來進(jìn)行邏輯處理
耗內(nèi)存
每部手機(jī)的內(nèi)存有限,我們這里所說的內(nèi)存指的是手機(jī)的RAM,它是Ramdom Access Memory的縮寫,我們應(yīng)用程序的需要隨機(jī)讀寫的數(shù)據(jù)就存在RAM中,Android手機(jī)之所以會(huì)比較耗內(nèi)存,這跟Android后臺(tái)的處理有關(guān),我們知道Android應(yīng)用是使用Java開發(fā)的,運(yùn)行Java需要有虛擬機(jī),說明每開啟一個(gè)應(yīng)用都會(huì)創(chuàng)建一個(gè)虛擬機(jī),而這是需要內(nèi)存的,所以我們開的應(yīng)用越多,后臺(tái)進(jìn)程越多,內(nèi)存都分配出去了,才導(dǎo)致內(nèi)存消耗的嚴(yán)重。
其實(shí)這個(gè)問題我們是沒得破的,只要內(nèi)存不夠,我們的應(yīng)用還是會(huì)卡。我們開發(fā)的應(yīng)用依賴與系統(tǒng)給我們分配的堆內(nèi)存,一般上限在16M~48M,但我們可以通過在AndroidManifest設(shè)置Application屬性largeHeap=“true”來申請(qǐng)更多的堆內(nèi)存。
通過以下代碼獲取可用堆內(nèi)存限制:
mActivityManager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);mMaxMemory = mActivityManager.getMemoryClass();內(nèi)存泄露
內(nèi)存泄露這個(gè)問題已經(jīng)被說爛了,大家都知道有內(nèi)存泄露這個(gè)問題存在,但為什么會(huì)發(fā)生內(nèi)存泄露?
這里的內(nèi)存泄露并不是真正意思上的泄露,而是因?yàn)閮?nèi)存不足不能進(jìn)行GC操作,從而導(dǎo)致占用內(nèi)存過大,拋出out of memory異常,而被系統(tǒng)Kill掉。
JVM回收機(jī)制
是時(shí)候講講JVM的回收機(jī)制了,看下圖:
JVM對(duì)Java對(duì)象分了三個(gè)代進(jìn)行管理,分別為年輕代、年老代、永久代。
年輕代(Young Generation):絕大多數(shù)的Java對(duì)象會(huì)在年輕代被分配,也會(huì)在年輕代被回收。
年老代(Old Generation):在年輕代長(zhǎng)期存在沒有被回收的Java對(duì)象會(huì)轉(zhuǎn)移到年老代,這個(gè)堆空間通常會(huì)被比年輕代的堆空間要大。
永久代:存放VM和Java類的元數(shù)據(jù),以及interned字符串和類的靜態(tài)變量。
這里涉及到JVM的相關(guān)知識(shí),這里不繼續(xù)深入探討。
但我們應(yīng)該可以知道垃圾回收器的作用:
- 分配內(nèi)存
- 保證所有正在被引用的對(duì)象還存在于內(nèi)存中
- 回收?qǐng)?zhí)行代碼已經(jīng)不再引用的對(duì)象所占的內(nèi)存
對(duì)象引用
Java的引用類型可以分為以下幾種:
- 強(qiáng)引用(Strong Ref):強(qiáng)可達(dá),去掉強(qiáng)可達(dá),才會(huì)被回收。
- 軟引用(Soft Ref):內(nèi)存夠用,就保持,內(nèi)存吃緊,則回收,主要用來做緩存。
- 弱引用(Weak Ref):比Soft Ref弱,即使內(nèi)存不吃緊也會(huì)被回收。
- 虛引用(Phantom Ref):不會(huì)在內(nèi)存保持任何對(duì)象。
一圖勝千言:
利用Strong Ref,存儲(chǔ)大量數(shù)據(jù),直到heap撐破,利用inter strings(或者class loader加載大量的類)把perm gen撐破,然后就是內(nèi)存泄露了。
如何優(yōu)化?
前面講了一些背景知識(shí),對(duì)我們理解內(nèi)存優(yōu)化有一定的幫助,下面就簡(jiǎn)單說一下我們優(yōu)化的方向:
- 布局優(yōu)化
- 內(nèi)存優(yōu)化
布局優(yōu)化
大家可以拿出你們的Android機(jī)
開發(fā)者工具-Profile GPU Rendering-選擇在屏幕上顯示條形圖
-藍(lán)色代表測(cè)量繪制Display List的時(shí)間
-紅色代表OpenGL渲染Display List所需要的時(shí)間
-黃色代表CPU等待GPU處理的時(shí)間
-中間綠色橫線代表VSYNC時(shí)間16ms,盡量將所有條形圖控制在這條綠線下
為什么是16ms?
Android 通知界面渲染和重繪的時(shí)間要在16ms內(nèi)完成,如果超過16ms,就會(huì)導(dǎo)致丟幀,也就是我們常說的卡頓。
優(yōu)化點(diǎn):
- 避免OverDraw
- 優(yōu)化布局層級(jí)
- 避免過多無用嵌套
- 使用<include>標(biāo)簽重用layout
- 使用<ViewStub>延遲加載
- Hierarchy View進(jìn)行層級(jí)分析
具體的使用方法,這里不介紹了,不懂就百度。
內(nèi)存優(yōu)化
內(nèi)存優(yōu)化的點(diǎn)有很多,這里我主要分為兩大塊:
- Bitmap優(yōu)化
- 代碼優(yōu)化
Bitmap優(yōu)化
第一點(diǎn),就是按需顯示,比如列表中的圖片,你可以顯示縮略圖,詳情頁,你就可以加載相應(yīng)的分辨率的圖片,這樣可以減少內(nèi)存消耗,一般可以要求服務(wù)端提供多種分辨率的圖片。
第二點(diǎn),Bitmap是很耗內(nèi)存,尤其是加載比較大的bitmap,可以想到的優(yōu)化方案就是使用記得回收,對(duì)Bitmap進(jìn)行壓縮,使用BitmapFactory.Options設(shè)置inSampleSize就可以縮小圖片。
第三點(diǎn),圖像緩存,這個(gè)可以利用成熟的圖片加載框架,比如Universal-ImageLoader、Fresco、Picasso,這些框架都對(duì)圖片進(jìn)行了很好的優(yōu)化,大家可以對(duì)比一下,選擇使用即可。
代碼優(yōu)化
關(guān)于代碼這個(gè)就有的說了,任何能改進(jìn)我們程序的優(yōu)化點(diǎn)都能寫在這里,這里沒辦法把所有優(yōu)化的點(diǎn)列在這里,只提供相關(guān)的參考,剩下的就好各位經(jīng)驗(yàn)總結(jié)和積累了。
優(yōu)化點(diǎn):
- 對(duì)常量使用static修飾符
- 使用靜態(tài)方法
- 減少不必要的成員變量
- 盡量不要使用枚舉,少用迭代器
- 對(duì)Cursor、Receiver、Sensor、File等對(duì)象,要注意它們的創(chuàng)建、回收與注冊(cè)、反注冊(cè)
- 避免大量使用注解、反射
- 使用RenderScript、OpenGL來進(jìn)行復(fù)雜的繪圖操作
- 使用SurfaceView來替代View進(jìn)行大量、頻繁的繪圖操作
- 盡量使用視圖緩存,而不是每次都執(zhí)行inflate()方法解析視圖
注:這里引用了Android群英傳的相關(guān)優(yōu)化點(diǎn)
- 創(chuàng)建新的對(duì)象都需要額外的內(nèi)存空間,要盡量減少創(chuàng)建新的對(duì)象。
- 將類、變量、方法等等的可見性修改為最小。
- 針對(duì)字符串的拼接,使用StringBuffer替代String。
- 不要在循環(huán)當(dāng)中聲明臨時(shí)變量,不要在循環(huán)中捕獲異常。
- 如果對(duì)于線程安全沒有要求,盡量使用線程不安全的集合對(duì)象。
- 使用集合對(duì)象,如果事先知道其大小,則可以在構(gòu)造方法中設(shè)置初始大小。
- 文件讀取操作需要使用緩存類,及時(shí)關(guān)閉文件。
- 慎用異常,使用異常會(huì)導(dǎo)致性能降低。
- 如果程序會(huì)頻繁創(chuàng)建線程,則可以考慮使用線程池。
以上都是些經(jīng)驗(yàn)總結(jié),大致都相差無幾,朋友們?cè)谧龃a優(yōu)化的時(shí)候,可以根據(jù)這些優(yōu)化點(diǎn),有針對(duì)性去重構(gòu)代碼,其實(shí)最重要還是代碼的可讀性,結(jié)構(gòu)清晰。
性能優(yōu)化工具
- Memory Monitor - 內(nèi)存監(jiān)視工具
- TraceView
- MAT
Android開發(fā)者對(duì)與以上幾個(gè)性能調(diào)優(yōu)的工具一定不陌生,這里我也不再寫那么多廢話了,關(guān)于它們的使用方法,官網(wǎng)還有一些大牛的博客都有介紹。
最后
寫這篇文章的出發(fā)點(diǎn)也是對(duì)Android性能優(yōu)化有個(gè)比較清楚的認(rèn)識(shí),任何事情都不可能一蹴而就,需要循循漸進(jìn),對(duì)一個(gè)初學(xué)者你談優(yōu)化很不現(xiàn)實(shí),我們先把基本的做好,再去考慮相應(yīng)的優(yōu)化,筆者也在不斷學(xué)習(xí)當(dāng)中,借鑒別人好的優(yōu)化方案,提高產(chǎn)品的質(zhì)量,感謝大家對(duì)筆者的關(guān)注。
總結(jié)
- 上一篇: python 修改图片_Python实现
- 下一篇: java实现 GB35114 sip A