## 本節引言:
> 上節,我們已經學習了Bitmap的基本用法,而本節我們要來探討的Bitmap的OOM問題, 大家在實際開發中可能遇到過,或者沒遇到過因為Bitmap引起的OOM問題,本節我們 就來圍繞這個話題來進行學習~了解什么是OOM,為什么會引起OOM,改善因Bitmap引起的 OOM問題~
* * *
## 1.什么是OOM?為什么會引起OOM?
答:**Out Of Memory**(內存溢出),我們都知道Android系統會為每個APP分配一個獨立的工作空間, 或者說分配一個單獨的Dalvik虛擬機,這樣每個APP都可以獨立運行而不相互影響!而Android對于每個 Dalvik虛擬機都會有一個最大內存限制,如果當前占用的內存加上我們申請的內存資源超過了這個限制 ,系統就會拋出OOM錯誤!另外,這里別和RAM混淆了,即時當前RAM中剩余的內存有1G多,但是OOM還是會發生!別把RAM(物理內存)和OOM扯到一起!另外RAM不足的話,就是殺應用了,而不是僅僅是OOM了! 而這個Dalvik中的最大內存標準,不同的機型是不一樣的,可以調用:
~~~
ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
Log.e("HEHE","最大內存:" + activityManager.getMemoryClass());
~~~
獲得正常的最大內存標準,又或者直接在命令行鍵入:
~~~
adb shell getprop | grep dalvik.vm.heapgrowthlimit
~~~
你也可以打開系統源碼/system/build.prop文件,看下文件中這一部分的信息得出:
~~~
dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=192m
dalvik.vm.heapsize=512m
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapminfree=2m
dalvik.vm.heapmaxfree=8m
~~~
我們關注的地方有三個:heapstartsize堆內存的初始大小,heapgrowthlimit標準的應用的最大堆 內存大小,heapsize則是設置了使用android:largeHeap的應用的最大堆內存大小!
我這里試了下手頭幾個機型的正常最大內存分配標準:

你也可以試試自己手頭的機子~
好啦,不扯了,關于OOM問題的產生,就扯到這里,再扯就到內存管理那一塊了,可是個大塊頭, 現在還啃不動...下面我們來看下避免Bitmap OOM的一些技巧吧!
* * *
## 2.避免Bitmap引起的OOM技巧小結
* * *
### 1)采用低內存占用量的編碼方式
上一節說了**BitmapFactory.Options**這個類,我們可以設置下其中的inPreferredConfig屬性, 默認是**Bitmap.Config.ARGB_8888**,我們可以修改成**Bitmap.Config.ARGB_4444**
Bitmap.Config ARGB_4444:每個像素占四位,即A=4,R=4,G=4,B=4,那么一個像素點占4+4+4+4=16位
Bitmap.Config ARGB_8888:每個像素占八位,即A=8,R=8,G=8,B=8,那么一個像素點占8+8+8+8=32位
默認使用ARGB_8888,即一個像素占4個字節!
* * *
## 2)圖片壓縮
同樣是BitmapFactory.Options,我們通過**inSampleSize**設置縮放倍數,比如寫2,即長寬變為原來的1/2,圖片就是原來的1/4,如果不進行縮放的話設置為1即可!但是不能一味的壓縮,畢竟這個值太小 的話,圖片會很模糊,而且要避免圖片的拉伸變形,所以需要我們在程序中動態的計算,這個 inSampleSize的合適值,而Options中又有這樣一個方法:**inJustDecodeBounds**,將該參數設置為 true后,decodeFiel并不會分配內存空間,但是可以計算出原始圖片的長寬,調用 options.**outWidth**/**outHeight**獲取出圖片的寬高,然后通過一定的算法,即可得到適合的 inSampleSize,這里感謝**街神**提供的代碼——摘自鴻洋blog!
~~~
public static int caculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int width = options.outWidth;
int height = options.outHeight;
int inSampleSize = 1;
if (width > reqWidth || height > reqHeight) {
int widthRadio = Math.round(width * 1.0f / reqWidth);
int heightRadio = Math.round(height * 1.0f / reqHeight);
inSampleSize = Math.max(widthRadio, heightRadio);
}
return inSampleSize;
}
~~~
然后使用下上述的方法即可:
~~~
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 設置了此屬性一定要記得將值設置為false
Bitmap bitmap = null;
bitmap = BitmapFactory.decodeFile(url, options);
options.inSampleSize = computeSampleSize(options,128,128);
options.inPreferredConfig = Bitmap.Config.ARGB_4444;
/* 下面兩個字段需要組合使用 */
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
try {
bitmap = BitmapFactory.decodeFile(url, options);
} catch (OutOfMemoryError e) {
Log.e(TAG, "OutOfMemoryError");
}
~~~
* * *
## 3.及時回收圖像
如果引用了大量的Bitmap對象,而應用又不需要同時顯示所有圖片。可以將暫時不用到的Bitmap對象 及時回收掉。對于一些明確知道圖片使用情況的場景可以主動recycle回收,比如引導頁的圖片,使用 完就recycle,幀動畫,加載一張,畫一張,釋放一張!使用時加載,不顯示時直接置null或recycle! 比如:imageView.setImageResource(0); 不過某些情況下會出現特定圖片反復加載,釋放,再加載等,低效率的事情...
* * *
## 4.其他方法
下面這些方法,我并沒有用過,大家可以自行查閱相關資料:
### 1.簡單通過SoftReference引用方式管理圖片資源
建個SoftReference的hashmap 使用圖片時先查詢這個hashmap是否有softreference, softreference里的圖片是否為空, 如果為空就加載圖片到softreference并加入hashmap。 無需再代碼里顯式的處理圖片的回收與釋放,gc會自動處理資源的釋放。 這種方式處理起來簡單實用,能一定程度上避免前一種方法反復加載釋放的低效率。但還不夠優化。
**示例代碼:**
~~~
private Map<String, SoftReference<Bitmap>> imageMap
= new HashMap<String, SoftReference<Bitmap>>();
public Bitmap loadBitmap(final String imageUrl,final ImageCallBack imageCallBack) {
SoftReference<Bitmap> reference = imageMap.get(imageUrl);
if(reference != null) {
if(reference.get() != null) {
return reference.get();
}
}
final Handler handler = new Handler() {
public void handleMessage(final android.os.Message msg) {
//加入到緩存中
Bitmap bitmap = (Bitmap)msg.obj;
imageMap.put(imageUrl, new SoftReference<Bitmap>(bitmap));
if(imageCallBack != null) {
imageCallBack.getBitmap(bitmap);
}
}
};
new Thread(){
public void run() {
Message message = handler.obtainMessage();
message.obj = downloadBitmap(imageUrl);
handler.sendMessage(message);
}
}.start();
return null ;
}
// 從網上下載圖片
private Bitmap downloadBitmap (String imageUrl) {
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeStream(new URL(imageUrl).openStream());
return bitmap ;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public interface ImageCallBack{
void getBitmap(Bitmap bitmap);
}
~~~
### 2.LruCache + sd的緩存方式
> Android 3.1版本起,官方還提供了LruCache來進行cache處理,當存儲Image的大小大于LruCache 設定的值,那么近期使用次數最少的圖片就會被回收掉,系統會自動釋放內存!
**使用示例**:
步驟:
1)要先設置緩存圖片的內存大小,我這里設置為手機內存的1/8, 手機內存的獲取方式:int MAXMEMONRY = (int) (Runtime.getRuntime() .maxMemory() / 1024);
2)LruCache里面的鍵值對分別是URL和對應的圖片
3)重寫了一個叫做sizeOf的方法,返回的是圖片數量。
~~~
private LruCache<String, Bitmap> mMemoryCache;
private LruCacheUtils() {
if (mMemoryCache == null)
mMemoryCache = new LruCache<String, Bitmap>(
MAXMEMONRY / 8) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重寫此方法來衡量每張圖片的大小,默認返回圖片數量。
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
@Override
protected void entryRemoved(boolean evicted, String key,
Bitmap oldValue, Bitmap newValue) {
Log.v("tag", "hard cache is full , push to soft cache");
}
};
}
~~~
4)下面的方法分別是清空緩存、添加圖片到緩存、從緩存中取得圖片、從緩存中移除。
移除和清除緩存是必須要做的事,因為圖片緩存處理不當就會報內存溢出,所以一定要引起注意。
~~~
public void clearCache() {
if (mMemoryCache != null) {
if (mMemoryCache.size() > 0) {
Log.d("CacheUtils",
"mMemoryCache.size() " + mMemoryCache.size());
mMemoryCache.evictAll();
Log.d("CacheUtils", "mMemoryCache.size()" + mMemoryCache.size());
}
mMemoryCache = null;
}
}
public synchronized void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (mMemoryCache.get(key) == null) {
if (key != null && bitmap != null)
mMemoryCache.put(key, bitmap);
} else
Log.w(TAG, "the res is aready exits");
}
public synchronized Bitmap getBitmapFromMemCache(String key) {
Bitmap bm = mMemoryCache.get(key);
if (key != null) {
return bm;
}
return null;
}
/**
* 移除緩存
*
* @param key
*/
public synchronized void removeImageCache(String key) {
if (key != null) {
if (mMemoryCache != null) {
Bitmap bm = mMemoryCache.remove(key);
if (bm != null)
bm.recycle();
}
}
}
~~~
上述內容摘自——[圖片緩存之內存緩存技術LruCache,軟引用](http://blog.chinaunix.net/uid-26930580-id-4138306.html)
* * *
## 本節小結:
> 本節給大家講解了OOM問題的發生緣由,也總結了一下網上給出的一些避免因Bitmap而引起OOM 的一些方案,因為公司做的APP都是地圖類的,很少涉及到圖片,所以筆者并沒有遇到過OOM的問題, 所以對此并不怎么熟悉~后續在進階課程的內存管理,我們再慢慢糾結這個OOM的問題,好的, 本節就到這里,謝謝~
- 第一章——環境搭建和開發相關
- 1.0 Android基礎入門教程
- 1.1 背景相關與系統架構分析
- 1.2 開發環境搭建
- 1.2.1 使用Eclipse + ADT + SDK開發Android APP
- 1.2.2 使用Android Studio開發Android APP
- 1.3 SDK更新不了問題解決
- 1.4 Genymotion模擬器安裝
- 1.5 GIT教程
- 1.5.1 Git使用教程之本地倉庫的基本操作
- 1.5.2 Git之使用GitHub搭建遠程倉庫
- 1.6 .9(九妹)圖片怎么玩
- 1.7 界面原型設計
- 1.8 工程相關解析(各種文件,資源訪問)
- 1.9 Android程序簽名打包
- 1.11 反編譯APK獲取代碼&資源
- 第二章——Android中的UI組件的詳解
- 2.1 View與ViewGroup的概念
- 2.2 布局
- 2.2.1 LinearLayout(線性布局)
- 2.2.2 RelativeLayout(相對布局)
- 2.2.3 TableLayout(表格布局)
- 2.2.4 FrameLayout(幀布局)
- 2.2.5 GridLayout(網格布局)
- 2.2.6 AbsoluteLayout(絕對布局)
- 2.3 表單
- 2.3.1 TextView(文本框)詳解
- 2.3.2 EditText(輸入框)詳解
- 2.3.3 Button(按鈕)與ImageButton(圖像按鈕)
- 2.3.4 ImageView(圖像視圖)
- 2.3.5.RadioButton(單選按鈕)&Checkbox(復選框)
- 2.3.6 開關按鈕ToggleButton和開關Switch
- 2.3.7 ProgressBar(進度條)
- 2.3.8 SeekBar(拖動條)
- 2.3.9 RatingBar(星級評分條)
- 2.4 控件
- 2.4.1 ScrollView(滾動條)
- 2.4.2 Date & Time組件(上)
- 2.4.3 Date & Time組件(下)
- 2.4.4 Adapter基礎講解
- 2.4.5 ListView簡單實用
- 2.4.6 BaseAdapter優化
- 2.4.7ListView的焦點問題
- 2.4.8 ListView之checkbox錯位問題解決
- 2.4.9 ListView的數據更新問題
- 2.5 Adapter類控件
- 2.5.0 構建一個可復用的自定義BaseAdapter
- 2.5.1 ListView Item多布局的實現
- 2.5.2 GridView(網格視圖)的基本使用
- 2.5.3 Spinner(列表選項框)的基本使用
- 2.5.4 AutoCompleteTextView(自動完成文本框)的基本使用
- 2.5.5 ExpandableListView(可折疊列表)的基本使用
- 2.5.6 ViewFlipper(翻轉視圖)的基本使用
- 2.5.7 Toast(吐司)的基本使用
- 2.5.8 Notification(狀態欄通知)詳解
- 2.5.9 AlertDialog(對話框)詳解
- 2.6 對話框控件
- 2.6.0 其他幾種常用對話框基本使用
- 2.6.1 PopupWindow(懸浮框)的基本使用
- 2.6.2 菜單(Menu)
- 2.6.3 ViewPager的簡單使用
- 2.6.4 DrawerLayout(官方側滑菜單)的簡單使用
- 第三章——Android的事件處理機制
- 3.1.1 基于監聽的事件處理機制
- 3.2 基于回調的事件處理機制
- 3.3 Handler消息傳遞機制淺析
- 3.4 TouchListener PK OnTouchEvent + 多點觸碰
- 3.5 監聽EditText的內容變化
- 3.6 響應系統設置的事件(Configuration類)
- 3.7 AnsyncTask異步任務
- 3.8 Gestures(手勢)
- 第四章——Android的四大組件
- 4.1.1 Activity初學乍練
- 4.1.2 Activity初窺門徑
- 4.1.3 Activity登堂入室
- 4.2.1 Service初涉
- 4.2.2 Service進階
- 4.2.3 Service精通
- 4.3.1 BroadcastReceiver牛刀小試
- 4.3.2 BroadcastReceiver庖丁解牛
- 4.4.1 ContentProvider初探
- 4.4.2 ContentProvider再探——Document Provider
- 4.5.1 Intent的基本使用
- 4.5.2 Intent之復雜數據的傳遞
- 第五章——Fragment(碎片)
- 5.1 Fragment基本概述
- 5.2.1 Fragment實例精講——底部導航欄的實現(方法1)
- 5.2.2 Fragment實例精講——底部導航欄的實現(方法2)
- 5.2.3 Fragment實例精講——底部導航欄的實現(方法3)
- 5.2.4 Fragment實例精講——底部導航欄+ViewPager滑動切換頁面
- 5.2.5 Fragment實例精講——新聞(購物)類App列表Fragment的簡單實現
- 第六章——Android數據存儲與訪問
- 6.1 數據存儲與訪問之——文件存儲讀寫
- 6.2 數據存儲與訪問之——SharedPreferences保存用戶偏好參數
- 6.3.1 數據存儲與訪問之——初見SQLite數據庫
- 6.3.2 數據存儲與訪問之——又見SQLite數據庫
- 第七章——Android網絡編程
- 7.1.1 Android網絡編程要學的東西與Http協議學習
- 7.1.2 Android Http請求頭與響應頭的學習
- 7.1.3 Android HTTP請求方式:HttpURLConnection
- 7.1.4 Android HTTP請求方式:HttpClient
- 7.2.1 Android XML數據解析
- 7.2.2 Android JSON數據解析
- 7.3.1 Android 文件上傳
- 7.3.2 Android 文件下載(1)
- 7.3.3 Android 文件下載(2)
- 7.4 Android 調用 WebService
- 7.5.1 WebView(網頁視圖)基本用法
- 7.5.2 WebView和JavaScrip交互基礎
- 7.5.3 Android 4.4后WebView的一些注意事項
- 7.5.4 WebView文件下載
- 7.5.5 WebView緩存問題
- 7.5.6 WebView處理網頁返回的錯誤碼信息
- 7.6.1 Socket學習網絡基礎準備
- 7.6.2 基于TCP協議的Socket通信(1)
- 7.6.3 基于TCP協議的Socket通信(2)
- 7.6.4 基于UDP協議的Socket通信
- 第八章——Android繪圖與動畫基礎
- 8.1.1 Android中的13種Drawable小結 Part 1
- 8.1.2 Android中的13種Drawable小結 Part 2
- 8.1.3 Android中的13種Drawable小結 Part 3
- 8.2.1 Bitmap(位圖)全解析 Part 1
- 8.2.2 Bitmap引起的OOM問題
- 8.3.1 三個繪圖工具類詳解
- 8.3.2 繪圖類實戰示例
- 8.3.3 Paint API之—— MaskFilter(面具)
- 8.3.4 Paint API之—— Xfermode與PorterDuff詳解(一)
- 8.3.5 Paint API之—— Xfermode與PorterDuff詳解(二)
- 8.3.6 Paint API之—— Xfermode與PorterDuff詳解(三)
- 8.3.7 Paint API之—— Xfermode與PorterDuff詳解(四)
- 8.3.8 Paint API之—— Xfermode與PorterDuff詳解(五)
- 8.3.9 Paint API之—— ColorFilter(顏色過濾器)(1/3)
- 8.3.10 Paint API之—— ColorFilter(顏色過濾器)(2-3)
- 8.3.11 Paint API之—— ColorFilter(顏色過濾器)(3-3)
- 8.3.12 Paint API之—— PathEffect(路徑效果)
- 8.3.13 Paint API之—— Shader(圖像渲染)
- 8.3.14 Paint幾個枚舉/常量值以及ShadowLayer陰影效果
- 8.3.15 Paint API之——Typeface(字型)
- 8.3.16 Canvas API詳解(Part 1)
- 8.3.17 Canvas API詳解(Part 2)剪切方法合集
- 8.3.18 Canvas API詳解(Part 3)Matrix和drawBitmapMash
- 8.4.1 Android動畫合集之幀動畫
- 8.4.2 Android動畫合集之補間動畫
- 8.4.3 Android動畫合集之屬性動畫-初見
- 8.4.4 Android動畫合集之屬性動畫-又見
- 第九章——Android中的多媒體開發
- 9.1 使用SoundPool播放音效(Duang~)
- 9.2 MediaPlayer播放音頻與視頻
- 9.3 使用Camera拍照
- 9.4 使用MediaRecord錄音
- 第十章——系統服務
- 10.1 TelephonyManager(電話管理器)
- 10.2 SmsManager(短信管理器)
- 10.3 AudioManager(音頻管理器)
- 10.4 Vibrator(振動器)
- 10.5 AlarmManager(鬧鐘服務)
- 10.6 PowerManager(電源服務)
- 10.7 WindowManager(窗口管理服務)
- 10.8 LayoutInflater(布局服務)
- 10.9 WallpaperManager(壁紙管理器)
- 10.10 傳感器專題(1)——相關介紹
- 10.11 傳感器專題(2)——方向傳感器
- 10.12 傳感器專題(3)——加速度/陀螺儀傳感器
- 10.12 傳感器專題(4)——其他傳感器了解
- 10.14 Android GPS初涉
- 第十一章——由來、答疑和資源
- 11.0《2015最新Android基礎入門教程》完結散花~