日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

Android最佳性能实践(一)——合理管理内存

發(fā)布時(shí)間:2024/3/7 Android 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android最佳性能实践(一)——合理管理内存 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/guolin_blog/article/details/42238627

有不少朋友都問過(guò)我,怎樣才能寫出高性能的應(yīng)用程序,如何避免程序出現(xiàn)OOM,或者當(dāng)程序內(nèi)存占用過(guò)高的時(shí)候該怎么樣去排查。確實(shí),一個(gè)優(yōu)秀的應(yīng)用程序,不僅僅要功能完成得好,性能問題也應(yīng)該處理得恰到好處。為此,我也是閱讀了不少Android官方給出的高性能編程建議,那么從本篇文章開始,我就準(zhǔn)備開始寫一個(gè)全新系列的博文,來(lái)把這些建議進(jìn)行整理和分析,幫助大家能夠?qū)懗龈映錾膽?yīng)用程序。

注意本系列文章的內(nèi)容基本源于Android Doc,如果想要閱讀更加詳細(xì)的關(guān)于性能方面的資料,可以直接去閱讀Android官方文檔。

內(nèi)存(RAM)對(duì)于任何一個(gè)軟件開發(fā)環(huán)境都是種非常珍貴的資源,而對(duì)于移動(dòng)操作系統(tǒng)來(lái)講的話,則會(huì)顯得更加珍貴,因?yàn)槭謾C(jī)的硬件條件相對(duì)于PC畢竟是比較落后的。盡管Android系統(tǒng)的虛擬機(jī)擁有自動(dòng)回收垃圾的機(jī)制,但這并不代表我們就可以忽視應(yīng)該在什么時(shí)候分配和釋放內(nèi)存。

為了使垃圾回收器可以正常釋放程序所占用的內(nèi)存,在編寫代碼的時(shí)候就一定要注意盡量避免出現(xiàn)內(nèi)存泄漏的情況(通常都是由于全局成員變量持有對(duì)象引用所導(dǎo)致的),并且在適當(dāng)?shù)臅r(shí)候去釋放對(duì)象引用。對(duì)于大多數(shù)的應(yīng)用程序而言,后面其它的事情就可以都交給垃圾回收器去完成了,如果一個(gè)對(duì)象的引用不再被其它對(duì)象所持有,那么系統(tǒng)就會(huì)將這個(gè)對(duì)象所分配的內(nèi)存進(jìn)行回收。

我們?cè)陂_發(fā)軟件的時(shí)候應(yīng)當(dāng)自始至終都把內(nèi)存的問題充分考慮進(jìn)去,這樣的話才能開發(fā)出更加高性能的軟件。而內(nèi)存問題也并不是無(wú)規(guī)律可行的,Android系統(tǒng)給我們提出了很多內(nèi)存優(yōu)化的建議技巧,只要按照這些技巧來(lái)編寫程序,就可以讓我們的程序在內(nèi)存性能發(fā)面表現(xiàn)得相當(dāng)不錯(cuò),下面我們就來(lái)一一學(xué)習(xí)一下這些技巧。

節(jié)制地使用Service

如果應(yīng)用程序當(dāng)中需要使用Service來(lái)執(zhí)行后臺(tái)任務(wù)的話,請(qǐng)一定要注意只有當(dāng)任務(wù)正在執(zhí)行的時(shí)候才應(yīng)該讓Service運(yùn)行起來(lái)。另外,當(dāng)任務(wù)執(zhí)行完之后去停止Service的時(shí)候,要小心Service停止失敗導(dǎo)致內(nèi)存泄漏的情況。

當(dāng)我們啟動(dòng)一個(gè)Service時(shí),系統(tǒng)會(huì)傾向于將這個(gè)Service所依賴的進(jìn)程進(jìn)行保留,這樣就會(huì)導(dǎo)致這個(gè)進(jìn)程變得非常消耗內(nèi)存。并且,系統(tǒng)可以在LRU cache當(dāng)中緩存的進(jìn)程數(shù)量也會(huì)減少,導(dǎo)致切換應(yīng)用程序的時(shí)候耗費(fèi)更多性能。嚴(yán)重的話,甚至有可能會(huì)導(dǎo)致崩潰,因?yàn)橄到y(tǒng)在內(nèi)存非常吃緊的時(shí)候可能已無(wú)法維護(hù)所有正在運(yùn)行的Service所依賴的進(jìn)程了。

為了能夠控制Service的生命周期,Android官方推薦的最佳解決方案就是使用IntentService,這種Service的最大特點(diǎn)就是當(dāng)后臺(tái)任務(wù)執(zhí)行結(jié)束后會(huì)自動(dòng)停止,從而極大程度上避免了Service內(nèi)存泄漏的可能性。關(guān)于IntentService更加詳細(xì)的用法講解,可以參考《第一行代碼——Android》的9.5.2節(jié)。

讓一個(gè)Service在后臺(tái)一直保持運(yùn)行,即使它并不執(zhí)行任何工作,這是編寫Android程序時(shí)最糟糕的做法之一。所以Android官方極度建議開發(fā)人員們不要過(guò)于貪婪,讓Service在后臺(tái)一直運(yùn)行,這不僅可能會(huì)導(dǎo)致手機(jī)和程序的性能非常低下,而且被用戶發(fā)現(xiàn)了之后也有可能直接導(dǎo)致我們的軟件被卸載(我個(gè)人就會(huì)這么做)。

當(dāng)界面不可見時(shí)釋放內(nèi)存

當(dāng)用戶打開了另外一個(gè)程序,我們的程序界面已經(jīng)不再可見的時(shí)候,我們應(yīng)當(dāng)將所有和界面相關(guān)的資源進(jìn)行釋放。在這種場(chǎng)景下釋放資源可以讓系統(tǒng)緩存后臺(tái)進(jìn)程的能力顯著增加,因此也會(huì)讓用戶體驗(yàn)變得更好。

那么我們?nèi)绾尾拍苤莱绦蚪缑媸遣皇且呀?jīng)不可見了呢?其實(shí)很簡(jiǎn)單,只需要在Activity中重寫onTrimMemory()方法,然后在這個(gè)方法中監(jiān)聽TRIM_MEMORY_UI_HIDDEN這個(gè)級(jí)別,一旦觸發(fā)了之后就說(shuō)明用戶已經(jīng)離開了我們的程序,那么此時(shí)就可以進(jìn)行資源釋放操作了,如下所示:

[java]?view plaincopy
  • @Override??
  • public?void?onTrimMemory(int?level)?{??
  • ????super.onTrimMemory(level);??
  • ????switch?(level)?{??
  • ????case?TRIM_MEMORY_UI_HIDDEN:??
  • ????????//?進(jìn)行資源釋放操作??
  • ????????break;??
  • ????}??
  • }??
  • 注意onTrimMemory()方法中的TRIM_MEMORY_UI_HIDDEN回調(diào)只有當(dāng)我們程序中的所有UI組件全部不可見的時(shí)候才會(huì)觸發(fā),這和onStop()方法還是有很大區(qū)別的,因?yàn)閛nStop()方法只是當(dāng)一個(gè)Activity完全不可見的時(shí)候就會(huì)調(diào)用,比如說(shuō)用戶打開了我們程序中的另一個(gè)Activity。因此,我們可以在onStop()方法中去釋放一些Activity相關(guān)的資源,比如說(shuō)取消網(wǎng)絡(luò)連接或者注銷廣播接收器等,但是像UI相關(guān)的資源應(yīng)該一直要等到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)這個(gè)回調(diào)之后才去釋放,這樣可以保證如果用戶只是從我們程序的一個(gè)Activity回到了另外一個(gè)Activity,界面相關(guān)的資源都不需要重新加載,從而提升響應(yīng)速度。

    當(dāng)內(nèi)存緊張時(shí)釋放內(nèi)存

    除了剛才講的TRIM_MEMORY_UI_HIDDEN這個(gè)回調(diào),onTrimMemory()方法還有很多種其它類型的回調(diào),可以在手機(jī)內(nèi)存降低的時(shí)候及時(shí)通知我們。我們應(yīng)該根據(jù)回調(diào)中傳入的級(jí)別來(lái)去決定如何釋放應(yīng)用程序的資源:

    • TRIM_MEMORY_RUNNING_MODERATE ? ?表示應(yīng)用程序正常運(yùn)行,并且不會(huì)被殺掉。但是目前手機(jī)的內(nèi)存已經(jīng)有點(diǎn)低了,系統(tǒng)可能會(huì)開始根據(jù)LRU緩存規(guī)則來(lái)去殺死進(jìn)程了。
    • TRIM_MEMORY_RUNNING_LOW ? ?表示應(yīng)用程序正常運(yùn)行,并且不會(huì)被殺掉。但是目前手機(jī)的內(nèi)存已經(jīng)非常低了,我們應(yīng)該去釋放掉一些不必要的資源以提升系統(tǒng)的性能,同時(shí)這也會(huì)直接影響到我們應(yīng)用程序的性能。
    • TRIM_MEMORY_RUNNING_CRITICAL ? ?表示應(yīng)用程序仍然正常運(yùn)行,但是系統(tǒng)已經(jīng)根據(jù)LRU緩存規(guī)則殺掉了大部分緩存的進(jìn)程了。這個(gè)時(shí)候我們應(yīng)當(dāng)盡可能地去釋放任何不必要的資源,不然的話系統(tǒng)可能會(huì)繼續(xù)殺掉所有緩存中的進(jìn)程,并且開始?xì)⒌粢恍┍緛?lái)應(yīng)當(dāng)保持運(yùn)行的進(jìn)程,比如說(shuō)后臺(tái)運(yùn)行的服務(wù)。

    以上是當(dāng)我們的應(yīng)用程序正在運(yùn)行時(shí)的回調(diào),那么如果我們的程序目前是被緩存的,則會(huì)收到以下幾種類型的回調(diào):

    • TRIM_MEMORY_BACKGROUND ? ?表示手機(jī)目前內(nèi)存已經(jīng)很低了,系統(tǒng)準(zhǔn)備開始根據(jù)LRU緩存來(lái)清理進(jìn)程。這個(gè)時(shí)候我們的程序在LRU緩存列表的最近位置,是不太可能被清理掉的,但這時(shí)去釋放掉一些比較容易恢復(fù)的資源能夠讓手機(jī)的內(nèi)存變得比較充足,從而讓我們的程序更長(zhǎng)時(shí)間地保留在緩存當(dāng)中,這樣當(dāng)用戶返回我們的程序時(shí)會(huì)感覺非常順暢,而不是經(jīng)歷了一次重新啟動(dòng)的過(guò)程。
    • TRIM_MEMORY_MODERATE ? ?表示手機(jī)目前內(nèi)存已經(jīng)很低了,并且我們的程序處于LRU緩存列表的中間位置,如果手機(jī)內(nèi)存還得不到進(jìn)一步釋放的話,那么我們的程序就有被系統(tǒng)殺掉的風(fēng)險(xiǎn)了。
    • TRIM_MEMORY_COMPLETE ? ?表示手機(jī)目前內(nèi)存已經(jīng)很低了,并且我們的程序處于LRU緩存列表的最邊緣位置,系統(tǒng)會(huì)最優(yōu)先考慮殺掉我們的應(yīng)用程序,在這個(gè)時(shí)候應(yīng)當(dāng)盡可能地把一切可以釋放的東西都進(jìn)行釋放。

    避免在Bitmap上浪費(fèi)內(nèi)存

    當(dāng)我們讀取一個(gè)Bitmap圖片的時(shí)候,有一點(diǎn)一定要注意,就是千萬(wàn)不要去加載不需要的分辨率。在一個(gè)很小的ImageView上顯示一張高分辨率的圖片不會(huì)帶來(lái)任何視覺上的好處,但卻會(huì)占用我們相當(dāng)多寶貴的內(nèi)存。需要僅記的一點(diǎn)是,將一張圖片解析成一個(gè)Bitmap對(duì)象時(shí)所占用的內(nèi)存并不是這個(gè)圖片在硬盤中的大小,可能一張圖片只有100k你覺得它并不大,但是讀取到內(nèi)存當(dāng)中是按照像素點(diǎn)來(lái)算的,比如這張圖片是1500*1000像素,使用的ARGB_8888顏色類型,那么每個(gè)像素點(diǎn)就會(huì)占用4個(gè)字節(jié),總內(nèi)存就是1500*1000*4字節(jié),也就是5.7M,這個(gè)數(shù)據(jù)看起來(lái)就比較恐怖了。

    至于如何去壓縮圖片,以及更多在圖片方面節(jié)省內(nèi)存的技術(shù),大家可以去參考我之前寫的一篇博客?Android高效加載大圖、多圖解決方案,有效避免程序OOM?。

    使用優(yōu)化過(guò)的數(shù)據(jù)集合

    Android API當(dāng)中提供了一些優(yōu)化過(guò)后的數(shù)據(jù)集合工具類,如SparseArray,SparseBooleanArray,以及LongSparseArray等,使用這些API可以讓我們的程序更加高效。傳統(tǒng)Java API中提供的HashMap工具類會(huì)相對(duì)比較低效,因?yàn)樗枰獮槊恳粋€(gè)鍵值對(duì)都提供一個(gè)對(duì)象入口,而SparseArray就避免掉了基本數(shù)據(jù)類型轉(zhuǎn)換成對(duì)象數(shù)據(jù)類型的時(shí)間。

    知曉內(nèi)存的開支情況

    我們還應(yīng)當(dāng)清楚我們所使用語(yǔ)言的內(nèi)存開支和消耗情況,并且在整個(gè)軟件的設(shè)計(jì)和開發(fā)當(dāng)中都應(yīng)該將這些信息考慮在內(nèi)。可能有一些看起來(lái)無(wú)關(guān)痛癢的寫法,結(jié)果卻會(huì)導(dǎo)致很大一部分的內(nèi)存開支,例如:

    • 使用枚舉通常會(huì)比使用靜態(tài)常量要消耗兩倍以上的內(nèi)存,在Android開發(fā)當(dāng)中我們應(yīng)當(dāng)盡可能地不使用枚舉。
    • 任何一個(gè)Java類,包括內(nèi)部類、匿名類,都要占用大概500字節(jié)的內(nèi)存空間。
    • 任何一個(gè)類的實(shí)例要消耗12-16字節(jié)的內(nèi)存開支,因此頻繁創(chuàng)建實(shí)例也是會(huì)一定程序上影響內(nèi)存的。
    • 在使用HashMap時(shí),即使你只設(shè)置了一個(gè)基本數(shù)據(jù)類型的鍵,比如說(shuō)int,但是也會(huì)按照對(duì)象的大小來(lái)分配內(nèi)存,大概是32字節(jié),而不是4字節(jié)。因此最好的辦法就是像上面所說(shuō)的一樣,使用優(yōu)化過(guò)的數(shù)據(jù)集合。

    謹(jǐn)慎使用抽象編程

    許多程序員都喜歡各種使用抽象來(lái)編程,認(rèn)為這是一種很好的編程習(xí)慣。當(dāng)然,這一點(diǎn)不可否認(rèn),因?yàn)榈某橄蟮木幊谭椒ǜ用嫦驅(qū)ο?#xff0c;而且在代碼的維護(hù)和可擴(kuò)展性方面都會(huì)有所提高。但是,在Android上使用抽象會(huì)帶來(lái)額外的內(nèi)存開支,因?yàn)槌橄蟮木幊谭椒ㄐ枰帉戭~外的代碼,雖然這些代碼根本執(zhí)行不到,但是卻也要映射到內(nèi)存當(dāng)中,不僅占用了更多的內(nèi)存,在執(zhí)行效率方面也會(huì)有所降低。當(dāng)然這里我并不是提倡大家完全不使用抽象編程,而是謹(jǐn)慎使用抽象編程,不要認(rèn)為這是一種很酷的編程方式而去肆意使用它,只在你認(rèn)為有必要的情況下才去使用。

    盡量避免使用依賴注入框架

    現(xiàn)在有很多人都喜歡在Android工程當(dāng)中使用依賴注入框架,比如說(shuō)像Guice或者RoboGuice等,因?yàn)樗鼈兛梢院?jiǎn)化一些復(fù)雜的編碼操作,比如可以將下面的一段代碼:

    [java]?view plaincopy
  • class?AndroidWay?extends?Activity?{???
  • ????TextView?name;???
  • ????ImageView?thumbnail;???
  • ????LocationManager?loc;???
  • ????Drawable?icon;???
  • ????String?myName;???
  • ??
  • ????public?void?onCreate(Bundle?savedInstanceState)?{???
  • ????????super.onCreate(savedInstanceState);???
  • ????????setContentView(R.layout.main);??
  • ????????name??????=?(TextView)?findViewById(R.id.name);???
  • ????????thumbnail?=?(ImageView)?findViewById(R.id.thumbnail);???
  • ????????loc???????=?(LocationManager)?getSystemService(Activity.LOCATION_SERVICE);???
  • ????????icon??????=?getResources().getDrawable(R.drawable.icon);???
  • ????????myName????=?getString(R.string.app_name);???
  • ????????name.setText(?"Hello,?"?+?myName?);???
  • ????}???
  • }???
  • 簡(jiǎn)化成這樣的一種寫法: [java]?view plaincopy
  • @ContentView(R.layout.main)??
  • class?RoboWay?extends?RoboActivity?{???
  • ????@InjectView(R.id.name)?????????????TextView?name;???
  • ????@InjectView(R.id.thumbnail)????????ImageView?thumbnail;???
  • ????@InjectResource(R.drawable.icon)???Drawable?icon;???
  • ????@InjectResource(R.string.app_name)?String?myName;???
  • ????@Inject????????????????????????????LocationManager?loc;???
  • ??
  • ????public?void?onCreate(Bundle?savedInstanceState)?{???
  • ????????super.onCreate(savedInstanceState);???
  • ????????name.setText(?"Hello,?"?+?myName?);???
  • ????}???
  • }??
  • 看上去確實(shí)十分誘人,我們甚至可以將findViewById()這一類的繁瑣操作全部省去了。但是這些框架為了要搜尋代碼中的注解,通常都需要經(jīng)歷較長(zhǎng)的初始化過(guò)程,并且還可能將一些你用不到的對(duì)象也一并加載到內(nèi)存當(dāng)中。這些用不到的對(duì)象會(huì)一直占用著內(nèi)存空間,可能要過(guò)很久之后才會(huì)得到釋放,相較之下,也許多敲幾行看似繁瑣的代碼才是更好的選擇。

    使用ProGuard簡(jiǎn)化代碼

    ProGuard相信大家都不會(huì)陌生,很多人都會(huì)使用這個(gè)工具來(lái)混淆代碼,但是除了混淆之外,它還具有壓縮和優(yōu)化代碼的功能。ProGuard會(huì)對(duì)我們的代碼進(jìn)行檢索,刪除一些無(wú)用的代碼,并且會(huì)對(duì)類、字段、方法等進(jìn)行重命名,重命名之后的類、字段和方法名都會(huì)比原來(lái)簡(jiǎn)短很多,這樣的話也就對(duì)內(nèi)存的占用變得更少了。

    使用多個(gè)進(jìn)程

    這個(gè)技巧其實(shí)并不是非常建議使用,但它確實(shí)是一種可以幫助我們節(jié)省和管理內(nèi)存的高級(jí)技巧。如果你要使用它的話一定要謹(jǐn)慎使用,因?yàn)榻^大多數(shù)的應(yīng)用程序都不應(yīng)該在多個(gè)進(jìn)程當(dāng)中運(yùn)行的,一旦使用不當(dāng),它甚至?xí)黾宇~外的內(nèi)存而不是幫我們節(jié)省內(nèi)存。這個(gè)技巧比較適用于那些需要在后臺(tái)去完成一項(xiàng)獨(dú)立的任務(wù),和前臺(tái)的功能是可以完全區(qū)分開的場(chǎng)景。

    這里舉一個(gè)比較適合去使用多進(jìn)程技巧的場(chǎng)景,比如說(shuō)我們正在做一個(gè)音樂播放器軟件,其中播放音樂的功能應(yīng)該是一個(gè)獨(dú)立的功能,它不需要和UI方面有任何關(guān)系,即使軟件已經(jīng)關(guān)閉了也應(yīng)該可以正常播放音樂。如果此時(shí)我們只使用一個(gè)進(jìn)程,那么即使用戶關(guān)閉了軟件,已經(jīng)完全由Service來(lái)控制音樂播放了,系統(tǒng)仍然會(huì)將許多UI方面的內(nèi)存進(jìn)行保留。在這種場(chǎng)景下就非常適合使用兩個(gè)進(jìn)程,一個(gè)用于UI展示,另一個(gè)則用于在后臺(tái)持續(xù)地播放音樂。

    想要實(shí)現(xiàn)多進(jìn)程的功能也非常簡(jiǎn)單,只需要在AndroidManifest文件的應(yīng)用程序組件中聲明一個(gè)android:process屬性就可以了,比如說(shuō)我們希望播放音樂的Service可以運(yùn)行在一個(gè)單獨(dú)的進(jìn)程當(dāng)中,就可以這樣寫:

    [java]?view plaincopy
  • <service?android:name=".PlaybackService"??
  • ?????????android:process=":background"?/>??
  • 這里指定的進(jìn)程名是background,你也可以將它改成任意你喜歡的名字。需要注意的是,進(jìn)程名的前面都應(yīng)該加上一個(gè)冒號(hào),表示該進(jìn)程是一個(gè)當(dāng)前應(yīng)用程序的私有進(jìn)程。

    遵循以上的所有編程建議,我們就可以讓應(yīng)用程序內(nèi)存的使用變得更加合理化。但這只是第一步而已,為了要讓程序擁有最佳性能,我們要學(xué)習(xí)的東西還有很多,下篇文章當(dāng)中將會(huì)介紹如何分析內(nèi)存的使用情況,感興趣的朋友請(qǐng)繼續(xù)閱讀?Android最佳性能實(shí)踐(二)——分析內(nèi)存的使用情況?。

    第一時(shí)間獲得博客更新提醒,以及更多技術(shù)信息分享,歡迎關(guān)注我的微信公眾號(hào),掃一掃下方二維碼或搜索微信號(hào)guolin_blog,即可關(guān)注。

    總結(jié)

    以上是生活随笔為你收集整理的Android最佳性能实践(一)——合理管理内存的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。