Android優(yōu)化總結(jié)

目錄介紹

  • 1.OOM和崩潰優(yōu)化
    • 1.1 OOM優(yōu)化
    • 1.2 ANR優(yōu)化
    • 1.3 Crash優(yōu)化
  • 2.內(nèi)存泄漏優(yōu)化
    • 2.0 動(dòng)畫資源未釋放
    • 2.1 錯(cuò)誤使用單利
    • 2.2 錯(cuò)誤使用靜態(tài)變量
    • 2.3 handler內(nèi)存泄漏
    • 2.4 線程造成內(nèi)存泄漏
    • 2.5 非靜態(tài)內(nèi)部類
    • 2.6 未移除監(jiān)聽
    • 2.7 持有activity引用
    • 2.8 資源未關(guān)閉
    • 2.9 其他原因
  • 3.布局優(yōu)化
    • 3.1 include優(yōu)化
    • 3.2 ViewStub優(yōu)化
    • 3.3 merge優(yōu)化
    • 3.4 其他建議
  • 4.代碼優(yōu)化
    • 4.1 lint代碼檢測(cè)
    • 4.2 代碼規(guī)范優(yōu)化
    • 4.3 View異常優(yōu)化
    • 4.4 去除淡黃色警告優(yōu)化
    • 4.5 合理使用集合
    • 4.6 Activity不可見優(yōu)化
    • 4.7 節(jié)制的使用Service
  • 5.網(wǎng)絡(luò)優(yōu)化
    • 5.1 圖片分類
    • 5.2 獲取網(wǎng)絡(luò)數(shù)據(jù)優(yōu)化
    • 5.3 網(wǎng)絡(luò)請(qǐng)求異常攔截優(yōu)化
  • 6.線程優(yōu)化
    • 6.1 使用線程池
  • 7.圖片優(yōu)化
    • 7.1 bitmap優(yōu)化
    • 7.2 glide加載優(yōu)化
  • 8.加載優(yōu)化
    • 8.1 懶加載優(yōu)化
    • 8.2 啟動(dòng)頁(yè)優(yōu)化
  • 9.其他優(yōu)化
    • 9.1 靜態(tài)變量?jī)?yōu)化
    • 9.2 注解替代枚舉
    • 9.3 多渠道打包優(yōu)化
    • 9.4 TrimMemory和LowMemory優(yōu)化
    • 9.5 輪詢操作優(yōu)化
    • 9.6 去除重復(fù)依賴庫(kù)優(yōu)化
    • 9.7 四種引用優(yōu)化
    • 9.8 加載loading優(yōu)化
    • 9.9 對(duì)象池Pools優(yōu)化
  • 10.RecyclerView優(yōu)化
    • 10.1 頁(yè)面為何卡頓
    • 10.2 具體優(yōu)化方案

好消息

  • 博客筆記大匯總【16年3月到至今】逮京,包括Java基礎(chǔ)及深入知識(shí)點(diǎn)草描,Android技術(shù)博客妻导,Python學(xué)習(xí)筆記等等术浪,還包括平時(shí)開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長(zhǎng)期更新維護(hù)并且修正鲤孵,持續(xù)完善……開源的文件是markdown格式的!同時(shí)也開源了生活博客,從12年起廊散,積累共計(jì)N篇[近100萬(wàn)字,陸續(xù)搬到網(wǎng)上]缭受,轉(zhuǎn)載請(qǐng)注明出處,謝謝胰丁!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好妻顶,可以star一下幔嗦,謝謝!當(dāng)然也歡迎提出建議汇恤,萬(wàn)事起于忽微,量變引起質(zhì)變!

1.OOM和崩潰優(yōu)化

1.2 ANR優(yōu)化

  • ANR的產(chǎn)生需要滿足三個(gè)條件
    • 主線程:只有應(yīng)用程序進(jìn)程的主線程響應(yīng)超時(shí)才會(huì)產(chǎn)生ANR匠璧;
    • 超時(shí)時(shí)間:產(chǎn)生ANR的上下文不同媳维,超時(shí)時(shí)間也會(huì)不同执虹,但只要在這個(gè)時(shí)間上限內(nèi)沒有響應(yīng)就會(huì)ANR当叭;
    • 輸入事件/特定操作:輸入事件是指按鍵磺芭、觸屏等設(shè)備輸入事件,特定操作是指BroadcastReceiver和Service的生命周期中的各個(gè)函數(shù),產(chǎn)生ANR的上下文不同间螟,導(dǎo)致ANR的原因也會(huì)不同;
  • ANR優(yōu)化具體措施
    • 將所有耗時(shí)操作,比如訪問網(wǎng)絡(luò)见坑,Socket通信,查詢大量SQL 語(yǔ)句,復(fù)雜邏輯計(jì)算等都放在子線程中去孙蒙,然
      后通過(guò)handler.sendMessage、runonUIThread坦胶、AsyncTask 等方式更新UI。無(wú)論如何都要確保用戶界面作的流暢
      度凑队。如果耗時(shí)操作需要讓用戶等待,那么可以在界面上顯示度條叫惊。
    • 使用AsyncTask處理耗時(shí)IO操作缓待。在一些同步的操作主線程有可能被鎖步悠,需要等待其他線程釋放相應(yīng)鎖才能繼續(xù)執(zhí)行,這樣會(huì)有一定的ANR風(fēng)險(xiǎn)谚咬,對(duì)于這種情況有時(shí)也可以用異步線程來(lái)執(zhí)行相應(yīng)的邏輯郎嫁。另外尚辑,要避免死鎖的發(fā)生月褥。
    • 使用Handler處理工作線程結(jié)果灯荧,而不是使用Thread.wait()或者Thread.sleep()來(lái)阻塞主線程。
    • Activity的onCreate和onResume回調(diào)中盡量避免耗時(shí)的代碼
    • BroadcastReceiver中onReceive代碼也要盡量減少耗時(shí),建議使用IntentService處理擦秽。
    • 各個(gè)組件的生命周期函數(shù)都不應(yīng)該有太耗時(shí)的操作越败,即使對(duì)于后臺(tái)Service或者ContentProvider來(lái)講究飞,應(yīng)用在后臺(tái)運(yùn)行時(shí)候其onCreate()時(shí)候不會(huì)有用戶輸入引起事件無(wú)響應(yīng)ANR,但其執(zhí)行時(shí)間過(guò)長(zhǎng)也會(huì)引起Service的ANR和ContentProvider的ANR

2.內(nèi)存泄漏優(yōu)化

  • 內(nèi)存檢測(cè)第一種:代碼方式獲取內(nèi)存
    /**
     * 內(nèi)存使用檢測(cè):可以調(diào)用系統(tǒng)的getMemoryInfo()來(lái)獲取當(dāng)前內(nèi)存的使用情況
     */
    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) {
                // 運(yùn)行在低內(nèi)存環(huán)境
            }
        }
    }
    
  • 內(nèi)存檢測(cè)第二種:leakcanary工具
    • LeakCanary的原理是監(jiān)控每個(gè)activity,在activity ondestory后奔穿,在后臺(tái)線程檢測(cè)引用男摧,然后過(guò)一段時(shí)間進(jìn)行g(shù)c拇颅,gc后如果引用還在,那么dump出內(nèi)存堆棧,并解析進(jìn)行可視化顯示。

2.0 動(dòng)畫資源未釋放

  • 問題代碼
    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();
        }
    }
    
  • 解決辦法
    • 在屬性動(dòng)畫中有一類無(wú)限循環(huán)動(dòng)畫,如果在Activity中播放這類動(dòng)畫并且在onDestroy中去停止動(dòng)畫打月,那么這個(gè)動(dòng)畫將會(huì)一直播放下去鱼冀,這時(shí)候Activity會(huì)被View所持有千绪,從而導(dǎo)致Activity無(wú)法被釋放充易。解決此類問題則是需要早Activity中onDestroy去去調(diào)用objectAnimator.cancel()來(lái)停止動(dòng)畫。
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mAnimator.cancel();
    }
    

2.1 錯(cuò)誤使用單利

  • 在開發(fā)中單例經(jīng)常需要持有Context對(duì)象荸型,如果持有的Context對(duì)象生命周期與單例生命周期更短時(shí)盹靴,或?qū)е翪ontext無(wú)法被釋放回收,則有可能造成內(nèi)存泄漏瑞妇。比如:在一個(gè)Activity中調(diào)用的悬钳,然后關(guān)閉該Activity則會(huì)出現(xiàn)內(nèi)存泄漏。
  • 解決辦法:
    • 要保證Context和AppLication的生命周期一樣,修改后代碼如下:
    • this.mContext = context.getApplicationContext();
    • 1望门、如果此時(shí)傳入的是 Application 的 Context祷膳,因?yàn)?Application 的生命周期就是整個(gè)應(yīng)用的生命周期,所以這將沒有任何問題。
    • 2咖气、如果此時(shí)傳入的是 Activity 的 Context乳幸,當(dāng)這個(gè) Context 所對(duì)應(yīng)的 Activity 退出時(shí),由于該 Context 的引用被單例對(duì)象所持有兵多,其生命周期等于整個(gè)應(yīng)用程序的生命周期,所以當(dāng)前 Activity 退出時(shí)它的內(nèi)存并不會(huì)被回收慈格,這就造成泄漏了述吸。

2.2 錯(cuò)誤使用靜態(tài)變量

  • 使用靜態(tài)方法是十分方便的。但是創(chuàng)建的對(duì)象,建議不要全局化椰棘,全局化的變量必須加上static。全局化后的變量或者對(duì)象會(huì)導(dǎo)致內(nèi)存泄漏河闰!
  • 原因分析
    • 這里內(nèi)部類AClass隱式的持有外部類Activity的引用,而在Activity的onCreate方法中調(diào)用了。這樣AClass就會(huì)在Activity創(chuàng)建的時(shí)候是有了他的引用谴蔑,而AClass是靜態(tài)類型的不會(huì)被垃圾回收,Activity在執(zhí)行onDestory方法的時(shí)候由于被AClass持有了引用而無(wú)法被回收劫乱,所以這樣Activity就總是被AClass持有而無(wú)法回收造成內(nèi)存泄露搏嗡。

2.3 handler內(nèi)存泄漏

  • 造成內(nèi)存泄漏原因分析
    • 通過(guò)內(nèi)部類的方式創(chuàng)建mHandler對(duì)象,此時(shí)mHandler會(huì)隱式地持有一個(gè)外部類對(duì)象引用這里就是MainActivity叉橱,當(dāng)執(zhí)行postDelayed方法時(shí),該方法會(huì)將你的Handler裝入一個(gè)Message工腋,并把這條Message推到MessageQueue中姨丈,MessageQueue是在一個(gè)Looper線程中不斷輪詢處理消息,那么當(dāng)這個(gè)Activity退出時(shí)消息隊(duì)列中還有未處理的消息或者正在處理消息擅腰,而消息隊(duì)列中的Message持有mHandler實(shí)例的引用蟋恬,mHandler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無(wú)法及時(shí)回收趁冈,引發(fā)內(nèi)存泄漏歼争。
  • 解決Handler內(nèi)存泄露主要2點(diǎn)
    • 有延時(shí)消息,要在Activity銷毀的時(shí)候移除Messages監(jiān)聽
    • 匿名內(nèi)部類導(dǎo)致的泄露改為匿名靜態(tài)內(nèi)部類渗勘,并且對(duì)上下文或者Activity使用弱引用沐绒。

2.4 線程造成內(nèi)存泄漏

  • 早時(shí)期的時(shí)候處理耗時(shí)操作多數(shù)都是采用Thread+Handler的方式,后來(lái)逐步被AsyncTask取代旺坠,直到現(xiàn)在采用RxJava的方式來(lái)處理異步乔遮。
  • 造成內(nèi)存泄漏原因分析
    • 在處理一個(gè)比較耗時(shí)的操作時(shí),可能還沒處理結(jié)束MainActivity就執(zhí)行了退出操作取刃,但是此時(shí)AsyncTask依然持有對(duì)MainActivity的引用就會(huì)導(dǎo)致MainActivity無(wú)法釋放回收引發(fā)內(nèi)存泄漏蹋肮。
  • 解決辦法
    • 在使用AsyncTask時(shí),在Activity銷毀時(shí)候也應(yīng)該取消相應(yīng)的任務(wù)AsyncTask.cancel()方法璧疗,避免任務(wù)在后臺(tái)執(zhí)行浪費(fèi)資源坯辩,進(jìn)而避免內(nèi)存泄漏的發(fā)生。

2.5 非靜態(tài)內(nèi)部類

  • 非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實(shí)例造成的內(nèi)存泄漏崩侠。有的時(shí)候我們可能會(huì)在啟動(dòng)頻繁的Activity中漆魔,為了避免重復(fù)創(chuàng)建相同的數(shù)據(jù)資源,可能會(huì)出現(xiàn)這種寫法啦膜。
  • 問題代碼
    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會(huì)導(dǎo)致內(nèi)存泄漏
    }
    
  • 解決辦法
    • 將該內(nèi)部類設(shè)為靜態(tài)內(nèi)部類或?qū)⒃搩?nèi)部類抽取出來(lái)封裝成一個(gè)單例淌喻,如果需要使用Context僧家,請(qǐng)按照上面推薦的使用Application 的 Context。
  • 分析問題
    • 這樣就在Activity內(nèi)部創(chuàng)建了一個(gè)非靜態(tài)內(nèi)部類的單例裸删,每次啟動(dòng)Activity時(shí)都會(huì)使用該單例的數(shù)據(jù)八拱,這樣雖然避免了資源的重復(fù)創(chuàng)建,不過(guò)這種寫法卻會(huì)造成內(nèi)存泄漏,因?yàn)榉庆o態(tài)內(nèi)部類默認(rèn)會(huì)持有外部類的引用肌稻,而該非靜態(tài)內(nèi)部類又創(chuàng)建了一個(gè)靜態(tài)的實(shí)例清蚀,該實(shí)例的生命周期和應(yīng)用的一樣長(zhǎng),這就導(dǎo)致了該靜態(tài)實(shí)例一直會(huì)持有該Activity的引用爹谭,導(dǎo)致Activity的內(nèi)存資源不能正臣闲埃回收。

2.6 未移除監(jiān)聽

  • 問題代碼
    //add監(jiān)聽诺凡,放到集合里面
    tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
        @Override
        public void onWindowFocusChanged(boolean b) {
            //監(jiān)聽view的加載东揣,view加載出來(lái)的時(shí)候,計(jì)算他的寬高等腹泌。
        }
    });
    
  • 解決辦法
    //計(jì)算完后嘶卧,一定要移除這個(gè)監(jiān)聽
    tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
    
  • 注意事項(xiàng):
    tv.setOnClickListener();//監(jiān)聽執(zhí)行完回收對(duì)象,不用考慮內(nèi)存泄漏
    tv.getViewTreeObserver().addOnWindowFocusChangeListene,add監(jiān)聽凉袱,放到集合里面芥吟,需要考慮內(nèi)存泄漏
    

2.7 持有activity引用

2.8 資源未關(guān)閉

  • 在使用IO、File流或者Sqlite专甩、Cursor等資源時(shí)要及時(shí)關(guān)閉钟鸵。這些資源在進(jìn)行讀寫操作時(shí)通常都使用了緩沖,如果及時(shí)不關(guān)閉配深,這些緩沖對(duì)象就會(huì)一直被占用而得不到釋放携添,以致發(fā)生內(nèi)存泄露。因此我們?cè)诓恍枰褂盟鼈兊臅r(shí)候就及時(shí)關(guān)閉篓叶,以便緩沖能及時(shí)得到釋放烈掠,從而避免內(nèi)存泄露。
  • BroadcastReceiver缸托,ContentObserver左敌,F(xiàn)ileObserver,Cursor俐镐,Callback等在 Activity onDestroy 或者某類生命周期結(jié)束之后一定要 unregister 或者 close 掉矫限,否則這個(gè) Activity 類會(huì)被 system 強(qiáng)引用,不會(huì)被內(nèi)存回收佩抹。值得注意的是叼风,關(guān)閉的語(yǔ)句必須在finally中進(jìn)行關(guān)閉,否則有可能因?yàn)楫惓N搓P(guān)閉資源棍苹,致使activity泄漏无宿。

2.9 其他原因

  • 靜態(tài)集合使用不當(dāng)導(dǎo)致的內(nèi)存泄漏
    • 有時(shí)候我們需要把一些對(duì)象加入到集合容器(例如ArrayList)中,當(dāng)不再需要當(dāng)中某些對(duì)象時(shí)枢里,如果不把該對(duì)象的引用從集合中清理掉孽鸡,也會(huì)使得GC無(wú)法回收該對(duì)象蹂午。如果集合是static類型的話,那內(nèi)存泄漏情況就會(huì)更為嚴(yán)重彬碱。因此豆胸,當(dāng)不再需要某對(duì)象時(shí),需要主動(dòng)將之從集合中移除巷疼。
  • 不需要用的監(jiān)聽未移除會(huì)發(fā)生內(nèi)存泄露
    • 問題代碼
      //add監(jiān)聽晚胡,放到集合里面
      tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
          @Override
          public void onWindowFocusChanged(boolean b) {
              //監(jiān)聽view的加載,view加載出來(lái)的時(shí)候皮迟,計(jì)算他的寬高等搬泥。
          }
      });
      
    • 解決辦法
      //計(jì)算完后,一定要移除這個(gè)監(jiān)聽
      tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
      
    • 注意事項(xiàng):
      tv.setOnClickListener();//監(jiān)聽執(zhí)行完回收對(duì)象伏尼,不用考慮內(nèi)存泄漏
      tv.getViewTreeObserver().addOnWindowFocusChangeListene,add監(jiān)聽忿檩,放到集合里面,需要考慮內(nèi)存泄漏
      

3.布局優(yōu)化

3.1 include優(yōu)化

  • 重用布局文件
    • 標(biāo)簽可以允許在一個(gè)布局當(dāng)中引入另一個(gè)布局爆阶,那么比如說(shuō)我們程序的所有界面都有一個(gè)公共的部分燥透,這個(gè)時(shí)候最好的做法就是將這個(gè)公共的部分提取到一個(gè)獨(dú)立的布局中,然后每個(gè)界面的布局文件當(dāng)中來(lái)引用這個(gè)公共的布局辨图。
    • 如果我們要在標(biāo)簽中覆寫layout屬性班套,必須要將layout_width和layout_height這兩個(gè)屬性也進(jìn)行覆寫,否則覆寫效果將不會(huì)生效故河。
    • 標(biāo)簽是作為標(biāo)簽的一種輔助擴(kuò)展來(lái)使用的吱韭,它的主要作用是為了防止在引用布局文件時(shí)引用文件時(shí)產(chǎn)生多余的布局嵌套。布局嵌套越多鱼的,解析起來(lái)就越耗時(shí)理盆,性能就越差。因此編寫布局文件時(shí)應(yīng)該讓嵌套的層數(shù)越少越好凑阶。
    • 舉例:比如在LinearLayout里邊使用一個(gè)布局猿规。里邊又有一個(gè)LinearLayout,那么其實(shí)就存在了多余的布局嵌套宙橱,使用merge可以解決這個(gè)問題姨俩。

3.2 ViewStub優(yōu)化

  • 僅在需要時(shí)才加載布局[ViewStub]
    • 某個(gè)布局當(dāng)中的元素不是一起顯示出來(lái)的,普通情況下只顯示部分常用的元素师郑,而那些不常用的元素只有在用戶進(jìn)行特定操作時(shí)才會(huì)顯示出來(lái)环葵。
    • 舉例:填信息時(shí)不是需要全部填的,有一個(gè)添加更多字段的選項(xiàng)宝冕,當(dāng)用戶需要添加其他信息的時(shí)候张遭,才將另外的元素顯示到界面上。用VISIBLE性能表現(xiàn)一般猬仁,可以用ViewStub帝璧。
    • ViewStub也是View的一種,但是沒有大小湿刽,沒有繪制功能的烁,也不參與布局,資源消耗非常低诈闺,可以認(rèn)為完全不影響性能渴庆。
    • ViewStub所加載的布局是不可以使用標(biāo)簽的,因此這有可能導(dǎo)致加載出來(lái)出來(lái)的布局存在著多余的嵌套結(jié)構(gòu)雅镊。
  • 自定義全局的狀態(tài)管理器【充分使用ViewStub】
    • 針對(duì)多狀態(tài)襟雷,有數(shù)據(jù),空數(shù)據(jù)仁烹,加載失敗耸弄,加載異常,網(wǎng)絡(luò)異常等卓缰。針對(duì)空數(shù)據(jù)计呈,加載失敗,異常使用viewStub布局征唬,一鍵設(shè)置自定義布局捌显,也是優(yōu)化的一種。
    • 項(xiàng)目地址:

3.3 merge優(yōu)化

  • 視圖層級(jí)<merge/>
    • 這個(gè)標(biāo)簽在UI的結(jié)構(gòu)優(yōu)化中起著非常重要的作用总寒,它可以刪減多余的層級(jí)扶歪,優(yōu)化UI。但是就有一點(diǎn)不好摄闸,無(wú)法預(yù)覽布局效果善镰!

3.4 其他建議

  • 減少太多重疊的背景(overdraw)
    • 這個(gè)問題其實(shí)最容易解決,建議就是檢查你在布局和代碼中設(shè)置的背景贪薪,有些背景是隱藏在底下的媳禁,它永遠(yuǎn)不可能顯示出來(lái),這種沒必要的背景一定要移除画切,因?yàn)樗芸赡軙?huì)嚴(yán)重影響到app的性能竣稽。如果采用的是selector的背景,將normal狀態(tài)的color設(shè)置為”@android:color/transparent”,也同樣可以解決問題霍弹。
  • 避免復(fù)雜的Layout層級(jí)
    • 這里的建議比較多一些毫别,首先推薦使用Android提供的布局工具Hierarchy Viewer來(lái)檢查和優(yōu)化布局。第一個(gè)建議是:如果嵌套的線性布局加深了布局層次典格,可以使用相對(duì)布局來(lái)取代岛宦。第二個(gè)建議是:用標(biāo)簽來(lái)合并布局。第三個(gè)建議是:用標(biāo)簽來(lái)重用布局耍缴,抽取通用的布局可以讓布局的邏輯更清晰明了砾肺。記住挽霉,這些建議的最終目的都是使得你的Layout在Hierarchy Viewer里變得寬而淺,而不是窄而深变汪。
    • 總結(jié):可以考慮多使用merge和include,ViewStub实胸。盡量使布局淺平番官,根布局盡量少使用RelactivityLayout,因?yàn)镽elactivityLayout每次需要測(cè)量2次。

4.代碼優(yōu)化

  • 都是一些微優(yōu)化门躯,在性能方面看不出有什么顯著的提升的。使用合適的算法和數(shù)據(jù)結(jié)構(gòu)是優(yōu)化程序性能的最主要手段酷师。

4.1 建議使用lint檢查去除無(wú)效代碼

  • lint去除無(wú)效資源和代碼
    • 如何檢測(cè)哪些圖片未被使用
      • 點(diǎn)擊菜單欄 Analyze -> Run Inspection by Name -> unused resources -> Moudule ‘a(chǎn)pp’ -> OK,這樣會(huì)搜出來(lái)哪些未被使用到未使用到xml和圖片缀遍,如下:
    • 如何檢測(cè)哪些無(wú)效代碼
      • 使用Android Studio的Lint域醇,步驟:點(diǎn)擊菜單欄 Analyze -> Run Inspection by Name -> unused declaration -> Moudule ‘a(chǎn)pp’ -> OK

4.2 代碼規(guī)范優(yōu)化

  • 避免創(chuàng)建不必要的對(duì)象 不必要的對(duì)象應(yīng)該避免創(chuàng)建:
    • 如果有需要拼接的字符串譬挚,那么可以優(yōu)先考慮使用StringBuffer或者StringBuilder來(lái)進(jìn)行拼接减宣,而不是加號(hào)連接符漆腌,因?yàn)槭褂眉犹?hào)連接符會(huì)創(chuàng)建多余的對(duì)象闷尿,拼接的字符串越長(zhǎng)填具,加號(hào)連接符的性能越低匆骗。
    • 當(dāng)一個(gè)方法的返回值是String的時(shí)候,通常需要去判斷一下這個(gè)String的作用是什么描融,如果明確知道調(diào)用方會(huì)將返回的String再進(jìn)行拼接操作的話,可以考慮返回一個(gè)StringBuffer對(duì)象來(lái)代替毛甲,因?yàn)檫@樣可以將一個(gè)對(duì)象的引用進(jìn)行返回,而返回String的話就是創(chuàng)建了一個(gè)短生命周期的臨時(shí)對(duì)象七咧。
    • 盡可能地少創(chuàng)建臨時(shí)對(duì)象艾栋,越少的對(duì)象意味著越少的GC操作蝗砾。
    • nDraw方法里面不要執(zhí)行對(duì)象的創(chuàng)建
  • 靜態(tài)優(yōu)于抽象
    • 如果你并不需要訪問一個(gè)對(duì)系那個(gè)中的某些字段悼粮,只是想調(diào)用它的某些方法來(lái)去完成一項(xiàng)通用的功能扣猫,那么可以將這個(gè)方法設(shè)置成靜態(tài)方法,調(diào)用速度提升15%-20%瀑凝,同時(shí)也不用為了調(diào)用這個(gè)方法去專門創(chuàng)建對(duì)象了粤咪,也不用擔(dān)心調(diào)用這個(gè)方法后是否會(huì)改變對(duì)象的狀態(tài)(靜態(tài)方法無(wú)法訪問非靜態(tài)字段)寥枝。
  • 對(duì)常量使用static final修飾符
    • static int intVal = 42; static String strVal = "Hello, world!";
    • 編譯器會(huì)為上面的代碼生成一個(gè)初始方法某筐,稱為方法南誊,該方法會(huì)在定義類第一次被使用的時(shí)候調(diào)用抄囚。這個(gè)方法會(huì)將42的值賦值到intVal當(dāng)中幔托,從字符串常量表中提取一個(gè)引用賦值到strVal上。當(dāng)賦值完成后谬哀,我們就可以通過(guò)字段搜尋的方式去訪問具體的值了玻粪。
    • final進(jìn)行優(yōu)化:
    • static final int intVal = 42; static final String strVal = "Hello, world!";
    • 這樣劲室,定義類就不需要方法了,因?yàn)樗械某A慷紩?huì)在dex文件的初始化器當(dāng)中進(jìn)行初始化喉磁。當(dāng)我們調(diào)用intVal時(shí)可以直接指向42的值协怒,而調(diào)用strVal會(huì)用一種相對(duì)輕量級(jí)的字符串常量方式孕暇,而不是字段搜尋的方式隧哮。
    • 這種優(yōu)化方式只對(duì)基本數(shù)據(jù)類型以及String類型的常量有效沮翔,對(duì)于其他數(shù)據(jù)類型的常量是無(wú)效的采蚀。
  • 在沒有特殊原因的情況下,盡量使用基本數(shù)據(jù)類型來(lái)代替封裝數(shù)據(jù)類型,int比Integer要更加有效读虏,其它數(shù)據(jù)類型也是一樣盖桥。
    • 基本數(shù)據(jù)類型的數(shù)組也要優(yōu)于對(duì)象數(shù)據(jù)類型的數(shù)組。另外兩個(gè)平行的數(shù)組要比一個(gè)封裝好的對(duì)象數(shù)組更加高效忽妒,舉個(gè)例子齿税,F(xiàn)oo[]和Bar[]這樣的數(shù)組凌箕,使用起來(lái)要比Custom(Foo,Bar)[]這樣的一個(gè)數(shù)組高效的多。

4.3 View異常優(yōu)化

  • view自定義控件異常銷毀保存狀態(tài)
    • 經(jīng)常容易被人忽略芜壁,但是為了追求高質(zhì)量代碼沿盅,這個(gè)也有必要加上韧掩。舉個(gè)例子疗锐!
      @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 去除淡黃色警告優(yōu)化

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

4.5 合理使用集合

  • 使用優(yōu)化過(guò)的數(shù)據(jù)集合
    • Android提供了一系列優(yōu)化過(guò)后的數(shù)據(jù)集合工具類贮折,如SparseArray、SparseBooleanArray每庆、LongSparseArray扣孟,使用這些API可以讓我們的程序更加高效。HashMap工具類會(huì)相對(duì)比較低效利诺,因?yàn)樗枰獮槊恳粋€(gè)鍵值對(duì)都提供一個(gè)對(duì)象入口慢逾,而SparseArray就避免掉了基本數(shù)據(jù)類型轉(zhuǎn)換成對(duì)象數(shù)據(jù)類型的時(shí)間口注。

4.6 Activity不可見優(yōu)化

  • 當(dāng)Activity界面不可見時(shí)釋放內(nèi)存
    • 當(dāng)用戶打開了另外一個(gè)程序寝志,我們的程序界面已經(jīng)不可見的時(shí)候,我們應(yīng)當(dāng)將所有和界面相關(guān)的資源進(jìn)行釋放乐导。重寫Activity的onTrimMemory()方法,然后在這個(gè)方法中監(jiān)聽TRIM_MEMORY_UI_HIDDEN這個(gè)級(jí)別棵磷,一旦觸發(fā)說(shuō)明用戶離開了程序淘太,此時(shí)就可以進(jìn)行資源釋放操作了蒲牧。
  • 當(dāng)時(shí)看到這個(gè)覺得很新奇的冰抢,但是具體還是沒有用到翠订,要是那個(gè)大神有具體操作方案尽超,可以分享一下。

4.7 節(jié)制的使用Service

  • 節(jié)制的使用Service
    • 如果應(yīng)用程序需要使用Service來(lái)執(zhí)行后臺(tái)任務(wù)的話巩踏,只有當(dāng)任務(wù)正在執(zhí)行的時(shí)候才應(yīng)該讓Service運(yùn)行起來(lái)菠净。當(dāng)啟動(dòng)一個(gè)Service時(shí)嗤练,系統(tǒng)會(huì)傾向于將這個(gè)Service所依賴的進(jìn)程進(jìn)行保留,系統(tǒng)可以在LRUcache當(dāng)中緩存的進(jìn)程數(shù)量也會(huì)減少革答,導(dǎo)致切換程序的時(shí)候耗費(fèi)更多性能。我們可以使用IntentService溪食,當(dāng)后臺(tái)任務(wù)執(zhí)行結(jié)束后會(huì)自動(dòng)停止,避免了Service的內(nèi)存泄漏枢析。

5.網(wǎng)絡(luò)優(yōu)化

5.1 圖片分類

  • 圖片網(wǎng)絡(luò)優(yōu)化
    • 比如我之前看到豆瓣接口,提供一種加載圖片方式特別好把沼。接口返回圖片的數(shù)據(jù)有三種,一種是高清大圖续捂,一種是正常圖片劫拗,一種是縮略小圖页慷。當(dāng)用戶處于wifi下給控件設(shè)置高清大圖,當(dāng)4g或者3g模式下加載正常圖片州袒,當(dāng)弱網(wǎng)條件下加載縮略圖【也稱與加載圖】。
    • 簡(jiǎn)單來(lái)說(shuō)根據(jù)用戶的當(dāng)前的網(wǎng)絡(luò)質(zhì)量來(lái)判斷下載什么質(zhì)量的圖片(電商用的比較多)夸研。豆瓣開源接口可以參考一下!

5.2 獲取網(wǎng)絡(luò)數(shù)據(jù)優(yōu)化

  • 移動(dòng)端獲取網(wǎng)絡(luò)數(shù)據(jù)優(yōu)化的幾個(gè)點(diǎn)
  • 連接復(fù)用:節(jié)省連接建立時(shí)間,如開啟 keep-alive溶握。
    • 對(duì)于Android來(lái)說(shuō)默認(rèn)情況下HttpURLConnection和HttpClient都開啟了keep-alive萍肆。只是2.2之前HttpURLConnection存在影響連接池的Bug包雀,具體可見:Android HttpURLConnection及HttpClient選擇
  • 請(qǐng)求合并:即將多個(gè)請(qǐng)求合并為一個(gè)進(jìn)行請(qǐng)求葡兑,比較常見的就是網(wǎng)頁(yè)中的CSS Image Sprites讹堤。如果某個(gè)頁(yè)面內(nèi)請(qǐng)求過(guò)多,也可以考慮做一定的請(qǐng)求合并梗醇。
  • 減少請(qǐng)求數(shù)據(jù)的大小:對(duì)于post請(qǐng)求,body可以做gzip壓縮的虫溜,header也可以做數(shù)據(jù)壓縮(不過(guò)只支持http
    • 返回?cái)?shù)據(jù)的body也可以做gzip壓縮,body數(shù)據(jù)體積可以縮小到原來(lái)的30%左右瘾境。(也可以考慮壓縮返回的json數(shù)據(jù)的key數(shù)據(jù)的體積,尤其是針對(duì)返回?cái)?shù)據(jù)格式變化不大的情況兑凿,支付寶聊天返回的數(shù)據(jù)用到了)

5.3 網(wǎng)絡(luò)請(qǐng)求異常攔截優(yōu)化

  • 在獲取數(shù)據(jù)的流程中,訪問接口和解析數(shù)據(jù)時(shí)都有可能會(huì)出錯(cuò)茵瘾,我們可以通過(guò)攔截器在這兩層攔截錯(cuò)誤礼华。
    • 1.在訪問接口時(shí),我們不用設(shè)置攔截器拗秘,因?yàn)橐坏┏霈F(xiàn)錯(cuò)誤圣絮,Retrofit會(huì)自動(dòng)拋出異常雕旨。比如扮匠,常見請(qǐng)求異常404捧请,500,503等等棒搜。
    • 2.在解析數(shù)據(jù)時(shí)血久,我們?cè)O(shè)置一個(gè)攔截器,判斷Result里面的code是否為成功帮非,如果不成功氧吐,則要根據(jù)與服務(wù)器約定好的錯(cuò)誤碼來(lái)拋出對(duì)應(yīng)的異常。比如末盔,token失效筑舅,禁用同賬號(hào)登陸多臺(tái)設(shè)備,缺少參數(shù)陨舱,參數(shù)傳遞異常等等翠拣。
    • 3.除此以外,為了我們要盡量避免在View層對(duì)錯(cuò)誤進(jìn)行判斷游盲,處理误墓,我們必須還要設(shè)置一個(gè)攔截器,攔截onError事件益缎,然后使用ExceptionUtils谜慌,讓其根據(jù)錯(cuò)誤類型來(lái)分別處理。
    • 具體可以直接看lib中的ExceptionUtils類莺奔,那么如何調(diào)用呢欣范?入侵性極低,不用改變之前的代碼令哟!
    @Override
    public void onError(Throwable e) {
        //直接調(diào)用即可
        ExceptionUtils.handleException(e);
    }
    

6.線程優(yōu)化

6.1 使用線程池

  • 將全局線程用線程池管理
    • 直接創(chuàng)建Thread實(shí)現(xiàn)runnable方法的弊端
      • 大量的線程的創(chuàng)建和銷毀很容易導(dǎo)致GC頻繁的執(zhí)行恼琼,從而發(fā)生內(nèi)存抖動(dòng)現(xiàn)象,而發(fā)生了內(nèi)存抖動(dòng)屏富,對(duì)于移動(dòng)端來(lái)說(shuō)晴竞,最大的影響就是造成界面卡頓
      • 線程的創(chuàng)建和銷毀都需要時(shí)間,當(dāng)有大量的線程創(chuàng)建和銷毀時(shí)狠半,那么這些時(shí)間的消耗則比較明顯噩死,將導(dǎo)致性能上的缺失
    • 為什么要用線程池
      • 重用線程池中的線程,避免頻繁地創(chuàng)建和銷毀線程帶來(lái)的性能消耗典予;有效控制線程的最大并發(fā)數(shù)量甜滨,防止線程過(guò)大導(dǎo)致?lián)屨假Y源造成系統(tǒng)阻塞乐严;可以對(duì)線程進(jìn)行一定地管理瘤袖。
    • 使用線程池管理的經(jīng)典例子
      • RxJava,RxAndroid昂验,底層對(duì)線程池的封裝管理特別值得參考
    • 關(guān)于線程池捂敌,線程艾扮,多線程的具體內(nèi)容
      • 參考:輕量級(jí)線程池封裝庫(kù),支持異步回調(diào)占婉,可以檢測(cè)線程執(zhí)行的狀態(tài)
      • 該項(xiàng)目中哪里用到頻繁new Thread
        • 保存圖片[注意泡嘴,尤其是大圖和多圖場(chǎng)景下注意耗時(shí)太久];某些頁(yè)面從數(shù)據(jù)庫(kù)查詢數(shù)據(jù)逆济;設(shè)置中心清除圖片酌予,視頻,下載文件奖慌,日志抛虫,系統(tǒng)緩存等緩存內(nèi)容
        • 使用線程池管理庫(kù)好處,比如保存圖片简僧,耗時(shí)操作放到子線程中建椰,處理過(guò)程中,可以檢測(cè)到執(zhí)行開始岛马,異常棉姐,成功,失敗等多種狀態(tài)啦逆。

7.圖片優(yōu)化

7.1 bitmap優(yōu)化

  • 加載圖片所占的內(nèi)存大小計(jì)算方式
    • 加載網(wǎng)絡(luò)圖片:bitmap內(nèi)存大小 = 圖片長(zhǎng)度 x 圖片寬度 x 單位像素占用的字節(jié)數(shù)【看到網(wǎng)上很多都是這樣寫的伞矩,但是不全面】
    • 加載本地圖片:bitmap內(nèi)存大小 = width * height * nTargetDensity/inDensity 一個(gè)像素所占的內(nèi)存。注意不要忽略了一個(gè)影響項(xiàng):Density
  • 第一種加載圖片優(yōu)化處理:壓縮圖片
    • 質(zhì)量壓縮方法:在保持像素的前提下改變圖片的位深及透明度等夏志,來(lái)達(dá)到壓縮圖片的目的扭吁,這樣適合去傳遞二進(jìn)制的圖片數(shù)據(jù),比如分享圖片盲镶,要傳入二進(jìn)制數(shù)據(jù)過(guò)去侥袜,限制500kb之內(nèi)。
    • 采樣率壓縮方法:設(shè)置inSampleSize的值(int類型)后溉贿,假如設(shè)為n枫吧,則寬和高都為原來(lái)的1/n,寬高都減少宇色,內(nèi)存降低九杂。
    • 縮放法壓縮:Android中使用Matrix對(duì)圖像進(jìn)行縮放、旋轉(zhuǎn)宣蠕、平移例隆、斜切等變換的。功能十分強(qiáng)大抢蚀!
  • 第二種加載圖片優(yōu)化:不壓縮加載高清圖片如何做镀层?
    • 使用BitmapRegionDecoder,主要用于顯示圖片的某一塊矩形區(qū)域皿曲,如果你需要顯示某個(gè)圖片的指定區(qū)域唱逢,那么這個(gè)類非常合適吴侦。

7.2 glide加載優(yōu)化

  • 在畫廊中加載大圖
    • 假如你滑動(dòng)特別快,glide加載優(yōu)化就顯得非常重要呢坞古,具體優(yōu)化方法如下所示
      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"+ "恢復(fù)Glide加載圖片");
                  Glide.with(ImageBrowseActivity.this).resumeRequests();
              }else {
                  LoggerUtils.e("initRecyclerView"+"禁止Glide加載圖片");
                  Glide.with(ImageBrowseActivity.this).pauseRequests();
              }
          }
      });
      

8.加載優(yōu)化

8.1 懶加載優(yōu)化

  • 該優(yōu)化在新聞?lì)恆pp中十分常見
    • ViewPager+Fragment的搭配在日常開發(fā)中也比較常見备韧,可用于切換展示不同類別的頁(yè)面。
    • 懶加載,其實(shí)也就是延遲加載,就是等到該頁(yè)面的UI展示給用戶時(shí),再加載該頁(yè)面的數(shù)據(jù)(從網(wǎng)絡(luò)痪枫、數(shù)據(jù)庫(kù)等),而不是依靠ViewPager預(yù)加載機(jī)制提前加載兩三個(gè)织堂,甚至更多頁(yè)面的數(shù)據(jù)。這樣可以提高所屬Activity的初始化速度,也可以為用戶節(jié)省流量.而這種懶加載的方式也已經(jīng)/正在被諸多APP所采用奶陈。
  • 具體看這篇文章

8.2 啟動(dòng)頁(yè)優(yōu)化

  • 啟動(dòng)時(shí)間分析
    • 系統(tǒng)創(chuàng)建進(jìn)程的時(shí)間和應(yīng)用進(jìn)程啟動(dòng)的時(shí)間捧挺,前者是由系統(tǒng)自行完成的,一般都會(huì)很快尿瞭,我們也干預(yù)不了闽烙,我覺得能做的就是去優(yōu)化應(yīng)用進(jìn)程啟動(dòng),具體說(shuō)來(lái)就是從發(fā)Application的onCreate()執(zhí)行開始到MainActivity的onCreate()執(zhí)行結(jié)束這一段時(shí)間声搁。
  • 啟動(dòng)時(shí)間優(yōu)化
    • Application的onCreate()方法
    • MainActivity的onCreate()方法
    • 優(yōu)化的手段也無(wú)非三種黑竞,如下所示:
      • 延遲初始化
      • 后臺(tái)任務(wù)
      • 啟動(dòng)界面預(yù)加載
  • 啟動(dòng)頁(yè)白屏優(yōu)化
    • 為什么存在這個(gè)問題?
      • 當(dāng)系統(tǒng)啟動(dòng)一個(gè)APP時(shí)疏旨,zygote進(jìn)程會(huì)首先創(chuàng)建一個(gè)新的進(jìn)程去運(yùn)行這個(gè)APP很魂,但是進(jìn)程的創(chuàng)建是需要時(shí)間的,在創(chuàng)建完成之前檐涝,界面是呈現(xiàn)假死狀態(tài)遏匆,于是系統(tǒng)根據(jù)你的manifest文件設(shè)置的主題顏色的不同來(lái)展示一個(gè)白屏或者黑屏。而這個(gè)黑(白)屏正式的稱呼應(yīng)該是Preview Window谁榜,即預(yù)覽窗口幅聘。
      • 實(shí)際上就是是activity默認(rèn)的主題中的android:windowBackground為白色或者黑色導(dǎo)致的。
      • 總結(jié)來(lái)說(shuō)啟動(dòng)順序就是:app啟動(dòng)——Preview Window(也稱為預(yù)覽窗口)——啟動(dòng)頁(yè)
    • 解決辦法
      • 常見有三種窃植,這里解決辦法是給當(dāng)前啟動(dòng)頁(yè)添加一個(gè)有背景的style樣式帝蒿,然后SplashActivity引用當(dāng)前theme主題,注意在該頁(yè)面將window的背景圖設(shè)置為空巷怜!
      • 更多關(guān)于啟動(dòng)頁(yè)為什么白屏閃屏葛超,以及不同解決辦法,可以看我這篇博客:App啟動(dòng)頁(yè)面優(yōu)化
  • 啟動(dòng)時(shí)間優(yōu)化
    • IntentService子線程分擔(dān)部分初始化工作
      • 現(xiàn)在application初始化內(nèi)容有:阿里云推送初始化延塑,騰訊bugly初始化绣张,im初始化,神策初始化关带,內(nèi)存泄漏工具初始化侥涵,頭條適配方案初始化,阿里云熱修復(fù)……等等。將部分邏輯放到IntentService中處理独令,可以縮短很多時(shí)間端朵。
      • 開啟IntentSerVice線程好芭,將部分邏輯和耗時(shí)的初始化操作放到這里處理燃箭,可以減少application初始化時(shí)間
      • 關(guān)于IntentService使用和源碼分析,性能分析等可以參考博客:IntentService源碼分析

9.其他優(yōu)化

9.1 靜態(tài)變量?jī)?yōu)化

  • 盡量不使用靜態(tài)變量保存核心數(shù)據(jù)舍败。這是為什么呢招狸?
    - 這是因?yàn)閍ndroid的進(jìn)程并不是安全的,包括application對(duì)象以及靜態(tài)變量在內(nèi)的進(jìn)程級(jí)別變量并不會(huì)一直呆著內(nèi)存里面邻薯,因?yàn)樗苡袝?huì)被kill掉裙戏。
    - 當(dāng)被kill掉之后,實(shí)際上app不會(huì)重新開始啟動(dòng)厕诡。Android系統(tǒng)會(huì)創(chuàng)建一個(gè)新的Application對(duì)象累榜,然后啟動(dòng)上次用戶離開時(shí)的activity以造成這個(gè)app從來(lái)沒有被kill掉的假象。而這時(shí)候靜態(tài)變量等數(shù)據(jù)由于進(jìn)程已經(jīng)被殺死而被初始化灵嫌,所以就有了不推薦在靜態(tài)變量(包括Application中保存全局?jǐn)?shù)據(jù)靜態(tài)數(shù)據(jù))的觀點(diǎn)壹罚。

9.2 注解替代枚舉

  • 使用注解限定傳入類型
    • 比如,尤其是寫第三方開源庫(kù)寿羞,對(duì)于有些暴露給開發(fā)者的方法猖凛,需要限定傳入類型是有必要的。舉個(gè)例子:
    • 剛開始的代碼
      /**
       * 設(shè)置播放器類型绪穆,必須設(shè)置
       * 注意:感謝某人建議辨泳,這里限定了傳入值類型
       * 輸入值:111   或者  222
       * @param playerType IjkPlayer or MediaPlayer.
       */
      public void setPlayerType(int playerType) {
          mPlayerType = playerType;
      }
      
    • 優(yōu)化后的代碼,有效避免第一種方式開發(fā)者傳入值錯(cuò)誤
      /**
       * 設(shè)置播放器類型玖院,必須設(shè)置
       * 注意:感謝某人建議菠红,這里限定了傳入值類型
       * 輸入值:ConstantKeys.IjkPlayerType.TYPE_IJK   或者  ConstantKeys.IjkPlayerType.TYPE_NATIVE
       * @param playerType IjkPlayer or MediaPlayer.
       */
      public void setPlayerType(@ConstantKeys.PlayerType int playerType) {
          mPlayerType = playerType;
      }
      
      /**
       * 通過(guò)注解限定類型
       * 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 多渠道打包優(yōu)化

  • 還在手動(dòng)打包嗎?嘗試一下python自動(dòng)化打包吧……
    • 瓦力多渠道打包的Python腳本測(cè)試工具扔傅,通過(guò)該自動(dòng)化腳本耍共,自需要run一下或者命令行運(yùn)行腳本即可實(shí)現(xiàn)美團(tuán)瓦力多渠道打包,打包速度很快猎塞。配置信息十分簡(jiǎn)單试读,代碼中已經(jīng)注釋十分詳細(xì)≤ⅲ可以自定義輸出文件路徑钩骇,可以修改多渠道配置信息,簡(jiǎn)單實(shí)用。 項(xiàng)目地址:https://github.com/yangchong211/YCWalleHelper

9.4 TrimMemory和LowMemory優(yōu)化

  • 可以優(yōu)化什么倘屹?
    • 在 onTrimMemory() 回調(diào)中银亲,應(yīng)該在一些狀態(tài)下清理掉不重要的內(nèi)存資源。對(duì)于這些緩存纽匙,只要是讀進(jìn)內(nèi)存內(nèi)的都算务蝠,例如最常見的圖片緩存、文件緩存等烛缔。拿圖片緩存來(lái)說(shuō)馏段,市場(chǎng)上,常規(guī)的圖片加載庫(kù)践瓷,一般而言都是三級(jí)緩存院喜,所以在內(nèi)存吃緊的時(shí)候,我們就應(yīng)該優(yōu)先清理掉這部分圖片緩存晕翠,畢竟圖片是吃內(nèi)存大戶喷舀,而且再次回來(lái)的時(shí)候,雖然內(nèi)存中的資源被回收掉了淋肾,依然可以從磁盤或者網(wǎng)絡(luò)上恢復(fù)它硫麻。
  • 大概的思路如下所示
    • 在lowMemory的時(shí)候,調(diào)用Glide.cleanMemory()清理掉所有的內(nèi)存緩存巫员。
    • 在App被置換到后臺(tái)的時(shí)候庶香,調(diào)用Glide.cleanMemory()清理掉所有的內(nèi)存緩存。
    • 在其它情況的onTrimMemory()回調(diào)中简识,直接調(diào)用Glide.trimMemory()方法來(lái)交給Glide處理內(nèi)存情況赶掖。

9.5 輪詢操作優(yōu)化

  • 什么叫輪訓(xùn)請(qǐng)求?
    • 簡(jiǎn)單理解就是App端每隔一定的時(shí)間重復(fù)請(qǐng)求的操作就叫做輪訓(xùn)請(qǐng)求七扰,比如:App端每隔一段時(shí)間上報(bào)一次定位信息奢赂,App端每隔一段時(shí)間拉去一次用戶狀態(tài)等,這些應(yīng)該都是輪訓(xùn)請(qǐng)求颈走。比如膳灶,電商類項(xiàng)目,某個(gè)抽獎(jiǎng)活動(dòng)頁(yè)面立由,隔1分鐘調(diào)用一次接口轧钓,彈出一些獲獎(jiǎng)人信息,你應(yīng)該某個(gè)階段看過(guò)這類輪詢操作锐膜!
  • 具體優(yōu)化操作
    • 長(zhǎng)連接并不是穩(wěn)定的可靠的毕箍,而執(zhí)行輪訓(xùn)操作的時(shí)候一般都是要穩(wěn)定的網(wǎng)絡(luò)請(qǐng)求,而且輪訓(xùn)操作一般都是有生命周期的道盏,即在一定的生命周期內(nèi)執(zhí)行輪訓(xùn)操作而柑,而長(zhǎng)連接一般都是整個(gè)進(jìn)程生命周期的文捶,所以從這方面講也不太適合。
    • 建議在service中做輪詢操作媒咳,輪詢請(qǐng)求接口粹排,具體做法和注意要點(diǎn),可以直接看該項(xiàng)目代碼涩澡⊥缍看app包下的LoopRequestService類即可。
    • 大概思路:當(dāng)用戶打開這個(gè)頁(yè)面的時(shí)候初始化TimerTask對(duì)象筏养,每個(gè)一分鐘請(qǐng)求一次服務(wù)器拉取訂單信息并更新UI斧抱,當(dāng)用戶離開頁(yè)面的時(shí)候清除TimerTask對(duì)象常拓,即取消輪訓(xùn)請(qǐng)求操作渐溶。

9.6 去除重復(fù)依賴庫(kù)優(yōu)化

  • 我相信你看到了這里會(huì)有疑問,網(wǎng)上有許多博客作了這方面說(shuō)明弄抬。但是我在這里想說(shuō)茎辐,如何查找自己項(xiàng)目的所有依賴關(guān)系樹
    • 注意要點(diǎn):其中app就是項(xiàng)目mudule名字。 正常情況下就是app掂恕!
    gradlew app:dependencies
    
  • 關(guān)于依賴關(guān)系樹的結(jié)構(gòu)圖如下所示拖陆,此處省略很多代碼
    |    |    |    |    |    |    \--- 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
    
  • 然后查看哪些重復(fù)jar
    • image
  • 然后修改gradle配置代碼
    api (rootProject.ext.dependencies["zxing"]){
        exclude module: 'support-v4'
        exclude module: 'appcompat-v7'
    }
    

9.7 四種引用優(yōu)化

  • 軟引用使用場(chǎng)景
    • 正常是用來(lái)處理大圖片這種占用內(nèi)存大的情況
      • 代碼如下所示
      Bitmap bitmap = bitmaps.get(position);
      //正常是用來(lái)處理圖片這種占用內(nèi)存大的情況
      bitmapSoftReference = new SoftReference<>(bitmap);
      if(bitmapSoftReference.get() != null) {
          viewHolder.imageView.setImageBitmap(bitmapSoftReference.get());
      }
      //其實(shí)看glide底層源碼可知,也做了相關(guān)軟引用的操作
      
    • 這樣使用軟引用好處
      • 通過(guò)軟引用的get()方法懊亡,取得bitmap對(duì)象實(shí)例的強(qiáng)引用依啰,發(fā)現(xiàn)對(duì)象被未回收。在GC在內(nèi)存充足的情況下店枣,不會(huì)回收軟引用對(duì)象速警。此時(shí)view的背景顯示
      • 實(shí)際情況中,我們會(huì)獲取很多圖片.然后可能給很多個(gè)view展示, 這種情況下很容易內(nèi)存吃緊導(dǎo)致oom,內(nèi)存吃緊,系統(tǒng)開始會(huì)GC鸯两。這次GC后闷旧,bitmapSoftReference.get()不再返回bitmap對(duì)象,而是返回null钧唐,這時(shí)屏幕上背景圖不顯示忙灼,說(shuō)明在系統(tǒng)內(nèi)存緊張的情況下,軟引用被回收钝侠。
      • 使用軟引用以后该园,在OutOfMemory異常發(fā)生之前,這些緩存的圖片資源的內(nèi)存空間可以被釋放掉的帅韧,從而避免內(nèi)存達(dá)到上限里初,避免Crash發(fā)生。
  • 弱引用使用場(chǎng)景
    • 弱引用–>隨時(shí)可能會(huì)被垃圾回收器回收弱匪,不一定要等到虛擬機(jī)內(nèi)存不足時(shí)才強(qiáng)制回收青瀑。
    • 對(duì)于使用頻次少的對(duì)象璧亮,希望盡快回收,使用弱引用可以保證內(nèi)存被虛擬機(jī)回收斥难。比如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){
            }
        }
    }
    
  • 到底什么時(shí)候使用軟引用哑诊,什么時(shí)候使用弱引用呢群扶?
    • 個(gè)人認(rèn)為,如果只是想避免OutOfMemory異常的發(fā)生镀裤,則可以使用軟引用竞阐。如果對(duì)于應(yīng)用的性能更在意,想盡快回收一些占用內(nèi)存比較大的對(duì)象暑劝,則可以使用弱引用骆莹。
    • 還有就是可以根據(jù)對(duì)象是否經(jīng)常使用來(lái)判斷。如果該對(duì)象可能會(huì)經(jīng)常使用的担猛,就盡量用軟引用幕垦。如果該對(duì)象不被使用的可能性更大些,就可以用弱引用傅联。

9.8 加載loading優(yōu)化

  • 一般實(shí)際開發(fā)中會(huì)至少有兩種loading
    • 第一種是從A頁(yè)面進(jìn)入B頁(yè)面時(shí)的加載loading先改,這個(gè)時(shí)候特點(diǎn)是顯示loading的時(shí)候,頁(yè)面是純白色的蒸走,加載完數(shù)據(jù)后才顯示內(nèi)容頁(yè)面仇奶。
    • 第二種是在某個(gè)頁(yè)面操作某種邏輯,比如某些耗時(shí)操作比驻,這個(gè)時(shí)候是局部loading[一般用個(gè)幀動(dòng)畫或者補(bǔ)間動(dòng)畫]该溯,由于使用頻繁,因?yàn)榻ㄗh在銷毀彈窗時(shí)嫁艇,添加銷毀動(dòng)畫的操作朗伶。
  • 自定義loading加載

9.9 對(duì)象池Pools優(yōu)化

  • 對(duì)象池Pools優(yōu)化頻繁創(chuàng)建和銷毀對(duì)象
  • 使用對(duì)象池,可以防止頻繁創(chuàng)建和銷毀對(duì)象而出現(xiàn)內(nèi)存抖動(dòng)
    • 在某些時(shí)候步咪,我們需要頻繁使用一些臨時(shí)對(duì)象论皆,如果每次使用的時(shí)候都申請(qǐng)新的資源,很有可能會(huì)引發(fā)頻繁的 gc 而影響應(yīng)用的流暢性猾漫。這個(gè)時(shí)候如果對(duì)象有明確的生命周期点晴,那么就可以通過(guò)定義一個(gè)對(duì)象池來(lái)高效的完成復(fù)用對(duì)象。
    • 具體參考案例悯周,可以看該項(xiàng)目:https://github.com/yangchong211/YCZoomImage

10.RecyclerView優(yōu)化

10.1 頁(yè)面為何卡頓

  • RecyclerView滑動(dòng)卡頓的原因有哪些粒督?
    • 第一種:嵌套布局滑動(dòng)沖突
      • 導(dǎo)致嵌套滑動(dòng)難處理的關(guān)鍵原因在于當(dāng)子控件消費(fèi)了事件, 那么父控件就不會(huì)再有機(jī)會(huì)處理這個(gè)事件了, 所以一旦內(nèi)部的滑動(dòng)控件消費(fèi)了滑動(dòng)操作, 外部的滑動(dòng)控件就再也沒機(jī)會(huì)響應(yīng)這個(gè)滑動(dòng)操作了
    • 第二種:嵌套布局層次太深,比如六七層等
      • 測(cè)量禽翼,繪制布局可能會(huì)導(dǎo)致滑動(dòng)卡頓
    • 第三種:比如用RecyclerView實(shí)現(xiàn)畫廊屠橄,加載比較大的圖片族跛,如果快速滑動(dòng),則可能會(huì)出現(xiàn)卡頓锐墙,主要是加載圖片需要時(shí)間
    • 第四種:在onCreateViewHolder或者在onBindViewHolder中做了耗時(shí)的操作導(dǎo)致卡頓礁哄。按stackoverflow上面比較通俗的解釋:RecyclerView.Adapter里面的onCreateViewHolder()方法和onBindViewHolder()方法對(duì)時(shí)間都非常敏感。類似I/O讀寫溪北,Bitmap解碼一類的耗時(shí)操作桐绒,最好不要在它們里面進(jìn)行。
  • 關(guān)于RecyclerView封裝庫(kù)

10.2 具體優(yōu)化方案

  • 03.SparseArray替代HashMap
  • 04.瀑布流圖片錯(cuò)亂問題解決
  • 05.item點(diǎn)擊事件放在哪里優(yōu)化
  • 06.ViewHolder優(yōu)化
  • 07.連續(xù)上拉加載更多優(yōu)化
  • 08.拖拽排序與滑動(dòng)刪除優(yōu)化
  • 09.暫椭Γ或停止加載數(shù)據(jù)優(yōu)化
  • 11.異常情況下保存狀態(tài)
  • 12.多線程下插入數(shù)據(jù)優(yōu)化
  • 14.recyclerView優(yōu)化處理
  • 15.adapter優(yōu)化
  • 具體看這篇博客:recyclerView優(yōu)化

關(guān)于其他內(nèi)容介紹

01.關(guān)于博客匯總鏈接

02.關(guān)于我的博客

項(xiàng)目開源地址:https://github.com/yangchong211/YCBlogs

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蚀乔,隨后出現(xiàn)的幾起案子烁竭,更是在濱河造成了極大的恐慌,老刑警劉巖乙墙,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颖变,死亡現(xiàn)場(chǎng)離奇詭異生均,居然都是意外死亡听想,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門马胧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)汉买,“玉大人,你說(shuō)我怎么就攤上這事佩脊⊥苷常” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我合蔽,道長(zhǎng)悟衩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任食听,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘伯复。我一直安慰自己,他們只是感情好邢笙,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布啸如。 她就那樣靜靜地躺著,像睡著了一般氮惯。 火紅的嫁衣襯著肌膚如雪叮雳。 梳的紋絲不亂的頭發(fā)上想暗,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音帘不,去河邊找鬼江滨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛厌均,可吹牛的內(nèi)容都是我干的唬滑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼棺弊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼晶密!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起模她,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤稻艰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后侈净,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尊勿,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年畜侦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了元扔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡旋膳,死狀恐怖澎语,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情验懊,我是刑警寧澤擅羞,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站义图,受9級(jí)特大地震影響减俏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜碱工,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一娃承、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧痛垛,春花似錦草慧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蹂析,卻和暖如春舔示,著一層夾襖步出監(jiān)牢的瞬間碟婆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工惕稻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留竖共,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓俺祠,卻偏偏與公主長(zhǎng)得像公给,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蜘渣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355