图片加载避免OOM+代码示例

时间:2022-03-03 20:56:51

总结学习笔记!
一、Android四大图片缓存框架

Android四大图片缓存(Imageloader,Picasso,Glide,Fresco)原理、特性对比

二、OOM问题剖析和解决方案

Android应用中OOM问题剖析和解决方案

三、避免OOM加载图片代码示例
1、ImageLoader
三级的缓存结构:内存绑定url和bitmap,内存绑定url和imageview,内存绑定url和file
先再内存中取,得不到了去读取缓存文件,仍然得不到去请求网络下载,下载到缓存中,然后绑定到内存中
DisplayImage(url,ImageView );调用入口

public class ImageLoader {

MemoryCache memoryCache=new MemoryCache();
FileCache fileCache;
private Map<ImageView, String> imageViews=Collections.synchronizedMap(new WeakHashMap<ImageView, String>());
ExecutorService executorService;
Handler handler=new Handler();//handler to display images in UI thread

public ImageLoader(Context context){
fileCache=new FileCache(context);
executorService=Executors.newFixedThreadPool(5);
}

final int stub_id=R.drawable.stub;
public void DisplayImage(String url, ImageView imageView)
{
//每个条目对应一个url
imageViews.put(imageView, url);
//先从内存中拿到url对应的bitmap
Bitmap bitmap=memoryCache.get(url);
if(bitmap!=null)
//内存拿到立即渲染
imageView.setImageBitmap(bitmap);
else
{
//去走缓存和网络
queuePhoto(url, imageView);
imageView.setImageResource(stub_id);
}
}

private void queuePhoto(String url, ImageView imageView)
{
//主要是为了防止错位
PhotoToLoad p=new PhotoToLoad(url, imageView);
//线程池执行加载
executorService.submit(new PhotosLoader(p));
}

private Bitmap getBitmap(String url)
{
File f=fileCache.getFile(url);

//from SD cache
Bitmap b = decodeFile(f);
if(b!=null)
return b;

//from web
try {
Bitmap bitmap=null;
URL imageUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection)imageUrl.openConnection();
conn.setConnectTimeout(30000);
conn.setReadTimeout(30000);
conn.setInstanceFollowRedirects(true);
InputStream is=conn.getInputStream();
OutputStream os = new FileOutputStream(f);
Utils.CopyStream(is, os);
os.close();
conn.disconnect();
bitmap = decodeFile(f);
return bitmap;
} catch (Throwable ex){
ex.printStackTrace();
if(ex instanceof OutOfMemoryError)
memoryCache.clear();
return null;
}
}

//decodes image and scales it to reduce memory consumption
//控制加载进入的大小,进行图片压缩,防止单张图片过大造成oom并且节省内存,还能拿到需要的大小的图片
private Bitmap decodeFile(File f){
try {
//decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream stream1=new FileInputStream(f);
BitmapFactory.decodeStream(stream1,null,o);
stream1.close();

//Find the correct scale value. It should be the power of 2.
final int REQUIRED_SIZE=70;
int width_tmp=o.outWidth, height_tmp=o.outHeight;
int scale=1;
while(true){
if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
break;
width_tmp/=2;
height_tmp/=2;
scale*=2;
}

//decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
FileInputStream stream2=new FileInputStream(f);
Bitmap bitmap=BitmapFactory.decodeStream(stream2, null, o2);
stream2.close();
return bitmap;
} catch (FileNotFoundException e) {
}
catch (IOException e) {
e.printStackTrace();
}
return null;
}

//Task for the queue
private class PhotoToLoad
{
public String url;
public ImageView imageView;
public PhotoToLoad(String u, ImageView i){
url=u;
imageView=i;
}
}

class PhotosLoader implements Runnable {
PhotoToLoad photoToLoad;
PhotosLoader(PhotoToLoad photoToLoad){
this.photoToLoad=photoToLoad;
}

@Override
public void run() {
try{
if(imageViewReused(photoToLoad))
return;
//网络下载到本地缓存
Bitmap bmp=getBitmap(photoToLoad.url);
/绑定到内存
memoryCache.put(photoToLoad.url, bmp);
if(imageViewReused(photoToLoad))
return;
BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad);
handler.post(bd);
}catch(Throwable th){
th.printStackTrace();
}
}
}

boolean imageViewReused(PhotoToLoad photoToLoad){
String tag=imageViews.get(photoToLoad.imageView);
if(tag==null || !tag.equals(photoToLoad.url))
return true;
return false;
}

//Used to display bitmap in the UI thread
class BitmapDisplayer implements Runnable
{
Bitmap bitmap;
PhotoToLoad photoToLoad;
public BitmapDisplayer(Bitmap b, PhotoToLoad p){bitmap=b;photoToLoad=p;}
public void run()
{
if(imageViewReused(photoToLoad))
return;
if(bitmap!=null)
photoToLoad.imageView.setImageBitmap(bitmap);
else
photoToLoad.imageView.setImageResource(stub_id);
}
}

public void clearCache() {
memoryCache.clear();
fileCache.clear();
}

}

2、MemoryCache
LinkedHashMap有lru的特性。
MemoryCache设置了内存限制,为当前应用运行内存的25%。
关注MemoryCache get() put()方法


public class MemoryCache {

private static final String TAG = "MemoryCache";
private Map<String, Bitmap> cache=Collections.synchronizedMap(
new LinkedHashMap<String, Bitmap>(10,1.5f,true));//Last argument true for LRU ordering
private long size=0;//current allocated size
private long limit=1000000;//max memory in bytes

public MemoryCache(){
//use 25% of available heap size
setLimit(Runtime.getRuntime().maxMemory()/4);
}

public void setLimit(long new_limit){
limit=new_limit;
Log.i(TAG, "MemoryCache will use up to "+limit/1024./1024.+"MB");
}

public Bitmap get(String id){
try{
if(!cache.containsKey(id))
return null;
//NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78
return cache.get(id);
}catch(NullPointerException ex){
ex.printStackTrace();
return null;
}
}

public void put(String id, Bitmap bitmap){
try{
if(cache.containsKey(id))
size-=getSizeInBytes(cache.get(id));
cache.put(id, bitmap);
size+=getSizeInBytes(bitmap);
//检查加载进来新的bitmap后内存情况,准备清理内存
checkSize();
}catch(Throwable th){
th.printStackTrace();
}
}

private void checkSize() {
Log.i(TAG, "cache size="+size+" length="+cache.size());
if(size>limit){//加载进来后大于限制,就开始清理内存
Iterator<Entry<String, Bitmap>> iter=cache.entrySet().iterator();//least recently accessed item will be the first one iterated
while(iter.hasNext()){
Entry<String, Bitmap> entry=iter.next();
size-=getSizeInBytes(entry.getValue());
iter.remove();
if(size<=limit)//直到达到限制内
break;
}
Log.i(TAG, "Clean cache. New size "+cache.size());
}
}

public void clear() {
try{
//NullPointerException sometimes happen here http://code.google.com/p/osmdroid/issues/detail?id=78
cache.clear();
size=0;
}catch(NullPointerException ex){
ex.printStackTrace();
}
}

long getSizeInBytes(Bitmap bitmap) {
if(bitmap==null)
return 0;
return bitmap.getRowBytes() * bitmap.getHeight();
}
}

3.FileCache


public class FileCache {

private File cacheDir;

public FileCache(Context context){
//Find the dir to save cached images
if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"LazyList");
else
cacheDir=context.getCacheDir();
if(!cacheDir.exists())
cacheDir.mkdirs();
}

public File getFile(String url){
//I identify images by hashcode. Not a perfect solution, good for the demo.
String filename=String.valueOf(url.hashCode());
//Another possible solution (thanks to grantland)
//String filename = URLEncoder.encode(url);
File f = new File(cacheDir, filename);
return f;

}

public void clear(){
File[] files=cacheDir.listFiles();
if(files==null)
return;
for(File f:files)
f.delete();
}

}

四、图片的压缩式必须的

因为图片的大小=长 X 宽 X 每个像素点占用的字节大小
所以压缩图片就从以上几个方面入手

[bitmap的六种压缩方式,Android图片压缩]
(http://blog.csdn.net/harryweasley/article/details/51955467)

最详细的Android图片压缩解释