最简单的解决办法,用现成的框架,推荐glide和picasso
一、glide下载地址:https://github.com/bumptech/glide
用法:在build.gradle中加入:
repositories { mavenCentral() maven { url 'https://maven.google.com' } } dependencies { compile 'com.github.bumptech.glide:glide:4.1.1' annotationProcessor 'com.github.bumptech.glide:compiler:4.1.1' }
使用:
ImageView imageView = (ImageView) findViewById(R.id.my_image_view); Glide.with(this).load("http://goo.gl/gEgYUd").into(imageView);
this:上下文 load里面要加载的图片网址
二、picasso下载地址:https://github.com/square/picasso
用法:在build.gradle中加入:
compile 'com.squareup.picasso:picasso:2.5.2'
使用:
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").resize(50, 50).into(imageView);
三、内存溢出解决原理:(转自:http://www.cnblogs.com/Free-Thinker/p/6078765.html)
方案一、读取图片时注意方法的调用,适当压缩 尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。 因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source,decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。
InputStream is = this.getResources().openRawResource(R.drawable.pic1);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = 10; // width,hight设为原来的十分一
Bitmap btp = BitmapFactory.decodeStream(is, null, options);
如果在读取时加上图片的Config参数,可以跟有效减少加载的内存,从而跟有效阻止抛out of Memory异常。
/**
* 以最省内存的方式读取本地资源的图片
* @param context
* @param resId
* @return
*/
public static Bitmap readBitMap(Context context, int resId){
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
// 获取资源图片
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
另外,decodeStream直接拿图片来读取字节码, 不会根据机器的各种分辨率来自动适应,使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图片资源, 否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。
方案二、在适当的时候及时回收图片占用的内存 通常Activity或者Fragment在onStop/onDestroy时候就可以释放图片资源:
if(imageView != null && imageView.getDrawable() != null){
Bitmap oldBitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
imageView.setImageDrawable(null);
if(oldBitmap != null){
oldBitmap.recycle();
oldBitmap = null;
}
}
// Other code.
System.gc();
在释放资源时,需要注意释放的Bitmap或者相关的Drawable是否有被其它类引用。如果正常的调用,可以通过Bitmap.isRecycled()方法来判断是否有被标记回收;而如果是被UI线程的界面相关代码使用,就需要特别小心避免回收有可能被使用的资源,不然有可能抛出系统异常: E/AndroidRuntime: java.lang.IllegalArgumentException: Cannot draw recycled
bitmaps 并且该异常无法有效捕捉并处理。
方案三、不必要的时候避免图片的完整加载 只需要知道图片大小的情形下,可以不完整加载图片到内存。 在使用BitmapFactory压缩图片的时候,BitmapFactory.Options设置inJustDecodeBounds为true后,再使用decodeFile()等方法,可以在不分配空间状态下计算出图片的大小。示例:
BitmapFactory.Options opts = new BitmapFactory.Options();
// 设置inJustDecodeBounds为false
opts.inJustDecodeBounds = false;
// 使用decodeFile方法得到图片的宽和高
BitmapFactory.decodeFile(path, opts);
// 打印出图片的宽和高
Log.d("example", opts.outWidth + "," + opts.outHeight);
(ps:原理其实就是通过图片的头部信息读取图片的基本信息)
方案四、优化Dalvik虚拟机的堆内存分配 堆(HEAP)是VM中占用内存最多的部分,通常是动态分配的。堆的大小不是一成不变的,通常有一个分配机制来控制它的大小。比如初始的HEAP是4M大,当4M的空间被占用超过75%的时候,重新分配堆为8M大;当8M被占用超过75%,分配堆为16M大。倒过来,当16M的堆利用不足30%的时候,缩减它的大小为8M大。重新设置堆的大小,尤其是压缩,一般会涉及到内存的拷贝,所以变更堆的大小对效率有不良影响。 Heap
Utilization是堆的利用率。当实际的利用率偏离这个百分比的时候,虚拟机会在GC的时候调整堆内存大小,让实际占用率向个百分比靠拢。使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法可以增强程序堆内存的处理效率。
private final static float TARGET_HEAP_UTILIZATION = 0.75f;
// 在程序onCreate时就可以调用
VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION);
方案五、自定义堆(Heap)内存大小 对于一些Android项目,影响性能瓶颈的主要是Android自己内存管理机制问题,目前手机厂商对RAM都比较吝啬,对于软件的流畅性来说RAM对性能的影响十分敏感,除了优化Dalvik虚拟机的堆内存分配外,我们还可以强制定义自己软件的对内存大小,我们使用Dalvik提供的
dalvik.system.VMRuntime类来设置最小堆内存为例:
private final static int CWJ_HEAP_SIZE = 6 * 1024 * 1024 ;
VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); // 设置最小heap内存为6MB大小。
但是上面方法还是存在问题,函数setMinimumHeapSize其实只是改变了堆的下限值,它可以防止过于频繁的堆内存分配,当设置最小堆内存大小超过上限值(Max Heap Size)时仍然采用堆的上限值,对于内存不足没什么作用。
在默认情况下android进程的内存占用量为16M,因为Bitmap他除了java中持有数据外,底层C++的 skia图形库还会持有一个SKBitmap对象,因此一般图片占用内存推荐大小应该不超过8M。这个可以调整,编译源代码时可以设置参数。
参考资料:http://www.tuicool.com/articles/yemM7zf
方案六:在Manifest.xml文件里面的<application 里面添加Android:largeHeap="true"
简单粗暴。这种方法允许应用需要耗费手机很多的内存空间,但却是最快捷的解决办法
四、三级缓存实现原理:(转自:http://blog.csdn.net/lovoo/article/details/51456515)
实现图片缓存也不难,需要有相应的cache策略。这里我采用 内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference),其实网络不算cache,这里姑且也把它划到缓存的层次结构中。当根据url向网络拉取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求拉取图片。在键值对(key-value)中,这个图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。
但这里不使用SoftReference,而使用LruCache进行图片的缓存
为什么使用LruCache:
这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
具体实现:
1)在构造方法里初始化LruCache mCache
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
2)去内存中取
- 1
- 2
- 3
- 4
- 5
- 6
3)去硬盘上取
- 1
- 2
- 3
- 4
- 5
- 6
4)从网络加载
详细代码:public class ImageHelper { // 内存缓存池 // private Map<String, SoftReference<Bitmap>> mCache = new // LinkedHashMap<String, SoftReference<Bitmap>>(); // LRUCahce 池子 private static LruCache<String, Bitmap> mCache; private static Handler mHandler; private static ExecutorService mThreadPool; private static Map<ImageView, Future<?>> mTaskTags = new LinkedHashMap<ImageView, Future<?>>(); private Context mContext; public ImageHelper(Context context) { this.mContext = context; if (mCache == null) { // 最大使用的内存空间 int maxSize = (int) (Runtime.getRuntime().freeMemory() / 4); mCache = new LruCache<String, Bitmap>(maxSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight(); } }; } if (mHandler == null) { mHandler = new Handler(); } if (mThreadPool == null) { // 最多同时允许的线程数为3个 mThreadPool = Executors.newFixedThreadPool(3); } } public void display(ImageView iv, String url) { // 1.去内存中取 Bitmap bitmap = mCache.get(url); if (bitmap != null) { // 直接显示 iv.setImageBitmap(bitmap); return; } // 2.去硬盘上取 bitmap = loadBitmapFromLocal(url); if (bitmap != null) { // 直接显示 iv.setImageBitmap(bitmap); return; } // 3. 去网络获取图片 loadBitmapFromNet(iv, url); } private void loadBitmapFromNet(ImageView iv, String url) { // 开线程去网络获取 // 使用线程池管理 // new Thread(new ImageLoadTask(iv, url)).start(); // 判断是否有线程在为 imageView加载数据 Future<?> futrue = mTaskTags.get(iv); if (futrue != null && !futrue.isCancelled() && !futrue.isDone()) { System.out.println("取消 任务"); // 线程正在执行 futrue.cancel(true); futrue = null; } // mThreadPool.execute(new ImageLoadTask(iv, url)); futrue = mThreadPool.submit(new ImageLoadTask(iv, url)); // Future 和 callback/Runable // 返回值,持有正在执行的线程 // 保存 mTaskTags.put(iv, futrue); System.out.println("标记 任务"); } class ImageLoadTask implements Runnable { private String mUrl; private ImageView iv; public ImageLoadTask(ImageView iv, String url) { this.mUrl = url; this.iv = iv; } @Override public void run() { // HttpUrlconnection try { // 获取连接 HttpURLConnection conn = (HttpURLConnection) new URL(mUrl).openConnection(); conn.setConnectTimeout(30 * 1000);// 设置连接服务器超时时间 conn.setReadTimeout(30 * 1000);// 设置读取响应超时时间 // 连接网络 conn.connect(); // 获取响应码 int code = conn.getResponseCode(); if (200 == code) { InputStream is = conn.getInputStream(); // 将流转换为bitmap Bitmap bitmap = BitmapFactory.decodeStream(is); // 存储到本地 write2Local(mUrl, bitmap); // 存储到内存 mCache.put(mUrl, bitmap); // 图片显示:不可取 // iv.setImageBitmap(bitmap); mHandler.post(new Runnable() { @Override public void run() { // iv.setImageBitmap(bitmap); display(iv, mUrl); } }); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * 本地种去去图片 * * @param url */ private Bitmap loadBitmapFromLocal(String url) { // 去找文件,将文件转换为bitmap String name; try { name = MD5Encoder.encode(url); File file = new File(getCacheDir(), name); if (file.exists()) { Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath()); // 存储到内存 mCache.put(url, bitmap); return bitmap; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } private void write2Local(String url, Bitmap bitmap) { String name; FileOutputStream fos = null; try { name = MD5Encoder.encode(url); File file = new File(getCacheDir(), name); fos = new FileOutputStream(file); // 将图像写到流中 bitmap.compress(CompressFormat.JPEG, 100, fos); } catch (Exception e) { e.printStackTrace(); } finally { if (fos != null) { try { fos.close(); fos = null; } catch (IOException e) { e.printStackTrace(); } } } } private String getCacheDir() { String state = Environment.getExternalStorageState(); File dir = null; if (Environment.MEDIA_MOUNTED.equals(state)) { // 有sd卡 dir = new File(Environment.getExternalStorageDirectory(), "/Android/data/" + mContext.getPackageName() + "/icon"); } else { // 没有sd卡 dir = new File(mContext.getCacheDir(), "/icon"); } if (!dir.exists()) { dir.mkdirs(); } return dir.getAbsolutePath(); } }
使用方法:
ImageView iv = (contentView)findViewById(R.id.iv); String url = "http://localhost:8080/web/1.jpg"; new IamgeHelper(this).display(iv,url);