为什么Android项目mainactivity中有一个变量R_博客笔记大汇总,Android优化总结篇
博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識點(diǎn),Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長期更新維護(hù)并且修正,持續(xù)完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計(jì)N篇[近100萬字,陸續(xù)搬到網(wǎng)上],轉(zhuǎn)載請注明出處,謝謝!
鏈接地址:https://github.com/yangchong211/YCBlogs
如果覺得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬事起于忽微,量變引起質(zhì)變!
1.OOM和崩潰優(yōu)化
1.2 ANR優(yōu)化
ANR的產(chǎn)生需要滿足三個條件
主線程:只有應(yīng)用程序進(jìn)程的主線程響應(yīng)超時才會產(chǎn)生ANR;
超時時間:產(chǎn)生ANR的上下文不同,超時時間也會不同,但只要在這個時間上限內(nèi)沒有響應(yīng)就會ANR;
輸入事件/特定操作:輸入事件是指按鍵、觸屏等設(shè)備輸入事件,特定操作是指BroadcastReceiver和Service的生命周期中的各個函數(shù),產(chǎn)生ANR的上下文不同,導(dǎo)致ANR的原因也會不同;
ANR優(yōu)化具體措施
將所有耗時操作,比如訪問網(wǎng)絡(luò),Socket通信,查詢大量SQL 語句,復(fù)雜邏輯計(jì)算等都放在子線程中去,然
后通過handler.sendMessage、runonUIThread、AsyncTask 等方式更新UI。無論如何都要確保用戶界面作的流暢
度。如果耗時操作需要讓用戶等待,那么可以在界面上顯示度條。使用AsyncTask處理耗時IO操作。在一些同步的操作主線程有可能被鎖,需要等待其他線程釋放相應(yīng)鎖才能繼續(xù)執(zhí)行,這樣會有一定的ANR風(fēng)險(xiǎn),對于這種情況有時也可以用異步線程來執(zhí)行相應(yīng)的邏輯。另外,要避免死鎖的發(fā)生。
使用Handler處理工作線程結(jié)果,而不是使用Thread.wait()或者Thread.sleep()來阻塞主線程。
Activity的onCreate和onResume回調(diào)中盡量避免耗時的代碼
BroadcastReceiver中onReceive代碼也要盡量減少耗時,建議使用IntentService處理。
各個組件的生命周期函數(shù)都不應(yīng)該有太耗時的操作,即使對于后臺Service或者ContentProvider來講,應(yīng)用在后臺運(yùn)行時候其onCreate()時候不會有用戶輸入引起事件無響應(yīng)ANR,但其執(zhí)行時間過長也會引起Service的ANR和ContentProvider的ANR
2.內(nèi)存泄漏優(yōu)化
內(nèi)存檢測第一種:代碼方式獲取內(nèi)存
/**?*?內(nèi)存使用檢測:可以調(diào)用系統(tǒng)的getMemoryInfo()來獲取當(dāng)前內(nèi)存的使用情況
?*/
內(nèi)存檢測第二種:leakcanary工具
LeakCanary的原理是監(jiān)控每個activity,在activity ondestory后,在后臺線程檢測引用,然后過一段時間進(jìn)行g(shù)c,gc后如果引用還在,那么dump出內(nèi)存堆棧,并解析進(jìn)行可視化顯示。
2.0 動畫資源未釋放
問題代碼
public?解決辦法
在屬性動畫中有一類無限循環(huán)動畫,如果在Activity中播放這類動畫并且在onDestroy中去停止動畫,那么這個動畫將會一直播放下去,這時候Activity會被View所持有,從而導(dǎo)致Activity無法被釋放。解決此類問題則是需要早Activity中onDestroy去去調(diào)用objectAnimator.cancel()來停止動畫。
2.1 錯誤使用單利
在開發(fā)中單例經(jīng)常需要持有Context對象,如果持有的Context對象生命周期與單例生命周期更短時,或?qū)е翪ontext無法被釋放回收,則有可能造成內(nèi)存泄漏。比如:在一個Activity中調(diào)用的,然后關(guān)閉該Activity則會出現(xiàn)內(nèi)存泄漏。
解決辦法:
要保證Context和AppLication的生命周期一樣,修改后代碼如下:
this.mContext = context.getApplicationContext();
1、如果此時傳入的是 Application 的 Context,因?yàn)?Application 的生命周期就是整個應(yīng)用的生命周期,所以這將沒有任何問題。
2、如果此時傳入的是 Activity 的 Context,當(dāng)這個 Context 所對應(yīng)的 Activity 退出時,由于該 Context 的引用被單例對象所持有,其生命周期等于整個應(yīng)用程序的生命周期,所以當(dāng)前 Activity 退出時它的內(nèi)存并不會被回收,這就造成泄漏了。
2.2 錯誤使用靜態(tài)變量
使用靜態(tài)方法是十分方便的。但是創(chuàng)建的對象,建議不要全局化,全局化的變量必須加上static。全局化后的變量或者對象會導(dǎo)致內(nèi)存泄漏!
原因分析
這里內(nèi)部類AClass隱式的持有外部類Activity的引用,而在Activity的onCreate方法中調(diào)用了。這樣AClass就會在Activity創(chuàng)建的時候是有了他的引用,而AClass是靜態(tài)類型的不會被垃圾回收,Activity在執(zhí)行onDestory方法的時候由于被AClass持有了引用而無法被回收,所以這樣Activity就總是被AClass持有而無法回收造成內(nèi)存泄露。
2.3 handler內(nèi)存泄漏
造成內(nèi)存泄漏原因分析
通過內(nèi)部類的方式創(chuàng)建mHandler對象,此時mHandler會隱式地持有一個外部類對象引用這里就是MainActivity,當(dāng)執(zhí)行postDelayed方法時,該方法會將你的Handler裝入一個Message,并把這條Message推到MessageQueue中,MessageQueue是在一個Looper線程中不斷輪詢處理消息,那么當(dāng)這個Activity退出時消息隊(duì)列中還有未處理的消息或者正在處理消息,而消息隊(duì)列中的Message持有mHandler實(shí)例的引用,mHandler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無法及時回收,引發(fā)內(nèi)存泄漏。
解決Handler內(nèi)存泄露主要2點(diǎn)
有延時消息,要在Activity銷毀的時候移除Messages監(jiān)聽
匿名內(nèi)部類導(dǎo)致的泄露改為匿名靜態(tài)內(nèi)部類,并且對上下文或者Activity使用弱引用。
2.4 線程造成內(nèi)存泄漏
早時期的時候處理耗時操作多數(shù)都是采用Thread+Handler的方式,后來逐步被AsyncTask取代,直到現(xiàn)在采用RxJava的方式來處理異步。
造成內(nèi)存泄漏原因分析
在處理一個比較耗時的操作時,可能還沒處理結(jié)束MainActivity就執(zhí)行了退出操作,但是此時AsyncTask依然持有對MainActivity的引用就會導(dǎo)致MainActivity無法釋放回收引發(fā)內(nèi)存泄漏。
解決辦法
在使用AsyncTask時,在Activity銷毀時候也應(yīng)該取消相應(yīng)的任務(wù)AsyncTask.cancel()方法,避免任務(wù)在后臺執(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)存泄漏。有的時候我們可能會在啟動頻繁的Activity中,為了避免重復(fù)創(chuàng)建相同的數(shù)據(jù)資源,可能會出現(xiàn)這種寫法。
問題代碼
private?解決辦法
將該內(nèi)部類設(shè)為靜態(tài)內(nèi)部類或?qū)⒃搩?nèi)部類抽取出來封裝成一個單例,如果需要使用Context,請按照上面推薦的使用Application 的 Context。
分析問題
這樣就在Activity內(nèi)部創(chuàng)建了一個非靜態(tài)內(nèi)部類的單例,每次啟動Activity時都會使用該單例的數(shù)據(jù),這樣雖然避免了資源的重復(fù)創(chuàng)建,不過這種寫法卻會造成內(nèi)存泄漏,因?yàn)榉庆o態(tài)內(nèi)部類默認(rèn)會持有外部類的引用,而該非靜態(tài)內(nèi)部類又創(chuàng)建了一個靜態(tài)的實(shí)例,該實(shí)例的生命周期和應(yīng)用的一樣長,這就導(dǎo)致了該靜態(tài)實(shí)例一直會持有該Activity的引用,導(dǎo)致Activity的內(nèi)存資源不能正常回收。
2.6 未移除監(jiān)聽
問題代碼
//add監(jiān)聽,放到集合里面解決辦法
//計(jì)算完后,一定要移除這個監(jiān)聽注意事項(xiàng):
//監(jiān)聽執(zhí)行完回收對象,不用考慮內(nèi)存泄漏2.7 持有activity引用
2.8 資源未關(guān)閉
在使用IO、File流或者Sqlite、Cursor等資源時要及時關(guān)閉。這些資源在進(jìn)行讀寫操作時通常都使用了緩沖,如果及時不關(guān)閉,這些緩沖對象就會一直被占用而得不到釋放,以致發(fā)生內(nèi)存泄露。因此我們在不需要使用它們的時候就及時關(guān)閉,以便緩沖能及時得到釋放,從而避免內(nèi)存泄露。
BroadcastReceiver,ContentObserver,FileObserver,Cursor,Callback等在 Activity onDestroy 或者某類生命周期結(jié)束之后一定要 unregister 或者 close 掉,否則這個 Activity 類會被 system 強(qiáng)引用,不會被內(nèi)存回收。值得注意的是,關(guān)閉的語句必須在finally中進(jìn)行關(guān)閉,否則有可能因?yàn)楫惓N搓P(guān)閉資源,致使activity泄漏。
2.9 其他原因
靜態(tài)集合使用不當(dāng)導(dǎo)致的內(nèi)存泄漏
有時候我們需要把一些對象加入到集合容器(例如ArrayList)中,當(dāng)不再需要當(dāng)中某些對象時,如果不把該對象的引用從集合中清理掉,也會使得GC無法回收該對象。如果集合是static類型的話,那內(nèi)存泄漏情況就會更為嚴(yán)重。因此,當(dāng)不再需要某對象時,需要主動將之從集合中移除。
不需要用的監(jiān)聽未移除會發(fā)生內(nèi)存泄露
問題代碼
//add監(jiān)聽,放到集合里面解決辦法
//計(jì)算完后,一定要移除這個監(jiān)聽注意事項(xiàng):
//監(jiān)聽執(zhí)行完回收對象,不用考慮內(nèi)存泄漏3.布局優(yōu)化
3.1 include優(yōu)化
重用布局文件
標(biāo)簽可以允許在一個布局當(dāng)中引入另一個布局,那么比如說我們程序的所有界面都有一個公共的部分,這個時候最好的做法就是將這個公共的部分提取到一個獨(dú)立的布局中,然后每個界面的布局文件當(dāng)中來引用這個公共的布局。
如果我們要在標(biāo)簽中覆寫layout屬性,必須要將layout_width和layout_height這兩個屬性也進(jìn)行覆寫,否則覆寫效果將不會生效。
標(biāo)簽是作為標(biāo)簽的一種輔助擴(kuò)展來使用的,它的主要作用是為了防止在引用布局文件時引用文件時產(chǎn)生多余的布局嵌套。布局嵌套越多,解析起來就越耗時,性能就越差。因此編寫布局文件時應(yīng)該讓嵌套的層數(shù)越少越好。
舉例:比如在LinearLayout里邊使用一個布局。里邊又有一個LinearLayout,那么其實(shí)就存在了多余的布局嵌套,使用merge可以解決這個問題。
3.2 ViewStub優(yōu)化
僅在需要時才加載布局[ViewStub]
某個布局當(dāng)中的元素不是一起顯示出來的,普通情況下只顯示部分常用的元素,而那些不常用的元素只有在用戶進(jìn)行特定操作時才會顯示出來。
舉例:填信息時不是需要全部填的,有一個添加更多字段的選項(xiàng),當(dāng)用戶需要添加其他信息的時候,才將另外的元素顯示到界面上。用VISIBLE性能表現(xiàn)一般,可以用ViewStub。
ViewStub也是View的一種,但是沒有大小,沒有繪制功能,也不參與布局,資源消耗非常低,可以認(rèn)為完全不影響性能。
ViewStub所加載的布局是不可以使用標(biāo)簽的,因此這有可能導(dǎo)致加載出來出來的布局存在著多余的嵌套結(jié)構(gòu)。
自定義全局的狀態(tài)管理器【充分使用ViewStub】
針對多狀態(tài),有數(shù)據(jù),空數(shù)據(jù),加載失敗,加載異常,網(wǎng)絡(luò)異常等。針對空數(shù)據(jù),加載失敗,異常使用viewStub布局,一鍵設(shè)置自定義布局,也是優(yōu)化的一種。
3.3 merge優(yōu)化
視圖層級
這個標(biāo)簽在UI的結(jié)構(gòu)優(yōu)化中起著非常重要的作用,它可以刪減多余的層級,優(yōu)化UI。但是就有一點(diǎn)不好,無法預(yù)覽布局效果!
3.4 其他建議
減少太多重疊的背景(overdraw)
這個問題其實(shí)最容易解決,建議就是檢查你在布局和代碼中設(shè)置的背景,有些背景是隱藏在底下的,它永遠(yuǎn)不可能顯示出來,這種沒必要的背景一定要移除,因?yàn)樗芸赡軙?yán)重影響到app的性能。如果采用的是selector的背景,將normal狀態(tài)的color設(shè)置為”@android:color/transparent”,也同樣可以解決問題。
避免復(fù)雜的Layout層級
這里的建議比較多一些,首先推薦使用Android提供的布局工具Hierarchy Viewer來檢查和優(yōu)化布局。第一個建議是:如果嵌套的線性布局加深了布局層次,可以使用相對布局來取代。第二個建議是:用標(biāo)簽來合并布局。第三個建議是:用標(biāo)簽來重用布局,抽取通用的布局可以讓布局的邏輯更清晰明了。記住,這些建議的最終目的都是使得你的Layout在Hierarchy Viewer里變得寬而淺,而不是窄而深。
總結(jié):可以考慮多使用merge和include,ViewStub。盡量使布局淺平,根布局盡量少使用RelactivityLayout,因?yàn)镽elactivityLayout每次需要測量2次。
4.代碼優(yōu)化
都是一些微優(yōu)化,在性能方面看不出有什么顯著的提升的。使用合適的算法和數(shù)據(jù)結(jié)構(gòu)是優(yōu)化程序性能的最主要手段。
4.1 建議使用lint檢查去除無效代碼
lint去除無效資源和代碼
如何檢測哪些圖片未被使用
點(diǎn)擊菜單欄 Analyze -> Run Inspection by Name -> unused resources -> Moudule ‘a(chǎn)pp’ -> OK,這樣會搜出來哪些未被使用到未使用到xml和圖片,如下:
如何檢測哪些無效代碼
使用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)建不必要的對象 不必要的對象應(yīng)該避免創(chuàng)建:
如果有需要拼接的字符串,那么可以優(yōu)先考慮使用StringBuffer或者StringBuilder來進(jìn)行拼接,而不是加號連接符,因?yàn)槭褂眉犹栠B接符會創(chuàng)建多余的對象,拼接的字符串越長,加號連接符的性能越低。
當(dāng)一個方法的返回值是String的時候,通常需要去判斷一下這個String的作用是什么,如果明確知道調(diào)用方會將返回的String再進(jìn)行拼接操作的話,可以考慮返回一個StringBuffer對象來代替,因?yàn)檫@樣可以將一個對象的引用進(jìn)行返回,而返回String的話就是創(chuàng)建了一個短生命周期的臨時對象。
盡可能地少創(chuàng)建臨時對象,越少的對象意味著越少的GC操作。
nDraw方法里面不要執(zhí)行對象的創(chuàng)建
靜態(tài)優(yōu)于抽象
如果你并不需要訪問一個對系那個中的某些字段,只是想調(diào)用它的某些方法來去完成一項(xiàng)通用的功能,那么可以將這個方法設(shè)置成靜態(tài)方法,調(diào)用速度提升15%-20%,同時也不用為了調(diào)用這個方法去專門創(chuàng)建對象了,也不用擔(dān)心調(diào)用這個方法后是否會改變對象的狀態(tài)(靜態(tài)方法無法訪問非靜態(tài)字段)。
對常量使用static final修飾符
static int intVal = 42; ?static String strVal = "Hello, world!";
編譯器會為上面的代碼生成一個初始方法,稱為方法,該方法會在定義類第一次被使用的時候調(diào)用。這個方法會將42的值賦值到intVal當(dāng)中,從字符串常量表中提取一個引用賦值到strVal上。當(dāng)賦值完成后,我們就可以通過字段搜尋的方式去訪問具體的值了。
final進(jìn)行優(yōu)化:
static final int intVal = 42; ?static final String strVal = "Hello, world!";
這樣,定義類就不需要方法了,因?yàn)樗械某A慷紩赿ex文件的初始化器當(dāng)中進(jìn)行初始化。當(dāng)我們調(diào)用intVal時可以直接指向42的值,而調(diào)用strVal會用一種相對輕量級的字符串常量方式,而不是字段搜尋的方式。
這種優(yōu)化方式只對基本數(shù)據(jù)類型以及String類型的常量有效,對于其他數(shù)據(jù)類型的常量是無效的。
在沒有特殊原因的情況下,盡量使用基本數(shù)據(jù)類型來代替封裝數(shù)據(jù)類型,int比Integer要更加有效,其它數(shù)據(jù)類型也是一樣。
基本數(shù)據(jù)類型的數(shù)組也要優(yōu)于對象數(shù)據(jù)類型的數(shù)組。另外兩個平行的數(shù)組要比一個封裝好的對象數(shù)組更加高效,舉個例子,Foo[]和Bar[]這樣的數(shù)組,使用起來要比Custom[][]和Bar[]這樣的數(shù)組,使用起來要比Custom(Foo,Bar)[]這樣的一個數(shù)組高效的多。
4.3 View異常優(yōu)化
view自定義控件異常銷毀保存狀態(tài)
經(jīng)常容易被人忽略,但是為了追求高質(zhì)量代碼,這個也有必要加上。舉個例子!
4.4 去除淡黃色警告優(yōu)化
淡黃色警告雖然不會造成崩潰,但是作為程序員還是要盡量去除淡黃色警告,規(guī)范代碼
4.5 合理使用集合
使用優(yōu)化過的數(shù)據(jù)集合
Android提供了一系列優(yōu)化過后的數(shù)據(jù)集合工具類,如SparseArray、SparseBooleanArray、LongSparseArray,使用這些API可以讓我們的程序更加高效。HashMap工具類會相對比較低效,因?yàn)樗枰獮槊恳粋€鍵值對都提供一個對象入口,而SparseArray就避免掉了基本數(shù)據(jù)類型轉(zhuǎn)換成對象數(shù)據(jù)類型的時間。
4.6 Activity不可見優(yōu)化
當(dāng)Activity界面不可見時釋放內(nèi)存
當(dāng)用戶打開了另外一個程序,我們的程序界面已經(jīng)不可見的時候,我們應(yīng)當(dāng)將所有和界面相關(guān)的資源進(jìn)行釋放。重寫Activity的onTrimMemory()方法,然后在這個方法中監(jiān)聽TRIM_MEMORY_UI_HIDDEN這個級別,一旦觸發(fā)說明用戶離開了程序,此時就可以進(jìn)行資源釋放操作了。
當(dāng)時看到這個覺得很新奇的,但是具體還是沒有用到,要是那個大神有具體操作方案,可以分享一下。
4.7 節(jié)制的使用Service
節(jié)制的使用Service
如果應(yīng)用程序需要使用Service來執(zhí)行后臺任務(wù)的話,只有當(dāng)任務(wù)正在執(zhí)行的時候才應(yīng)該讓Service運(yùn)行起來。當(dāng)啟動一個Service時,系統(tǒng)會傾向于將這個Service所依賴的進(jìn)程進(jìn)行保留,系統(tǒng)可以在LRUcache當(dāng)中緩存的進(jìn)程數(shù)量也會減少,導(dǎo)致切換程序的時候耗費(fèi)更多性能。我們可以使用IntentService,當(dāng)后臺任務(wù)執(zhí)行結(jié)束后會自動停止,避免了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)條件下加載縮略圖【也稱與加載圖】。
簡單來說根據(jù)用戶的當(dāng)前的網(wǎng)絡(luò)質(zhì)量來判斷下載什么質(zhì)量的圖片(電商用的比較多)。豆瓣開源接口可以參考一下!
5.2 獲取網(wǎng)絡(luò)數(shù)據(jù)優(yōu)化
移動端獲取網(wǎng)絡(luò)數(shù)據(jù)優(yōu)化的幾個點(diǎn)
連接復(fù)用:節(jié)省連接建立時間,如開啟 keep-alive。
對于Android來說默認(rèn)情況下HttpURLConnection和HttpClient都開啟了keep-alive。只是2.2之前HttpURLConnection存在影響連接池的Bug,具體可見:Android HttpURLConnection及HttpClient選擇
請求合并:即將多個請求合并為一個進(jìn)行請求,比較常見的就是網(wǎng)頁中的CSS Image Sprites。如果某個頁面內(nèi)請求過多,也可以考慮做一定的請求合并。
減少請求數(shù)據(jù)的大小:對于post請求,body可以做gzip壓縮的,header也可以做數(shù)據(jù)壓縮(不過只支持http
返回?cái)?shù)據(jù)的body也可以做gzip壓縮,body數(shù)據(jù)體積可以縮小到原來的30%左右。(也可以考慮壓縮返回的json數(shù)據(jù)的key數(shù)據(jù)的體積,尤其是針對返回?cái)?shù)據(jù)格式變化不大的情況,支付寶聊天返回的數(shù)據(jù)用到了)
5.3 網(wǎng)絡(luò)請求異常攔截優(yōu)化
在獲取數(shù)據(jù)的流程中,訪問接口和解析數(shù)據(jù)時都有可能會出錯,我們可以通過攔截器在這兩層攔截錯誤。
1.在訪問接口時,我們不用設(shè)置攔截器,因?yàn)橐坏┏霈F(xiàn)錯誤,Retrofit會自動拋出異常。比如,常見請求異常404,500,503等等。
2.在解析數(shù)據(jù)時,我們設(shè)置一個攔截器,判斷Result里面的code是否為成功,如果不成功,則要根據(jù)與服務(wù)器約定好的錯誤碼來拋出對應(yīng)的異常。比如,token失效,禁用同賬號登陸多臺設(shè)備,缺少參數(shù),參數(shù)傳遞異常等等。
3.除此以外,為了我們要盡量避免在View層對錯誤進(jìn)行判斷,處理,我們必須還要設(shè)置一個攔截器,攔截onError事件,然后使用ExceptionUtils,讓其根據(jù)錯誤類型來分別處理。
具體可以直接看lib中的ExceptionUtils類,那么如何調(diào)用呢?入侵性極低,不用改變之前的代碼!
6.線程優(yōu)化
6.1 使用線程池
將全局線程用線程池管理
直接創(chuàng)建Thread實(shí)現(xiàn)runnable方法的弊端
大量的線程的創(chuàng)建和銷毀很容易導(dǎo)致GC頻繁的執(zhí)行,從而發(fā)生內(nèi)存抖動現(xiàn)象,而發(fā)生了內(nèi)存抖動,對于移動端來說,最大的影響就是造成界面卡頓
線程的創(chuàng)建和銷毀都需要時間,當(dāng)有大量的線程創(chuàng)建和銷毀時,那么這些時間的消耗則比較明顯,將導(dǎo)致性能上的缺失
為什么要用線程池
重用線程池中的線程,避免頻繁地創(chuàng)建和銷毀線程帶來的性能消耗;有效控制線程的最大并發(fā)數(shù)量,防止線程過大導(dǎo)致?lián)屨假Y源造成系統(tǒng)阻塞;可以對線程進(jìn)行一定地管理。
使用線程池管理的經(jīng)典例子
RxJava,RxAndroid,底層對線程池的封裝管理特別值得參考
關(guān)于線程池,線程,多線程的具體內(nèi)容
參考:輕量級線程池封裝庫,支持異步回調(diào),可以檢測線程執(zhí)行的狀態(tài)
該項(xiàng)目中哪里用到頻繁new Thread
保存圖片[注意,尤其是大圖和多圖場景下注意耗時太久];某些頁面從數(shù)據(jù)庫查詢數(shù)據(jù);設(shè)置中心清除圖片,視頻,下載文件,日志,系統(tǒng)緩存等緩存內(nèi)容
使用線程池管理庫好處,比如保存圖片,耗時操作放到子線程中,處理過程中,可以檢測到執(zhí)行開始,異常,成功,失敗等多種狀態(tài)。
7.圖片優(yōu)化
7.1 bitmap優(yōu)化
加載圖片所占的內(nèi)存大小計(jì)算方式
加載網(wǎng)絡(luò)圖片:bitmap內(nèi)存大小 = 圖片長度 x 圖片寬度 x 單位像素占用的字節(jié)數(shù)【看到網(wǎng)上很多都是這樣寫的,但是不全面】
加載本地圖片:bitmap內(nèi)存大小 = width * height * nTargetDensity/inDensity 一個像素所占的內(nèi)存。注意不要忽略了一個影響項(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對圖像進(jìn)行縮放、旋轉(zhuǎn)、平移、斜切等變換的。功能十分強(qiáng)大!
第二種加載圖片優(yōu)化:不壓縮加載高清圖片如何做?
使用BitmapRegionDecoder,主要用于顯示圖片的某一塊矩形區(qū)域,如果你需要顯示某個圖片的指定區(qū)域,那么這個類非常合適。
7.2 glide加載優(yōu)化
在畫廊中加載大圖
假如你滑動特別快,glide加載優(yōu)化就顯得非常重要呢,具體優(yōu)化方法如下所示
8.加載優(yōu)化
8.1 懶加載優(yōu)化
該優(yōu)化在新聞類app中十分常見
ViewPager+Fragment的搭配在日常開發(fā)中也比較常見,可用于切換展示不同類別的頁面。
懶加載,其實(shí)也就是延遲加載,就是等到該頁面的UI展示給用戶時,再加載該頁面的數(shù)據(jù)(從網(wǎng)絡(luò)、數(shù)據(jù)庫等),而不是依靠ViewPager預(yù)加載機(jī)制提前加載兩三個,甚至更多頁面的數(shù)據(jù)。這樣可以提高所屬Activity的初始化速度,也可以為用戶節(jié)省流量.而這種懶加載的方式也已經(jīng)/正在被諸多APP所采用。
8.2 啟動頁優(yōu)化
啟動時間分析
系統(tǒng)創(chuàng)建進(jìn)程的時間和應(yīng)用進(jìn)程啟動的時間,前者是由系統(tǒng)自行完成的,一般都會很快,我們也干預(yù)不了,我覺得能做的就是去優(yōu)化應(yīng)用進(jìn)程啟動,具體說來就是從發(fā)Application的onCreate()執(zhí)行開始到MainActivity的onCreate()執(zhí)行結(jié)束這一段時間。
啟動時間優(yōu)化
Application的onCreate()方法
MainActivity的onCreate()方法
優(yōu)化的手段也無非三種,如下所示:
延遲初始化
后臺任務(wù)
啟動界面預(yù)加載
啟動頁白屏優(yōu)化
為什么存在這個問題?
當(dāng)系統(tǒng)啟動一個APP時,zygote進(jìn)程會首先創(chuàng)建一個新的進(jìn)程去運(yùn)行這個APP,但是進(jìn)程的創(chuàng)建是需要時間的,在創(chuàng)建完成之前,界面是呈現(xiàn)假死狀態(tài),于是系統(tǒng)根據(jù)你的manifest文件設(shè)置的主題顏色的不同來展示一個白屏或者黑屏。而這個黑(白)屏正式的稱呼應(yīng)該是Preview Window,即預(yù)覽窗口。
實(shí)際上就是是activity默認(rèn)的主題中的android:windowBackground為白色或者黑色導(dǎo)致的。
總結(jié)來說啟動順序就是:app啟動——Preview Window(也稱為預(yù)覽窗口)——啟動頁
解決辦法
常見有三種,這里解決辦法是給當(dāng)前啟動頁添加一個有背景的style樣式,然后SplashActivity引用當(dāng)前theme主題,注意在該頁面將window的背景圖設(shè)置為空!
更多關(guān)于啟動頁為什么白屏閃屏,以及不同解決辦法,可以看我這篇博客:App啟動頁面優(yōu)化
啟動時間優(yōu)化
IntentService子線程分擔(dān)部分初始化工作
現(xiàn)在application初始化內(nèi)容有:阿里云推送初始化,騰訊bugly初始化,im初始化,神策初始化,內(nèi)存泄漏工具初始化,頭條適配方案初始化,阿里云熱修復(fù)……等等。將部分邏輯放到IntentService中處理,可以縮短很多時間。
開啟IntentSerVice線程,將部分邏輯和耗時的初始化操作放到這里處理,可以減少application初始化時間
關(guān)于IntentService使用和源碼分析,性能分析等可以參考博客:IntentService源碼分析
9.其他優(yōu)化
9.1 靜態(tài)變量優(yōu)化
盡量不使用靜態(tài)變量保存核心數(shù)據(jù)。這是為什么呢?
這是因?yàn)閍ndroid的進(jìn)程并不是安全的,包括application對象以及靜態(tài)變量在內(nèi)的進(jìn)程級別變量并不會一直呆著內(nèi)存里面,因?yàn)樗苡袝籯ill掉。
當(dāng)被kill掉之后,實(shí)際上app不會重新開始啟動。Android系統(tǒng)會創(chuàng)建一個新的Application對象,然后啟動上次用戶離開時的activity以造成這個app從來沒有被kill掉的假象。而這時候靜態(tài)變量等數(shù)據(jù)由于進(jìn)程已經(jīng)被殺死而被初始化,所以就有了不推薦在靜態(tài)變量(包括Application中保存全局?jǐn)?shù)據(jù)靜態(tài)數(shù)據(jù))的觀點(diǎn)。
9.2 注解替代枚舉
使用注解限定傳入類型
比如,尤其是寫第三方開源庫,對于有些暴露給開發(fā)者的方法,需要限定傳入類型是有必要的。舉個例子:
剛開始的代碼
?*?設(shè)置播放器類型,必須設(shè)置
?*?注意:感謝某人建議,這里限定了傳入值類型
?*?輸入值:111???或者??222
?*?@param?playerType?IjkPlayer?or?MediaPlayer.
?*/
優(yōu)化后的代碼,有效避免第一種方式開發(fā)者傳入值錯誤
?*?設(shè)置播放器類型,必須設(shè)置
?*?注意:感謝某人建議,這里限定了傳入值類型
?*?輸入值:ConstantKeys.IjkPlayerType.TYPE_IJK???或者??ConstantKeys.IjkPlayerType.TYPE_NATIVE
?*?@param?playerType?IjkPlayer?or?MediaPlayer.
?*/
使用注解替代枚舉,代碼如下所示
@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)化
還在手動打包嗎?嘗試一下python自動化打包吧……
瓦力多渠道打包的Python腳本測試工具,通過該自動化腳本,自需要run一下或者命令行運(yùn)行腳本即可實(shí)現(xiàn)美團(tuán)瓦力多渠道打包,打包速度很快。配置信息十分簡單,代碼中已經(jīng)注釋十分詳細(xì)。可以自定義輸出文件路徑,可以修改多渠道配置信息,簡單實(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)存資源。對于這些緩存,只要是讀進(jìn)內(nèi)存內(nèi)的都算,例如最常見的圖片緩存、文件緩存等。拿圖片緩存來說,市場上,常規(guī)的圖片加載庫,一般而言都是三級緩存,所以在內(nèi)存吃緊的時候,我們就應(yīng)該優(yōu)先清理掉這部分圖片緩存,畢竟圖片是吃內(nèi)存大戶,而且再次回來的時候,雖然內(nèi)存中的資源被回收掉了,依然可以從磁盤或者網(wǎng)絡(luò)上恢復(fù)它。
大概的思路如下所示
在lowMemory的時候,調(diào)用Glide.cleanMemory()清理掉所有的內(nèi)存緩存。
在App被置換到后臺的時候,調(diào)用Glide.cleanMemory()清理掉所有的內(nèi)存緩存。
在其它情況的onTrimMemory()回調(diào)中,直接調(diào)用Glide.trimMemory()方法來交給Glide處理內(nèi)存情況。
9.5 輪詢操作優(yōu)化
什么叫輪訓(xùn)請求?
簡單理解就是App端每隔一定的時間重復(fù)請求的操作就叫做輪訓(xùn)請求,比如:App端每隔一段時間上報(bào)一次定位信息,App端每隔一段時間拉去一次用戶狀態(tài)等,這些應(yīng)該都是輪訓(xùn)請求。比如,電商類項(xiàng)目,某個抽獎活動頁面,隔1分鐘調(diào)用一次接口,彈出一些獲獎人信息,你應(yīng)該某個階段看過這類輪詢操作!
具體優(yōu)化操作
長連接并不是穩(wěn)定的可靠的,而執(zhí)行輪訓(xùn)操作的時候一般都是要穩(wěn)定的網(wǎng)絡(luò)請求,而且輪訓(xùn)操作一般都是有生命周期的,即在一定的生命周期內(nèi)執(zhí)行輪訓(xùn)操作,而長連接一般都是整個進(jìn)程生命周期的,所以從這方面講也不太適合。
建議在service中做輪詢操作,輪詢請求接口,具體做法和注意要點(diǎn),可以直接看該項(xiàng)目代碼。看app包下的LoopRequestService類即可。
大概思路:當(dāng)用戶打開這個頁面的時候初始化TimerTask對象,每個一分鐘請求一次服務(wù)器拉取訂單信息并更新UI,當(dāng)用戶離開頁面的時候清除TimerTask對象,即取消輪訓(xùn)請求操作。
9.6 去除重復(fù)依賴庫優(yōu)化
我相信你看到了這里會有疑問,網(wǎng)上有許多博客作了這方面說明。但是我在這里想說,如何查找自己項(xiàng)目的所有依賴關(guān)系樹
注意要點(diǎn):其中app就是項(xiàng)目mudule名字。 正常情況下就是app!
關(guān)于依賴關(guān)系樹的結(jié)構(gòu)圖如下所示,此處省略很多代碼
|????|????然后查看哪些重復(fù)jar
然后修改gradle配置代碼
"zxing"]){9.7 四種引用優(yōu)化
軟引用使用場景
正常是用來處理大圖片這種占用內(nèi)存大的情況
代碼如下所示
get(position);這樣使用軟引用好處
通過軟引用的get()方法,取得bitmap對象實(shí)例的強(qiáng)引用,發(fā)現(xiàn)對象被未回收。在GC在內(nèi)存充足的情況下,不會回收軟引用對象。此時view的背景顯示
實(shí)際情況中,我們會獲取很多圖片.然后可能給很多個view展示, 這種情況下很容易內(nèi)存吃緊導(dǎo)致oom,內(nèi)存吃緊,系統(tǒng)開始會GC。這次GC后,bitmapSoftReference.get()不再返回bitmap對象,而是返回null,這時屏幕上背景圖不顯示,說明在系統(tǒng)內(nèi)存緊張的情況下,軟引用被回收。
使用軟引用以后,在OutOfMemory異常發(fā)生之前,這些緩存的圖片資源的內(nèi)存空間可以被釋放掉的,從而避免內(nèi)存達(dá)到上限,避免Crash發(fā)生。
弱引用使用場景
弱引用–>隨時可能會被垃圾回收器回收,不一定要等到虛擬機(jī)內(nèi)存不足時才強(qiáng)制回收。
對于使用頻次少的對象,希望盡快回收,使用弱引用可以保證內(nèi)存被虛擬機(jī)回收。比如handler,如果希望使用完后盡快回收,看下面代碼
到底什么時候使用軟引用,什么時候使用弱引用呢?
個人認(rèn)為,如果只是想避免OutOfMemory異常的發(fā)生,則可以使用軟引用。如果對于應(yīng)用的性能更在意,想盡快回收一些占用內(nèi)存比較大的對象,則可以使用弱引用。
還有就是可以根據(jù)對象是否經(jīng)常使用來判斷。如果該對象可能會經(jīng)常使用的,就盡量用軟引用。如果該對象不被使用的可能性更大些,就可以用弱引用。
9.8 加載loading優(yōu)化
一般實(shí)際開發(fā)中會至少有兩種loading
第一種是從A頁面進(jìn)入B頁面時的加載loading,這個時候特點(diǎn)是顯示loading的時候,頁面是純白色的,加載完數(shù)據(jù)后才顯示內(nèi)容頁面。
第二種是在某個頁面操作某種邏輯,比如某些耗時操作,這個時候是局部loading[一般用個幀動畫或者補(bǔ)間動畫],由于使用頻繁,因?yàn)榻ㄗh在銷毀彈窗時,添加銷毀動畫的操作。
自定義loading加載
https://github.com/yangchong211/YCDialog
9.9 對象池Pools優(yōu)化
對象池Pools優(yōu)化頻繁創(chuàng)建和銷毀對象
使用對象池,可以防止頻繁創(chuàng)建和銷毀對象而出現(xiàn)內(nèi)存抖動
在某些時候,我們需要頻繁使用一些臨時對象,如果每次使用的時候都申請新的資源,很有可能會引發(fā)頻繁的 gc 而影響應(yīng)用的流暢性。這個時候如果對象有明確的生命周期,那么就可以通過定義一個對象池來高效的完成復(fù)用對象。
具體參考案例,可以看該項(xiàng)目:https://github.com/yangchong211/YCZoomImage
10.RecyclerView優(yōu)化
10.1 頁面為何卡頓
RecyclerView滑動卡頓的原因有哪些?
第一種:嵌套布局滑動沖突
導(dǎo)致嵌套滑動難處理的關(guān)鍵原因在于當(dāng)子控件消費(fèi)了事件, 那么父控件就不會再有機(jī)會處理這個事件了, 所以一旦內(nèi)部的滑動控件消費(fèi)了滑動操作, 外部的滑動控件就再也沒機(jī)會響應(yīng)這個滑動操作了
第二種:嵌套布局層次太深,比如六七層等
測量,繪制布局可能會導(dǎo)致滑動卡頓
第三種:比如用RecyclerView實(shí)現(xiàn)畫廊,加載比較大的圖片,如果快速滑動,則可能會出現(xiàn)卡頓,主要是加載圖片需要時間
第四種:在onCreateViewHolder或者在onBindViewHolder中做了耗時的操作導(dǎo)致卡頓。按stackoverflow上面比較通俗的解釋:RecyclerView.Adapter里面的onCreateViewHolder()方法和onBindViewHolder()方法對時間都非常敏感。類似I/O讀寫,Bitmap解碼一類的耗時操作,最好不要在它們里面進(jìn)行。
關(guān)于RecyclerView封裝庫
https://github.com/yangchong211/YCRefreshView
10.2 具體優(yōu)化方案
03.SparseArray替代HashMap
04.瀑布流圖片錯亂問題解決
05.item點(diǎn)擊事件放在哪里優(yōu)化
06.ViewHolder優(yōu)化
07.連續(xù)上拉加載更多優(yōu)化
08.拖拽排序與滑動刪除優(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)化
項(xiàng)目開源地址:https://github.com/yangchong211/YCBlogs
大家都在看
Android Notification(通知)(含兼容問題)
一篇文章帶你領(lǐng)略Android混淆的魅力
編寫一個非常精美的Flutter Todo-List項(xiàng)目
探索 Android 多線程優(yōu)化方法
歡迎前往安卓巴士博客區(qū)投稿,技術(shù)成長于分享
期待巴友留言,共同探討學(xué)習(xí)
總結(jié)
以上是生活随笔為你收集整理的为什么Android项目mainactivity中有一个变量R_博客笔记大汇总,Android优化总结篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: sql 数据库前两列值乘_数据库的基本概
- 下一篇: linux sqlserver_SQLS