在Android應用裏,最耗費內存的就是圖片資源。並且在Android系統中,讀取位圖Bitmap時,分給虛擬機中的圖片的堆棧大小隻有8M,若是超出了,就會出現OutOfMemory異常。因此,對於圖片的內存優化,是Android應用開發中比較重要的內容。緩存
1) 要及時回收Bitmap的內存網絡
Bitmap類有一個方法recycle(),從方法名能夠看出意思是回收。這裏就有疑問了,Android系統有本身的垃圾回收機制,能夠不按期的回收掉不使用的內存空間,固然也包括Bitmap的空間。那爲何還須要這個方法呢?優化
Bitmap類的構造方法都是私有的,因此開發者不能直接new出一個Bitmap對象,只能經過BitmapFactory類的各類靜態方法來實例化一個Bitmap。仔細查看BitmapFactory的源代碼能夠看到,生成Bitmap對象最終都是經過JNI調用方式實現的。因此,加載Bitmap到內存裏之後,是包含兩部份內存區域的。簡單的說,一部分是Java部分的,一部分是C部分的。這個Bitmap對象是由Java部分分配的,不用的時候系統就會自動回收了,可是那個對應的C可用的內存區域,虛擬機是不能直接回收的,這個只能調用底層的功能釋放。因此須要調用recycle()方法來釋放C部分的內存。從Bitmap類的源代碼也能夠看到,recycle()方法裏也的確是調用了JNI方法了的。spa
那若是不調用recycle(),是否就必定存在內存泄露呢?也不是的。Android的每一個應用都運行在獨立的進程裏,有着獨立的內存,若是整個進程被應用自己或者系統殺死了,內存也就都被釋放掉了,固然也包括C部分的內存。code
Android對於進程的管理是很是複雜的。簡單的說,Android系統的進程分爲幾個級別,系統會在內存不足的狀況下殺死一些低優先級的進程,以提供給其它進程充足的內存空間。在實際項目開發過程當中,有的開發者會在退出程序的時候使用Process.killProcess(Process.myPid())的方式將本身的進程殺死,可是有的應用僅僅會使用調用Activity.finish()方法的方式關閉掉全部的Activity。對象
經驗分享:進程 Android手機的用戶,根據習慣不一樣,可能會有兩種方式退出整個應用程序:一種是按Home鍵直接退到桌面;另外一種是從應用程序的退出按鈕或者按Back鍵退出程序。那麼從系統的角度來講,這兩種方式有什麼區別呢?按Home鍵,應用程序並無被關閉,而是成爲了後臺應用程序。按Back鍵,通常來講,應用程序關閉了,可是進程並無被殺死,而是成爲了空進程(程序自己對退出作了特殊處理的不考慮在內)。圖片 Android系統已經作了大量進程管理的工做,這些已經能夠知足用戶的需求。我的建議,應用程序在退出應用的時候不須要手動殺死本身所在的進程。對於應用程序自己的進程管理,交給Android系統來處理就能夠了。應用程序須要作的,是儘可能作好程序自己的內存管理工做。內存 |
通常來講,若是可以得到Bitmap對象的引用,就須要及時的調用Bitmap的recycle()方法來釋放Bitmap佔用的內存空間,而不要等Android系統來進行釋放。ci
下面是釋放Bitmap的示例代碼片斷。
// 先判斷是否已經回收 if(bitmap != null && !bitmap.isRecycled()){ // 回收而且置爲null bitmap.recycle(); bitmap = null; } System.gc(); |
從上面的代碼能夠看到,bitmap.recycle()方法用於回收該Bitmap所佔用的內存,接着將bitmap置空,最後使用System.gc()調用一下系統的垃圾回收器進行回收,能夠通知垃圾回收器儘快進行回收。這裏須要注意的是,調用System.gc()並不能保證當即開始進行回收過程,而只是爲了加快回收的到來。
如何調用recycle()方法進行回收已經瞭解了,那何時釋放Bitmap的內存比較合適呢?通常來講,若是代碼已經再也不須要使用Bitmap對象了,就能夠釋放了。釋放內存之後,就不能再使用該Bitmap對象了,若是再次使用,就會拋出異常。因此必定要保證再也不使用的時候釋放。好比,若是是在某個Activity中使用Bitmap,就能夠在Activity的onStop()或者onDestroy()方法中進行回收。
2) 捕獲異常
由於Bitmap是吃內存大戶,爲了不應用在分配Bitmap內存的時候出現OutOfMemory異常之後Crash掉,須要特別注意實例化Bitmap部分的代碼。一般,在實例化Bitmap的代碼中,必定要對OutOfMemory異常進行捕獲。
如下是代碼示例。
Bitmap bitmap = null; try { // 實例化Bitmap bitmap = BitmapFactory.decodeFile(path); } catch (OutOfMemoryError e) { // } if (bitmap == null) { // 若是實例化失敗 返回默認的Bitmap對象 return defaultBitmapMap; } |
這裏對初始化Bitmap對象過程當中可能發生的OutOfMemory異常進行了捕獲。若是發生了OutOfMemory異常,應用不會崩潰,而是獲得了一個默認的Bitmap圖。
經驗分享: 不少開發者會習慣性的在代碼中直接捕獲Exception。可是對於OutOfMemoryError來講,這樣作是捕獲不到的。由於OutOfMemoryError是一種Error,而不是Exception。在此僅僅作一下提醒,避免寫錯代碼而捕獲不到OutOfMemoryError。 |
3) 緩存通用的Bitmap對象
有時候,可能須要在一個Activity裏屢次用到同一張圖片。好比一個Activity會展現一些用戶的頭像列表,而若是用戶沒有設置頭像的話,則會顯示一個默認頭像,而這個頭像是位於應用程序自己的資源文件中的。
若是有相似上面的場景,就能夠對同一Bitmap進行緩存。若是不進行緩存,儘管看到的是同一張圖片文件,可是使用BitmapFactory類的方法來實例化出來的Bitmap,是不一樣的Bitmap對象。緩存能夠避免新建多個Bitmap對象,避免內存的浪費。
經驗分享: Web開發者對於緩存技術是很熟悉的。其實在Android應用開發過程當中,也會常用緩存的技術。這裏所說的緩存有兩個級別,一個是硬盤緩存,一個是內存緩存。好比說,在開發網絡應用過程當中,能夠將一些從網絡上獲取的數據保存到SD卡中,下次直接從SD卡讀取,而不從網絡中讀取,從而節省網絡流量。這種方式就是硬盤緩存。再好比,應用程序常常會使用同一對象,也能夠放到內存中緩存起來,須要的時候直接從內存中讀取。這種方式就是內存緩存。 |
4) 壓縮圖片
若是圖片像素過大,使用BitmapFactory類的方法實例化Bitmap的過程當中,須要大於8M的內存空間,就一定會發生OutOfMemory異常。這個時候該如何處理呢?若是有這種狀況,則能夠將圖片縮小,以減小載入圖片過程當中的內存的使用,避免異常發生。
使用BitmapFactory.Options設置inSampleSize就能夠縮小圖片。屬性值inSampleSize表示縮略圖大小爲原始圖片大小的幾分之一。即若是這個值爲2,則取出的縮略圖的寬和高都是原始圖片的1/2,圖片的大小就爲原始大小的1/4。
若是知道圖片的像素過大,就能夠對其進行縮小。那麼如何才知道圖片過大呢?
使用BitmapFactory.Options設置inJustDecodeBounds爲true後,再使用decodeFile()等方法,並不會真正的分配空間,即解碼出來的Bitmap爲null,可是可計算出原始圖片的寬度和高度,即options.outWidth和options.outHeight。經過這兩個值,就能夠知道圖片是否過大了。
BitmapFactory.Options opts = new BitmapFactory.Options(); // 設置inJustDecodeBounds爲true opts.inJustDecodeBounds = true; // 使用decodeFile方法獲得圖片的寬和高 BitmapFactory.decodeFile(path, opts); // 打印出圖片的寬和高 Log.d("example", opts.outWidth + "," + opts.outHeight); |
在實際項目中,能夠利用上面的代碼,先獲取圖片真實的寬度和高度,而後判斷是否須要跑縮小。若是不須要縮小,設置inSampleSize的值爲1。若是須要縮小,則動態計算並設置inSampleSize的值,對圖片進行縮小。須要注意的是,在下次使用BitmapFactory的decodeFile()等方法實例化Bitmap對象前,別忘記將opts.inJustDecodeBound設置回false。不然獲取的bitmap對象仍是null。