1.什么是性能優(yōu)化
百度百科:
性能優(yōu)化(Optimize)
簡(jiǎn)而言之昧狮,就是在不影響系統(tǒng)運(yùn)行正確性的前提下,使之運(yùn)行地更快鞋真,完成特定功能所需的時(shí)間更短。
維基百科:
大多數(shù)系統(tǒng)會(huì)響應(yīng)增加的負(fù)載而導(dǎo)致性能會(huì)有一定程度的下降亿虽,修改系統(tǒng)以處理更高負(fù)載就是性能優(yōu)化。
總結(jié)就是苞也,提高負(fù)載能力讓程序運(yùn)行更快洛勉,用更少的資源做更多的活就是性能優(yōu)化。
2.為什么要性能優(yōu)化
隨著科技不斷發(fā)展如迟、移動(dòng)互聯(lián)網(wǎng)的迅猛發(fā)展收毫,手機(jī)硬件不斷進(jìn)步以及使用手機(jī)的人口增多,這樣就導(dǎo)致我們的程序的實(shí)際運(yùn)行環(huán)境是無(wú)法控制的殷勘,除開(kāi)程序本身的質(zhì)量而言此再,我們不能完全拋棄低端手機(jī)用戶群體,這是我們的人口紅利玲销,一句話簡(jiǎn)而概之输拇,我們要提升用戶留存,不能讓程序在低端手機(jī)運(yùn)行不流暢甚至ANR贤斜。
2015 年上半年策吠,Pinterest 的工程師進(jìn)行了一次實(shí)驗(yàn),借此將移動(dòng) Web 首頁(yè)的頁(yè)面加載性能提升了 60%瘩绒,同時(shí)移動(dòng)注冊(cè)轉(zhuǎn)化率提升了 40%猴抹。然而該實(shí)驗(yàn)使用了一種極為煩瑣的解決方案,用到了大量“抄近道”的方法锁荔,例如提供預(yù)先生成的 HTML 頁(yè)面蟀给,而沒(méi)有使用內(nèi)部模版渲染引擎或其他通用資源(JS、CSS)阳堕。為了將實(shí)驗(yàn)學(xué)到的經(jīng)驗(yàn)實(shí)用化跋理,整個(gè)前端引擎、所有頁(yè)面模版嘱丢,以及通用元素都必須重寫薪介。
亞馬遜近10年前的一項(xiàng)研究證明,即使在那時(shí)越驻,頁(yè)面加載時(shí)間每減少100毫秒,收入也會(huì)增加1%道偷。最近的另一項(xiàng)研究強(qiáng)調(diào)了這樣一個(gè)事實(shí):超過(guò)一半的受訪網(wǎng)站所有者表示缀旁,由于應(yīng)用程序性能不佳,他們失去了收入或客戶勺鸦。
總結(jié)并巍,產(chǎn)品的意義是解決現(xiàn)實(shí)生活的需求,一個(gè)好的產(chǎn)品必定有著優(yōu)秀的性能换途,而優(yōu)秀甚至極致的性能能夠提升用戶的主觀感受懊渡,讓用戶愿意繼續(xù)消費(fèi)刽射,也為后續(xù)的用戶轉(zhuǎn)化打下基礎(chǔ)。
3.如何性能優(yōu)化
至于如何具體的深入到項(xiàng)目中去進(jìn)行性能優(yōu)化呢
1剃执、快速響應(yīng)用戶的觸碰事件(不要在主線程干耗時(shí)操作)
2誓禁、設(shè)置動(dòng)畫或滾動(dòng)時(shí),在16毫秒以內(nèi)生成幀
3肾档、最大程度的減少內(nèi)存分配摹恰,避免短時(shí)大量分配內(nèi)存(頻繁的GC會(huì)造成內(nèi)存抖動(dòng),
JVM在進(jìn)行回收時(shí)會(huì)發(fā)出stop world 指令怒见,該指令會(huì)暫停所有線程俗慈,從而導(dǎo)致UI卡頓)
4、持續(xù)吸引用戶遣耍,一個(gè)頁(yè)面的數(shù)據(jù)盡可能在1000毫秒以內(nèi)呈現(xiàn)交互內(nèi)容
Render
首先說(shuō)說(shuō)渲染方面,在正式開(kāi)車之前棋傍,我們先了解一下其他的知識(shí)點(diǎn)救拉,Android系統(tǒng)每隔16ms就重新繪制一次頁(yè)面,就是說(shuō)應(yīng)用要在16ms內(nèi)完成屏幕刷新瘫拣,如果16ms內(nèi)沒(méi)有渲染完畢亿絮,那就是我們常說(shuō)的卡頓、不跟手麸拄,專業(yè)的說(shuō)法就是掉幀派昧。至于安卓系統(tǒng)為什么設(shè)置每個(gè)16ms來(lái)觸發(fā)渲染這就跟人眼的生理結(jié)構(gòu)有關(guān),人的眼睛可以感知每秒60幀的動(dòng)畫拢切,如果低于60幀就不會(huì)認(rèn)為它是連續(xù)性的蒂萎。
舉個(gè)例子,比如一個(gè)按鈕 Button 淮椰,LayoutInflater遍歷 XML 文件然后把 Button 實(shí)例加載到內(nèi)存五慈,內(nèi)存里面保存了 width、height主穗、left泻拦、top、right忽媒、bottom以及一些內(nèi)外邊距争拐,CPU經(jīng)過(guò)計(jì)算生成多維向量圖形,最后將計(jì)算好的圖形給GPU來(lái)進(jìn)行柵格化像素填充晦雨。
所有在屏幕上我們看到的圖片放大一百倍架曹,可以發(fā)現(xiàn)就是一個(gè)個(gè)發(fā)光點(diǎn)隘冲,屏幕的顏色呈像原理是基于三基色RGB,所有的顏色的都是由RGB組成绑雄,或者再加上Alpha透明度展辞,這也不難發(fā)覺(jué)為什么我們做圖片優(yōu)化的時(shí)將 ARGB8888改成ARGB4444、RGB565绳慎,一個(gè)8進(jìn)制位是一個(gè)字節(jié)纵竖,一個(gè)ARGB8888像素點(diǎn)的呈像信息是4個(gè)字節(jié),ARGB4444杏愤、RGB565將內(nèi)存降低了一半靡砌,縮小寬高和矩陣壓縮也是同理,通過(guò)控制寬高來(lái)降低內(nèi)存開(kāi)銷珊楼。
具體在實(shí)際的安卓開(kāi)發(fā)中通殃,我們要盡量避免過(guò)度繪制、XML層級(jí)過(guò)深厕宗、測(cè)量耗時(shí)等
我們知道安卓是根據(jù)XML從上而下遍歷渲染的画舌,圖中的藍(lán)色區(qū)域是一次繪制,綠色區(qū)域的控件也有顏色已慢,這樣就造成了二次繪制曲聂,以此類推,這就是過(guò)度繪制佑惠。根據(jù)渲染的原理朋腋,CPU通過(guò)計(jì)算后交給GPU來(lái)柵格化,過(guò)度繪制就導(dǎo)致我們做了很多無(wú)用的計(jì)算膜楷。
關(guān)于XML層級(jí)過(guò)深的問(wèn)題旭咽,這個(gè)無(wú)需多說(shuō),頁(yè)面通過(guò)setContentView加載到頁(yè)面是通過(guò)XML遍歷處理的赌厅,層級(jí)越深速度越慢穷绵,一般情況下能夠用 LinearLayout 就不要用 RelativeLayout ,RelativeLayout 的測(cè)量會(huì)觸發(fā)兩次特愿,測(cè)量左右關(guān)系仲墨、然后上下關(guān)系,LinearLayout 在沒(méi)有用 weight 時(shí)只測(cè)量一次就能夠確定位置效率相對(duì)比較高揍障,比如一個(gè)布局需要最左邊和最右邊都顯示一個(gè)按鈕宗收,這種布局用 FrameLayout 最合適,因?yàn)镕rameLayout是效率最高的 ViewGroup亚兄。
以及一些組合類型的自定義View,當(dāng)我們的自定義View繼承了響應(yīng)的 ViewGroup 時(shí)采驻,然后XML里面又有一個(gè)父布局审胚,這樣多了一層嵌套匈勋,這個(gè)時(shí)候可以通過(guò) merge 標(biāo)簽來(lái)去除。
Compute
計(jì)算方面的優(yōu)化比較雜膳叨,舉個(gè)例子
ArrayList list = new ArrayList();
for(int i = 0; i < list.size(); i++){
}
上面這個(gè)for 循環(huán)是低效的洽洁,尤其是大數(shù)據(jù)量的循環(huán)尤為明顯,建議改為
ArrayList list = new ArrayList();
for(int i = 0, y = list.size(); i <y; i++){
}
在安卓系統(tǒng)中菲嘴,谷歌為我們提供了一些平臺(tái)比較高效的數(shù)據(jù)結(jié)構(gòu)饿自,android.util包下一共有如下幾個(gè)類:SparseArray系列(SparseArray,SparseBooleanArray龄坪,SparseIntArray昭雌,SparseLongArray,LongSparseArray)健田, SparseArray 在安卓平臺(tái)上效率高于 HashMap烛卧,這些特定的數(shù)據(jù)結(jié)構(gòu)從讀取速度、內(nèi)存消耗都有做特殊的優(yōu)化妓局,可以在合適的地方采用來(lái)提高效率总放。當(dāng)然Java傳統(tǒng)的數(shù)據(jù)結(jié)構(gòu)也并不是一無(wú)是處,合適的數(shù)據(jù)運(yùn)用在合適的業(yè)務(wù)場(chǎng)景看個(gè)人抉擇好爬,比如讀取場(chǎng)景比較頻繁的建議采用 ArrayList 線性隊(duì)列局雄,添加移除比較頻繁的則選擇 LinkedList ,建議使用 StringBuilder 來(lái)代替 + 拼接字符串存炮,字符串轉(zhuǎn)換建議使用String.valueOf() 炬搭,強(qiáng)轉(zhuǎn)和字符串拼接轉(zhuǎn) String 比較低效率,因?yàn)?String.valueOf() JVM 對(duì)這個(gè)方法做了特定的性能優(yōu)化僵蛛,一些大數(shù)據(jù)量的計(jì)算建議放在子線程執(zhí)行尚蝌,最好用線程池來(lái)操作,直接使用線程不可控充尉,可使用RxJava 的 IO 線程來(lái)處理大計(jì)算量飘言,很多框架有做相關(guān)的優(yōu)化,還有一些"計(jì)算優(yōu)化"是屬于特定的安卓下的計(jì)算優(yōu)化驼侠,比如我們 RecycleView 上滑刷新數(shù)據(jù)時(shí)最好不要用 notifyDataSetChanged(); 建議使用 notifyItemInserted();還有item移除用 notifyItemRemoved();有很多 api 是可以高效率處理的姿鸿,比如Gif動(dòng)態(tài)圖的加載,Glide雖然也可以加載倒源,但是計(jì)算量和內(nèi)存開(kāi)銷是比較大的苛预,這個(gè)時(shí)候通過(guò) JNI 通訊采用 giflib 來(lái)加載 Gif 動(dòng)態(tài)圖就比較高效了,giflib 是一個(gè) native 的框架笋熬,這樣做的好處就是 C 的運(yùn)行速度比 Java 快热某,而且 Native 分配的內(nèi)存不受限制,不會(huì) OOM,建議使用直接類型 int > Integer昔馋,計(jì)算優(yōu)化的方式千千萬(wàn)筹吐,看每個(gè)人實(shí)際的業(yè)務(wù)場(chǎng)景。
Memory
內(nèi)存優(yōu)化丘薛,簡(jiǎn)單說(shuō)圖片優(yōu)化不外乎寬高、質(zhì)量邦危、矩陣縮放洋侨,然后大圖預(yù)覽局部渲染,在實(shí)際的開(kāi)發(fā)過(guò)程中有很多優(yōu)秀的第三方框架已經(jīng)幫我們做了優(yōu)化倦蚪,比如Glide的圖片加載策略希坚,先從內(nèi)存中尋找,沒(méi)有則去磁盤找审丘,再?zèng)]有則請(qǐng)求網(wǎng)絡(luò)圖片吏够,當(dāng)下載完畢保存到內(nèi)存和磁盤,這里就要提到一個(gè)算法 LruCache滩报,最近最少用到锅知,簡(jiǎn)單的說(shuō)就是,當(dāng)內(nèi)存不足時(shí)脓钾,最少被用的圖片會(huì)被回收售睹,像我們?cè)陂_(kāi)發(fā)中,如果不是非常有必要可训,建議不要使用反射昌妹,因?yàn)榉瓷鋾?huì)生成大量的臨時(shí)變量,生成變量開(kāi)辟內(nèi)存空間耗時(shí)握截,同時(shí)GC回收也耗時(shí)飞崖,有一些在程序運(yùn)行之前就可以確定的數(shù)據(jù)建議直接根據(jù)數(shù)量初始化,避免浪費(fèi)內(nèi)存谨胞,比如首頁(yè)有4個(gè)Fragment固歪,那我要用一個(gè)隊(duì)列來(lái)保存它,那就直接 ArrayList<Fragment> fragments = new ArrayList<>(4);
Network
網(wǎng)絡(luò)優(yōu)化
關(guān)于服務(wù)器端的網(wǎng)絡(luò)優(yōu)化不做過(guò)多解釋胯努,關(guān)于客戶端的連接優(yōu)化牢裳,IP直接鏈接,這個(gè)涉及到Http的原理叶沛,我們請(qǐng)求一個(gè)接口蒲讯,實(shí)際上請(qǐng)求一臺(tái)電腦的數(shù)據(jù),每一個(gè)生產(chǎn)環(huán)境的地址都是需要通過(guò)NDS服務(wù)器來(lái)解析的灰署,如果直接訪問(wèn)IP是可以優(yōu)化連接速度判帮,提高網(wǎng)絡(luò)性能優(yōu)化局嘁,很重要的一點(diǎn)就是降低延遲和提升響應(yīng)速度。
通常我們?cè)跒g覽器中發(fā)起請(qǐng)求的時(shí)候header部分往往是這樣的
keep-alive
就是瀏覽器和服務(wù)端之間保持長(zhǎng)連接脊另,這個(gè)連接是可以復(fù)用的导狡。在HTTP1.1中是默認(rèn)開(kāi)啟的。
連接的復(fù)用為什么會(huì)提高性能呢偎痛?
通常我們?cè)诎l(fā)起http請(qǐng)求的時(shí)候首先要完成tcp的三次握手,然后傳輸數(shù)據(jù)独郎,最后再釋放連接踩麦。三次握手的過(guò)程可以參考這里 TCP三次握手詳解及釋放連接過(guò)程
一次響應(yīng)的過(guò)程
在高并發(fā)的請(qǐng)求連接情況下或者同個(gè)客戶端多次頻繁的請(qǐng)求操作,無(wú)限制的創(chuàng)建會(huì)導(dǎo)致性能低下氓癌。
如果使用keep-alive
在timeout空閑時(shí)間內(nèi)谓谦,連接不會(huì)關(guān)閉,相同重復(fù)的request將復(fù)用原先的connection贪婉,減少握手的次數(shù)反粥,大幅提高效率。并非keep-alive的timeout設(shè)置時(shí)間越長(zhǎng)疲迂,就越能提升性能才顿,長(zhǎng)久不關(guān)閉會(huì)造成過(guò)多的僵尸連接和泄露連接出現(xiàn)。合理的復(fù)用時(shí)間能夠提升效率尤蒿,okttp也做了類似于keep-alive的鏈接復(fù)用機(jī)制郑气。
合并請(qǐng)求就是能夠一個(gè)接口返回的不要通過(guò)兩三個(gè)接口去請(qǐng)求,同一個(gè)頁(yè)面的數(shù)據(jù)初始化最好一個(gè)接口帶回腰池。并發(fā)連接很好理解尾组,高效率利用CPU,避免CPU閑置示弓。分優(yōu)先級(jí)請(qǐng)求網(wǎng)絡(luò)就是比如一個(gè)頁(yè)面分為頭部和尾部讳侨,首先展示給用戶的是頭部區(qū)域,那我們就先請(qǐng)求頭部數(shù)據(jù)奏属,等頭部數(shù)據(jù)出來(lái)后再請(qǐng)求尾部數(shù)據(jù)跨跨,這屬于策略請(qǐng)求優(yōu)化。關(guān)于數(shù)據(jù)優(yōu)化就是字面意思拍皮,不做解釋歹叮。
Battery
電量?jī)?yōu)化,其實(shí)主要就是注意自己的一些代碼問(wèn)題铆帽,有些操作沒(méi)有及時(shí)或正確的關(guān)閉會(huì)耗費(fèi)大量的電量咆耿,我們可以在得到充電狀態(tài)信息之后,有針對(duì)性的對(duì)部分代碼做優(yōu)化爹橱。比如我們可以判斷只有當(dāng)前 手機(jī)為 AC 充電狀態(tài)時(shí)才去執(zhí)行一些非常耗電的操作萨螺,像定位、傳感器用完記得及時(shí)關(guān)閉,使用傳感器慰技,選擇合適的采樣率椭盏,越高的采樣率類型則越費(fèi)電。后臺(tái)下載耗時(shí)任務(wù)建議使用JobScheduler吻商,其工作方式有 利于用戶在適當(dāng)?shù)臅r(shí)機(jī)執(zhí)行正確的事情掏颊。應(yīng)用可以在安排作業(yè)的同時(shí)允許系統(tǒng)基于內(nèi)存、電源 和連接情況進(jìn)行優(yōu)化艾帐。JobSchedule 的宗旨就是把一些不是特別緊急的任務(wù)放到更合適的時(shí)機(jī) 批量處理乌叶。這樣做有兩個(gè)好處:避免頻繁的喚醒硬件模塊,造成不必要的電量消耗柒爸。避免在不合適的時(shí)間(例如低電量情況下准浴、弱網(wǎng)絡(luò)或者移動(dòng)網(wǎng)絡(luò)情況下的)執(zhí)行過(guò)多的任務(wù)消耗電量。
關(guān)于后續(xù)
1捎稚、并發(fā)的優(yōu)化
2乐横、單例模式的選擇優(yōu)化
3、自定義View的注意事項(xiàng)
4今野、動(dòng)態(tài)代理葡公、反射的優(yōu)化
5、ART虛擬機(jī)和Dalvik的區(qū)別
6腥泥、View 的繪制流程
7匾南、APK包的瘦身優(yōu)化
8、應(yīng)用的啟動(dòng)優(yōu)化
9蛔外、dex文件的優(yōu)化
......