Android网络加载图片时内存缓存的方法和注意点

加载图片内存溢出的问题和解决办法

以下我从四个个方面来讨论:

  • 为什么加载图片会内存溢出?
  • 如何通过网络加载图片?
  • 如何解决内存溢出?
  • 日常开发如何做?

安卓的内存机制(为什么会产生内存溢出?)

  • 安卓内存机制:
    安卓中应用程序在运行时,系统会给该应用分配一定的内存,系统不同大小可能存在出入,一般为16M,所以如果加载大量图片,特别是高清图片,内存就有点吃紧。而我们知道java虚拟机的内存回收机制,不会去回收强引用的对象,况且垃圾回收机制本身就不及时。(下文会谈到java的内存回收机制)。

  • 图片所占内存较大,容易导致OOM(内存溢出)

  • java的内存回收机制:我们都知道java虚拟机中有堆内存和栈内存:
    • 栈中一般存放成员变量,对象的引用等。
    • 堆内存一般存放对象,类,集合等。
    • 默认栈中的引用对堆中的对象是强引用关系,不会被回收

概念:引用

  • 默认强引用, 垃圾回收器不会回收
  • 软引用, 垃圾回收器会考虑回收 SoftReference
  • 弱引用, 垃圾回收器更会考虑回收 WeakReference
  • 虚引用, 垃圾回收器最优先回收 PhantomReference

通过网络加载图片有哪些方法

  • 1.通过URLConnection获取请求连接,将输入流转为Bitmap对象在ImageView中呈现,这种是最简单粗暴的做法,没有进行任何优化。**
  public Bitmap download(String url) {
        HttpURLConnection conn;
        try {
            URL murl = new URL(url);
            conn = (HttpURLConnection) murl.openConnection();
            conn.setRequestMethod("GET");// 请求方式,必须大写
            conn.setConnectTimeout(5000);// 连接超时时间
            conn.setReadTimeout(5000);// 读取超时时间
            conn.connect();// 开始连接
            int code = conn.getResponseCode();// 获取返回码

            if (code == 200) {
                // 成功后获取流,进行处理
                InputStream is = conn.getInputStream();
                // 根据流来获取对应的数据,这里是图片,所以直接根据流得到bitmap对象
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                return bitmap;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }  
  • 2.通过开源框架xUtils中的BitmapUtils,调用display(ImageView iv,String url)方法,将对应url的图片展示在ImageView中,这是开发中常用的解决方案,并且解决了内存溢出的问题,并且提供了三级缓存的机制**
//代码说明:这是一个ListView的适配器,每一个item中展示的图片都来自网络,每次加载图片时,调用了BitmapUtils 的display方法,就会下载对应的图片并显示在ImageView中
class PicListAdapter extends BaseAdapter {      
        public BitmapUtils bitmapUtils; //xUtils的图片加载工具类
//      public MyBitmapUtils bitmapUtils;
        public PicListAdapter() {
            bitmapUtils = new BitmapUtils(mActivity);
//设置默认加载的图片,当网络请求失败,默认显示的图片 
        bitmapUtils.configDefaultLoadFailedImage(R.drawable.pic_item_list_default);
            bitmapUtils = new MyBitmapUtils();
        }

        @Override
        public int getCount() {
            return photosDataList.size();
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder ;
            if(convertView==null){
                holder = new ViewHolder();
                convertView = View.inflate(mActivity, R.layout.item_list_pic, null);
                holder.ivNews = (ImageView) convertView.findViewById(R.id.iv_pic);
                holder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title);

                convertView.setTag(holder);
            }else{
                holder = (ViewHolder) convertView.getTag();
            }

            PhotosData item = photosDataList.get(position);
            bitmapUtils.display(holder.ivNews, item.listimage);
    //      bitmapUtils.display(holder.ivNews, item.listimage, R.drawable.pic_item_list_default);
            holder.tvTitle.setText(item.title);

            return convertView;
        }

        @Override
        public PhotosData getItem(int position) {
            return photosDataList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

    }
  • 3.自己写一个利用三级缓存加载图片的方案,并且讨论解决内存溢出的问题(我们自己写是为了更深入了解安卓中内存优化的机制,图片加载的细节等等,实际上xUtils已经解决的很完美了)**

MyBitmapUtils工具类(自己实现的加载图片的工具类)使用时,只需要调用display方法,传入相应的ImageView 图片的url和默认显示的图片资源ID, 其中用到的工具类代码附在后面。

package com.cattsoft.zhhxa.utils;

import android.graphics.Bitmap;
import android.widget.ImageView;

/**
 * 利用三级缓存加载图片的工具类
 * 
 * @author ZXJM
 * @date 2016年8月23日
 *
 */
public class MyBitmapUtils {

    private NetCacheUtils mNetCacheUtil;//网络缓存工具类
    private LocalCacheUtils mLocalCacheUtil;//本地缓存工具类
    private MemoryCacheUtils mMemoryCacheUtil;//内存缓存工具类

    public MyBitmapUtils() {
        mMemoryCacheUtil = new MemoryCacheUtils();
        mLocalCacheUtil = new LocalCacheUtils();
        mNetCacheUtil = new NetCacheUtils(mLocalCacheUtil, mMemoryCacheUtil);
    }

    /**
     * 
     * @param imageView
     *            要展示加载图片的ImageView
     * @param url
     *            加载图片的链接
     * @param resId
     *            默认图片的资源id
     */
    public void display(ImageView imageView, String url, int resId) {
        // 设置默认图片
        imageView.setImageResource(resId);

        Bitmap bitmap = null;
        // 0.先从内存加载,如果内存中有值
        bitmap = mMemoryCacheUtil.getMemoryCache(url);
        if (bitmap != null) {
            System.out.println("从内存中加载");
            imageView.setImageBitmap(bitmap);
            return;
        }

        // 1.先从本地加载,判断是否有本地缓存
        bitmap = mLocalCacheUtil.getLocalCache(url);
        if (bitmap != null) {
            System.out.println("从本地加载");
            imageView.setImageBitmap(bitmap);
            //写内存缓存
            mMemoryCacheUtil.setMemoryCache(url, bitmap);
            return;
        }

        // 2.从网络加载
        mNetCacheUtil.getBitmapFromNet(imageView, url);
        System.out.println("从网络加载");

    }

}

NetCacheUtils 网络缓存工具类

package com.cattsoft.zhhxa.utils;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ImageView;

/**
 * 网络缓存工具
 * 
 * @author ZXJM
 * @date 2016年8月23日
 *
 */
public class NetCacheUtils {

    private LocalCacheUtils mLocalCacheUtil;
    private MemoryCacheUtils mMemoryCacheUtil;

    public NetCacheUtils(LocalCacheUtils localCacheUtil,
            MemoryCacheUtils memoryCacheUtil) {
        super();
        this.mLocalCacheUtil = localCacheUtil;
        this.mMemoryCacheUtil = memoryCacheUtil;
    }

    // 从网络加载图片
    public void getBitmapFromNet(ImageView imageView, String url) {
        // imageView,url 两个参数会封装为数组传给doInBackground
        new BitmapTask().execute(imageView, url);
    }

    /**
     * AsyncTask的用法
     * 
     * @author ZXJM
     * @date 2016年8月23日 三个泛型的含义:参1:doInBackground的参数类型 参2:onProgressUpdate的参数类型
     *       参3:doInBackground 加载完的返回类型,和onPostExecute 加载结束后处理的入参类型
     */
    class BitmapTask extends AsyncTask {

        private ImageView imageView;
        private String url;

        /**
         * 1.预加载 ,运行在主线程
         */
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
//          System.out.println("预加载");
        }

        /**
         * 2.正在加载(核心方法),运行在子线程
         */
        @Override
        protected Bitmap doInBackground(Object... params) {
//          System.out.println("正在加载");
            imageView = (ImageView) params[0];
            url = (String) params[1];

            imageView.setTag(url);// 打标记

            Bitmap bitmap = download(url);
            return bitmap;
        }

        /**
         * 3.进度更新(如果下载文件),运行在主线程
         */
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
        }

        /**
         * 4.加载结束,运行在主线程
         */
        @Override
        protected void onPostExecute(Bitmap result) {
//          System.out.println("加载结束");

            // 由于listview的重用机制导致imageview对象可能被多个item共用,
            // 从而可能将错误的图片设置给了imageView对象
            // 所以需要在此处校验, 判断是否是正确的图片
            if (result != null) {
                String url = (String) imageView.getTag();
                if (url != null && url.equals(this.url)) {
                    // 从网络加载图片
                    imageView.setImageBitmap(result);
                    System.out.println("从网络加载图片啦.....");
                    // 写本地缓存
                    mLocalCacheUtil.setLocalCache(url, result);
                    //写内存缓存
                    mMemoryCacheUtil.setMemoryCache(url, result);
                }
            }
        }
    }

    /**
     * 根据url下载图片
     * 
     * @param url
     * @return
     */
    public Bitmap download(String url) {
        HttpURLConnection conn;
        try {
            URL murl = new URL(url);
            conn = (HttpURLConnection) murl.openConnection();
            conn.setRequestMethod("GET");// 请求方式,必须大写
            conn.setConnectTimeout(5000);// 连接超时时间
            conn.setReadTimeout(5000);// 读取超时时间
            conn.connect();// 开始连接
            int code = conn.getResponseCode();// 获取返回码

            if (code == 200) {
                // 成功后获取流,进行处理
                InputStream is = conn.getInputStream();
                // 根据流来获取对应的数据,这里是图片,所以直接根据流得到bitmap对象
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                return bitmap;
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

LocalCacheUtils 本地缓存工具类

package com.cattsoft.zhhxa.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.os.Environment;

/**
 * 本地缓存工具类
 * 
 * @author ZXJM
 * @date 2016年8月23日
 *
 */
public class LocalCacheUtils {
    private static final String LOCAL_CACHE_PATH = Environment
            .getExternalStorageDirectory().getAbsolutePath()+"/zhxa_cache";

    //写本地缓存
    public void setLocalCache(String url,Bitmap bitmap) {
        File dir = new File(LOCAL_CACHE_PATH);
        if(!dir.exists() || !dir.isDirectory()){
            dir.mkdirs();//创建文件夹
        }

        try {
            String fileName = MD5Encoder.encode(url);//采用MD5加密文件名
            File cacheFile = new File(dir, fileName);
            // 参1:图片格式;参2:压缩比例0-100; 参3:输出流
            bitmap.compress(CompressFormat.JPEG, 100, new FileOutputStream(cacheFile));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //读本地缓存
    public Bitmap getLocalCache(String url) {
        try {
            File cacheFile = new File(LOCAL_CACHE_PATH,MD5Encoder.encode(url));
            if(cacheFile.exists()){
                Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(cacheFile));
                return bitmap;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

MemoryCacheUtils 内存缓存工具类

package com.cattsoft.zhhxa.utils;

import java.lang.ref.SoftReference;
import java.util.HashMap;

import android.graphics.Bitmap;

/**
 * 内存缓存工具类
 * 1.第一次优化,利用软引用解决可能的OOM异常
 * 
 * @author ZXJM
 * @date 2016年8月23日
 *
 */
public class MemoryCacheUtils {

//  private HashMap hash;
    private HashMap> hash;

    // 写内存缓存
    public void setMemoryCache(String url, Bitmap bitmap) {
//      if (hash == null) {
//          hash = new HashMap();
//      }
//      hash.put(url, bitmap);
        if(hash == null){
            hash = new HashMap>();
        }
        //使用软引用把Bitmap包装起来
        SoftReference soft = new SoftReference(bitmap);
        hash.put(url, soft);
    }

    // 读内存缓存
    public Bitmap getMemoryCache(String url) {
//      if (hash != null && hash.containsKey(url)) {
//          Bitmap bitmap = hash.get(url);
//          return bitmap;
//      }

        if(hash!=null && hash.containsKey(url)){
            SoftReference soft = hash.get(url);
            Bitmap bitmap = soft.get();
            return bitmap;
        }
        return null;
    }
}

如何处理内存溢出?

上面我们给出了一套自己实现的利用三级缓存加载图片的方案,这很好解决了加载速度,节省用户流量等体验问题,但随着图片数量的增多,内存缓存这一环节就会暴露内存溢出的问题。

优化1.利用软引用解决内存缓存时可能出现的内存溢出

  • 上文我们提到安卓中的几种引用,我们在内存缓存时,直接将Bitmap对象缓存在集合中,默认是强引用,随着图片数量的增多,应用所分配到的内存就越用越少,最后OOM,但如果我们把缓存的Bitmap对象封装成软引用,那么在内存将要不够时,系统就会回收软引用对应的内存。上面代码注释部分有。

优化2.利用LruCache解决内存缓存

  • 上面我们用软引用做了优化,但是在Android2.3之后,软引用更倾向于被系统垃圾回收机制回收,也就是说,这时候软引用变的不可靠,刚一创建,还没使用,就被回收,这当然不是我们想要的。因此LruCache这个类很好的解决了内存缓存的问题,可以替代软引用。见上面代码。

小结(重要)

以上分析,我们知道,在通过网络加载图片时,为了更好的体验,我们使用了三级缓存的一个机制,并且在内存缓存中利用Lrucache进行处理,防止OOM异常。但是,我们上面自己实现的方案最多算做一个Demo,可以加深我们对网络加载图片内存溢出机制有更深入的理解。开发中,我们还是使用xUtils,因为xUtils已经对这些问题做了很好的规避,并且非常稳定,出现异常的概率远比我们自己写的Demo低。

你可能感兴趣的:(Android项目总结,android,java,图片,内存,网络)