2015新年伊始爆雹,Google發(fā)布了關(guān)于Android性能優(yōu)化典范的專題叔营,一共16個短視頻有巧,每個3-5分鐘,幫助開發(fā)者創(chuàng)建更快更優(yōu)秀的Android App芬膝。課程專題不僅僅介紹了Android系統(tǒng)中有關(guān)性能問題的底層工作原理望门,同時也介紹了如何通過工具來找出性能問題以及提升性能的建議。主要從三個方面展開锰霜,Android的渲染機制怒允,內(nèi)存與GC,電量優(yōu)化锈遥。下面是對這些問題和建議的總結(jié)梳理。
0)為什么感覺到卡頓勘畔?
大多數(shù)用戶感知到的卡頓等性能問題的最主要根源都是因為渲染性能(GPU渲染的幀率)所灸。從設(shè)計師的角度,他們希望App能夠有更多的動畫炫七,圖片等時尚元素來實現(xiàn)流暢的用戶體驗爬立。但是Android系統(tǒng)很有可能無法及時完成那些復(fù)雜的界面渲染操作。Android系統(tǒng)每隔16ms發(fā)出VSYNC信號万哪,觸發(fā)對UI進行渲染侠驯,如果每次渲染都成功,這樣就能夠達到流暢的畫面所需要的60fps奕巍,為了能夠?qū)崿F(xiàn)60fps吟策,這意味著程序的大多數(shù)操作都必須在16ms內(nèi)完成。
用戶容易在UI執(zhí)行動畫或者滑動ListView的時候感知到卡頓不流暢匾委,是因為這里的操作相對復(fù)雜,容易發(fā)生丟幀的現(xiàn)象氓润,從而感覺卡頓赂乐。有很多原因可以導(dǎo)致丟幀,也許是因為你的layout太過復(fù)雜咖气,無法在16ms內(nèi)完成渲染挨措,有可能是因為你的UI上有層疊太多的繪制單元挖滤,還有可能是因為動畫執(zhí)行的次數(shù)過多。這些都會導(dǎo)致CPU或者GPU負載過重运嗜。
我們可以通過一些工具來定位問題壶辜,比如可以使用HierarchyViewer來查找Activity中的布局是否過于復(fù)雜,也可以使用手機設(shè)置里面的開發(fā)者選項担租,打開Show GPU Overdraw等選項進行觀察砸民。你還可以使用TraceView來觀察CPU的執(zhí)行情況,更加快捷的找到性能瓶頸奋救。
1)Understanding Overdraw
Overdraw(過度繪制)描述的是屏幕上的某個像素在同一幀的時間內(nèi)被繪制了多次岭参。在多層次的UI結(jié)構(gòu)里面,如果不可見的UI也在做繪制的操作尝艘,這就會導(dǎo)致某些像素區(qū)域被繪制了多次演侯。這就浪費大量的CPU以及GPU資源。
當(dāng)設(shè)計上追求更華麗的視覺效果的時候背亥,我們就容易陷入采用越來越多的層疊組件來實現(xiàn)這種視覺效果的怪圈秒际。這很容易導(dǎo)致大量的性能問題,為了獲得最佳的性能狡汉,我們必須盡量減少Overdraw的情況發(fā)生娄徊。
幸運的是,我們可以通過手機設(shè)置里面的開發(fā)者選項盾戴,打開Show GPU Overdraw的選項寄锐,可以觀察UI上的Overdraw情況。
藍色尖啡,淡綠橄仆,淡紅,深紅代表了4種不同程度的Overdraw情況衅斩,我們的目標就是盡量減少紅色Overdraw盆顾,看到更多的藍色區(qū)域。
Overdraw有時候是因為你的UI布局存在大量重疊的部分矛渴,還有的時候是因為非必須的重疊背景椎扬。例如某個Activity有一個背景,然后里面的Layout又有自己的背景具温,同時子View又分別有自己的背景映跟。僅僅是通過移除非必須的背景圖片凭需,這就能夠減少大量的紅色Overdraw區(qū)域,增加藍色區(qū)域的占比。這一措施能夠顯著提升程序性能罗捎。
2)Understanding VSYNC(垂直同步(Vertical Sync))
為了理解App是如何進行渲染的柿隙,我們必須了解手機硬件是如何工作,那么就必須理解什么是VSYNC
在講解VSYNC之前,我們需要了解兩個相關(guān)的概念:
Refresh Rate(刷新頻率):代表了屏幕在一秒內(nèi)刷新屏幕的次數(shù)贿肩,這取決于硬件的固定參數(shù),例如60Hz龄寞。
Frame Rate(幀率):代表了GPU在一秒內(nèi)繪制操作的幀數(shù)汰规,例如30fps,60fps物邑。
GPU會獲取圖形數(shù)據(jù)進行渲染溜哮,然后硬件負責(zé)把渲染后的內(nèi)容呈現(xiàn)到屏幕上,他們兩者不停的進行協(xié)作色解。
不幸的是茂嗓,刷新頻率和幀率并不是總能夠保持相同的節(jié)奏。如果發(fā)生幀率與刷新頻率不一致的情況科阎,就會容易出現(xiàn)Tearing的現(xiàn)象(畫面上下兩部分顯示內(nèi)容發(fā)生斷裂述吸,來自不同的兩幀數(shù)據(jù)發(fā)生重疊)。
理解圖像渲染里面的雙重與三重緩存機制锣笨,這個概念比較復(fù)雜蝌矛,請移步查看這里:http://source.android.com/devices/graphics/index.html,還有這里http://article.yeeyan.org/view/37503/304664错英。
通常來說朴读,幀率超過刷新頻率只是一種理想的狀況,在超過60fps的情況下走趋,GPU所產(chǎn)生的幀數(shù)據(jù)會因為等待VSYNC的刷新信息而被Hold住,這樣能夠保持每次刷新都有實際的新的數(shù)據(jù)可以顯示噪伊。但是我們遇到更多的情況是幀率小于刷新頻率簿煌。
在這種情況下,某些幀顯示的畫面內(nèi)容就會與上一幀的畫面相同鉴吹。糟糕的事情是姨伟,幀率從超過60fps突然掉到60fps以下,這樣就會發(fā)生LAG豆励,JANK夺荒,HITCHING等卡頓掉幀的不順滑的情況。這也是用戶感受不好的原因所在良蒸。
3)Tool:Profile GPU Rendering(GPU呈現(xiàn)模式分析)
性能問題如此的麻煩技扼,幸好我們可以有工具來進行調(diào)試。打開手機里面的開發(fā)者選項嫩痰,選擇Profile GPU Rendering剿吻,選中On screen as bars的選項。
選擇了這樣以后串纺,我們可以在手機畫面上看到豐富的GPU繪制圖形信息丽旅,分別關(guān)于StatusBar椰棘,NavBar,激活的程序Activity區(qū)域的GPU Rending信息榄笙。
隨著界面的刷新邪狞,界面上會滾動顯示垂直的柱狀圖來表示每幀畫面所需要渲染的時間,柱狀圖越高表示花費的渲染時間越長茅撞。
中間有一根綠色的橫線帆卓,代表16ms,我們需要確保每一幀花費的總時間都低于這條橫線乡翅,這樣才能夠避免出現(xiàn)卡頓的問題鳞疲。
每一條柱狀線都包含三部分,藍色代表測量繪制Display List的時間蠕蚜,紅色代表OpenGL渲染Display List所需要的時間尚洽,黃色代表CPU等待GPU處理的時間。
4)Why 60fps?
我們通常都會提到60fps與16ms靶累,可是知道為何會是以程序是否達到60fps來作為App性能的衡量標準嗎腺毫?這是因為人眼與大腦之間的協(xié)作無法感知超過60fps的畫面更新。
12fps大概類似手動快速翻動書籍的幀率挣柬,這明顯是可以感知到不夠順滑的潮酒。24fps使得人眼感知的是連續(xù)線性的運動,這其實是歸功于運動模糊的效果邪蛔。24fps是電影膠圈通常使用的幀率急黎,因為這個幀率已經(jīng)足夠支撐大部分電影畫面需要表達的內(nèi)容,同時能夠最大的減少費用支出侧到。但是低于30fps是無法順暢表現(xiàn)絢麗的畫面內(nèi)容的勃教,此時就需要用到60fps來達到想要的效果,當(dāng)然超過60fps是沒有必要的匠抗。
開發(fā)app的性能目標就是保持60fps故源,這意味著每一幀你只有16ms=1000/60的時間來處理所有的任務(wù)。
5)Android, UI and the GPU
了解Android是如何利用GPU進行畫面渲染有助于我們更好的理解性能問題汞贸。那么一個最實際的問題是:activity的畫面是如何繪制到屏幕上的绳军?那些復(fù)雜的XML布局文件又是如何能夠被識別并繪制出來的?
Resterization柵格化是繪制那些Button矢腻,Shape门驾,Path,String多柑,Bitmap等組件最基礎(chǔ)的操作猎唁。它把那些組件拆分到不同的像素上進行顯示。這是一個很費時的操作,GPU的引入就是為了加快柵格化的操作诫隅。
CPU負責(zé)把UI組件計算成Polygons腐魂,Texture紋理,然后交給GPU進行柵格化渲染逐纬。
然而每次從CPU轉(zhuǎn)移到GPU是一件很麻煩的事情蛔屹,所幸的是OpenGL ES可以把那些需要渲染的紋理Hold在GPU Memory里面,在下次需要渲染的時候直接進行操作豁生。所以如果你更新了GPU所hold住的紋理內(nèi)容兔毒,那么之前保存的狀態(tài)就丟失了。
在Android里面那些由主題所提供的資源甸箱,例如Bitmaps育叁,Drawables都是一起打包到統(tǒng)一的Texture紋理當(dāng)中,然后再傳遞到GPU里面芍殖,這意味著每次你需要使用這些資源的時候豪嗽,都是直接從紋理里面進行獲取渲染的。當(dāng)然隨著UI組件的越來越豐富豌骏,有了更多演變的形態(tài)龟梦。例如顯示圖片的時候,需要先經(jīng)過CPU的計算加載到內(nèi)存中窃躲,然后傳遞給GPU進行渲染计贰。文字的顯示更加復(fù)雜,需要先經(jīng)過CPU換算成紋理蒂窒,然后再交給GPU進行渲染躁倒,回到CPU繪制單個字符的時候,再重新引用經(jīng)過GPU渲染的內(nèi)容洒琢。動畫則是一個更加復(fù)雜的操作流程樱溉。
為了能夠使得App流暢,我們需要在每一幀16ms以內(nèi)處理完所有的CPU與GPU計算纬凤,繪制,渲染等等操作撩嚼。
6)Invalidations, Layouts, and Performance
順滑精妙的動畫是app設(shè)計里面最重要的元素之一停士,這些動畫能夠顯著提升用戶體驗。下面會講解Android系統(tǒng)是如何處理UI組件的更新操作的完丽。
通常來說恋技,Android需要把XML布局文件轉(zhuǎn)換成GPU能夠識別并繪制的對象。這個操作是在DisplayList的幫助下完成的逻族。DisplayList持有所有將要交給GPU繪制到屏幕上的數(shù)據(jù)信息蜻底。
在某個View第一次需要被渲染時,DisplayList會因此而被創(chuàng)建聘鳞,當(dāng)這個View要顯示到屏幕上時薄辅,我們會執(zhí)行GPU的繪制指令來進行渲染要拂。如果你在后續(xù)有執(zhí)行類似移動這個View的位置等操作而需要再次渲染這個View時,我們就僅僅需要額外操作一次渲染指令就夠了站楚。然而如果你修改了View中的某些可見組件脱惰,那么之前的DisplayList就無法繼續(xù)使用了,我們需要回頭重新創(chuàng)建一個DisplayList并且重新執(zhí)行渲染指令并更新到屏幕上窿春。
需要注意的是:任何時候View中的繪制內(nèi)容發(fā)生變化時拉一,都會重新執(zhí)行創(chuàng)建DisplayList,渲染DisplayList旧乞,更新到屏幕上等一系列操作蔚润。這個流程的表現(xiàn)性能取決于你的View的復(fù)雜程度,View的狀態(tài)變化以及渲染管道的執(zhí)行性能尺栖。舉個例子嫡纠,假設(shè)某個Button的大小需要增大到目前的兩倍,在增大Button大小之前决瞳,需要通過父View重新計算并擺放其他子View的位置货徙。修改View的大小會觸發(fā)整個HierarcyView的重新計算大小的操作。如果是修改View的位置則會觸發(fā)HierarchView重新計算其他View的位置皮胡。如果布局很復(fù)雜痴颊,這就會很容易導(dǎo)致嚴重的性能問題。我們需要盡量減少Overdraw屡贺。
我們可以通過前面介紹的Monitor GPU Rendering來查看渲染的表現(xiàn)性能如何蠢棱,另外也可以通過開發(fā)者選項里面的Show GPU view updates來查看視圖更新的操作,最后我們還可以通過HierarchyViewer這個工具來查看布局甩栈,使得布局盡量扁平化泻仙,移除非必需的UI組件,這些操作能夠減少Measure量没,Layout的計算時間玉转。
7)Overdraw, Cliprect, QuickReject
引起性能問題的一個很重要的方面是因為過多復(fù)雜的繪制操作。我們可以通過工具來檢測并修復(fù)標準UI組件的Overdraw問題究抓,但是針對高度自定義的UI組件則顯得有些力不從心。
有一個竅門是我們可以通過執(zhí)行幾個APIs方法來顯著提升繪制操作的性能袭灯。前面有提到過刺下,非可見的UI組件進行繪制更新會導(dǎo)致Overdraw。例如Nav Drawer從前置可見的Activity滑出之后稽荧,如果還繼續(xù)繪制那些在Nav Drawer里面不可見的UI組件橘茉,這就導(dǎo)致了Overdraw。為了解決這個問題,Android系統(tǒng)會通過避免繪制那些完全不可見的組件來盡量減少Overdraw畅卓。那些Nav Drawer里面不可見的View就不會被執(zhí)行浪費資源擅腰。
但是不幸的是,對于那些過于復(fù)雜的自定義的View(重寫了onDraw方法)髓介,Android系統(tǒng)無法檢測具體在onDraw里面會執(zhí)行什么操作惕鼓,系統(tǒng)無法監(jiān)控并自動優(yōu)化,也就無法避免Overdraw了唐础。但是我們可以通過canvas.clipRect()來幫助系統(tǒng)識別那些可見的區(qū)域箱歧。這個方法可以指定一塊矩形區(qū)域,只有在這個區(qū)域內(nèi)才會被繪制一膨,其他的區(qū)域會被忽視呀邢。這個API可以很好的幫助那些有多組重疊組件的自定義View來控制顯示的區(qū)域。同時clipRect方法還可以幫助節(jié)約CPU與GPU資源豹绪,在clipRect區(qū)域之外的繪制指令都不會被執(zhí)行价淌,那些部分內(nèi)容在矩形區(qū)域內(nèi)的組件,仍然會得到繪制瞒津。
除了clipRect方法之外蝉衣,我們還可以使用canvas.quickreject()來判斷是否沒和某個矩形相交,從而跳過那些非矩形區(qū)域內(nèi)的繪制操作巷蚪。做了那些優(yōu)化之后病毡,我們可以通過上面介紹的Show GPU Overdraw來查看效果。
8)Memory Churn and performance
雖然Android有自動管理內(nèi)存的機制屁柏,但是對內(nèi)存的不恰當(dāng)使用仍然容易引起嚴重的性能問題啦膜。在同一幀里面創(chuàng)建過多的對象是件需要特別引起注意的事情。
Android系統(tǒng)里面有一個Generational Heap Memory的模型淌喻,系統(tǒng)會根據(jù)內(nèi)存中不同的內(nèi)存數(shù)據(jù)類型分別執(zhí)行不同的GC操作僧家。例如,最近剛分配的對象會放在Young Generation區(qū)域裸删,這個區(qū)域的對象通常都是會快速被創(chuàng)建并且很快被銷毀回收的八拱,同時這個區(qū)域的GC操作速度也是比Old Generation區(qū)域的GC操作速度更快的。
除了速度差異之外涯塔,執(zhí)行GC操作的時候肌稻,所有線程的任何操作都會需要暫停,等待GC操作完成之后伤塌,其他操作才能夠繼續(xù)運行。
通常來說轧铁,單個的GC并不會占用太多時間每聪,但是大量不停的GC操作則會顯著占用幀間隔時間(16ms)。如果在幀間隔時間里面做了過多的GC操作,那么自然其他類似計算药薯,渲染等操作的可用時間就變得少了绑洛。
導(dǎo)致GC頻繁執(zhí)行有兩個原因:
Memory Churn內(nèi)存抖動,內(nèi)存抖動是因為大量的對象被創(chuàng)建又在短時間內(nèi)馬上被釋放童本。
瞬間產(chǎn)生大量的對象會嚴重占用Young Generation的內(nèi)存區(qū)域真屯,當(dāng)達到閥值,剩余空間不夠的時候穷娱,也會觸發(fā)GC绑蔫。即使每次分配的對象占用了很少的內(nèi)存,但是他們疊加在一起會增加Heap的壓力泵额,從而觸發(fā)更多其他類型的GC配深。這個操作有可能會影響到幀率,并使得用戶感知到性能問題嫁盲。
解決上面的問題有簡潔直觀方法篓叶,如果你在Memory Monitor里面查看到短時間發(fā)生了多次內(nèi)存的漲跌,這意味著很有可能發(fā)生了內(nèi)存抖動羞秤。
同時我們還可以通過Allocation Tracker來查看在短時間內(nèi)缸托,同一個棧中不斷進出的相同對象。這是內(nèi)存抖動的典型信號之一瘾蛋。
當(dāng)你大致定位問題之后俐镐,接下去的問題修復(fù)也就顯得相對直接簡單了。例如瘦黑,你需要避免在for循環(huán)里面分配對象占用內(nèi)存京革,需要嘗試把對象的創(chuàng)建移到循環(huán)體之外,自定義View中的onDraw方法也需要引起注意幸斥,每次屏幕發(fā)生繪制以及動畫執(zhí)行過程中匹摇,onDraw方法都會被調(diào)用到,避免在onDraw方法里面執(zhí)行復(fù)雜的操作甲葬,避免創(chuàng)建對象廊勃。對于那些無法避免需要創(chuàng)建對象的情況,我們可以考慮對象池模型经窖,通過對象池來解決頻繁創(chuàng)建與銷毀的問題坡垫,但是這里需要注意結(jié)束使用之后,需要手動釋放對象池中的對象画侣。
9)Garbage Collection in Android
JVM的回收機制給開發(fā)人員帶來很大的好處冰悠,不用時刻處理對象的分配與回收,可以更加專注于更加高級的代碼實現(xiàn)配乱。相比起Java溉卓,C與C++等語言具備更高的執(zhí)行效率皮迟,他們需要開發(fā)人員自己關(guān)注對象的分配與回收,但是在一個龐大的系統(tǒng)當(dāng)中桑寨,還是免不了經(jīng)常發(fā)生部分對象忘記回收的情況伏尼,這就是內(nèi)存泄漏。
原始JVM中的GC機制在Android中得到了很大程度上的優(yōu)化尉尾。Android里面是一個三級Generation的內(nèi)存模型爆阶,最近分配的對象會存放在Young Generation區(qū)域,當(dāng)這個對象在這個區(qū)域停留的時間達到一定程度沙咏,它會被移動到Old Generation辨图,最后到Permanent Generation區(qū)域。
每一個級別的內(nèi)存區(qū)域都有固定的大小芭碍,此后不斷有新的對象被分配到此區(qū)域徒役,當(dāng)這些對象總的大小快達到這一級別內(nèi)存區(qū)域的閥值時,會觸發(fā)GC的操作窖壕,以便騰出空間來存放其他新的對象忧勿。
前面提到過每次GC發(fā)生的時候,所有的線程都是暫停狀態(tài)的瞻讽。GC所占用的時間和它是哪一個Generation也有關(guān)系鸳吸,Young Generation的每次GC操作時間是最短的,Old Generation其次速勇,Permanent Generation最長晌砾。執(zhí)行時間的長短也和當(dāng)前Generation中的對象數(shù)量有關(guān),遍歷查找20000個對象比起遍歷50個對象自然是要慢很多的烦磁。
雖然Google的工程師在盡量縮短每次GC所花費的時間养匈,但是特別注意GC引起的性能問題還是很有必要。如果不小心在最小的for循環(huán)單元里面執(zhí)行了創(chuàng)建對象的操作都伪,這將很容易引起GC并導(dǎo)致性能問題呕乎。通過Memory Monitor我們可以查看到內(nèi)存的占用情況,每一次瞬間的內(nèi)存降低都是因為此時發(fā)生了GC操作陨晶,如果在短時間內(nèi)發(fā)生大量的內(nèi)存上漲與降低的事件猬仁,這說明很有可能這里有性能問題。我們還可以通過Heap and Allocation Tracker工具來查看此時內(nèi)存中分配的到底有哪些對象先誉。
10)Performance Cost of Memory Leaks
雖然Java有自動回收的機制湿刽,可是這不意味著Java中不存在內(nèi)存泄漏的問題,而內(nèi)存泄漏會很容易導(dǎo)致嚴重的性能問題褐耳。
內(nèi)存泄漏指的是那些程序不再使用的對象無法被GC識別诈闺,這樣就導(dǎo)致這個對象一直留在內(nèi)存當(dāng)中,占用了寶貴的內(nèi)存空間铃芦。顯然雅镊,這還使得每級Generation的內(nèi)存區(qū)域可用空間變小把曼,GC就會更容易被觸發(fā),從而引起性能問題漓穿。
尋找內(nèi)存泄漏并修復(fù)這個漏洞是件很棘手的事情,你需要對執(zhí)行的代碼很熟悉注盈,清楚的知道在特定環(huán)境下是如何運行的晃危,然后仔細排查。例如老客,你想知道程序中的某個activity退出的時候僚饭,它之前所占用的內(nèi)存是否有完整的釋放干凈了?首先你需要在activity處于前臺的時候使用Heap Tool獲取一份當(dāng)前狀態(tài)的內(nèi)存快照胧砰,然后你需要創(chuàng)建一個幾乎不這么占用內(nèi)存的空白activity用來給前一個Activity進行跳轉(zhuǎn)鳍鸵,其次在跳轉(zhuǎn)到這個空白的activity的時候主動調(diào)用System.gc()方法來確保觸發(fā)一個GC操作。最后尉间,如果前面這個activity的內(nèi)存都有全部正確釋放偿乖,那么在空白activity被啟動之后的內(nèi)存快照中應(yīng)該不會有前面那個activity中的任何對象了。
如果你發(fā)現(xiàn)在空白activity的內(nèi)存快照中有一些可疑的沒有被釋放的對象存在哲嘲,那么接下去就應(yīng)該使用Alocation Track Tool來仔細查找具體的可疑對象贪薪。我們可以從空白activity開始監(jiān)聽,啟動到觀察activity眠副,然后再回到空白activity結(jié)束監(jiān)聽画切。這樣操作以后,我們可以仔細觀察那些對象囱怕,找出內(nèi)存泄漏的真兇霍弹。
11)Memory Performance
通常來說,Android對GC做了大量的優(yōu)化操作娃弓,雖然執(zhí)行GC操作的時候會暫停其他任務(wù)典格,可是大多數(shù)情況下,GC操作還是相對很安靜并且高效的忘闻。但是如果我們對內(nèi)存的使用不恰當(dāng)钝计,導(dǎo)致GC頻繁執(zhí)行,這樣就會引起不小的性能問題齐佳。
為了尋找內(nèi)存的性能問題私恬,Android Studio提供了工具來幫助開發(fā)者。
Memory Monitor:查看整個app所占用的內(nèi)存炼吴,以及發(fā)生GC的時刻本鸣,短時間內(nèi)發(fā)生大量的GC操作是一個危險的信號。
Allocation Tracker:使用此工具來追蹤內(nèi)存的分配硅蹦,前面有提到過荣德。
Heap Tool:查看當(dāng)前內(nèi)存快照闷煤,便于對比分析哪些對象有可能是泄漏了的,請參考前面的Case涮瞻。
12)Tool - Memory Monitor
Android Studio中的Memory Monitor可以很好的幫助我們查看程序的內(nèi)存使用情況鲤拿。
13)Battery Performance
電量其實是目前手持設(shè)備最寶貴的資源之一,大多數(shù)設(shè)備都需要不斷的充電來維持繼續(xù)使用署咽。不幸的是近顷,對于開發(fā)者來說,電量優(yōu)化是他們最后才會考慮的的事情宁否。但是可以確定的是窒升,千萬不能讓你的應(yīng)用成為消耗電量的大戶。
Purdue University研究了最受歡迎的一些應(yīng)用的電量消耗慕匠,平均只有30%左右的電量是被程序最核心的方法例如繪制圖片饱须,擺放布局等等所使用掉的,剩下的70%左右的電量是被上報數(shù)據(jù)台谊,檢查位置信息蓉媳,定時檢索后臺廣告信息所使用掉的。如何平衡這兩者的電量消耗锅铅,就顯得非常重要了督怜。
有下面一些措施能夠顯著減少電量的消耗:
我們應(yīng)該盡量減少喚醒屏幕的次數(shù)與持續(xù)的時間,使用WakeLock來處理喚醒的問題狠角,能夠正確執(zhí)行喚醒操作并根據(jù)設(shè)定及時關(guān)閉操作進入睡眠狀態(tài)号杠。
某些非必須馬上執(zhí)行的操作,例如上傳歌曲丰歌,圖片處理等姨蟋,可以等到設(shè)備處于充電狀態(tài)或者電量充足的時候才進行。
觸發(fā)網(wǎng)絡(luò)請求的操作立帖,每次都會保持無線信號持續(xù)一段時間眼溶,我們可以把零散的網(wǎng)絡(luò)請求打包進行一次操作,避免過多的無線信號引起的電量消耗晓勇。關(guān)于網(wǎng)絡(luò)請求引起無線信號的電量消耗堂飞,還可以參考這里http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/efficient-network-access.html
我們可以通過手機設(shè)置選項找到對應(yīng)App的電量消耗統(tǒng)計數(shù)據(jù)。我們還可以通過Battery Historian Tool來查看詳細的電量消耗绑咱。
如果發(fā)現(xiàn)我們的App有電量消耗過多的問題绰筛,我們可以使用JobScheduler API來對一些任務(wù)進行定時處理,例如我們可以把那些任務(wù)重的操作等到手機處于充電狀態(tài)描融,或者是連接到WiFi的時候來處理铝噩。 關(guān)于JobScheduler的更多知識可以參考http://hukai.me/android-training-course-in-chinese/background-jobs/scheduling/index.html
14)Understanding Battery Drain on Android
電量消耗的計算與統(tǒng)計是一件麻煩而且矛盾的事情,記錄電量消耗本身也是一個費電量的事情窿克。唯一可行的方案是使用第三方監(jiān)測電量的設(shè)備骏庸,這樣才能夠獲取到真實的電量消耗毛甲。
當(dāng)設(shè)備處于待機狀態(tài)時消耗的電量是極少的,以N5為例具被,打開飛行模式玻募,可以待機接近1個月∫蛔耍可是點亮屏幕补箍,硬件各個模塊就需要開始工作,這會需要消耗很多電量啸蜜。
使用WakeLock或者JobScheduler喚醒設(shè)備處理定時的任務(wù)之后,一定要及時讓設(shè)備回到初始狀態(tài)辈挂。每次喚醒無線信號進行數(shù)據(jù)傳遞衬横,都會消耗很多電量,它比WiFi等操作更加的耗電终蒂,詳情請關(guān)注http://hukai.me/android-training-course-in-chinese/connectivity/efficient-downloads/efficient-network-access.html
修復(fù)電量的消耗是另外一個很大的課題蜂林,這里就不展開繼續(xù)了。
15)Battery Drain and WakeLocks
高效的保留更多的電量與不斷促使用戶使用你的App會消耗電量拇泣,這是矛盾的選擇題噪叙。不過我們可以使用一些更好的辦法來平衡兩者。
假設(shè)你的手機里面裝了大量的社交類應(yīng)用霉翔,即使手機處于待機狀態(tài)睁蕾,也會經(jīng)常被這些應(yīng)用喚醒用來檢查同步新的數(shù)據(jù)信息。Android會不斷關(guān)閉各種硬件來延長手機的待機時間债朵,首先屏幕會逐漸變暗直至關(guān)閉子眶,然后CPU進入睡眠,這一切操作都是為了節(jié)約寶貴的電量資源序芦。但是即使在這種睡眠狀態(tài)下臭杰,大多數(shù)應(yīng)用還是會嘗試進行工作,他們將不斷的喚醒手機谚中。一個最簡單的喚醒手機的方法是使用PowerManager.WakeLock的API來保持CPU工作并防止屏幕變暗關(guān)閉渴杆。這使得手機可以被喚醒,執(zhí)行工作宪塔,然后回到睡眠狀態(tài)磁奖。知道如何獲取WakeLock是簡單的,可是及時釋放WakeLock也是非常重要的某筐,不恰當(dāng)?shù)氖褂肳akeLock會導(dǎo)致嚴重錯誤点寥。例如網(wǎng)絡(luò)請求的數(shù)據(jù)返回時間不確定,導(dǎo)致本來只需要10s的事情一直等待了1個小時来吩,這樣會使得電量白白浪費了敢辩。這也是為何使用帶超時參數(shù)的wakelock.acquice()方法是很關(guān)鍵的蔽莱。但是僅僅設(shè)置超時并不足夠解決問題,例如設(shè)置多長的超時比較合適戚长?什么時候進行重試等等盗冷?
解決上面的問題,正確的方式可能是使用非精準定時器同廉。通常情況下仪糖,我們會設(shè)定一個時間進行某個操作,但是動態(tài)修改這個時間也許會更好迫肖。例如锅劝,如果有另外一個程序需要比你設(shè)定的時間晚5分鐘喚醒,最好能夠等到那個時候蟆湖,兩個任務(wù)捆綁一起同時進行故爵,這就是非精確定時器的核心工作原理。我們可以定制計劃的任務(wù)隅津,可是系統(tǒng)如果檢測到一個更好的時間诬垂,它可以推遲你的任務(wù),以節(jié)省電量消耗伦仍。
這正是JobScheduler API所做的事情结窘。它會根據(jù)當(dāng)前的情況與任務(wù),組合出理想的喚醒時間充蓝,例如等到正在充電或者連接到WiFi的時候隧枫,或者集中任務(wù)一起執(zhí)行。我們可以通過這個API實現(xiàn)很多免費的調(diào)度算法谓苟。
從Android 5.0開始發(fā)布了Battery History Tool悠垛,它可以查看程序被喚醒的頻率,又誰喚醒的娜谊,持續(xù)了多長的時間确买,這些信息都可以獲取到。
請關(guān)注程序的電量消耗纱皆,用戶可以通過手機的設(shè)置選項觀察到那些耗電量大戶湾趾,并可能決定卸載他們。所以盡量減少程序的電量消耗是非常有必要的。