5. 優(yōu)化問題
1. ANR
Application Not Responding即應(yīng)用無響應(yīng)。
通常主線程中進行耗時操作就會引起。應(yīng)用啟動時,系統(tǒng)會為應(yīng)用創(chuàng)建一個名為“主線程”的執(zhí)行線程。 此線程非常重要,因為它負責(zé)將事件分派給相應(yīng)的用戶界面小部件,其中包括繪圖事件。 此外,它也是應(yīng)用與 Android UI 工具包組件(來自 android.widget 和 android.view 軟件包的組件)進行交互的線程。因此,主線程有時也稱為 UI 線程。
系統(tǒng)不會為每個組件實例創(chuàng)建單獨的線程。運行于同一進程的所有組件均在 UI 線程中實例化,并且對每個組件的系統(tǒng)調(diào)用均由該線程進行分派。 因此,響應(yīng)系統(tǒng)回調(diào)的方法(例如,報告用戶操作的 onKeyDown() 或生命周期回調(diào)方法)始終在進程的 UI 線程中運行。如果 UI 線程用來需要處理耗時很長的操作(例如,網(wǎng)絡(luò)訪問或數(shù)據(jù)庫查詢)將會阻塞整個 UI,被阻塞超過5秒鐘時間,就會引發(fā)“應(yīng)用無響應(yīng)”(ANR)。
1.1 主要分三種情況
A)KeyDispatchTimeout
頁面按鍵無響應(yīng)超時,這個Key事件分發(fā)超時的時間,Android默認是5秒,主要是定義在ActivityTaskManagerService.java
KEY_DISPATCHING_TIMEOUT_MS = 5*1000;
B)BroadcastTimeOut
廣播的超時時間,分為FG前臺廣播和BG后臺廣播,分別是10秒和60秒(這里是指單個廣播接收器可以處理的最大的時間)。定義在ActivityManagerService.java
BROADCAST_FG_TIMEOUT = 101000;
BROADCAST_BG_TIMEOUT = 601000;
前臺廣播和后臺廣播都有各自對應(yīng)的廣播隊列,主要區(qū)別就是隊列最大處理時間不同,前者10S后者60S。
發(fā)送時系統(tǒng)默認是后臺廣播。
若要修改為前臺廣播,intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
如果想要廣播能夠更快的被接收,那么可以將它定義為前臺廣播。
因為系統(tǒng)默認是后臺廣播隊列,前臺廣播隊列比較空閑,可以更快響應(yīng)。
C)ServiceTimeOut
Service的超時時間為20秒(后端是200S),定義在ActiveServices.java
static final int SERVICE_TIMEOUT = 20*1000;
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
1.2 如何避免ANR
a)UI主線程盡量只做跟UI相關(guān)的工作.
b)耗時的操作,如I/O,網(wǎng)絡(luò)連接,把它放入單獨的線程處理
c)盡量用Handler來處理UI線程和非UI線程之間的交互
2. 內(nèi)存
2.1 堆、棧和方法區(qū)
JAVA是在JVM所虛擬出的內(nèi)存環(huán)境中運行的,內(nèi)存分為三個區(qū):堆、棧和方法區(qū)。
棧(stack):是簡單的數(shù)據(jù)結(jié)構(gòu),程序運行時系統(tǒng)自動分配,使用完畢后自動釋放。優(yōu)點:速度快。
堆(heap):用于存放由new創(chuàng)建的對象和數(shù)組。在堆中分配的內(nèi)存,一方面由java虛擬機自動垃圾回收器來管理,另一方面還需要程序員提供修養(yǎng),防止內(nèi)存泄露問題。
方法區(qū)(method):又叫靜態(tài)區(qū),跟堆一樣,被所有的線程共享。方法區(qū)包含所有的class和static變量。
2.2 內(nèi)存溢出、內(nèi)存泄漏、內(nèi)存抖動
內(nèi)存溢出(Out of Memory):系統(tǒng)會給每個APP分配內(nèi)存也就是Heap Size值。當(dāng)APP占用的內(nèi)存加上我們申請的內(nèi)存資源超過了Dalvik虛擬機的最大內(nèi)存時就會拋出的Out Of Memory異常。
內(nèi)存泄漏(Memory Leak):當(dāng)一個對象不在使用了,本應(yīng)該被垃圾回收器(JVM)回收。但是這個對象由于被其他正在使用的對象所持有,造成無法被回收的結(jié)果。內(nèi)存泄漏最終會導(dǎo)致內(nèi)存溢出。
內(nèi)存抖動:內(nèi)存抖動是指在短時間內(nèi)有大量的對象被創(chuàng)建或者被回收的現(xiàn)象,主要是循環(huán)中大量創(chuàng)建、回收對象。這種情況應(yīng)當(dāng)盡量避免。
它們?nèi)叩闹匾燃壏謩e:內(nèi)存溢出 > 內(nèi)存泄露 > 內(nèi)存抖動。
內(nèi)存溢出對我們的App來說,影響是非常大的。有可能導(dǎo)致程序閃退,無響應(yīng)等現(xiàn)象,因此,我們一定要優(yōu)先解決OOM的問題。
原因及處理方法:
1. 單例導(dǎo)致內(nèi)存泄露,它對應(yīng)應(yīng)用程序的生命周期,所以我們在構(gòu)造單例的時候盡量避免使用`Activity`的上下文,而是使用`Application`的上下文。否則被持有的`Activity`在關(guān)閉時候無法被回收。
2. 靜態(tài)變量導(dǎo)致內(nèi)存泄露,靜態(tài)變量存儲在方法區(qū),它的生命周期從類加載開始,到整個進程結(jié)束。一旦靜態(tài)變量初始化后,它所持有的引用只有等到進程結(jié)束才會釋放。
3. 非靜態(tài)內(nèi)部類導(dǎo)致內(nèi)存泄露,非靜態(tài)內(nèi)部類(包括匿名內(nèi)部類)默認就會持有外部類的引用,當(dāng)非靜態(tài)內(nèi)部類對象的生命周期比外部類對象的生命周期長時,就會導(dǎo)致內(nèi)存泄露。
> 非靜態(tài)內(nèi)部類導(dǎo)致的內(nèi)存泄露在Android開發(fā)中有一種典型的場景就是使用`Handler`。
>
> 非靜態(tài)內(nèi)部類造成內(nèi)存泄露還有一種情況就是使用`Thread`或者`AsyncTask`。
> 通常在Android開發(fā)中如果要使用內(nèi)部類,但又要規(guī)避內(nèi)存泄露,一般都會采用*靜態(tài)內(nèi)部類+弱引用*的方式。
>
> 同時還需要在`Activity`銷毀時就將`mHandler`的回調(diào)和發(fā)送的消息給移除掉。
4. 未取消注冊或回調(diào)導(dǎo)致內(nèi)存泄露。
5. 集合中的對象未清理造成內(nèi)存泄露。
6. 資源未關(guān)閉或釋放導(dǎo)致內(nèi)存泄露。在使用`IO`、`File`流或者`Sqlite`、`Cursor`等資源時要及時關(guān)閉。
7. 屬性動畫造成內(nèi)存泄露。在`Activity`銷毀的時候`cancel`掉屬性動畫,雖然看不到這個動畫了,但是它還在不斷播放。
8. `Timer`和`TimerTask`導(dǎo)致內(nèi)存泄露。當(dāng)我們`Activity`銷毀的時,有可能`Timer`還在繼續(xù)等待執(zhí)行`TimerTask`,它持有Activity的引用不能被回收,因此當(dāng)我們Activity銷毀的時候要立即`cancel`掉`Timer`和`TimerTask`,以避免發(fā)生內(nèi)存泄漏。
9. `WebView`造成內(nèi)存泄露。因為WebView在加載網(wǎng)頁后會長期占用內(nèi)存而不能被釋放,在銷毀`WebView`之前需要先將`WebView從`父容器中移除,后要調(diào)用它的`destory()`方法來銷毀它以釋放內(nèi)存。
2.3 強引用、軟引用、弱引用、虛引用
強引用:強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。 當(dāng)內(nèi)存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內(nèi)存不足的問題。
軟引用:如果一個對象只具有軟引用,但內(nèi)存空間足夠時,垃圾回收器就不會回收它;直到虛擬機報告內(nèi)存不夠時才會回收, 只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現(xiàn)內(nèi)存敏感的高速緩存。 軟引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關(guān)聯(lián)的引用隊列中。
弱引用(谷歌推薦Android使用):只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當(dāng)前內(nèi)存空間是否足夠,都會回收它的內(nèi)存。 不過,由于垃圾回收器是一個優(yōu)先級很低的線程,因此不一定會很快發(fā)現(xiàn)那些只具有弱引用的對象。 弱引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關(guān)聯(lián)的引用隊列中。
-
虛引用:虛引用可以理解為虛設(shè)的引用,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。 虛引用主要用來跟蹤對象被垃圾回收器回收的活動。
虛引用與軟引用和弱引用的一個區(qū)別在于:虛引用必須和引用隊列 (ReferenceQueue)聯(lián)合使用。 當(dāng)垃圾回收器準備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用,就會在回收對象的內(nèi)存之前,把這個虛引用加入到與之 關(guān)聯(lián)的引用隊列中。 程序可以通過判斷引用隊列中是否已經(jīng)加入了虛引用,來了解被引用的對象是否將要被垃圾回收。 如果程序發(fā)現(xiàn)某個虛引用已經(jīng)被加入到引用隊列,那么就可以在所引用的對象的內(nèi)存被回收之前采取必要的行動。
2.4 常用檢測內(nèi)存是否泄漏的工具
LeackCanary、Memory Monitor、DDMS
2.5 GC回收機制與優(yōu)化
當(dāng)系統(tǒng)內(nèi)存不足時就會觸發(fā)GC回收機制。 還有就是當(dāng)系統(tǒng)空閑時候會觸發(fā),在優(yōu)先級最低的進程中進行。優(yōu)先級有五個等級:前臺進程,可見進程,服務(wù)進程,后臺進程,空進程。
1)盡早釋放無用對象。
2)合理使用軟引用(內(nèi)存不夠時候,遇到就會清理)和弱引用(遇到就會清理)。
3)如果需要使用經(jīng)常使用的圖片,可以使用軟引用類型。它可以盡可能將圖片保存在內(nèi)存中,供程序調(diào)用,而不引起OutOfMemory;
4)盡量少用靜態(tài)對象變量,靜態(tài)變量屬于全局變量,不會被GC回收,它們會一直占用內(nèi)存。
5)能用基本類型如int,long,就不用Integer,Long對象?;绢愋妥兞空加玫膬?nèi)存資源比相應(yīng)對象占用的少得多
3. RecyclerView優(yōu)化
- 數(shù)據(jù)處理與視圖綁定分離
RecyclerView的bindViewHolder方法是在UI線程進行的,如果在該方法進行耗時操作,將會影響滑動的流暢性。
- 數(shù)據(jù)優(yōu)化
分頁加載遠端數(shù)據(jù),對拉取的遠端數(shù)據(jù)進行緩存,提高二次加載速度;
- 布局優(yōu)化
減少布局層級,可以考慮使用自定義View來減少層級,或者更合理的設(shè)置布局來減少層級。
- 減少View對象的創(chuàng)建
一個稍微復(fù)雜的 Item 會包含大量的 View,而大量的 View 的創(chuàng)建也會消耗大量時間,所以要盡可能簡化 ItemView;設(shè)計 ItemType 時,對多 ViewType 能夠共用的部分盡量設(shè)計成自定義 View,減少 View 的構(gòu)造和嵌套
- 設(shè)置高度固定
如果item高度是固定的話,可以使用RecyclerView.setHasFixedSize(true);來避免requestLayout浪費資源。
- 加大RecyclerView的緩存
用空間換時間,來提高滾動的流暢性。
- 減少ItemView監(jiān)聽器的創(chuàng)建
對ItemView設(shè)置監(jiān)聽器,不要對每個item都創(chuàng)建一個監(jiān)聽器,而應(yīng)該共用一個Listener,然后根據(jù)ID來進行不同的操作,優(yōu)化了對象的頻繁創(chuàng)建帶來的資源消耗。
- 優(yōu)化滑動操作
設(shè)置RecyclerView.addOnScrollListener();來在滑動過程中停止加載的操作
- 回收資源
通過重寫RecyclerView.onViewRecycled(holder)來回收資源。
- 共用RecycledViewPool
如果多個 RecycledView 的 Adapter 是一樣的,比如嵌套的 RecyclerView 中存在一樣的 Adapter,可以通過設(shè)置 RecyclerView.setRecycledViewPool(pool); 來共用一個 RecycledViewPool。
- 處理刷新閃爍
用notifyDataSetChange時,適配器不知道整個數(shù)據(jù)集中的那些內(nèi)容以及存在,再重新匹配ViewHolder時會花生閃爍。
設(shè)置adapter.setHasStableIds(true),并重寫getItemId()來給每個Item一個唯一的ID
4. 如何優(yōu)化頁面啟動速度
- 不要在Application的構(gòu)造方法、attachBaseContext()、onCreate()里面進行初始化耗時操作。盡可能使用IntentService代替初始化工作。
- 不要Activity在onCreate、onStart、onResume當(dāng)中做耗時操作。
- 合理使用延遲加載。
$ UI適配
1.smallestWidth 限定符屏幕適配方案
根據(jù)主流屏幕的 最小寬度 (smallestWidth) 生成一系列 values-sw<N>dp 文件夾 (含有 dimens.xml 文件),當(dāng)把項目運行到設(shè)備上時,系統(tǒng)會根據(jù)當(dāng)前設(shè)備屏幕的 最小寬度 (smallestWidth) 去匹配對應(yīng)的 values-sw<N>dp 文件夾,而對應(yīng)的 values-sw<N>dp 文件夾中的 dimens.xml 文字中的值,又是根據(jù)當(dāng)前設(shè)備屏幕的 最小寬度 (smallestWidth) 而定制的,所以一定能適配當(dāng)前設(shè)備。
2.AndroidAutoSize適配
是今日頭條適配方案適配方案的升級版。它支持對Activity、Fragment進行取消適配,靈活性會更強。
在確保設(shè)計圖總寬度(單位dp)一定時,通過修改density值,確保所有不同尺寸分辨率設(shè)備計算出的真實寬度值正好是屏幕寬度,這樣就能達到適配所有設(shè)備的目的。