【安卓笔记】OOM解决方案

时间:2022-10-16 20:58:41

主流方案无非是以下三种:

1:对图片进行缩放;

2:内存缓存;

3:文件缓存。

--------------------------------------------------

方法1:压缩图片

package com.example.utils;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

/**
* 防止OOM的第一种解决方案------>压缩bitmap
*/
public class BitmapUtils
{
private static final String TAG = "BitmapUtils";
/**
* 计算采样比
*/
private static int calculateInSampleSize(BitmapFactory.Options opts,int reqHeight,int reqWidth)
{
if(opts == null)
return -1;
int width = opts.outWidth;
int height = opts.outHeight;

int sampleSize = 1;

if(width > reqWidth || height > reqHeight)
{
int heightRatio = (int) (height/(float)reqHeight);
int widthRatio = (int) (width/(float)reqWidth);
sampleSize = (heightRatio > widthRatio) ? widthRatio : heightRatio;
}
return sampleSize;
}

/**
* 根据需要的宽高压缩一张图片
*
* @param res 资源
* @param resId 资源id
* @param reqWidth 需求宽度
* @param reqHeight 需求高度
* @return
*/
public static Bitmap decodeSampledBitmapFromResource(Resources res,int resId,int reqWidth,int reqHeight)
{
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, opts);
int sampleSize = calculateInSampleSize(opts, reqHeight, reqWidth);
Log.i(TAG,"before[width:"+opts.outWidth+",height:"+opts.outHeight+"]");
opts.inJustDecodeBounds = false;
opts.inSampleSize = sampleSize;
Log.i(TAG,"insamplesize="+sampleSize);
Bitmap bitmap = BitmapFactory.decodeResource(res, resId, opts);
Log.i(TAG,"after[width:"+bitmap.getWidth()+",height:"+bitmap.getHeight()+"]");
return bitmap;
}

/**
* 根据需要的宽高压缩一张图片
*
* @param data 包含图片信息的byte数组
* @param reqWidth
* @param reqHeight
* @return
*/
public static Bitmap decodeSampledBitmapFromByteArray(byte[] data,int reqWidth,int reqHeight)
{
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, opts);
Log.i(TAG,"before[width:"+opts.outWidth+",height:"+opts.outHeight+"]");
opts.inSampleSize = calculateInSampleSize(opts, reqHeight, reqWidth);
Log.i(TAG,"insamplesize="+opts.inSampleSize);
opts.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, opts);
Log.i(TAG,"after[width:"+bitmap.getWidth()+",height:"+bitmap.getHeight()+"]");
return bitmap;
}
}
方法2:使用LruCache进行图片缓存(内存缓存)

package com.example.utils;

import android.graphics.Bitmap;
import android.support.v4.util.LruCache;
import android.util.Log;

/**
*解决OOM的方式2---->使用LruCache进行内存缓存
*/
public class BitmapLruCacheUtils
{
private static final String TAG = "BitmapCacheUtils";
private static BitmapLruCacheUtils instance = new BitmapLruCacheUtils();
private BitmapLruCache cache = null;
private BitmapLruCacheUtils()
{
int maxSize = (int) Runtime.getRuntime().maxMemory();//以字节单位,注意和sizeof方法单位一致
int cacheSize = maxSize / 8;
cache = new BitmapLruCache(cacheSize);
}

public static BitmapLruCacheUtils getInstance()
{
return instance;
}
/**
* 将bitmap图片加入内存缓存
* @param key
* @param bitmap
*/
public void addBitmapToMemoryCache(String key,Bitmap bitmap)
{
if(cache!=null && getBitmapFromMemoryCache(key)==null)
{
cache.put(key, bitmap);
Log.i(TAG,"---->>>put");
}
}

/**
* 根据指定的键获取内存缓存中的图片
* @param key
* @return
*/
public Bitmap getBitmapFromMemoryCache(String key)
{
if(key == null || cache == null)
{
return null;
}
Bitmap bitmap = cache.get(key);
if(bitmap == null)
{
Log.i(TAG,"---->>>get failed");
}else
{
Log.i(TAG,"---->>>get success");
}
return bitmap;
}

private class BitmapLruCache extends LruCache<String,Bitmap>
{
public BitmapLruCache(int cacheSize)
{
super(cacheSize);
}
@Override
protected int sizeOf(String key, Bitmap value)
{
return value.getRowBytes() * value.getHeight();//以字节为单位
}
}
}

方法3:文件缓存 基本原理就是将图片存到文件中,下次加载图片时就从文件缓存中获取,如果没有获取到才去网络上下载。 我们需要定义一个阀值,即最大缓存的空间,当超过阀值时,就会根据近期最少使用的淘汰算法来删除部分缓存。另外当sd卡存储空间不足时也会清除部分缓存。
package com.example.utils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Comparator;

import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log;

/**
* @author Rowand jj
*
*文件缓存
*/
public class BitmapFileCacheUtils
{
/**
*图片缓存路径
*/
private static final String IMG_CACH_DIR = "/imgCache";

/**
*缓存的扩展名
*/
private static final String CACHE_TAIL = ".cach";

/**
* 最大缓存空间,单位是mb
*/
private static final int CACHE_SIZE = 10;

/**
* sd卡内存低于此值时将会清理缓存,单位是mb
*/
private static final int NEED_TO_CLEAN = 10;

private static final String TAG = "BitmapFileCacheUtils";
/**
* 从缓存中获取一张图片
*/
public static Bitmap getBitmapFromFile(String key)
{
if(key==null || !isSdcardAvailable())
{
return null;
}
String path = Environment.getExternalStorageDirectory().getPath()+IMG_CACH_DIR+"/"+convertKeyToFilename(key);
File file = new File(path);
if(file.exists())
{
Bitmap bitmap = BitmapFactory.decodeFile(path);
if(bitmap == null)
{
file.delete();
}
else
{
updateFileModifiedTime(path);
Log.i(TAG,"get file success...");
return bitmap;
}
}
return null;
}
/**
* 将图片存入文件缓存
*/
public static void addBitmapToFile(String key,Bitmap bm)
{
if(bm == null || key == null|| !isSdcardAvailable())
{
return;
}
//视情况清除部分缓存
removeCache(Environment.getExternalStorageDirectory().getPath()+IMG_CACH_DIR);

String filename = convertKeyToFilename(key);
File dir = new File(Environment.getExternalStorageDirectory().getPath()+IMG_CACH_DIR);
if(!dir.exists())
{
dir.mkdirs();
}
File file = new File(dir, filename);
try
{
OutputStream out = new FileOutputStream(file);//这里需要注意,如果指定目录不存在,应该先调用mkdirs生成目录,否则可能创建文件失败
bm.compress(CompressFormat.JPEG,100, out);
out.close();
Log.i(TAG,"add to fils success...");
} catch (Exception e)
{
e.printStackTrace();
}
}
/**
*
* 清除40%的缓存,这些缓存被删除的优先级根据近期使用时间排列,越久没被使用,越容易被删除
*/
private static void removeCache(String dirPath)
{
File dir = new File(dirPath);
File[] files = dir.listFiles();
if(files == null)
{
return;
}
double total_size = 0;
for(File file : files)
{
total_size+=file.length();
}
total_size = total_size/1024/1024;
Log.i(TAG,"total"+total_size);
if(total_size > CACHE_SIZE || getSdCardFreeSpace() <= NEED_TO_CLEAN)
{
Log.i(TAG,"remove cache...");
int removeFactor = (int) (files.length*0.4);
Arrays.sort(files, new FileLastModifiedComparator());
for(int i = 0; i < removeFactor; i++)
{
files[i].delete();
}
}
}

/**
*获取sd卡可用空间
*/
private static int getSdCardFreeSpace()
{
StatFs stat = new StatFs(Environment.getExternalStorageDirectory().getPath());
double freespace = stat.getAvailableBlocks()*stat.getBlockSize();
return (int) (freespace/1024/1024);
}
/**
*判断sd卡是否可用
* @return
*/
private static boolean isSdcardAvailable()
{
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
/**
* 将关键字转化为文件名
*/
private static String convertKeyToFilename(String key)
{
if(key == null)
{
return "";
}
return key.hashCode()+CACHE_TAIL;
}
/**
* 更新文件最后修改时间
*/
private static void updateFileModifiedTime(String path)
{
File file = new File(path);
file.setLastModified(System.currentTimeMillis());
}

private static class FileLastModifiedComparator implements Comparator<File>
{
@Override
public int compare(File lhs, File rhs)
{
if(lhs.lastModified() > rhs.lastModified())
{
return 1;
}else if(lhs.lastModified() == rhs.lastModified())
{
return 0;
}else
{
return -1;
}
}
}
}