Android利用双缓存技术加载网络多图

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_23179075/article/details/52146197

Android中用双缓存技术,加载网络图片

最近在学校参加一个比赛,写的一个Android应用,里面要加载大量的网络图片,可是用传统的方法图片一多就会造成程序出现内存溢出而崩溃.因为自己也在学习中,所以看了很多博客和视频,然后参照这些大神的写源码,自己写了一个加载网络图片工具类.
里面要用到一个经典的图片缓存库DiskLruCache 下载地址为:  DiskLruCache下载

下面是使用这个类实现的 双缓存网络图片加载

public class DiskLruCacheUtils {
    private static DiskLruCacheUtils diskLruCacheUtils;

    private DiskLruCache diskLruCache; //LRU 磁盘缓存
    private LruCache<String, Bitmap> lruCache; //LRU 内存缓存

    private Context context;

    public DiskLruCacheUtils() {
    }

    public static DiskLruCacheUtils getInstance() {
        if (diskLruCacheUtils == null) {
            diskLruCacheUtils = new DiskLruCacheUtils();
        }
        return diskLruCacheUtils;
    }


    public void open(Context context, String disk_cache_subdir, int disk_cache_size) {
        try {
            this.context = context;

            // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
            // LruCache通过构造函数传入缓存值,以KB为单位。
            int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
            // 使用最大可用内存值的1/8作为缓存的大小。
            int cacheSize = maxMemory / 8;
            lruCache = new LruCache<>(cacheSize);

            /**
             * open()方法接受四个参数:
             * 第一个参数: 指定缓存地址
             * 第二个参数: 指定当前引用程序的版本号
             * 第三个参数: 指定同一个key可以对应多少个缓存文件,基本都是传1
             * 第四个参数: 指定最多可以缓存的字节数. 通常是10MB
             */

            diskLruCache = DiskLruCache.open(getCacheDir(disk_cache_subdir), getAppVersion(), 1, disk_cache_size);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取磁盘缓存
     * @param url
     * @return
     */
    public InputStream getDiskCache(String url) {
        String key = hashkeyForDisk(url);
        try {
            DiskLruCache.Snapshot snapshot = diskLruCache.get(key);
            if (snapshot != null) {
                return snapshot.getInputStream(0);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 下载图片并缓存到内存和磁盘中
     * @param url
     * @param callBack
     */
    public void putCache(final String url, final CallBack callBack){
        new AsyncTask<String,Void,Bitmap>(){

            @Override
            protected Bitmap doInBackground(String... params) {
                String key = hashkeyForDisk(params[0]);
//                System.out.println("Key = "+key);
                DiskLruCache.Editor editor = null;
                Bitmap bitmap = null;

                URL url = null;
                try {
                    url = new URL(params[0]);
                    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                    conn.setReadTimeout(30*1000);
                    conn.setConnectTimeout(30*1000);
                    ByteArrayOutputStream baos = null;

                    if (conn.getResponseCode()==HttpURLConnection.HTTP_OK){
                        BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
                        baos = new ByteArrayOutputStream();
                        byte[] bytes = new byte[1024];
                        int len = -1;
                        while ((len = bis.read(bytes)) != -1) {
                            baos.write(bytes, 0, len);
                        }

                        bis.close();
                        baos.close();
                        conn.disconnect();
                    }
                    if (baos !=null){
                        bitmap = decodeSampleadBitmapFromStream(baos.toByteArray(),300,300);
//                        bitmap = BitmapFactory.decodeByteArray(baos.toByteArray(),0,baos.toByteArray().length);
                        addBitmapToCache(params[0],bitmap); // 添加到内存缓存
                        editor = diskLruCache.edit(key); // 加入磁盘缓存
//                        System.out.println(url.getFile());
                        //位图压缩后输出(参数1: 压缩格式, 参数2: 质量(100 表示不压缩,30 表示压缩70%),参数3: 输出流)
                        bitmap.compress(Bitmap.CompressFormat.JPEG,30,editor.newOutputStream(0));
                        editor.commit();//提交
                    }


                } catch (Exception e) {
                    try {
                        editor.abort();//放弃写入
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                    e.printStackTrace();
                }


                return bitmap;
            }

            @Override
            protected void onPostExecute(Bitmap bitmap) {
                super.onPostExecute(bitmap);
                callBack.response(bitmap);
            }
        }.execute(url);

    }

    /**
     * 关闭磁盘缓存
     */
    public void close(){
        if (diskLruCache!=null&& !diskLruCache.isClosed()){
            try {
                diskLruCache.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 刷新磁盘缓存
     */
    public void flush(){
        if (diskLruCache!=null){
            try {
                diskLruCache.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 回调接口
     * @param <T>
     */
    public interface  CallBack<T>{
        public void response(T entity);
    }


    /**
     * 位图重新采样
     *
     * @param reqWidth  自定义的宽高
     * @param reqHeight
     * @return
     */
    public static Bitmap decodeSampleadBitmapFromStream(byte[] bytes, int reqWidth, int reqHeight) {

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;//只解析边界,不加载到内存中
        BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
        options.inSampleSize = calculatInSampleSize(options, reqWidth, reqHeight);//设置采样比为计算出的采样比例
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);//重新解析图片
    }


    //添加缓存的对象
    public  void addBitmapToCache(String url,Bitmap bitmap){
        String key = hashkeyForDisk(url);
        if (getBitmapFromMenCache(key)==null){
            lruCache.put(key,bitmap);
        }
    }

    //从缓存中获取对象
    public  Bitmap getBitmapFromMenCache(String url){
        String key = hashkeyForDisk(url);
        return  lruCache.get(key);
    }


    /**
     * 计算位图的采样比例大小
     *
     * @param options
     * @param reqWidth  需要的宽高
     * @param reqHeight
     * @return
     */
    private static int calculatInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        //获取位图的原宽高
        final int w = options.outWidth;
        final int h = options.outHeight;
        int inSampleSize = 1;
        //如果原图的宽高比需要的图片宽高大
        if (w > reqWidth || h > reqHeight) {
            if (w > h) {
                inSampleSize = Math.round((float) h / (float) reqHeight);
            } else {
                inSampleSize = Math.round((float) w / (float) reqWidth);
            }
        }
        return inSampleSize;
    }


    /**
     * MD5加密计算
     *
     * @param key
     * @return
     */
    private String hashkeyForDisk(String key) {
        String cachekey;

        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cachekey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cachekey = String.valueOf(key.hashCode());
        }

        return cachekey;
    }

    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xff & bytes[i]);
            if (hex.length() == 1) {
                sb.append(0);
            }
            sb.append(hex);
        }
        return sb.toString();
    }


    /**
     * 获取缓存的地址
     *
     * @param name
     * @return
     */
    private File getCacheDir(String name) {
        String cachePath = Environment.getExternalStorageState()
                == Environment.MEDIA_MOUNTED || !Environment.isExternalStorageRemovable() ?
                context.getExternalCacheDir().getPath() : context.getCacheDir().getPath();

        return new File(cachePath + File.separator + name);
    }

    /**
     * 获取App的版本号
     *
     * @return
     */
    private int getAppVersion() {
        try {
            return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }
}

decodeSampleadBitmapFromStream(byte[] bytes, int reqWidth, int reqHeight)这个函数的实现可以参照 郭大神的博客:Android高效加载大图、多图方案,有效避免程序OOM

自己也是小白,好多都是复制粘贴,嘿嘿 !  这里就不进行代码的分析了(其实好多我也不懂...),下面就自己上demo把:

现将上面的DiskLruCache, 在项目中创建一个libcore.io包,将这.jar文件复制进去,然后实现上边的代码(有点多哈!直接复制过去把!). 我这里直接创建了一个DiskLruCacheUtils类里面就是上面的代码! 还是截个图:↓↓↓↓


使用这个工具类的方法:
在你需要的使用这类的Activity 或fragment中,首先:

    private DiskLruCacheUtils diskLruCacheUtils;//创建对象
    private static final String DISK_CACHE_SUBDIR = "temp"; //设置图片缓存的文件
    private static final int DISK_CACHE_SIZE= 100*1024*1024; // 设置SD卡缓存的大小

然后在他们的声明周期中:
@Override
    protected void onResume() {
        super.onResume();
        diskLruCacheUtils = DiskLruCacheUtils.getInstance();
        diskLruCacheUtils.open(this,DISK_CACHE_SUBDIR,DISK_CACHE_SIZE);//打开缓存
    }

    @Override
    protected void onPause() {
        super.onPause();
        diskLruCacheUtils.flush(); //刷新缓存
    }

    @Override
    protected void onStop() {
        super.onStop();
        diskLruCacheUtils.close(); //关闭缓存
    }

加了这代码就可以真正的使用这个工具类了.
但是这样还不行,还得写个图片加载方法:

private void loadBitmap(String url, final ImageView imageView) {
        if (imageView.getTag().equals(url)) {
            //从内存缓存中取图片
            Bitmap bitmap = diskLruCacheUtils.getBitmapFromMenCache(url);
            if (bitmap == null) {
                //如果内存中为空 从磁盘缓存中取
                InputStream in = diskLruCacheUtils.getDiskCache(url);

                if (in == null) {
                    //如果缓存中都为空,就通过网络加载,并加入缓存
                    diskLruCacheUtils.putCache(url, new DiskLruCacheUtils.CallBack<Bitmap>() {
                        @Override
                        public void response(Bitmap entity) {
//                            System.out.println("网络中下载...");
                            imageView.setImageBitmap(entity);
                        }
                    });
                } else {
                    System.out.println("磁盘中取出...");
                    bitmap = BitmapFactory.decodeStream(in);
                    diskLruCacheUtils.addBitmapToCache(url, bitmap);
                    imageView.setImageBitmap(bitmap);
                }
            } else {
//                System.out.println("内存中取出...");
                imageView.setImageBitmap(bitmap);
            }
        }
    }

然后在你需要加载图片的地方使用该方法就OK, 看起复杂其实还挺简单的  ...(复制过去不就行了...)

直接上Demo:
这是activity_main.xml文件

下面上布局文件 挺简单的 RecyclerView+CardView:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="zhengliang.com.bitmaplrucache.MainActivity">

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rlv_list"
        >
    </android.support.v7.widget.RecyclerView>
</RelativeLayout>

挺简单的就一个 RecyclerView 因为要加载很多图片所以就用这个了,(哈哈! 我喜欢他的瀑布流! 爽到爆炸啊...)


这是item.xml文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_margin="2dp"
    app:cardBackgroundColor="@color/colorAccent"
    app:cardCornerRadius="2dp"
    android:background="@color/colorAccent"
    >

    <ImageView
        android:id="@+id/pic"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:scaleType="centerCrop"
        />

</android.support.v7.widget.CardView>

就一个CardView ,里面放了一个ImageView 

MainActivity类中代码如下:
      因为这里没有图片资源所以自己用Volley框架写了一个获取图片资源的getImageUrl()方法里面返回一些图片资源的URL地址  (找图片真的很恼火啊,一条一条的把图片地址复制过来不是我的风范啊! 就在百度图片中经过千辛万苦扒了个图片API接口下来,哈哈 有图片咯!)


<pre name="code" class="java">public class MainActivity extends AppCompatActivity{


    private List<String> data;
    private DiskLruCacheUtils diskLruCacheUtils;
    private static final String DISK_CACHE_SUBDIR = "temp";
    private static final int DISK_CACHE_SIZE= 100*1024*1024;
    private RecyclerView rlvlist;

    private MyAdapter myAdapter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initViews();
        getImageUrl("http://image.baidu.com/channel/listjson?pn=0&rn=200&tag1=美女&tag2=小清新&ie=utf8");
    }


    private void initViews() {
        data = new ArrayList<String>();
        this.rlvlist = (RecyclerView) findViewById(R.id.rlv_list);
        rlvlist.setLayoutManager(new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL));
    }

    @Override
    protected void onResume() {
        super.onResume();
        diskLruCacheUtils = DiskLruCacheUtils.getInstance();
        diskLruCacheUtils.open(this,DISK_CACHE_SUBDIR,DISK_CACHE_SIZE);
    }

    @Override
    protected void onPause() {
        super.onPause();
        diskLruCacheUtils.flush();
    }

    @Override
    protected void onStop() {
        super.onStop();
        diskLruCacheUtils.close();
    }

    public void getImageUrl(String url){

        final RequestQueue mQueue = Volley.newRequestQueue(this);

        JsonObjectRequest stringRequest = new JsonObjectRequest(url, null,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject jsonObject) {
//                        System.out.println(jsonObject);
                        try {
                            JSONArray jsonArray = jsonObject.getJSONArray("data");
                            for (int i = 0; i <jsonArray.length() ; i++) {
                                JSONObject item = jsonArray.getJSONObject(i);
                                String url = item.getString("image_url");
                                String name = item.getString("tags");
                                data.add(url);

                                myAdapter = new MyAdapter(data,MainActivity.this,diskLruCacheUtils);
                                rlvlist.setAdapter(myAdapter);
                                myAdapter.notifyDataSetChanged();
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {

            }
        }
        );

        mQueue.add(stringRequest);

        if (data.size()==200){
            getImageUrl("http://image.baidu.com/channel/listjson?pn=0&rn=200&tag1=美女&tag2=全部&ie=utf8");
        }

    }
    public void getImageUrl2(String url){

        final RequestQueue mQueue = Volley.newRequestQueue(this);

        JsonObjectRequest stringRequest = new JsonObjectRequest(url, null,
                new Response.Listener<JSONObject>() {
                    @Override
                    public void onResponse(JSONObject jsonObject) {
//                        System.out.println(jsonObject);
                        try {
                            JSONArray jsonArray = jsonObject.getJSONArray("imgs");
                            for (int i = 0; i <jsonArray.length() ; i++) {
                                JSONObject item = jsonArray.getJSONObject(i);
                                String url = item.getString("hoverURL");
                                String name = item.getString("fromPageTitle");
                                data.add(url);

                                myAdapter = new MyAdapter(data,MainActivity.this,diskLruCacheUtils);
                                rlvlist.setAdapter(myAdapter);
                                myAdapter.notifyDataSetChanged();
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {

            }
        }
        );

        mQueue.add(stringRequest);


    }



}


 
  

   然后是就是实现RecyclerView 的Adapter,因为网络图片的加载都要在Adapter中,所以loadBitmap()方法我就直接写在这里了  废话少说直接上代码

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

    private List<String> list;
    private Context context;
    private DiskLruCacheUtils diskLruCacheUtils;

    public MyAdapter(List<String> list, Context context, DiskLruCacheUtils diskLruCacheUtils) {
        this.list = list;
        this.context = context;
        this.diskLruCacheUtils = diskLruCacheUtils;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_view,parent,false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.pic.setTag(list.get(position));
        loadBitmap(list.get(position),holder.pic);
        System.out.println(position);
    }

    @Override
    public int getItemCount() {
        return list==null?0:list.size();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ImageView pic;

        public ViewHolder(View itemView) {
            super(itemView);
            pic = (ImageView) itemView.findViewById(R.id.pic);

        }
    }

    private void loadBitmap(String url, final ImageView imageView) {
        if (imageView.getTag().equals(url)) {
            //从内存缓存中取图片
            Bitmap bitmap = diskLruCacheUtils.getBitmapFromMenCache(url);
            if (bitmap == null) {
                //如果内存中为空 从磁盘缓存中取
                InputStream in = diskLruCacheUtils.getDiskCache(url);

                if (in == null) {
                    //如果缓存中都为空,就通过网络加载,并加入缓存
                    diskLruCacheUtils.putCache(url, new DiskLruCacheUtils.CallBack<Bitmap>() {
                        @Override
                        public void response(Bitmap entity) {
//                            System.out.println("网络中下载...");
                            imageView.setImageBitmap(entity);
                        }
                    });
                } else {
                    System.out.println("磁盘中取出...");
                    bitmap = BitmapFactory.decodeStream(in);
                    diskLruCacheUtils.addBitmapToCache(url, bitmap);
                    imageView.setImageBitmap(bitmap);
                }
            } else {
//                System.out.println("内存中取出...");
                imageView.setImageBitmap(bitmap);
            }
        }
    }

}


大工告成 看看效果


     第一进入时全是从 显示"网络中下载..."  因为RecyclerView和ListView一样,超出屏幕的Item都会被回收,当再次滑动回到上次的位置就会重新获取item,并且会重新获取图片,.
   下面看看滑动回去打印的log 


      全是显示从内存中取出,并没有再重网络中下载,说明刚才的图片都缓存到内存中了,这样就加快的图片的显示,还节省了流量!(这年头流量伤不起啊!)
下面再看看关闭应用再打开是什么效果吧!


        全部显示的是从磁盘中取出... 因为关闭应用,这个时候内存中缓存的图片就会被清空. 这个时候就会自动看SD中是否有缓存了. 并且从SD中取出的图片会再一次缓存到内存中去...我这里是加载的200张图片,完全没有问题,嘿嘿...


      第一次写博客,就到这了, 好累啊!





猜你喜欢

转载自blog.csdn.net/qq_23179075/article/details/52146197