Android優化總結

極力推薦文章:歡迎收藏
Android 乾貨分享python


文章轉載網絡
原文地址以下:https://juejin.im/post/5d072dbc51882540b7104709android

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.內存泄漏優化

  • 內存檢測第一種:代碼方式獲取內存git

    /**
     * 內存使用檢測:能夠調用系統的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();
    • 一、若是此時傳入的是 Application 的 Context,由於 Application 的生命週期就是整個應用的生命週期,因此這將沒有任何問題。
    • 二、若是此時傳入的是 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中,爲了不重複建立相同的數據資源,可能會出現這種寫法。
  • 問題代碼github

    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加載出來的時候,計算他的寬高等。
          }
      });
    • 解決辦法json

      //計算完後,必定要移除這個監聽
      tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
    • 注意事項:api

      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所採用。
  • 具體看這篇文章

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
    • [圖片上傳失敗...(image-b9d450-1563928824510)]

  • 而後修改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 而影響應用的流暢性。這個時候若是對象有明確的生命週期,那麼就能夠經過定義一個對象池來高效的完成複用對象。
    • 具體參考案例,能夠看該項目:github.com/yangchong21…

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優化
  • 具體看這篇博客:recyclerView優化

至此,本篇已結束,若有不對的地方,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!

微信關注公衆號: 程序員Android,領福利

相關文章
相關標籤/搜索