Android性能優化總結 [復制鏈接]

2019-11-5 09:56
技術小兵 閱讀:167 評論:1 贊:0
Tag:  性能優化

1.OOM和崩潰優化

1.2 ANR優化

  • ANR的產生需要滿足三個條件
  • 主線程:只有應用程序進程的主線程響應超時才會產生ANR;
  • 超時時間:產生ANR的上下文不同,超時時間也會不同,但只要在這個時間上限內沒有響應就會ANR;
  • 輸入事件/特定操作:輸入事件是指按鍵、觸屏等設備輸入事件,特定操作是指BroadcastReceiver和Service的生命周期中的各個函數,產生ANR的上下文不同,導致ANR的原因也會不同;
  • ANR優化具體措施
  • 將所有耗時操作,比如訪問網絡,Socket通信,查詢大量SQL 語句,復雜邏輯計算等都放在子線程中去,然 后通過handler.sendMessage、runonUIThread、AsyncTask 等方式更新UI。無論如何都要確保用戶界面作的流暢 度。如果耗時操作需要讓用戶等待,那么可以在界面上顯示度條。
  • 使用AsyncTask處理耗時IO操作。在一些同步的操作主線程有可能被鎖,需要等待其他線程釋放相應鎖才能繼續執行,這樣會有一定的ANR風險,對于這種情況有時也可以用異步線程來執行相應的邏輯。另外,要避免死鎖的發生。
  • 使用Handler處理工作線程結果,而不是使用Thread.wait()或者Thread.sleep()來阻塞主線程。
  • Activity的onCreate和onResume回調中盡量避免耗時的代碼
  • BroadcastReceiver中onReceive代碼也要盡量減少耗時,建議使用IntentService處理。
  • 各個組件的生命周期函數都不應該有太耗時的操作,即使對于后臺Service或者ContentProvider來講,應用在后臺運行時候其onCreate()時候不會有用戶輸入引起事件無響應ANR,但其執行時間過長也會引起Service的ANR和ContentProvider的ANR

2.內存泄漏優化

  • 內存檢測第一種:代碼方式獲取內存
/**
* 內存使用檢測:可以調用系統的getMemoryInfo()來獲取當前內存的使用情況
*/
private void initMemoryInfo() {
ActivityManager activityManager = (ActivityManager) Utils.getApp()
.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
if (activityManager != null) {
activityManager.getMemoryInfo(memoryInfo);
LogUtils.d("totalMem=" + memoryInfo.totalMem + ",availMem=" + memoryInfo.availMem);
if (!memoryInfo.lowMemory) {
// 運行在低內存環境
}
}
}
  • 內存檢測第二種:leakcanary工具
  • LeakCanary的原理是監控每個activity,在activity ondestory后,在后臺線程檢測引用,然后過一段時間進行gc,gc后如果引用還在,那么dump出內存堆棧,并解析進行可視化顯示。

2.0 動畫資源未釋放

  • 問題代碼
public class LeakActivity extends AppCompatActivity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak);
textView = (TextView)findViewById(R.id.text_view);
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(textView,"rotation",0,360);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.start();
}
}
  • 解決辦法
  • 在屬性動畫中有一類無限循環動畫,如果在Activity中播放這類動畫并且在onDestroy中去停止動畫,那么這個動畫將會一直播放下去,這時候Activity會被View所持有,從而導致Activity無法被釋放。解決此類問題則是需要早Activity中onDestroy去去調用objectAnimator.cancel()來停止動畫。
@Override
protected void onDestroy() {
super.onDestroy();
mAnimator.cancel();
}

2.1 錯誤使用單利

  • 在開發中單例經常需要持有Context對象,如果持有的Context對象生命周期與單例生命周期更短時,或導致Context無法被釋放回收,則有可能造成內存泄漏。比如:在一個Activity中調用的,然后關閉該Activity則會出現內存泄漏。
  • 解決辦法:
  • 要保證Context和AppLication的生命周期一樣,修改后代碼如下:
  • this.mContext = context.getApplicationContext();
  • 1、如果此時傳入的是 Application 的 Context,因為 Application 的生命周期就是整個應用的生命周期,所以這將沒有任何問題。
  • 2、如果此時傳入的是 Activity 的 Context,當這個 Context 所對應的 Activity 退出時,由于該 Context 的引用被單例對象所持有,其生命周期等于整個應用程序的生命周期,所以當前 Activity 退出時它的內存并不會被回收,這就造成泄漏了。

2.2 錯誤使用靜態變量

  • 使用靜態方法是十分方便的。但是創建的對象,建議不要全局化,全局化的變量必須加上static。全局化后的變量或者對象會導致內存泄漏!
  • 原因分析
  • 這里內部類AClass隱式的持有外部類Activity的引用,而在Activity的onCreate方法中調用了。這樣AClass就會在Activity創建的時候是有了他的引用,而AClass是靜態類型的不會被垃圾回收,Activity在執行onDestory方法的時候由于被AClass持有了引用而無法被回收,所以這樣Activity就總是被AClass持有而無法回收造成內存泄露。

2.3 handler內存泄漏

  • 造成內存泄漏原因分析
  • 通過內部類的方式創建mHandler對象,此時mHandler會隱式地持有一個外部類對象引用這里就是MainActivity,當執行postDelayed方法時,該方法會將你的Handler裝入一個Message,并把這條Message推到MessageQueue中,MessageQueue是在一個Looper線程中不斷輪詢處理消息,那么當這個Activity退出時消息隊列中還有未處理的消息或者正在處理消息,而消息隊列中的Message持有mHandler實例的引用,mHandler又持有Activity的引用,所以導致該Activity的內存資源無法及時回收,引發內存泄漏。
  • 解決Handler內存泄露主要2點
  • 有延時消息,要在Activity銷毀的時候移除Messages監聽
  • 匿名內部類導致的泄露改為匿名靜態內部類,并且對上下文或者Activity使用弱引用。

2.4 線程造成內存泄漏

  • 早時期的時候處理耗時操作多數都是采用Thread+Handler的方式,后來逐步被AsyncTask取代,直到現在采用RxJava的方式來處理異步。
  • 造成內存泄漏原因分析
  • 在處理一個比較耗時的操作時,可能還沒處理結束MainActivity就執行了退出操作,但是此時AsyncTask依然持有對MainActivity的引用就會導致MainActivity無法釋放回收引發內存泄漏。
  • 解決辦法
  • 在使用AsyncTask時,在Activity銷毀時候也應該取消相應的任務AsyncTask.cancel()方法,避免任務在后臺執行浪費資源,進而避免內存泄漏的發生。

2.5 非靜態內部類

  • 非靜態內部類創建靜態實例造成的內存泄漏。有的時候我們可能會在啟動頻繁的Activity中,為了避免重復創建相同的數據資源,可能會出現這種寫法。
  • 問題代碼
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mResource == null){
mResource = new TestResource();
}
}
class TestResource {
//里面代碼引用上下文,Activity.this會導致內存泄漏
}
  • 解決辦法
  • 將該內部類設為靜態內部類或將該內部類抽取出來封裝成一個單例,如果需要使用Context,請按照上面推薦的使用Application 的 Context。
  • 分析問題
  • 這樣就在Activity內部創建了一個非靜態內部類的單例,每次啟動Activity時都會使用該單例的數據,這樣雖然避免了資源的重復創建,不過這種寫法卻會造成內存泄漏,因為非靜態內部類默認會持有外部類的引用,而該非靜態內部類又創建了一個靜態的實例,該實例的生命周期和應用的一樣長,這就導致了該靜態實例一直會持有該Activity的引用,導致Activity的內存資源不能正常回收。

2.6 未移除監聽

  • 問題代碼
 //add監聽,放到集合里面
tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean b) {
//監聽view的加載,view加載出來的時候,計算他的寬高等。
}
});
  • 解決辦法
 //計算完后,一定要移除這個監聽
tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
  • 注意事項:
tv.setOnClickListener();//監聽執行完回收對象,不用考慮內存泄漏
tv.getViewTreeObserver().addOnWindowFocusChangeListene,add監聽,放到集合里面,需要考慮內存泄漏

2.7 持有activity引用

2.8 資源未關閉

  • 在使用IO、File流或者Sqlite、Cursor等資源時要及時關閉。這些資源在進行讀寫操作時通常都使用了緩沖,如果及時不關閉,這些緩沖對象就會一直被占用而得不到釋放,以致發生內存泄露。因此我們在不需要使用它們的時候就及時關閉,以便緩沖能及時得到釋放,從而避免內存泄露。
  • BroadcastReceiver,ContentObserver,FileObserver,Cursor,Callback等在 Activity onDestroy 或者某類生命周期結束之后一定要 unregister 或者 close 掉,否則這個 Activity 類會被 system 強引用,不會被內存回收。值得注意的是,關閉的語句必須在finally中進行關閉,否則有可能因為異常未關閉資源,致使activity泄漏。

2.9 其他原因

  • 靜態集合使用不當導致的內存泄漏
  • 有時候我們需要把一些對象加入到集合容器(例如ArrayList)中,當不再需要當中某些對象時,如果不把該對象的引用從集合中清理掉,也會使得GC無法回收該對象。如果集合是static類型的話,那內存泄漏情況就會更為嚴重。因此,當不再需要某對象時,需要主動將之從集合中移除。
  • 不需要用的監聽未移除會發生內存泄露
  • 問題代碼
//add監聽,放到集合里面
tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean b) {
//監聽view的加載,view加載出來的時候,計算他的寬高等。
}
});
  • 解決辦法
//計算完后,一定要移除這個監聽
tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
  • 注意事項:
tv.setOnClickListener();//監聽執行完回收對象,不用考慮內存泄漏
tv.getViewTreeObserver().addOnWindowFocusChangeListene,add監聽,放到集合里面,需要考慮內存泄漏

3.布局優化

3.1 include優化

  • 重用布局文件
  • 標簽可以允許在一個布局當中引入另一個布局,那么比如說我們程序的所有界面都有一個公共的部分,這個時候最好的做法就是將這個公共的部分提取到一個獨立的布局中,然后每個界面的布局文件當中來引用這個公共的布局。
  • 如果我們要在標簽中覆寫layout屬性,必須要將layout_width和layout_height這兩個屬性也進行覆寫,否則覆寫效果將不會生效。
  • 標簽是作為標簽的一種輔助擴展來使用的,它的主要作用是為了防止在引用布局文件時引用文件時產生多余的布局嵌套。布局嵌套越多,解析起來就越耗時,性能就越差。因此編寫布局文件時應該讓嵌套的層數越少越好。
  • 舉例:比如在LinearLayout里邊使用一個布局。里邊又有一個LinearLayout,那么其實就存在了多余的布局嵌套,使用merge可以解決這個問題。

3.2 ViewStub優化

  • 僅在需要時才加載布局[ViewStub]
  • 某個布局當中的元素不是一起顯示出來的,普通情況下只顯示部分常用的元素,而那些不常用的元素只有在用戶進行特定操作時才會顯示出來。
  • 舉例:填信息時不是需要全部填的,有一個添加更多字段的選項,當用戶需要添加其他信息的時候,才將另外的元素顯示到界面上。用VISIBLE性能表現一般,可以用ViewStub。
  • ViewStub也是View的一種,但是沒有大小,沒有繪制功能,也不參與布局,資源消耗非常低,可以認為完全不影響性能。
  • ViewStub所加載的布局是不可以使用標簽的,因此這有可能導致加載出來出來的布局存在著多余的嵌套結構。
  • 自定義全局的狀態管理器【充分使用ViewStub】
  • 針對多狀態,有數據,空數據,加載失敗,加載異常,網絡異常等。針對空數據,加載失敗,異常使用viewStub布局,一鍵設置自定義布局,也是優化的一種。
  • 項目地址:

3.3 merge優化

  • 視圖層級
  • 這個標簽在UI的結構優化中起著非常重要的作用,它可以刪減多余的層級,優化UI。但是就有一點不好,無法預覽布局效果!

3.4 其他建議

  • 減少太多重疊的背景(overdraw)
  • 這個問題其實最容易解決,建議就是檢查你在布局和代碼中設置的背景,有些背景是隱藏在底下的,它永遠不可能顯示出來,這種沒必要的背景一定要移除,因為它很可能會嚴重影響到app的性能。如果采用的是selector的背景,將normal狀態的color設置為”@android:color/transparent”,也同樣可以解決問題。
  • 避免復雜的Layout層級
  • 這里的建議比較多一些,首先推薦使用Android提供的布局工具Hierarchy Viewer來檢查和優化布局。第一個建議是:如果嵌套的線性布局加深了布局層次,可以使用相對布局來取代。第二個建議是:用標簽來合并布局。第三個建議是:用標簽來重用布局,抽取通用的布局可以讓布局的邏輯更清晰明了。記住,這些建議的最終目的都是使得你的Layout在Hierarchy Viewer里變得寬而淺,而不是窄而深。
  • 總結:可以考慮多使用merge和include,ViewStub。盡量使布局淺平,根布局盡量少使用RelactivityLayout,因為RelactivityLayout每次需要測量2次。

4.代碼優化

  • 都是一些微優化,在性能方面看不出有什么顯著的提升的。使用合適的算法和數據結構是優化程序性能的最主要手段。

4.1 建議使用lint檢查去除無效代碼

  • lint去除無效資源和代碼
  • 如何檢測哪些圖片未被使用
  • 點擊菜單欄 Analyze -> Run Inspection by Name -> unused resources -> Moudule ‘app’ -> OK,這樣會搜出來哪些未被使用到未使用到xml和圖片,如下:
  • 如何檢測哪些無效代碼
  • 使用Android Studio的Lint,步驟:點擊菜單欄 Analyze -> Run Inspection by Name -> unused declaration -> Moudule ‘app’ -> OK

4.2 代碼規范優化

  • 避免創建不必要的對象 不必要的對象應該避免創建:
  • 如果有需要拼接的字符串,那么可以優先考慮使用StringBuffer或者StringBuilder來進行拼接,而不是加號連接符,因為使用加號連接符會創建多余的對象,拼接的字符串越長,加號連接符的性能越低。
  • 當一個方法的返回值是String的時候,通常需要去判斷一下這個String的作用是什么,如果明確知道調用方會將返回的String再進行拼接操作的話,可以考慮返回一個StringBuffer對象來代替,因為這樣可以將一個對象的引用進行返回,而返回String的話就是創建了一個短生命周期的臨時對象。
  • 盡可能地少創建臨時對象,越少的對象意味著越少的GC操作。
  • nDraw方法里面不要執行對象的創建
  • 靜態優于抽象
  • 如果你并不需要訪問一個對系那個中的某些字段,只是想調用它的某些方法來去完成一項通用的功能,那么可以將這個方法設置成靜態方法,調用速度提升15%-20%,同時也不用為了調用這個方法去專門創建對象了,也不用擔心調用這個方法后是否會改變對象的狀態(靜態方法無法訪問非靜態字段)。
  • 對常量使用static final修飾符
  • static int intVal = 42; static String strVal = "Hello, world!";
  • 編譯器會為上面的代碼生成一個初始方法,稱為方法,該方法會在定義類第一次被使用的時候調用。這個方法會將42的值賦值到intVal當中,從字符串常量表中提取一個引用賦值到strVal上。當賦值完成后,我們就可以通過字段搜尋的方式去訪問具體的值了。
  • final進行優化:
  • static final int intVal = 42; static final String strVal = "Hello, world!";
  • 這樣,定義類就不需要方法了,因為所有的常量都會在dex文件的初始化器當中進行初始化。當我們調用intVal時可以直接指向42的值,而調用strVal會用一種相對輕量級的字符串常量方式,而不是字段搜尋的方式。
  • 這種優化方式只對基本數據類型以及String類型的常量有效,對于其他數據類型的常量是無效的。
  • 在沒有特殊原因的情況下,盡量使用基本數據類型來代替封裝數據類型,int比Integer要更加有效,其它數據類型也是一樣。
  • 基本數據類型的數組也要優于對象數據類型的數組。另外兩個平行的數組要比一個封裝好的對象數組更加高效,舉個例子,Foo[]和Bar[]這樣的數組,使用起來要比Custom(Foo,Bar)[]這樣的一個數組高效的多。

4.3 View異常優化

  • view自定義控件異常銷毀保存狀態
  • 經常容易被人忽略,但是為了追求高質量代碼,這個也有必要加上。舉個例子!
@Override
protected Parcelable onSaveInstanceState() {
//異常情況保存重要信息。
//return super.onSaveInstanceState();
final Bundle bundle = new Bundle();
bundle.putInt("selectedPosition",selectedPosition);
bundle.putInt("flingSpeed",mFlingSpeed);
bundle.putInt("orientation",orientation);
return bundle;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
final Bundle bundle = (Bundle) state;
selectedPosition = bundle.getInt("selectedPosition",selectedPosition);
mFlingSpeed = bundle.getInt("flingSpeed",mFlingSpeed);
orientation = bundle.getInt("orientation",orientation);
return;
}
super.onRestoreInstanceState(state);
}

4.4 去除淡黃色警告優化

  • 淡黃色警告雖然不會造成崩潰,但是作為程序員還是要盡量去除淡黃色警告,規范代碼

4.5 合理使用集合

  • 使用優化過的數據集合
  • Android提供了一系列優化過后的數據集合工具類,如SparseArray、SparseBooleanArray、LongSparseArray,使用這些API可以讓我們的程序更加高效。HashMap工具類會相對比較低效,因為它需要為每一個鍵值對都提供一個對象入口,而SparseArray就避免掉了基本數據類型轉換成對象數據類型的時間。

4.6 Activity不可見優化

  • 當Activity界面不可見時釋放內存
  • 當用戶打開了另外一個程序,我們的程序界面已經不可見的時候,我們應當將所有和界面相關的資源進行釋放。重寫Activity的onTrimMemory()方法,然后在這個方法中監聽TRIM_MEMORY_UI_HIDDEN這個級別,一旦觸發說明用戶離開了程序,此時就可以進行資源釋放操作了。
  • 當時看到這個覺得很新奇的,但是具體還是沒有用到,要是那個大神有具體操作方案,可以分享一下。

4.7 節制的使用Service

  • 節制的使用Service
  • 如果應用程序需要使用Service來執行后臺任務的話,只有當任務正在執行的時候才應該讓Service運行起來。當啟動一個Service時,系統會傾向于將這個Service所依賴的進程進行保留,系統可以在LRUcache當中緩存的進程數量也會減少,導致切換程序的時候耗費更多性能。我們可以使用IntentService,當后臺任務執行結束后會自動停止,避免了Service的內存泄漏。

5.網絡優化

5.1 圖片分類

  • 圖片網絡優化
  • 比如我之前看到豆瓣接口,提供一種加載圖片方式特別好。接口返回圖片的數據有三種,一種是高清大圖,一種是正常圖片,一種是縮略小圖。當用戶處于wifi下給控件設置高清大圖,當4g或者3g模式下加載正常圖片,當弱網條件下加載縮略圖【也稱與加載圖】。
  • 簡單來說根據用戶的當前的網絡質量來判斷下載什么質量的圖片(電商用的比較多)。豆瓣開源接口可以參考一下!

5.2 獲取網絡數據優化

  • 移動端獲取網絡數據優化的幾個點
  • 連接復用:節省連接建立時間,如開啟 keep-alive。
  • 對于Android來說默認情況下HttpURLConnection和HttpClient都開啟了keep-alive。只是2.2之前HttpURLConnection存在影響連接池的Bug,具體可見:Android HttpURLConnection及HttpClient選擇
  • 請求合并:即將多個請求合并為一個進行請求,比較常見的就是網頁中的CSS Image Sprites。如果某個頁面內請求過多,也可以考慮做一定的請求合并。
  • 減少請求數據的大小:對于post請求,body可以做gzip壓縮的,header也可以做數據壓縮(不過只支持http
  • 返回數據的body也可以做gzip壓縮,body數據體積可以縮小到原來的30%左右。(也可以考慮壓縮返回的json數據的key數據的體積,尤其是針對返回數據格式變化不大的情況,支付寶聊天返回的數據用到了)

5.3 網絡請求異常攔截優化

  • 在獲取數據的流程中,訪問接口和解析數據時都有可能會出錯,我們可以通過攔截器在這兩層攔截錯誤。
  • 1.在訪問接口時,我們不用設置攔截器,因為一旦出現錯誤,Retrofit會自動拋出異常。比如,常見請求異常404,500,503等等。
  • 2.在解析數據時,我們設置一個攔截器,判斷Result里面的code是否為成功,如果不成功,則要根據與服務器約定好的錯誤碼來拋出對應的異常。比如,token失效,禁用同賬號登陸多臺設備,缺少參數,參數傳遞異常等等。
  • 3.除此以外,為了我們要盡量避免在View層對錯誤進行判斷,處理,我們必須還要設置一個攔截器,攔截onError事件,然后使用ExceptionUtils,讓其根據錯誤類型來分別處理。
  • 具體可以直接看lib中的ExceptionUtils類,那么如何調用呢?入侵性極低,不用改變之前的代碼!
@Override
public void onError(Throwable e) {
//直接調用即可
ExceptionUtils.handleException(e);
}

6.線程優化

6.1 使用線程池

  • 將全局線程用線程池管理
  • 直接創建Thread實現runnable方法的弊端
  • 大量的線程的創建和銷毀很容易導致GC頻繁的執行,從而發生內存抖動現象,而發生了內存抖動,對于移動端來說,最大的影響就是造成界面卡頓
  • 線程的創建和銷毀都需要時間,當有大量的線程創建和銷毀時,那么這些時間的消耗則比較明顯,將導致性能上的缺失
  • 為什么要用線程池
  • 重用線程池中的線程,避免頻繁地創建和銷毀線程帶來的性能消耗;有效控制線程的最大并發數量,防止線程過大導致搶占資源造成系統阻塞;可以對線程進行一定地管理。
  • 使用線程池管理的經典例子
  • RxJava,RxAndroid,底層對線程池的封裝管理特別值得參考
  • 關于線程池,線程,多線程的具體內容
  • 參考:輕量級線程池封裝庫,支持異步回調,可以檢測線程執行的狀態
  • 該項目中哪里用到頻繁new Thread
  • 保存圖片[注意,尤其是大圖和多圖場景下注意耗時太久];某些頁面從數據庫查詢數據;設置中心清除圖片,視頻,下載文件,日志,系統緩存等緩存內容
  • 使用線程池管理庫好處,比如保存圖片,耗時操作放到子線程中,處理過程中,可以檢測到執行開始,異常,成功,失敗等多種狀態。

7.圖片優化

7.1 bitmap優化

  • 加載圖片所占的內存大小計算方式
  • 加載網絡圖片:bitmap內存大小 = 圖片長度 x 圖片寬度 x 單位像素占用的字節數【看到網上很多都是這樣寫的,但是不全面】
  • 加載本地圖片:bitmap內存大小 = width * height * nTargetDensity/inDensity 一個像素所占的內存。注意不要忽略了一個影響項:Density
  • 第一種加載圖片優化處理:壓縮圖片
  • 質量壓縮方法:在保持像素的前提下改變圖片的位深及透明度等,來達到壓縮圖片的目的,這樣適合去傳遞二進制的圖片數據,比如分享圖片,要傳入二進制數據過去,限制500kb之內。
  • 采樣率壓縮方法:設置inSampleSize的值(int類型)后,假如設為n,則寬和高都為原來的1/n,寬高都減少,內存降低。
  • 縮放法壓縮:Android中使用Matrix對圖像進行縮放、旋轉、平移、斜切等變換的。功能十分強大!
  • 第二種加載圖片優化:不壓縮加載高清圖片如何做?
  • 使用BitmapRegionDecoder,主要用于顯示圖片的某一塊矩形區域,如果你需要顯示某個圖片的指定區域,那么這個類非常合適。

7.2 glide加載優化

  • 在畫廊中加載大圖
  • 假如你滑動特別快,glide加載優化就顯得非常重要呢,具體優化方法如下所示
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
LoggerUtils.e("initRecyclerView"+ "恢復Glide加載圖片");
Glide.with(ImageBrowseActivity.this).resumeRequests();
}else {
LoggerUtils.e("initRecyclerView"+"禁止Glide加載圖片");
Glide.with(ImageBrowseActivity.this).pauseRequests();
}
}
});

8.加載優化

8.1 懶加載優化

  • 該優化在新聞類app中十分常見
  • ViewPager+Fragment的搭配在日常開發中也比較常見,可用于切換展示不同類別的頁面。
  • 懶加載,其實也就是延遲加載,就是等到該頁面的UI展示給用戶時,再加載該頁面的數據(從網絡、數據庫等),而不是依靠ViewPager預加載機制提前加載兩三個,甚至更多頁面的數據。這樣可以提高所屬Activity的初始化速度,也可以為用戶節省流量.而這種懶加載的方式也已經/正在被諸多APP所采用。
  • 具體看這篇文章
  • www.jianshu.com/p/cf1f4104d…

8.2 啟動頁優化

  • 啟動時間分析
  • 系統創建進程的時間和應用進程啟動的時間,前者是由系統自行完成的,一般都會很快,我們也干預不了,我覺得能做的就是去優化應用進程啟動,具體說來就是從發Application的onCreate()執行開始到MainActivity的onCreate()執行結束這一段時間。
  • 啟動時間優化
  • Application的onCreate()方法
  • MainActivity的onCreate()方法
  • 優化的手段也無非三種,如下所示:
  • 延遲初始化
  • 后臺任務
  • 啟動界面預加載
  • 啟動頁白屏優化
  • 為什么存在這個問題?
  • 當系統啟動一個APP時,zygote進程會首先創建一個新的進程去運行這個APP,但是進程的創建是需要時間的,在創建完成之前,界面是呈現假死狀態,于是系統根據你的manifest文件設置的主題顏色的不同來展示一個白屏或者黑屏。而這個黑(白)屏正式的稱呼應該是Preview Window,即預覽窗口。
  • 實際上就是是activity默認的主題中的android:windowBackground為白色或者黑色導致的。
  • 總結來說啟動順序就是:app啟動——Preview Window(也稱為預覽窗口)——啟動頁
  • 解決辦法
  • 常見有三種,這里解決辦法是給當前啟動頁添加一個有背景的style樣式,然后SplashActivity引用當前theme主題,注意在該頁面將window的背景圖設置為空!
  • 更多關于啟動頁為什么白屏閃屏,以及不同解決辦法,可以看我這篇博客:App啟動頁面優化
  • 啟動時間優化
  • IntentService子線程分擔部分初始化工作
  • 現在application初始化內容有:阿里云推送初始化,騰訊bugly初始化,im初始化,神策初始化,內存泄漏工具初始化,頭條適配方案初始化,阿里云熱修復……等等。將部分邏輯放到IntentService中處理,可以縮短很多時間。
  • 開啟IntentSerVice線程,將部分邏輯和耗時的初始化操作放到這里處理,可以減少application初始化時間
  • 關于IntentService使用和源碼分析,性能分析等可以參考博客:IntentService源碼分析

9.其他優化

9.1 靜態變量優化

  • 盡量不使用靜態變量保存核心數據。這是為什么呢? - 這是因為android的進程并不是安全的,包括application對象以及靜態變量在內的進程級別變量并不會一直呆著內存里面,因為它很有會被kill掉。 - 當被kill掉之后,實際上app不會重新開始啟動。Android系統會創建一個新的Application對象,然后啟動上次用戶離開時的activity以造成這個app從來沒有被kill掉的假象。而這時候靜態變量等數據由于進程已經被殺死而被初始化,所以就有了不推薦在靜態變量(包括Application中保存全局數據靜態數據)的觀點。

9.2 注解替代枚舉

  • 使用注解限定傳入類型
  • 比如,尤其是寫第三方開源庫,對于有些暴露給開發者的方法,需要限定傳入類型是有必要的。舉個例子:

  • 剛開始的代碼
/**
* 設置播放器類型,必須設置
* 注意:感謝某人建議,這里限定了傳入值類型
* 輸入值:111 或者 222
* @param playerType IjkPlayer or MediaPlayer.
*/
public void setPlayerType(int playerType) {
mPlayerType = playerType;
}
優化后的代碼,有效避免第一種方式開發者傳入值錯誤
/**
* 設置播放器類型,必須設置
* 注意:感謝某人建議,這里限定了傳入值類型
* 輸入值:ConstantKeys.IjkPlayerType.TYPE_IJK 或者 ConstantKeys.IjkPlayerType.TYPE_NATIVE
* @param playerType IjkPlayer or MediaPlayer.
*/
public void setPlayerType(@ConstantKeys.PlayerType int playerType) {
mPlayerType = playerType;
}
/**
* 通過注解限定類型
* TYPE_IJK IjkPlayer,基于IjkPlayer封裝播放器
* TYPE_NATIVE MediaPlayer,基于原生自帶的播放器控件
*/
@Retention(RetentionPolicy.SOURCE)
public @interface IjkPlayerType {
int TYPE_IJK = 111;
int TYPE_NATIVE = 222;
}

@IntDef({IjkPlayerType.TYPE_IJK,IjkPlayerType.TYPE_NATIVE})

public @interface PlayerType{}

  • 使用注解替代枚舉,代碼如下所示
@Retention(RetentionPolicy.SOURCE)
public @interface ViewStateType {
int HAVE_DATA = 1;
int EMPTY_DATA = 2;
int ERROR_DATA = 3;
int ERROR_NETWORK = 4;
}

9.3 多渠道打包優化

  • 還在手動打包嗎?嘗試一下python自動化打包吧……
  • 瓦力多渠道打包的Python腳本測試工具,通過該自動化腳本,自需要run一下或者命令行運行腳本即可實現美團瓦力多渠道打包,打包速度很快。配置信息十分簡單,代碼中已經注釋十分詳細。可以自定義輸出文件路徑,可以修改多渠道配置信息,簡單實用。 項目地址:github.com/yangchong21…

9.4 TrimMemory和LowMemory優化

  • 可以優化什么?
  • 在 onTrimMemory() 回調中,應該在一些狀態下清理掉不重要的內存資源。對于這些緩存,只要是讀進內存內的都算,例如最常見的圖片緩存、文件緩存等。拿圖片緩存來說,市場上,常規的圖片加載庫,一般而言都是三級緩存,所以在內存吃緊的時候,我們就應該優先清理掉這部分圖片緩存,畢竟圖片是吃內存大戶,而且再次回來的時候,雖然內存中的資源被回收掉了,依然可以從磁盤或者網絡上恢復它。
  • 大概的思路如下所示
  • 在lowMemory的時候,調用Glide.cleanMemory()清理掉所有的內存緩存。
  • 在App被置換到后臺的時候,調用Glide.cleanMemory()清理掉所有的內存緩存。
  • 在其它情況的onTrimMemory()回調中,直接調用Glide.trimMemory()方法來交給Glide處理內存情況。

9.5 輪詢操作優化

  • 什么叫輪訓請求?
  • 簡單理解就是App端每隔一定的時間重復請求的操作就叫做輪訓請求,比如:App端每隔一段時間上報一次定位信息,App端每隔一段時間拉去一次用戶狀態等,這些應該都是輪訓請求。比如,電商類項目,某個抽獎活動頁面,隔1分鐘調用一次接口,彈出一些獲獎人信息,你應該某個階段看過這類輪詢操作!
  • 具體優化操作
  • 長連接并不是穩定的可靠的,而執行輪訓操作的時候一般都是要穩定的網絡請求,而且輪訓操作一般都是有生命周期的,即在一定的生命周期內執行輪訓操作,而長連接一般都是整個進程生命周期的,所以從這方面講也不太適合。
  • 建議在service中做輪詢操作,輪詢請求接口,具體做法和注意要點,可以直接看該項目代碼。看app包下的LoopRequestService類即可。
  • 大概思路:當用戶打開這個頁面的時候初始化TimerTask對象,每個一分鐘請求一次服務器拉取訂單信息并更新UI,當用戶離開頁面的時候清除TimerTask對象,即取消輪訓請求操作。

9.6 去除重復依賴庫優化

  • 我相信你看到了這里會有疑問,網上有許多博客作了這方面說明。但是我在這里想說,如何查找自己項目的所有依賴關系樹
  • 注意要點:其中app就是項目mudule名字。 正常情況下就是app!

gradlew app:dependencies

  • 關于依賴關系樹的結構圖如下所示,此處省略很多代碼
| | | | | | \--- android.arch.core:common:1.1.1 (*)
| | | | \--- com.android.support:support-annotations:26.1.0 -> 28.0.0
| +--- com.journeyapps:zxing-android-embedded:3.6.0
| | +--- com.google.zxing:core:3.3.2
| | \--- com.android.support:support-v4:25.3.1
| | +--- com.android.support:support-compat:25.3.1 -> 28.0.0 (*)
| | +--- com.android.support:support-media-compat:25.3.1
| | | +--- com.android.support:support-annotations:25.3.1 -> 28.0.0
| | | \--- com.android.support:support-compat:25.3.1 -> 28.0.0 (*)
| | +--- com.android.support:support-core-utils:25.3.1 -> 28.0.0 (*)
| | +--- com.android.support:support-core-ui:25.3.1 -> 28.0.0 (*)
| | \--- com.android.support:support-fragment:25.3.1 -> 28.0.0 (*)
\--- com.android.support:multidex:1.0.2 -> 1.0.3
  • 然后查看哪些重復jar
  • <figcaption></figcaption>
  • 然后修改gradle配置代碼
api (rootProject.ext.dependencies["zxing"]){
exclude module: 'support-v4'
exclude module: 'appcompat-v7'
}

9.7 四種引用優化

  • 軟引用使用場景
  • 正常是用來處理大圖片這種占用內存大的情況
  • 代碼如下所示
Bitmap bitmap = bitmaps.get(position);
//正常是用來處理圖片這種占用內存大的情況
bitmapSoftReference = new SoftReference<>(bitmap);
if(bitmapSoftReference.get() != null) {
viewHolder.imageView.setImageBitmap(bitmapSoftReference.get());
}
//其實看glide底層源碼可知,也做了相關軟引用的操作
  • 這樣使用軟引用好處
  • 通過軟引用的get()方法,取得bitmap對象實例的強引用,發現對象被未回收。在GC在內存充足的情況下,不會回收軟引用對象。此時view的背景顯示
  • 實際情況中,我們會獲取很多圖片.然后可能給很多個view展示, 這種情況下很容易內存吃緊導致oom,內存吃緊,系統開始會GC。這次GC后,bitmapSoftReference.get()不再返回bitmap對象,而是返回null,這時屏幕上背景圖不顯示,說明在系統內存緊張的情況下,軟引用被回收。
  • 使用軟引用以后,在OutOfMemory異常發生之前,這些緩存的圖片資源的內存空間可以被釋放掉的,從而避免內存達到上限,避免Crash發生。
  • 弱引用使用場景
  • 弱引用–>隨時可能會被垃圾回收器回收,不一定要等到虛擬機內存不足時才強制回收。
  • 對于使用頻次少的對象,希望盡快回收,使用弱引用可以保證內存被虛擬機回收。比如handler,如果希望使用完后盡快回收,看下面代碼
private MyHandler handler = new MyHandler(this);
private static class MyHandler extends Handler{
WeakReference<FirstActivity> weakReference;
MyHandler(FirstActivity activity) {
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
}
}
}
  • 到底什么時候使用軟引用,什么時候使用弱引用呢?
  • 個人認為,如果只是想避免OutOfMemory異常的發生,則可以使用軟引用。如果對于應用的性能更在意,想盡快回收一些占用內存比較大的對象,則可以使用弱引用。
  • 還有就是可以根據對象是否經常使用來判斷。如果該對象可能會經常使用的,就盡量用軟引用。如果該對象不被使用的可能性更大些,就可以用弱引用。

9.8 加載loading優化

  • 一般實際開發中會至少有兩種loading
  • 第一種是從A頁面進入B頁面時的加載loading,這個時候特點是顯示loading的時候,頁面是純白色的,加載完數據后才顯示內容頁面。
  • 第二種是在某個頁面操作某種邏輯,比如某些耗時操作,這個時候是局部loading[一般用個幀動畫或者補間動畫],由于使用頻繁,因為建議在銷毀彈窗時,添加銷毀動畫的操作。
  • 自定義loading加載

9.9 對象池Pools優化

  • 對象池Pools優化頻繁創建和銷毀對象
  • 使用對象池,可以防止頻繁創建和銷毀對象而出現內存抖動
  • 在某些時候,我們需要頻繁使用一些臨時對象,如果每次使用的時候都申請新的資源,很有可能會引發頻繁的 gc 而影響應用的流暢性。這個時候如果對象有明確的生命周期,那么就可以通過定義一個對象池來高效的完成復用對象。

10.RecyclerView優化

10.1 頁面為何卡頓

  • RecyclerView滑動卡頓的原因有哪些?
  • 第一種:嵌套布局滑動沖突
  • 導致嵌套滑動難處理的關鍵原因在于當子控件消費了事件, 那么父控件就不會再有機會處理這個事件了, 所以一旦內部的滑動控件消費了滑動操作, 外部的滑動控件就再也沒機會響應這個滑動操作了
  • 第二種:嵌套布局層次太深,比如六七層等
  • 測量,繪制布局可能會導致滑動卡頓
  • 第三種:比如用RecyclerView實現畫廊,加載比較大的圖片,如果快速滑動,則可能會出現卡頓,主要是加載圖片需要時間
  • 第四種:在onCreateViewHolder或者在onBindViewHolder中做了耗時的操作導致卡頓。按stackoverflow上面比較通俗的解釋:RecyclerView.Adapter里面的onCreateViewHolder()方法和onBindViewHolder()方法對時間都非常敏感。類似I/O讀寫,Bitmap解碼一類的耗時操作,最好不要在它們里面進行。
  • 關于RecyclerView封裝庫

10.2 具體優化方案

  • 03.SparseArray替代HashMap
  • 04.瀑布流圖片錯亂問題解決
  • 05.item點擊事件放在哪里優化
  • 06.ViewHolder優化
  • 07.連續上拉加載更多優化
  • 08.拖拽排序與滑動刪除優化
  • 09.暫停或停止加載數據優化
  • 11.異常情況下保存狀態
  • 12.多線程下插入數據優化
  • 14.recyclerView優化處理
  • 15.adapter優化

我來說兩句
您需要登錄后才可以評論 登錄 | 立即注冊
facelist
所有評論(1)
mututd 2019-11-6 08:50
學習了
回復
領先的中文移動開發者社區
18620764416
7*24全天服務
意見反饋:[email protected]

掃一掃關注我們

Powered by Discuz! X3.2© 2001-2019 Comsenz Inc.( 粵ICP備15117877號 )

安卓版28杠游戏