摘要:錯(cuò)誤使用單利在開發(fā)中單例經(jīng)常需要持有對(duì)象,如果持有的對(duì)象生命周期與單例生命周期更短時(shí),或?qū)е聼o法被釋放回收,則有可能造成內(nèi)存泄漏。如果集合是類型的話,那內(nèi)存泄漏情況就會(huì)更為嚴(yán)重。
目錄介紹
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萬字,陸續(xù)搬到網(wǎng)上],轉(zhuǎn)載請(qǐng)注明出處,謝謝!
鏈接地址:https://github.com/yangchong2...
如果覺得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬事起于忽微,量變引起質(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ì)算等都放在子線程中去,然
后通過handler.sendMessage、runonUIThread、AsyncTask 等方式更新UI。無論如何都要確保用戶界面作的流暢 度。如果耗時(shí)操作需要讓用戶等待,那么可以在界面上顯示度條。 - 使用AsyncTask處理耗時(shí)IO操作。在一些同步的操作主線程有可能被鎖,需要等待其他線程釋放相應(yīng)鎖才能繼續(xù)執(zhí)行,這樣會(huì)有一定的ANR風(fēng)險(xiǎn),對(duì)于這種情況有時(shí)也可以用異步線程來執(zhí)行相應(yīng)的邏輯。另外,要避免死鎖的發(fā)生。 - 使用Handler處理工作線程結(jié)果,而不是使用Thread.wait()或者Thread.sleep()來阻塞主線程。 - Activity的onCreate和onResume回調(diào)中盡量避免耗時(shí)的代碼 - BroadcastReceiver中onReceive代碼也要盡量減少耗時(shí),建議使用IntentService處理。 - 各個(gè)組件的生命周期函數(shù)都不應(yīng)該有太耗時(shí)的操作,即使對(duì)于后臺(tái)Service或者ContentProvider來講,應(yīng)用在后臺(tái)運(yùn)行時(shí)候其onCreate()時(shí)候不會(huì)有用戶輸入引起事件無響應(yīng)ANR,但其執(zhí)行時(shí)間過長(zhǎng)也會(huì)引起Service的ANR和ContentProvider的ANR2.內(nèi)存泄漏優(yōu)化
內(nèi)存檢測(cè)第一種:代碼方式獲取內(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è)引用,然后過一段時(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)畫中有一類無限循環(huán)動(dòng)畫,如果在Activity中播放這類動(dòng)畫并且在onDestroy中去停止動(dòng)畫,那么這個(gè)動(dòng)畫將會(huì)一直播放下去,這時(shí)候Activity會(huì)被View所持有,從而導(dǎo)致Activity無法被釋放。解決此類問題則是需要早Activity中onDestroy去去調(diào)用objectAnimator.cancel()來停止動(dòng)畫。
@Override protected void onDestroy() { super.onDestroy(); mAnimator.cancel(); }2.1 錯(cuò)誤使用單利
在開發(fā)中單例經(jīng)常需要持有Context對(duì)象,如果持有的Context對(duì)象生命周期與單例生命周期更短時(shí),或?qū)е翪ontext無法被釋放回收,則有可能造成內(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持有了引用而無法被回收,所以這樣Activity就總是被AClass持有而無法回收造成內(nèi)存泄露。
2.3 handler內(nèi)存泄漏
造成內(nèi)存泄漏原因分析
通過內(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)存資源無法及時(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的方式,后來逐步被AsyncTask取代,直到現(xiàn)在采用RxJava的方式來處理異步。
造成內(nèi)存泄漏原因分析
在處理一個(gè)比較耗時(shí)的操作時(shí),可能還沒處理結(jié)束MainActivity就執(zhí)行了退出操作,但是此時(shí)AsyncTask依然持有對(duì)MainActivity的引用就會(huì)導(dǎo)致MainActivity無法釋放回收引發(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)部類抽取出來封裝成一個(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)建,不過這種寫法卻會(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加載出來的時(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無法回收該對(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加載出來的時(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è)布局,那么比如說我們程序的所有界面都有一個(gè)公共的部分,這個(gè)時(shí)候最好的做法就是將這個(gè)公共的部分提取到一個(gè)獨(dú)立的布局中,然后每個(gè)界面的布局文件當(dāng)中來引用這個(gè)公共的布局。
如果我們要在標(biāo)簽中覆寫layout屬性,必須要將layout_width和layout_height這兩個(gè)屬性也進(jìn)行覆寫,否則覆寫效果將不會(huì)生效。
標(biāo)簽是作為標(biāo)簽的一種輔助擴(kuò)展來使用的,它的主要作用是為了防止在引用布局文件時(shí)引用文件時(shí)產(chǎn)生多余的布局嵌套。布局嵌套越多,解析起來就越耗時(shí),性能就越差。因此編寫布局文件時(shí)應(yīng)該讓嵌套的層數(shù)越少越好。
舉例:比如在LinearLayout里邊使用一個(gè)布局。里邊又有一個(gè)LinearLayout,那么其實(shí)就存在了多余的布局嵌套,使用merge可以解決這個(gè)問題。
3.2 ViewStub優(yōu)化
僅在需要時(shí)才加載布局[ViewStub]
某個(gè)布局當(dāng)中的元素不是一起顯示出來的,普通情況下只顯示部分常用的元素,而那些不常用的元素只有在用戶進(jìn)行特定操作時(shí)才會(huì)顯示出來。
舉例:填信息時(shí)不是需要全部填的,有一個(gè)添加更多字段的選項(xiàng),當(dāng)用戶需要添加其他信息的時(shí)候,才將另外的元素顯示到界面上。用VISIBLE性能表現(xiàn)一般,可以用ViewStub。
ViewStub也是View的一種,但是沒有大小,沒有繪制功能,也不參與布局,資源消耗非常低,可以認(rèn)為完全不影響性能。
ViewStub所加載的布局是不可以使用標(biāo)簽的,因此這有可能導(dǎo)致加載出來出來的布局存在著多余的嵌套結(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í)
這個(gè)標(biāo)簽在UI的結(jié)構(gòu)優(yōu)化中起著非常重要的作用,它可以刪減多余的層級(jí),優(yōu)化UI。但是就有一點(diǎn)不好,無法預(yù)覽布局效果!
3.4 其他建議
減少太多重疊的背景(overdraw)
這個(gè)問題其實(shí)最容易解決,建議就是檢查你在布局和代碼中設(shè)置的背景,有些背景是隱藏在底下的,它永遠(yuǎn)不可能顯示出來,這種沒必要的背景一定要移除,因?yàn)樗芸赡軙?huì)嚴(yán)重影響到app的性能。如果采用的是selector的背景,將normal狀態(tài)的color設(shè)置為”@android:color/transparent”,也同樣可以解決問題。
避免復(fù)雜的Layout層級(jí)
這里的建議比較多一些,首先推薦使用Android提供的布局工具Hierarchy Viewer來檢查和優(yōu)化布局。第一個(gè)建議是:如果嵌套的線性布局加深了布局層次,可以使用相對(duì)布局來取代。第二個(gè)建議是:用標(biāo)簽來合并布局。第三個(gè)建議是:用標(biāo)簽來重用布局,抽取通用的布局可以讓布局的邏輯更清晰明了。記住,這些建議的最終目的都是使得你的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檢查去除無效代碼
lint去除無效資源和代碼
如何檢測(cè)哪些圖片未被使用
點(diǎn)擊菜單欄 Analyze -> Run Inspection by Name -> unused resources -> Moudule ‘a(chǎn)pp’ -> OK,這樣會(huì)搜出來哪些未被使用到未使用到xml和圖片,如下:
如何檢測(cè)哪些無效代碼
使用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來進(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ì)象來代替,因?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)用它的某些方法來去完成一項(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)方法無法訪問非靜態(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)賦值完成后,我們就可以通過字段搜尋的方式去訪問具體的值了。
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ù)類型的常量是無效的。
在沒有特殊原因的情況下,盡量使用基本數(shù)據(jù)類型來代替封裝數(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ù)組,使用起來要比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)化過的數(shù)據(jù)集合
Android提供了一系列優(yōu)化過后的數(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ā)說明用戶離開了程序,此時(shí)就可以進(jìn)行資源釋放操作了。
當(dāng)時(shí)看到這個(gè)覺得很新奇的,但是具體還是沒有用到,要是那個(gè)大神有具體操作方案,可以分享一下。
4.7 節(jié)制的使用Service
節(jié)制的使用Service
如果應(yīng)用程序需要使用Service來執(zhí)行后臺(tái)任務(wù)的話,只有當(dāng)任務(wù)正在執(zhí)行的時(shí)候才應(yīng)該讓Service運(yùn)行起來。當(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)單來說根據(jù)用戶的當(dāng)前的網(wǎng)絡(luò)質(zhì)量來判斷下載什么質(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來說默認(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)求過多,也可以考慮做一定的請(qǐng)求合并。
減少請(qǐng)求數(shù)據(jù)的大小:對(duì)于post請(qǐng)求,body可以做gzip壓縮的,header也可以做數(shù)據(jù)壓縮(不過只支持http
返回?cái)?shù)據(jù)的body也可以做gzip壓縮,body數(shù)據(jù)體積可以縮小到原來的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ò),我們可以通過攔截器在這兩層攔截錯(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ò)誤碼來拋出對(duì)應(yīng)的異常。比如,token失效,禁用同賬號(hào)登陸多臺(tái)設(shè)備,缺少參數(shù),參數(shù)傳遞異常等等。
3.除此以外,為了我們要盡量避免在View層對(duì)錯(cuò)誤進(jìn)行判斷,處理,我們必須還要設(shè)置一個(gè)攔截器,攔截onError事件,然后使用ExceptionUtils,讓其根據(jù)錯(cuò)誤類型來分別處理。
具體可以直接看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)端來說,最大的影響就是造成界面卡頓
線程的創(chuàng)建和銷毀都需要時(shí)間,當(dāng)有大量的線程創(chuàng)建和銷毀時(shí),那么這些時(shí)間的消耗則比較明顯,將導(dǎo)致性能上的缺失
為什么要用線程池
重用線程池中的線程,避免頻繁地創(chuàng)建和銷毀線程帶來的性能消耗;有效控制線程的最大并發(fā)數(shù)量,防止線程過大導(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í)操作放到子線程中,處理過程中,可以檢測(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ì)量壓縮方法:在保持像素的前提下改變圖片的位深及透明度等,來達(dá)到壓縮圖片的目的,這樣適合去傳遞二進(jìn)制的圖片數(shù)據(jù),比如分享圖片,要傳入二進(jìn)制數(shù)據(jù)過去,限制500kb之內(nèi)。
采樣率壓縮方法:設(shè)置inSampleSize的值(int類型)后,假如設(shè)為n,則寬和高都為原來的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所采用。
具體看這篇文章
https://www.jianshu.com/p/cf1...
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),具體說來就是從發(fā)Application的onCreate()執(zhí)行開始到MainActivity的onCreate()執(zhí)行結(jié)束這一段時(shí)間。
啟動(dòng)時(shí)間優(yōu)化
Application的onCreate()方法
MainActivity的onCreate()方法
優(yōu)化的手段也無非三種,如下所示:
延遲初始化
后臺(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è)置的主題顏色的不同來展示一個(gè)白屏或者黑屏。而這個(gè)黑(白)屏正式的稱呼應(yīng)該是Preview Window,即預(yù)覽窗口。
實(shí)際上就是是activity默認(rèn)的主題中的android:windowBackground為白色或者黑色導(dǎo)致的。
總結(jié)來說啟動(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從來沒有被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
*/ 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; } /** * 通過注解限定類型 * 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è)試工具,通過該自動(dòng)化腳本,自需要run一下或者命令行運(yùn)行腳本即可實(shí)現(xiàn)美團(tuán)瓦力多渠道打包,打包速度很快。配置信息十分簡(jiǎn)單,代碼中已經(jīng)注釋十分詳細(xì)??梢宰远x輸出文件路徑,可以修改多渠道配置信息,簡(jiǎn)單實(shí)用。 項(xiàng)目地址:https://github.com/yangchong2...
9.4 TrimMemory和LowMemory優(yōu)化
可以優(yōu)化什么?
在 onTrimMemory() 回調(diào)中,應(yīng)該在一些狀態(tài)下清理掉不重要的內(nèi)存資源。對(duì)于這些緩存,只要是讀進(jìn)內(nèi)存內(nèi)的都算,例如最常見的圖片緩存、文件緩存等。拿圖片緩存來說,市場(chǎng)上,常規(guī)的圖片加載庫(kù),一般而言都是三級(jí)緩存,所以在內(nèi)存吃緊的時(shí)候,我們就應(yīng)該優(yōu)先清理掉這部分圖片緩存,畢竟圖片是吃內(nè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()方法來交給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è)階段看過這類輪詢操作!
具體優(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)目代碼??碼pp包下的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)上有許多博客作了這方面說明。但是我在這里想說,如何查找自己項(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
然后修改gradle配置代碼
api (rootProject.ext.dependencies["zxing"]){ exclude module: "support-v4" exclude module: "appcompat-v7" }9.7 四種引用優(yōu)化
軟引用使用場(chǎng)景
正常是用來處理大圖片這種占用內(nèi)存大的情況
代碼如下所示
Bitmap bitmap = bitmaps.get(position); //正常是用來處理圖片這種占用內(nèi)存大的情況 bitmapSoftReference = new SoftReference<>(bitmap); if(bitmapSoftReference.get() != null) { viewHolder.imageView.setImageBitmap(bitmapSoftReference.get()); } //其實(shí)看glide底層源碼可知,也做了相關(guān)軟引用的操作
這樣使用軟引用好處
通過軟引用的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í)屏幕上背景圖不顯示,說明在系統(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{ WeakReferenceweakReference; 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)常使用來判斷。如果該對(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加載
https://github.com/yangchong2...
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ì)象有明確的生命周期,那么就可以通過定義一個(gè)對(duì)象池來高效的完成復(fù)用對(duì)象。
具體參考案例,可以看該項(xiàng)目:https://github.com/yangchong2...
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ù)
https://github.com/yangchong2...
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.暫?;蛲V辜虞d數(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)于博客匯總鏈接1.技術(shù)博客匯總
2.開源項(xiàng)目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
02.關(guān)于我的博客我的個(gè)人站點(diǎn):www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡(jiǎn)書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國(guó):https://my.oschina.net/zbj161...
泡在網(wǎng)上的日子:http://www.jcodecraeer.com/me...
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
項(xiàng)目開源地址:https://github.com/yangchong2...文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/77792.html
摘要:然而,中依然有可能發(fā)生內(nèi)存泄漏。所以你的安卓快速定位解決內(nèi)存泄漏掘金昨天是個(gè)好日子,程序員的節(jié)日,在這里給所有的程序員送上一份遲到的祝福。應(yīng)用內(nèi)存泄漏的定位分析與解決策略掘金,大家好,我是。 Android 性能優(yōu)化之巧用軟引用與弱引用優(yōu)化內(nèi)存使用 - Android - 掘金前言: 從事Android開發(fā)的同學(xué)都知道移動(dòng)設(shè)備的內(nèi)存使用是非常敏感的話題,今天我們來看下如何使用軟引用與弱...
摘要:導(dǎo)語(yǔ)智能手機(jī)發(fā)展到今天已經(jīng)有十幾個(gè)年頭,手機(jī)的軟硬件都已經(jīng)發(fā)生了翻天覆地的變化,特別是陣營(yíng),從一開始的一兩百到今天動(dòng)輒,內(nèi)存。恰好最近做了內(nèi)存優(yōu)化相關(guān)的工作,這里也對(duì)內(nèi)存優(yōu)化相關(guān)的知識(shí)做下總結(jié)。 導(dǎo)語(yǔ) 智能手機(jī)發(fā)展到今天已經(jīng)有十幾個(gè)年頭,手機(jī)的軟硬件都已經(jīng)發(fā)生了翻天覆地的變化,特別是Android陣營(yíng),從一開始的一兩百M(fèi)到今天動(dòng)輒4G,6G內(nèi)存。然而大部分的開發(fā)者觀看下自己的異常上報(bào)系...
閱讀 3225·2021-11-10 11:35
閱讀 1321·2019-08-30 13:20
閱讀 1145·2019-08-29 16:18
閱讀 2160·2019-08-26 13:54
閱讀 2184·2019-08-26 13:50
閱讀 982·2019-08-26 13:39
閱讀 2509·2019-08-26 12:08
閱讀 1974·2019-08-26 10:37