接下來我們來討論OOM的問題。
在做美食生活的項(xiàng)目的時(shí)候,有個(gè)功能是拍照上傳圖片的功能,沒有對(duì)圖片進(jìn)行壓縮,直接進(jìn)行了上傳,
11-21 22:05:28.774: E/dalvikvm-heap(13216): Out of memory on a 10485476-byte allocation.
11-21 22:05:28.784: E/AndroidRuntime(13216): java.lang.OutOfMemoryError
好了,出現(xiàn)了我們的OOM了!
一般造成OOM的最大的原因是Bitmap,Bitmap,Bitmap重要的事情說三遍!
一般的OOM經(jīng)常出現(xiàn)在加載很多圖片或者是加載高清大圖的時(shí)候!當(dāng)一個(gè)app啟動(dòng)后,虛擬機(jī)不停的申請(qǐng)內(nèi)存資源來裝載圖片,當(dāng)超過內(nèi)存上限時(shí)就出現(xiàn)OOM。這個(gè)現(xiàn)象如果你在跑monkey進(jìn)行測(cè)試的時(shí)候更容易出現(xiàn)!
Android的APP內(nèi)存組成:
APP內(nèi)存由 dalvik內(nèi)存 和 native內(nèi)存2部分組成,dalvik也就是java堆,創(chuàng)建的對(duì)象就是就是在這里分配的,而native是通過c/c++方式申請(qǐng)的內(nèi)存,Bitmap就是以這種方式分配的。(android3.0以后,系統(tǒng)都默認(rèn)通過dalvik分配的,native作為堆來管理)。這2部分加起來不能超過android對(duì)單個(gè)進(jìn)程,虛擬機(jī)的內(nèi)存限制。
從代碼中獲取每個(gè)手機(jī)的內(nèi)存限制大?。?/p>
ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)
activityManager.getMemoryClass();
而對(duì)于head堆的大小限制,可以查看/system/build.prop文件。
dalvik.vm.heapstartsize = 5m
dalvik.vm.heapgrowthlimit = 48m
dalvik.vm.heapsize = 256m
注: heapsize參數(shù)表示單個(gè)進(jìn)程heap可用的最大內(nèi)存,但如果存在以下參數(shù)"dalvik.vm.headgrowthlimit =48m"表示單個(gè)進(jìn)程heap內(nèi)存被限定在48m,即程序運(yùn)行過程實(shí)際只能使用48m內(nèi)存。
怎么查看APP內(nèi)存分配情況?
1 在我們的AS中可以通過Monitor監(jiān)控得到

2 在App里面我們可以通過totalMemory與freeMemory:
Runtime.getRuntime().freeMemory()
RUntime.getRuntime().totalMemory()
3 adb shell dumpsys meminfo com.android.demo
這個(gè)是adb命令了,com.android.demo是包名。不會(huì)的可以去看我的另一篇博客:ADB 常用命令及詳解

常見避免OOM的幾個(gè)注意點(diǎn):
適當(dāng)調(diào)整圖像大小 。因?yàn)槭謾C(jī)屏幕尺寸有限,分配給圖像的顯示區(qū)域有限,尤其對(duì)于超大圖片,加載自網(wǎng)絡(luò)或者sd卡,圖片文件提及達(dá)到幾M或者十幾個(gè)M的:
加載到內(nèi)存前,先算出該bitmap的大小,然后通過適當(dāng)調(diào)節(jié)采樣率使得加載的圖片剛好,或稍大在手機(jī)屏幕上顯示就滿意了:
BimtapFactory.Option opts = new BitampFactory.Option();
opts.inJustDecodeBounds = true ;
opts.inSampleSize=computeSample(opts, minSideLength, maxNumOfPixels); // Android 提供了一種動(dòng)態(tài)計(jì)算的方法 computeSampleSize
opts.inJustDecodeBounds = false ;
try {
return BitmapFactory.decodeFile(imageFile, opts);
} catch (OutOfMemoryError err){
圖像緩存
在listview或Gallery等控件中一次性加載大量圖片時(shí),只加載屏幕顯示的資源,尚未顯示的不加載,移出屏幕的資源及時(shí)釋放,采用強(qiáng)引用+軟引用2級(jí)緩存,提高加載性能。緩存圖像到內(nèi)存,采用軟引用緩存到內(nèi)存,而不是在每次使用的時(shí)候都從新加載到內(nèi)存。
及時(shí)回收?qǐng)D像 。
如果引用了大量的Bitmap對(duì)象,而應(yīng)用又不需要同時(shí)顯示所有圖片??梢詫簳r(shí)不用到的Bitmap對(duì)象及時(shí)回收掉。對(duì)于一些明確直到圖片使用情況的場(chǎng)景可以主動(dòng)recycle回收
App的啟動(dòng)splash畫面上的圖片資源,使用完就recycle。對(duì)于幀動(dòng)畫,可以加載一張,畫一張,釋放一張。
不要在循環(huán)中創(chuàng)建過多的本地變量 。
慎用static,用static來修飾成員變量時(shí),該變量就屬于該類,而不是該類實(shí)例,它的生命周期是很長(zhǎng)的。如果用它來引用一些內(nèi)存占用太多的實(shí)例,這時(shí)候就要謹(jǐn)慎對(duì)待了。
重點(diǎn)要來了?。?!

對(duì)于圖App使用圖片時(shí)避免OOM的幾種方式有三四種,我只講一種:
LruCache + sd的緩存方式 :##
很多圖片處理框架和網(wǎng)絡(luò)請(qǐng)求框架都已經(jīng)做了三級(jí)緩存技術(shù),所以在使用框架加載圖片時(shí),利用好他們給的API和方法就行了。
如果就是簡(jiǎn)單的保存幾張圖片,簡(jiǎn)單的緩存,不想引入框架,那么可以看看這個(gè)!
/**
* Bitmap緩存,簡(jiǎn)單緩存的設(shè)置類.
* Created by ChangMingShan on 2016/12/26.
*/
public class BitmapMemoryCache {
private static final String TAG = "BitmapMemoryCache";
private static BitmapMemoryCache sInstance = new BitmapMemoryCache();
private LruCache<String, Bitmap> mMemoryCache;
/**
* 單例模式.
*/
public static BitmapMemoryCache getInstance() {
return BitmapMemoryCache.sInstance;
}
private BitmapMemoryCache() {
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// 重寫此方法來衡量每張圖片的大小,默認(rèn)返回圖片數(shù)量。
return bitmap.getByteCount() / 1024;
}
};
}
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();
}
}
}
/**
* 移除緩存
*/
public synchronized void clearImageCache() {
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 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 Bitmap loadLocal(String path) {/*這是加載本地的圖片的步驟*/
Bitmap bitmap=BitmapFactory.decodeFile(path);
addBitmapToMemoryCache(path, bitmap);
return getBitmapFromMemCache(path);
}
/*
將圖片進(jìn)行壓縮代碼,本次未用到
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false; // 設(shè)置了此屬性一定要記得將值設(shè)置為false
Bitmap bitmap = null;
bitmap = BitmapFactory.decodeFile(url, options);
int be = (int) ((options.outHeight > options.outWidth ? options.outHeight / 150
: options.outWidth / 200));
if (be <= 0) // 判斷200是否超過原始圖片高度
be = 1; // 如果超過,則不進(jìn)行縮放
options.inSampleSize = be;
options.inPreferredConfig = Bitmap.Config.ARGB_4444;
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = false;
try {
bitmap = BitmapFactory.decodeFile(url, options);
} catch (OutOfMemoryError e) {
System.gc();
Log.e(TAG, "OutOfMemoryError");
}
*/
}
我們可以這樣定義:map里面的鍵是用來放圖片地址的,既可以是網(wǎng)絡(luò)上的圖片地址,也可以SDcard上的圖片地址,
map里面的值里面放的是持有軟引用的Bitmap
/**
* Bitmap緩存網(wǎng)絡(luò)圖片或者是本地圖片,簡(jiǎn)單緩存.
* Created by ChangMingShan on 2016/12/26.
*/
public class SaveBitmapMemoryCache {
private Map<String, SoftReference<Bitmap>> imageMap = new HashMap<String, SoftReference<Bitmap>>();
private static final String TAG = "NetBitmapMemoryCache";
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 ;
}
// 從網(wǎng)上下載圖片
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 static boolean saveUrlAs(String fileUrl, String savePath)/*fileUrl網(wǎng)絡(luò)資源地址*/
{//這是將圖片緩存到本地的代碼,此次也未用到,用到改一下就ok
try
{
URL url = new URL(fileUrl);/*將網(wǎng)絡(luò)資源地址傳給,即賦值給url*/
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
DataInputStream in = new DataInputStream(connection.getInputStream());
DataOutputStream out = new DataOutputStream(new FileOutputStream(savePath));
byte[] buffer = new byte[4096];
int count = 0;
while ((count = in.read(buffer)) > 0)/*將輸入流以字節(jié)的形式讀取并寫入buffer中*/
{
out.write(buffer, 0, count);
}
out.close();/*前額安吉的將流關(guān)閉*/
in.close();
connection.disconnect();
return true; /*網(wǎng)絡(luò)資源截取并存儲(chǔ)本地成功返回true*/
}
catch (Exception e)
{
return false;
}*/
}
public interface ImageCallBack{
void getBitmap(Bitmap bitmap);
}
}
如果能將Bitmap這個(gè)東西解決好,你的OOM基本不怎么會(huì)出現(xiàn)了,不建議修改系統(tǒng)或者是dalvike里面的東西。
還有一些OOM情況出現(xiàn)在加載非常多條目信息的listview,gridview等的情況,在這里推薦使用Recyclerview或者在這些adapter的getView函數(shù)里有個(gè)convertView參數(shù)。