Android,合理管理內存

轉載請註明出處:http://blog.csdn.net/guolin_blog/article/details/42238627 android

有很多朋友都問過我,怎樣才能寫出高性能的應用程序,如何避免程序出現OOM,或者當程序內存佔用太高的時候該怎麼樣去排查。確實,一個優秀的應用程序,不只僅要功能完成得好,性能問題也應該處理得恰到好處。爲此,我也是閱讀了很多Android官方給出的高性能編程建議,那麼從本篇文章開始,我就準備開始寫一個全新系列的博文,來把這些建議進行整理和分析,幫助你們可以寫出更加出色的應用程序。 程序員

注意本系列文章的內容基本源於Android Doc,若是想要閱讀更加詳細的關於性能方面的資料,能夠直接去閱讀Android官方文檔。 編程

內存(RAM)對於任何一個軟件開發環境都是種很是珍貴的資源,而對於移動操做系統來說的話,則會顯得更加珍貴,由於手機的硬件條件相對於PC畢竟是比較落後的。儘管Android系統的虛擬機擁有自動回收垃圾的機制,但這並不表明咱們就能夠忽視應該在何時分配和釋放內存。 緩存

爲了使垃圾回收器能夠正常釋放程序所佔用的內存,在編寫代碼的時候就必定要注意儘可能避免出現內存泄漏的狀況(一般都是因爲全局成員變量持有對象引用所致使的),而且在適當的時候去釋放對象引用。對於大多數的應用程序而言,後面其它的事情就能夠都交給垃圾回收器去完成了,若是一個對象的引用再也不被其它對象所持有,那麼系統就會將這個對象所分配的內存進行回收。 網絡

咱們在開發軟件的時候應當自始至終都把內存的問題充分考慮進去,這樣的話才能開發出更加高性能的軟件。而內存問題也並非無規律可行的,Android系統給咱們提出了不少內存優化的建議技巧,只要按照這些技巧來編寫程序,就可讓咱們的程序在內存性能發麪表現得至關不錯,下面咱們就來一一學習一下這些技巧。 app

節制地使用Service

若是應用程序當中須要使用Service來執行後臺任務的話,請必定要注意只有當任務正在執行的時候才應該讓Service運行起來。另外,當任務執行完以後去中止Service的時候,要當心Service中止失敗致使內存泄漏的狀況。 框架

當咱們啓動一個Service時,系統會傾向於將這個Service所依賴的進程進行保留,這樣就會致使這個進程變得很是消耗內存。而且,系統能夠在LRU cache當中緩存的進程數量也會減小,致使切換應用程序的時候耗費更多性能。嚴重的話,甚至有可能會致使崩潰,由於系統在內存很是吃緊的時候可能已沒法維護全部正在運行的Service所依賴的進程了。 ide

爲了可以控制Service的生命週期,Android官方推薦的最佳解決方案就是使用IntentService,這種Service的最大特色就是當後臺任務執行結束後會自動中止,從而極大程度上避免了Service內存泄漏的可能性。關於IntentService更加詳細的用法講解,能夠參考《第一行代碼——Android》的9.5.2節。 工具

讓一個Service在後臺一直保持運行,即便它並不執行任何工做,這是編寫Android程序時最糟糕的作法之一。因此Android官方極度建議開發人員們不要過於貪婪,讓Service在後臺一直運行,這不只可能會致使手機和程序的性能很是低下,並且被用戶發現了以後也有可能直接致使咱們的軟件被卸載(我我的就會這麼作)。

當界面不可見時釋放內存

當用戶打開了另一個程序,咱們的程序界面已經再也不可見的時候,咱們應當將全部和界面相關的資源進行釋放。在這種場景下釋放資源可讓系統緩存後臺進程的能力顯著增長,所以也會讓用戶體驗變得更好。

那麼咱們如何才能知道程序界面是否是已經不可見了呢?其實很簡單,只須要在Activity中重寫onTrimMemory()方法,而後在這個方法中監聽TRIM_MEMORY_UI_HIDDEN這個級別,一旦觸發了以後就說明用戶已經離開了咱們的程序,那麼此時就能夠進行資源釋放操做了,以下所示:

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. @Override  
  2. public void onTrimMemory(int level) {  
  3.     super.onTrimMemory(level);  
  4.     switch (level) {  
  5.     case TRIM_MEMORY_UI_HIDDEN:  
  6.         // 進行資源釋放操做  
  7.         break;  
  8.     }  
  9. }  
注意onTrimMemory()方法中的TRIM_MEMORY_UI_HIDDEN回調只有當咱們程序中的全部UI組件所有不可見的時候纔會觸發,這和onStop()方法仍是有很大區別的,由於onStop()方法只是當一個Activity徹底不可見的時候就會調用,好比說用戶打開了咱們程序中的另外一個Activity。所以,咱們能夠在onStop()方法中去釋放一些Activity相關的資源,好比說取消網絡鏈接或者註銷廣播接收器等,可是像UI相關的資源應該一直要等到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)這個回調以後纔去釋放,這樣能夠保證若是用戶只是從咱們程序的一個Activity回到了另一個Activity,界面相關的資源都不須要從新加載,從而提高響應速度。

當內存緊張時釋放內存

除了剛纔講的TRIM_MEMORY_UI_HIDDEN這個回調,onTrimMemory()方法還有不少種其它類型的回調,能夠在手機內存下降的時候及時通知咱們。咱們應該根據回調中傳入的級別來去決定如何釋放應用程序的資源:

  • TRIM_MEMORY_RUNNING_MODERATE    表示應用程序正常運行,而且不會被殺掉。可是目前手機的內存已經有點低了,系統可能會開始根據LRU緩存規則來去殺死進程了。
  • TRIM_MEMORY_RUNNING_LOW    表示應用程序正常運行,而且不會被殺掉。可是目前手機的內存已經很是低了,咱們應該去釋放掉一些沒必要要的資源以提高系統的性能,同時這也會直接影響到咱們應用程序的性能。
  • TRIM_MEMORY_RUNNING_CRITICAL    表示應用程序仍然正常運行,可是系統已經根據LRU緩存規則殺掉了大部分緩存的進程了。這個時候咱們應當儘量地去釋聽任何沒必要要的資源,否則的話系統可能會繼續殺掉全部緩存中的進程,而且開始殺掉一些原本應當保持運行的進程,好比說後臺運行的服務。

以上是當咱們的應用程序正在運行時的回調,那麼若是咱們的程序目前是被緩存的,則會收到如下幾種類型的回調:

  • TRIM_MEMORY_BACKGROUND    表示手機目前內存已經很低了,系統準備開始根據LRU緩存來清理進程。這個時候咱們的程序在LRU緩存列表的最近位置,是不太可能被清理掉的,但這時去釋放掉一些比較容易恢復的資源可以讓手機的內存變得比較充足,從而讓咱們的程序更長時間地保留在緩存當中,這樣當用戶返回咱們的程序時會感受很是順暢,而不是經歷了一次從新啓動的過程。
  • TRIM_MEMORY_MODERATE    表示手機目前內存已經很低了,而且咱們的程序處於LRU緩存列表的中間位置,若是手機內存還得不到進一步釋放的話,那麼咱們的程序就有被系統殺掉的風險了。
  • TRIM_MEMORY_COMPLETE    表示手機目前內存已經很低了,而且咱們的程序處於LRU緩存列表的最邊緣位置,系統會最優先考慮殺掉咱們的應用程序,在這個時候應當儘量地把一切能夠釋放的東西都進行釋放。

避免在Bitmap上浪費內存

當咱們讀取一個Bitmap圖片的時候,有一點必定要注意,就是千萬不要去加載不須要的分辨率。在一個很小的ImageView上顯示一張高分辨率的圖片不會帶來任何視覺上的好處,但卻會佔用咱們至關多寶貴的內存。須要僅記的一點是,將一張圖片解析成一個Bitmap對象時所佔用的內存並非這個圖片在硬盤中的大小,可能一張圖片只有100k你以爲它並不大,可是讀取到內存當中是按照像素點來算的,好比這張圖片是1500*1000像素,使用的ARGB_8888顏色類型,那麼每一個像素點就會佔用4個字節,總內存就是1500*1000*4字節,也就是5.7M,這個數據看起來就比較恐怖了。

至於如何去壓縮圖片,以及更多在圖片方面節省內存的技術,你們能夠去參考我以前寫的一篇博客 Android高效加載大圖、多圖解決方案,有效避免程序OOM 。

使用優化過的數據集合

Android API當中提供了一些優化事後的數據集合工具類,如SparseArray,SparseBooleanArray,以及LongSparseArray等,使用這些API可讓咱們的程序更加高效。傳統Java API中提供的HashMap工具類會相對比較低效,由於它須要爲每個鍵值對都提供一個對象入口,而SparseArray就避免掉了基本數據類型轉換成對象數據類型的時間。

知曉內存的開支狀況

咱們還應當清楚咱們所使用語言的內存開支和消耗狀況,而且在整個軟件的設計和開發當中都應該將這些信息考慮在內。可能有一些看起來無關痛癢的寫法,結果卻會致使很大一部分的內存開支,例如:

  • 使用枚舉一般會比使用靜態常量要消耗兩倍以上的內存,在Android開發當中咱們應當儘量地不使用枚舉。
  • 任何一個Java類,包括內部類、匿名類,都要佔用大概500字節的內存空間。
  • 任何一個類的實例要消耗12-16字節的內存開支,所以頻繁建立實例也是會必定程序上影響內存的。
  • 在使用HashMap時,即便你只設置了一個基本數據類型的鍵,好比說int,可是也會按照對象的大小來分配內存,大概是32字節,而不是4字節。所以最好的辦法就是像上面所說的同樣,使用優化過的數據集合。

謹慎使用抽象編程

許多程序員都喜歡各類使用抽象來編程,認爲這是一種很好的編程習慣。固然,這一點不能否認,由於的抽象的編程方法更加面向對象,並且在代碼的維護和可擴展性方面都會有所提升。可是,在Android上使用抽象會帶來額外的內存開支,由於抽象的編程方法須要編寫額外的代碼,雖然這些代碼根本執行不到,可是卻也要映射到內存當中,不只佔用了更多的內存,在執行效率方面也會有所下降。固然這裏我並非提倡你們徹底不使用抽象編程,而是謹慎使用抽象編程,不要認爲這是一種很酷的編程方式而去肆意使用它,只在你認爲有必要的狀況下才去使用。

儘可能避免使用依賴注入框架

如今有不少人都喜歡在Android工程當中使用依賴注入框架,好比說像Guice或者RoboGuice等,由於它們能夠簡化一些複雜的編碼操做,好比能夠將下面的一段代碼:

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. class AndroidWay extends Activity {   
  2.     TextView name;   
  3.     ImageView thumbnail;   
  4.     LocationManager loc;   
  5.     Drawable icon;   
  6.     String myName;   
  7.   
  8.     public void onCreate(Bundle savedInstanceState) {   
  9.         super.onCreate(savedInstanceState);   
  10.         setContentView(R.layout.main);  
  11.         name      = (TextView) findViewById(R.id.name);   
  12.         thumbnail = (ImageView) findViewById(R.id.thumbnail);   
  13.         loc       = (LocationManager) getSystemService(Activity.LOCATION_SERVICE);   
  14.         icon      = getResources().getDrawable(R.drawable.icon);   
  15.         myName    = getString(R.string.app_name);   
  16.         name.setText( "Hello, " + myName );   
  17.     }   
  18. }   
簡化成這樣的一種寫法:
[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. @ContentView(R.layout.main)  
  2. class RoboWay extends RoboActivity {   
  3.     @InjectView(R.id.name)             TextView name;   
  4.     @InjectView(R.id.thumbnail)        ImageView thumbnail;   
  5.     @InjectResource(R.drawable.icon)   Drawable icon;   
  6.     @InjectResource(R.string.app_name) String myName;   
  7.     @Inject                            LocationManager loc;   
  8.   
  9.     public void onCreate(Bundle savedInstanceState) {   
  10.         super.onCreate(savedInstanceState);   
  11.         name.setText( "Hello, " + myName );   
  12.     }   
  13. }  
看上去確實十分誘人,咱們甚至能夠將findViewById()這一類的繁瑣操做所有省去了。可是這些框架爲了要搜尋代碼中的註解,一般都須要經歷較長的初始化過程,而且還可能將一些你用不到的對象也一併加載到內存當中。這些用不到的對象會一直佔用着內存空間,可能要過好久以後纔會獲得釋放,相較之下,也許多敲幾行看似繁瑣的代碼纔是更好的選擇。

使用ProGuard簡化代碼

ProGuard相信你們都不會陌生,不少人都會使用這個工具來混淆代碼,可是除了混淆以外,它還具備壓縮和優化代碼的功能。ProGuard會對咱們的代碼進行檢索,刪除一些無用的代碼,而且會對類、字段、方法等進行重命名,重命名以後的類、字段和方法名都會比原來簡短不少,這樣的話也就對內存的佔用變得更少了。

使用多個進程

這個技巧其實並非很是建議使用,但它確實是一種能夠幫助咱們節省和管理內存的高級技巧。若是你要使用它的話必定要謹慎使用,由於絕大多數的應用程序都不該該在多個進程當中運行的,一旦使用不當,它甚至會增長額外的內存而不是幫咱們節省內存。這個技巧比較適用於那些須要在後臺去完成一項獨立的任務,和前臺的功能是能夠徹底區分開的場景。

這裏舉一個比較適合去使用多進程技巧的場景,好比說咱們正在作一個音樂播放器軟件,其中播放音樂的功能應該是一個獨立的功能,它不須要和UI方面有任何關係,即便軟件已經關閉了也應該能夠正常播放音樂。若是此時咱們只使用一個進程,那麼即便用戶關閉了軟件,已經徹底由Service來控制音樂播放了,系統仍然會將許多UI方面的內存進行保留。在這種場景下就很是適合使用兩個進程,一個用於UI展現,另外一個則用於在後臺持續地播放音樂。

想要實現多進程的功能也很是簡單,只須要在AndroidManifest文件的應用程序組件中聲明一個android:process屬性就能夠了,好比說咱們但願播放音樂的Service能夠運行在一個單獨的進程當中,就能夠這樣寫:

[java]  view plain copy 在CODE上查看代碼片 派生到個人代碼片
  1. <service android:name=".PlaybackService"  
  2.          android:process=":background" />  

這裏指定的進程名是background,你也能夠將它改爲任意你喜歡的名字。須要注意的是,進程名的前面都應該加上一個冒號,表示該進程是一個當前應用程序的私有進程。

遵循以上的全部編程建議,咱們就可讓應用程序內存的使用變得更加合理化。但這只是第一步而已,爲了要讓程序擁有最佳性能,咱們要學習的東西還有不少,下篇文章當中將會介紹如何分析內存的使用狀況,感興趣的朋友請繼續閱讀 Android最佳性能實踐(二)——分析內存的使用狀況 。

相關文章
相關標籤/搜索