Android--图片加载处理(内存溢出和三级缓存)

简介: 版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/chaoyu168/article/details/78052920 最简单的解决办法...
版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/chaoyu168/article/details/78052920

最简单的解决办法,用现成的框架,推荐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)

方案一、读取图片时注意方法的调用,适当压缩  尽量不要使用setImageBitmapsetImageResourceBitmapFactory.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;   // widthhight设为原来的十分一

         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

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();
                }
            };
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

2)去内存中取

Bitmap bitmap = mCache.get(url);
        if (bitmap != null) {
            // 直接显示
            iv.setImageBitmap(bitmap);
            return;
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

3)去硬盘上取

bitmap = loadBitmapFromLocal(url);
        if (bitmap != null) {
            // 直接显示
            iv.setImageBitmap(bitmap);
            return;
            }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

4)从网络加载

loadBitmapFromNet(iv, url);
详细代码:

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);

大神博客详细介绍: https://blog.csdn.net/guolin_blog/article/details/53759439

目录
相关文章
|
1月前
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
33 2
|
14天前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
37 20
Android经典面试题之图片Bitmap怎么做优化
|
16天前
|
Java 测试技术 Android开发
Android性能测试——发现和定位内存泄露和卡顿
本文详细介绍了Android应用性能测试中的内存泄漏与卡顿问题及其解决方案。首先,文章描述了使用MAT工具定位内存泄漏的具体步骤,并通过实例展示了如何分析Histogram图表和Dominator Tree。接着,针对卡顿问题,文章探讨了其产生原因,并提供了多种测试方法,包括GPU呈现模式分析、FPS Meter软件测试、绘制圆点计数法及Android Studio自带的GPU监控功能。最后,文章给出了排查卡顿问题的四个方向,帮助开发者优化应用性能。
58 4
Android性能测试——发现和定位内存泄露和卡顿
|
8天前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
29 8
|
14天前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
31 0
|
1月前
|
数据处理 开发工具 数据安全/隐私保护
Android平台RTMP推送|轻量级RTSP服务|GB28181接入之文字、png图片水印的精进之路
本文探讨了Android平台上推流模块中添加文字与PNG水印的技术演进。自2015年起,为了满足应急指挥及安防领域的需求,逐步发展出三代水印技术:第一代为静态文字与图像水印;第二代实现了动态更新水印内容的能力,例如实时位置与时间信息;至第三代,则优化了数据传输效率,直接使用Bitmap对象传递水印数据至JNI层,减少了内存拷贝次数。这些迭代不仅提升了用户体验和技术效率,也体现了开发者追求极致与不断创新的精神。
|
1月前
|
缓存 程序员
封装一个给 .NET Framework 用的内存缓存帮助类
封装一个给 .NET Framework 用的内存缓存帮助类
|
1月前
|
缓存 开发框架 .NET
看看 Asp.net core Webapi 项目如何优雅地使用内存缓存
看看 Asp.net core Webapi 项目如何优雅地使用内存缓存
|
1月前
|
编解码 Android开发 UED
【性能狂飙!】揭秘Android应用极速变身秘籍:内存瘦身+用户体验升级,打造丝滑流畅新境界!
【8月更文挑战第12天】构建高效Android应用需全方位优化,尤其重视内存管理和用户体验。通过弱引用降低内存占用,懒加载资源减少启动负担。运用Kotlin协程确保UI流畅不阻塞,响应式设计适配多屏需求。这些策略共同提升了应用性能与用户满意度。
46 1