目的
公司的新需求終於解決完了,離測試和釋出還有段時間,第一次體驗了下沒需求沒bug的感覺,真是舒爽~然後翻了翻有什麼可以學的。無意翻到了Android後期發展的五大趨勢。一、效能優化。二、高階UI。三、JNI/NDK開發。四、架構師。五、RN開發。這也許將會是我的進階趨勢。早已知道在瓶頸期的我,似乎看到了突破的希望的。
其實,關注我的或者在群裡的小夥伴也知道,UI那塊我問題不大。但是高階UI就有難度了。我們先不管他,一個一個來。先從效能優化來。其實我是拒絕寫這篇文章的。為什麼?效能優化的分類很多,一個分類寫一篇感覺篇幅量很小,結合在一起寫有感覺很大。而我目前打算整體的整理一下。
那麼我們先分析下效能優化有那幾個方面:一、記憶體優化。二、UI優化(佈局優化和繪製優化)。三、速度的優化(執行緒優化/網路優化)。四、電量優化。五、啟動優化。應該就這些了。那麼這只是五大方面,裡面還結合了各種細節方面的。不急,我們下面一個個的介紹。
記憶體優化
關於效能優化我們可以不知道其他的,但一定要知道記憶體優化。因為記憶體洩漏可以Android的常客。那麼什麼是記憶體洩漏呢?記憶體不在GC的掌控範圍之內了。那麼java的GC記憶體回收機制是什麼?某物件不在有任何引用的時候才會進行回收。那麼GC回收機制的原理是什麼?又或者說可以作為GC Root引用點的是啥?或許有人聽不懂我在講啥。我們先來看張圖。
當我們向上尋找,一直尋找到GC Root的時候,此物件不會進行回收,例如,一個Activity。那麼如果我們向上尋找,直到找到GC Root物件的時候,就說明它是不可以回收的,例如,我定義了一個int a;但是這個資料,我整個頁面或者說整個專案都沒有用到,則這個物件會被GC掉。
GC的引用點
java棧中引用的物件
方法靜態引用的物件
方法常量引用的物件
Native中JNI引用的物件
Thread——“活著的”執行緒
如何判斷
那麼我們如何判斷一個物件是一個垃圾物件,可以講他進行回收呢?舉了小例子教你們如何區分:
一般在學校吃飯,我們有兩種情況,第一:吃完飯就直接走人,碗筷留給阿姨來收拾處理。
第二:吃完之後把碗筷放到收盤處直接進行回收。
但我們是個有素質的人,一般採用第二種情況,但根據想法,我們更傾向於第一種。
那麼一般在飯店或者KFC中,都是第一種情況。
那麼此時,問題來了,如果我已經吃完飯,然後我並沒有離開飯店,做在位置上和朋友吹吹牛逼,談談理想,聊聊人生。
那麼桌上那一堆碗筷是收還是不收?講道理是不能收的。雖然實際也是不能收的。因為顧客是上帝~~~複製程式碼
So,我們如何判斷一個物件是一個可回收的垃圾物件呢?這是我們的一個主觀的判斷。但是有種情況我們是必須要考慮到的,沒錯,就是記憶體過多無法釋放的時候,會直接導致OOM。整個專案boom炸了。什麼鬼?outofmemory。沒錯就是它。
記憶體溢位
分析原因
我們需要分析記憶體溢位的原因,我們先來看一張圖:
記憶體洩漏一般導致應用卡頓,極端情況會導致專案boom。Boom的原因是因為超過記憶體的閾值。原因主要有兩方面:
程式碼存在洩漏,記憶體無法及時釋放導致oom(這個我們後面說)
一些邏輯消耗了大量記憶體,無法及時釋放或者超過導致oom
所謂消耗大量的記憶體的,絕大多數是因為圖片載入。這是我們oom出現最頻繁的地方。我前面有寫過圖片載入的方法,一個是控制每次載入的數量,第二,保證每次滑動的時候不進行載入,滑動完進行載入。一般情況使用先進後出,而不是先進先出。不過一般我們圖片載入都是使用fresco或者Glide等開源庫。我們來看下下面兩張圖:
對比兩張圖,我們可以在第一張的情況出現了oom情況,我們通過log列印發現,處理的好像沒什麼問題,換句話說,如果我不放那0.8M的圖片。然後繼續不停的操作同樣會出現OOM,然而我們就蒙了。沒什麼圖片載入怎麼就這麼崩掉了。
如何檢視
首先,我們確定我們專案或者某幾個類裡面是否存在記憶體溢位的問題。我們可以通過如下方法:
Android-->System Information-->MemoryUsage檢視Object裡面是否有沒有被釋放的Views和Activity
命令列模式:adb shell dumpsys meminfo 包名 -d
就那我公司的專案舉例把。首先,我們在這邊可以看到memory。CPU和net的使用情況。我們找到Object。看看我們記憶體的消耗情況。
隨便這麼一看,尼瑪蛋,1300左右的view和一個Activity。還有3個context。可怕。。可以理解為一個Activity裡面使用了將近1300個view。。。想都不敢想。。。
我們可以通過看Memory Monitor工具。 檢查一個一個的動作。(比如Activity的跳轉)。反覆多次執行某一個操作,不斷的通過這個工具檢視記憶體的大概變化情況。 前後兩個記憶體變化增加了不少。
我們可以更仔細的查詢洩漏的位置,在AS裡面使用 Heap SnapShot工具(堆疊快照)。如圖所示:我們點選後,他會進行一段時間的監控,然後會生成一個檔案。我們點選我們package tree view。我們找到自己專案的包名。然後進行進一步的分析。首先看一下2個列表的列名到底指的什麼。
例項化物件的詳細資訊:
我們來隨便的看一下記憶體中的數量:
這還是我們剛進手機,一個bean就被呼叫了這麼多次。簡直可怕。這個我們可以通過記憶體分析工具解決的。
記憶體分析工具
效能優化工具:
Heap SnapShot工具
Heap Viewer工具
LeakCanary工具
MAT工具
TraceView工具(Device Monitor)
第三方分析工具:
MemoryAnalyzer
GT Home
iTest
因為我沒有這些工具,無法進行演示。
注意事項
我們儘量不要使用Activity的上下文,而是使用application的上下文,因為application的生命週期長,程式退出時才會被銷燬。所以,單例模式是最容易造成記憶體溢位的原本所在,因為單例模式的生命週期的應該和application的生命週期一樣長,而不是和Activity的相同。
Animation也會導致記憶體溢位,為什麼?因為我們是通過view來進行演示的,導致view被Activity持有,而Activity又持有view。最後因為Activity無法釋放,導致記憶體洩漏。解決方法是在Activity的ondestory()方法中呼叫Animation.cancle()進行停止,當然一些簡單的動畫我們可以通過自定義view來解決。至少我現在已經很少使用Animation了。沒有一個動畫是自定義view解決不了的。如何有,那就是兩個~~~。
UI優化
UI優化主要包括佈局優化以及view的繪製優化。不急,我們接下來一個一個慢慢看~~。先說下UI的優化到底是什麼?有些時候我們開啟某個軟體,會出現卡頓的情況。這就是UI的問題。那麼我們想一下,什麼情況會導致卡頓呢?一般是如下幾種情況:
人為在UI執行緒中做輕微耗時操作,導致UI執行緒卡頓;
佈局Layout過於複雜,無法在16ms內完成渲染;
同一時間動畫執行的次數過多,導致CPU或GPU負載過重;
View過度繪製,導致某些畫素在同一幀時間內被繪製多次,從而使CPU或GPU負載過重;
View頻繁的觸發measure、layout,導致measure、layout累計耗時過多及整個View頻繁的重新渲染;
記憶體頻繁觸發GC過多(同一幀中頻繁建立記憶體),導致暫時阻塞渲染操作;
冗餘資源及邏輯等導致載入和執行緩慢;
臭名昭著的ANR;
可以看見,上面這些導致卡頓的原因都是我們平時開發中非常常見的。有些人可能會覺得自己的應用用著還蠻OK的,其實那是因為你沒進行一些瞬時測試和壓力測試,一旦在這種環境下執行你的App你就會發現很多效能問題。
佈局優化
GPU繪製
我們對於UI效能的優化還可以通過開發者選項中的GPU過度繪製工具來進行分析。在設定->開發者選項->除錯GPU過度繪製(不同裝置可能位置或者叫法不同)中開啟除錯後可以看見如下圖(對settings當前介面過度繪製進行分析):
這圖看著太亂,我們來一張簡潔明瞭的圖:
我們的目標就是儘量減少紅色Overdraw,看到更多的藍色區域。
可以發現,開啟後在我們想要除錯的應用介面中可以看到各種顏色的區域,具體含義如下:
Overdraw有時候是因為你的UI佈局存在大量重疊的部分,還有的時候是因為非必須的重疊背景。例如某個Activity有一個背景,然後裡面的Layout又有自己的背景,同時子View又分別有自己的背景。僅僅是通過移除非必須的背景圖片,這就能夠減少大量的紅色Overdraw區域,增加藍色區域的佔比。這一措施能夠顯著提升程式效能。
如果佈局中既能採用RealtiveLayout和LinearLayout,那麼直接使用LinearLayout,因為Relativelayout的佈局比較複雜,繪製的時候需要花費更多的CPU時間。如果需要多個LinearLayout或者Framelayout巢狀,那麼可採用Relativelayout。因為多層巢狀導致佈局的繪製有大部分是重複的,這會減少程式的效能。
GPU呈現模式分析
我們依舊開啟設定-->開發者選項-->GPU呈現模式分析-->在螢幕上顯示為條形圖,如圖所示:
當然,也可以在執行完UI滑動操作後在命令列輸入如下命令檢視命令列列印的GPU渲染資料(分析依據:Draw + Process + Execute = 完整的顯示一幀時間 < 16ms):
adb shell dumpsys gfxinfo [應用包名]複製程式碼
隨著介面的重新整理,介面上會以實時柱狀圖來顯示每幀的渲染時間,柱狀圖越高表示渲染時間越長,每個柱狀圖偏上都有一根代表16ms基準的綠色橫線,每一條豎著的柱狀線都包含三部分(藍色代表測量繪製Display List的時間,紅色代表OpenGL渲染Display List所需要的時間,黃色代表CPU等待GPU處理的時間),只要我們每一幀的總時間低於基準線就不會發生UI卡頓問題(個別超出基準線其實也不算啥問題的)。就簡單的看下我們公司專案剛啟動的時候:
突然就有那麼一種想吐槽的感覺.....我記得之前我做了瘦身的優化,但是要讓我做效能優化,我覺得應該沒那麼簡單........
程式碼優化
Android Studio和IntellJ idead都有自帶的程式碼檢查工具。開啟Analyze->Run Inspection by Name… –>unused resource 點選開始檢測,等待一下後會發現如下結果:
我們還可以這樣,將滑鼠放在程式碼區點選右鍵->Analyze->Inspect Code–>介面選擇你要檢測的模組->點選確認開始檢測,等待一下後會發現如下結果:
當然,我這只是擷取了少一部分,我們看下下面那個提示:@param v tag description is missing 。意味著v的型別缺少了,要麼補上介紹,要麼直接刪除。
上面那兩種方法是最容易找到程式碼缺陷以及無用程式碼的地方。所以盡情的入坑去填坑把~~~
繪製優化
那麼什麼是繪製優化?繪製優化主要是指View的Ondraw方法需要避免執行大量的操作。我將分為了2個方面。
ondraw方法不需要建立新的區域性物件,這是因為ondraw方法是實時執行的,這樣會產品大量的臨時物件,導致佔用了更多記憶體,並且使系統不斷的GC。降低了執行效率。
Ondraw方法不需要執行耗時操作,在ondraw方法裡少使用迴圈,因為迴圈會佔用CPU的時間。導致繪製不流暢,卡頓等等。Google官方指出,view的繪製幀率穩定在60dps,這要求每幀的繪製時間不超過16ms(1000/60)。雖然很難保證,但我們需要儘可能的降低。
60dps是目前最合適的影象顯示速度,也是絕大部分Android裝置設定的除錯頻率,如果在16ms內順利完成介面重新整理操作可以展示出流暢的畫面,而由於任何原因導致接收到VSYNC訊號的時候無法完成本次重新整理操作,就會產生掉幀的現象,重新整理幀率自然也就跟著下降(假定重新整理幀率由正常的60fps降到30fps,使用者就會明顯感知到卡頓)。So,前面我們說GPU的時候也談到了這個。總的而言,感覺還是蠻重要的.....
網路優化
執行緒是我們專案中不可缺少的重要部分,因為我們大多數資料都是從網路獲取的。So,執行緒這個是必備用品。我們依舊可以通過Memory下面的Net進行網路的監聽:
ANR問題
相信這個問題在座的各種沒少遇到過,那麼什麼是ANR?application not responding。應用程式無響應。那麼一般什麼時候會出現ANR。Android官方規定:activity如果5s內無響應事件(螢幕觸控事件或者鍵盤輸入事件)。BroadcastReceiver如果在10s內無法處理完成。Service如果20s內無法處理完成。這三種情況會導致ANR。用張簡潔的圖來介紹把。看起來方便~~
執行緒優化
上面說的三種導致ANR的情況,絕大多數就是因為執行緒阻塞導致的。那麼我們應該如何處理呢?Android系統為我們提供了若干組工具類來解決此問題。
Asynctask:為UI執行緒與工作執行緒之間進行快速處理的切換提供一種簡單便捷的機制。適用於當下立即需要啟動,但是非同步執行的生命週期短暫的場景。
HandlerThread:為某些回撥方法或者等待某些執行任務的執行設定一個專屬的執行緒,並提供執行緒任務的排程機制。
ThreadPool:把任務分解成不同的單元,分發到各個不同的執行緒上,進行同時併發處理。
IntentService:適合執行由Ui觸發的後臺任務。並可以把這些任務執行的情況通過一定的機制反饋給UI。
網路請求耗時會給使用者帶來卡頓的產品體驗,雖然可以使用Loading提升使用者體驗,但屬於治標不治本。例如,當網路差的時候我們公司的專案一個loading就是10多s。甚至更多.....我就記得我當時面試之前下了一次我們公司的專案,因為網差的問題...一個loading一分多鐘。。當時砸手機的衝動都有了,別說卸軟體了....
一般多執行緒的情況我們可以通過Asynctask處理。(這玩意我真沒怎麼用過- -)我前面有說過annotation。這是google官方推出的註解。比bufferknife強大很多。這個可以快捷方便的處理多執行緒而且不會導致執行緒阻塞,而且你也可以控制執行緒的順序,例如我要執行完執行緒A後,根據執行緒A的某個引數來執行執行緒B。以此類推.....
至於執行緒池麼,最多的還是要說道圖片載入了~~。圖片載入用三方就行了~想看詳細介紹,我前面有說,當然除了這個還有下載操作。這就和IntentService有關聯了。一般下載我很少涉及到。。用過幾次android原生的downloadmanager。。感覺略坑。
KO網路優化
現在講網路優化的重點了...重點..重點...,一般用到網最最最主要的是什麼?時間!!速度!!成功率!!,時間!!速度!!成功率!!,時間!!速度!!成功率!!重要的事說三遍哈。
圖片處理
這已經不是第一次在此文提到圖片了。可見圖片的重要性!!
使用WebP格式;同樣的照片,採用WebP格式可大幅節省流量,相對於JPG格式的圖片,流量能節省將近 25% 到 35 %;相對於 PNG 格式的圖片,流量可以節省將近80%。最重要的是使用WebP之後圖片質量也沒有改變。So,去和後臺的小夥伴們商量吧~~~
使用縮圖,我在前面寫圖片載入有說過,就是控制他的inside和option。然後進行圖片縮放。壓縮?講道理....我並不知道網路圖片怎麼壓縮,but,我會縮放啊~~反正也不會失真。啦啦啦~咬我啊?
網路請求處理
我們可以對服務端返回資料進行快取,設定有效時間,有效時間之內不走網路請求,減少流量消耗。對網路的快取可以參見HttpResponseCache。
在某些情況,我們儘量少使用GPS定位,如果條件允許,儘可能使用網路定位。
下載、上傳,我們儘可能使用斷點,說個簡單的,我在公司,準備下一個500M的遊戲,但是下到200M的時候我下班了,此時沒有了無線網,我們可以回家後用無線繼續下載。So,斷點續傳,斷點下載也是我們的必修課~,所以我前面單獨提了一篇斷點續傳的文章。
重新整理資料時,儘可能使用區域性重新整理,而不是全域性重新整理,第一、介面會閃屏一下,網差的介面直接白屏一段時間也不是不可能。第二、流量的使用!!我又要拿我們公司專案搞事情了。一個閃屏的快取60+M。。。沒錯,就是60+M。簡直可怕,我清個3、5次快取,在開啟個3、5次。好了,2分鐘時間,我一個月流量就沒了。。。So,我前面提到的網路快取很重要,至於會不會加在專案中,我還是要看了在說- - 一個不小心,整個專案炸了都有可能。。。
啟動優化
眾所周知,一個好的產品,除了功能強大,好的效能也必不可少。有調查顯示,近50%的受訪者因為apk太大而拒絕使用,近40%的受訪者會因為APP效能差而解除安裝,效能也是造成APP使用者沮喪的頭號原因。
安卓應用的啟動方式分為三種:冷啟動、暖啟動、熱啟動,不同的啟動方式決定了應用UI對使用者可見所需要花費的時間長短。顧名思義,冷啟動消耗的時間最長。基於冷啟動方式的優化工作也是最考驗產品使用者體驗的地方。談及優化之前,我們先看看這三種啟動方式的應用場景,以及啟動過程中系統都做了些什麼工作。
冷啟動
為什麼說冷啟動是耗時最長的。冷啟動是在啟動應用前,系統沒有獲取到當前app的activity、Service等等。例如,第一次啟動app。又或者說殺死程式後第一次啟動。那麼對比其他兩種方式。冷啟動自然是耗時最久的。
應用發生冷啟動時,系統一定會執行下面的三個任務:
開始載入並啟動應用
應用啟動後,顯示一個空白的啟動視窗(啟動閃屏頁)
建立應用資訊
那麼建立應用資訊,系統就需要做一屁股的事:
application的初始化
啟動UI執行緒
建立Activity
匯入檢視(inflate view)
計算檢視大小(onmesure view)
得到檢視排版(onlayout view)
繪製檢視(ondraw view)
這其中有兩個 creation 工作,分別為 Application 和 Activity creation。他們均在 View 繪製展示之前。所以,在應用自定義的 Application 類和 第一個 Activity 類中,onCreate() 方法做的事情越多,冷啟動消耗的時間越長。
暖啟動
當應用中的 Activities 被銷燬,但在記憶體中常駐時,應用的啟動方式就會變為暖啟動。相比冷啟動,暖啟動過程減少了物件初始化、佈局載入等工作,啟動時間更短。但啟動時,系統依然會展示閃屏頁,直到第一個 Activity 的內容呈現為止。
熱啟動
相比暖啟動,熱啟動時應用做的工作更少,啟動時間更短。熱啟動產生的場景很多,常見如:使用者使用返回鍵退出應用,然後馬上又重新啟動應用。
如何優化
我們先對比下三種啟動的時間對比:冷啟動:
暖啟動 :
熱啟動:
我們可以看到三者的明顯的差距,一個冷啟動將近一分鐘,反正我是不想看,每次跑專案都好慢~那麼我們應該怎麼做?看到有些人介紹說改變專案的theme。把它改成launcher的theme。但我覺得,這種做測試的確沒問題。但是一般專案都會有閃屏頁。然後從閃屏跳轉到首頁。我們可以按照大多數的專案來改善。怎麼說的,我們可以看到一般專案都有倒數計時顯示。也就是說倒數計時結束就自動進入首頁。或者可以直接跳過進入首頁。也就是說我們可以通過此方法來進行,也就是說只要他倒數計時結束,不管請求是否全部獲取完我們都直接進入首頁。我們可以在閃屏頁進行一些必要的載入,例如使用者資訊,定位等等,那麼至於其他的,我們可以進入主頁進行預載入。就和熱更新一樣,在使用者不知情的情況下,默默的更新bug。So,對於一些網路請求,例如廣告之類的。我們可以通過此方法進行預載入。
我們還可以這樣,閃屏頁我們把他當作一個fragment巢狀在MainActivity中,那麼我們可以在進入閃屏時直接預載入主頁的view。倒數計時我們把閃屏頁remove掉直接顯示首頁。
通過上面的介紹,我們對啟動優化有了一定的瞭解,其實總結的話很簡單。就是減少耗時操作,總結如下:
主執行緒中涉及到Shareperference能否在非UI執行緒執行。
Application的建立過程中儘量少的進行耗時操作。
減少佈局的層次,並且生命週期回撥的方法中儘量減少耗時的操作。
電量優化
有了UI優化、記憶體優化、程式碼優化、網路優化之後我們在來說說應用開發中很重要的一個優化模組—–電量優化。
耗電概念
其實大多數開發者對電量優化的重視程度極低,其實提到效能優化想到的就是記憶體優化,但我們不能忽視其他的優化,電量優化其實還是必要的,例如愛奇藝、優酷等等的視訊播放器以及音樂播放器。眾所周知,音樂和視訊其實是耗電量最大的。如果使用者一旦發現我們的應用非常耗電,不好意思,他們大多會選擇解除安裝來解決此類問題。為此,我們需要進行優化。
如何優化
其實我們把上面那四種優化解決了,就是最好的電量優化。So,對於電量優化,我在此提一些建議:
需要進行網路請求時,我們需先判斷網路當前的狀態。
在多網路請求的情況下,最好進行批量處理,儘量避免頻繁的間隔網路請求。
在同時有wifi和移動資料的情況下,我們應該直接螢幕移動資料的網路請求,只有當wifi斷開時在呼叫,因為,wifi請求的耗電量遠比移動資料的耗電量低的低。
後臺任務要儘可能少的喚醒CPU。(比方說,鎖屏時,QQ的訊息提示行就是喚醒了CPU。但是它的提示只有在你開啟鎖屏或者進行充電時才會進行提示。)
優化總結
效能優化是我們進階的畢竟之路。So,我們必須要會,至於“會”到什麼程度,就要看個人理解了。其實,上面介紹的只是效能問題的冰山一角,真正的優化,我們是在專案中總結出來的。但,我們不能一味的追求優化,就例如我,現在只是在進行優化的總結,而對於真正的實行,並沒有開始,因為,優化是有風險的,一個不小心,整個專案都可能炸了。所以這就需要你的經驗,以及各種總結,在改進行優化的地方先進行優化,看看效果如何,例如,UI的優化以及程式碼的優化。可以先拿一些網上的開源專案進行優化等等。最後,盡情的享受優化把~~~
文章參考
《Android藝術探索》
第一時間獲取本人的技術文章請關注微信公眾號!