"安卓网络请求图片三级缓存"-带您写一个自定义图片三级缓存.

今天跟大家讲解一下图片的三级缓存

开发中我们经常涉及到图片的处理,几乎每个开发者都会碰到这个问题,我们经常在开发工程中使用各种各样的第三方框架做图片缓存,比如最简单的picasso,但是今天我们就要模仿他,并自己写一个轻量版的图片三级缓存工具MyImageTool,好的下面我们就开始步入正题.


所谓三级缓存就是涉及到三个方面

  • 1.内存
  • 2.本地存储
  • 3.网络

原理就是如下方案(给大家提供两个方案,大家人选一个即可.建议第二个):

  • 方案一:
    • 1.先从内存”读取”照片,有就显示照片,没有就第2步;
    • 2.从磁盘”获取”照片,如果有照片,就把图片存到内存,再从内存”读取”显示,没有就第3步;
    • 3.从网络”获取”如果有照片,就存到磁盘,然后存到内存,在从内存读取显示,没有就给用户显示一个错误的图片

方案一的流程图如下:

流程图
这里写图片描述


  • 方案二:
    • 1.先从内存”读取”照片,有就显示照片,没有就第2步;
    • 2.从磁盘”获取”照片,如果有照片就显示,并把图片存到内存,没有就第3步;
    • 3.从网络”获取”如果有照片,如果有照片就显示,并存到磁盘,然后存到内存;没有就给用户显示一个错误的图片

方案二的流程图如下:

流程图
这里写图片描述

两种方案对比,并做出选择

方案一:从内存读取(有就显示)->从磁盘读取(有就存内存,内存再显示)->从网络(有就存磁盘(没有就显示错误图片)->再存内存->再显示)

(PS:一些老师为了方便同学理解,用了方案一,但是效率上每次都是存到了内存,再显示,都要跨几层,用方案二更合理(高效,且迅速))

方案二:从内存读取(有就显示)->从磁盘读取(有就转换并显示,再存内存)->从网络(有就显示,然后存磁盘->再存内存)->没有显示错误图片

其实一般磁盘有,那么肯定都存到了内存,出现三级缓存的目的也是为了开关机(内存的消失),或者清理了所有数据(内置卡程序数据的清空)

代码详情和内部讲解

package goodjobtome.com.lrucechepic;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.util.Log;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;

/**
 * 做成单例,安全
 */
public class MyImageTool {

    private static MyImageTool myImageTool = new MyImageTool();
    //HashMap当内存.
    private HashMap<String, Bitmap> mMCeche;
    private MyImageTool() {
        mMCeche = new HashMap();
    }
    //1.上下文
    static Context mContext;
    public static MyImageTool with(Context context) {
        mContext = context;
        return myImageTool;
    }
    //2.路径
    public RequestCreator load(String path) {
        RequestCreator creator = new RequestCreator(path);
        return creator;
    }
    Handler mHandler = new Handler();
    class RequestCreator {
        String mUrl = null;
        ImageView mIv;

        RequestCreator(String path) {
            mUrl = path;
        }
        //3.控件
        public void into(ImageView iv) {
            mIv = iv;
            load();
        }
        private void load() {
            /*-从内存里读取图片    HashMap-*/
            Bitmap bitmapCeche = mMCeche.get(mUrl);
            if (bitmapCeche != null) {
                Log.d("RequestCreator", "显示的是内存的图");
                mIv.setImageBitmap(bitmapCeche);
                return;
            }
            /*--从磁盘读取"文件"存入磁盘的是文件file-MD5统一名字,在内存中才是图片bitmap-用url当做名字就行,所以从磁盘读取需要转换成图片;--*/
            File file = getFileName();
            try {
                if (file.exists()) {
                    Bitmap bitmapDisk = BitmapFactory.decodeStream(new FileInputStream(file));
                    if (bitmapDisk != null) {
                        Log.d("RequestCreator", "显示的是磁盘的图");
                        mIv.setImageBitmap(bitmapDisk);
                        //立马且存到内存
                        saveCeche(bitmapDisk);
                        return;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            /*--从网络获取,开启线程,然后子线程显示即可,再存磁盘,存内存--*/
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        HttpURLConnection conn = (HttpURLConnection) new URL(mUrl).openConnection();
                        conn.setConnectTimeout(4 * 1000);
                        conn.setReadTimeout(4 * 1000);

                        InputStream is = conn.getInputStream();
                        final Bitmap bitmap = BitmapFactory.decodeStream(is);
                        if (bitmap == null) {
                            //没有就显示错误图片
                            showErrorPic();
                            return;
                        }
                        //子线程->主线程显示
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                Log.d("RequestCreator", "显示的是网络的图");
                                mIv.setImageBitmap(bitmap);
                            }
                        });
                        //存到磁盘,存到磁盘的是文件格式
                        saveDisk(bitmap);
                        //存到内存的是纯bitmap格式.
                        saveCeche(bitmap);
                        conn.disconnect();
                        is.close();
                    } catch (IOException e) {
                        //异常显示错误图片
                        showErrorPic();
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        //磁盘里的file文件名字
        @NonNull
        private File getFileName() {
            String packageName = mContext.getPackageName();
            File dir = new File("data/data/" + packageName + "/" + "myimage/");//自定义的
            if (!dir.exists()) {
                dir.mkdir();//没有就创建
            }
            String name = MD5Tool.getMD5(mUrl);
            return new File(dir, name);
        }
        //图片保存到磁盘,要弄成文件的
        private void saveDisk(Bitmap bitmap) {
            File fileName = getFileName();
            try {
                //图片变成文件,注意方法 bitmap->file:压缩成文件

                FileOutputStream fos = new FileOutputStream(fileName);
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
        //图片存到内存的方法
        private void saveCeche(Bitmap bitmap) {
            mMCeche.put(mUrl, bitmap);
        }
        //显示错误图片
        private void showErrorPic() {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    Bitmap error = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.errorpic);
                    Toast.makeText(mContext, "Error", Toast.LENGTH_SHORT).show();
                    mIv.setImageBitmap(error);
                }
            });
        }
    }
}

下面到我们精彩的演示过程了

一.演示从网络获取并显示:

1.点开电脑网络上的一张图片
这里写图片描述
2.,然后复制地址,粘贴到程序的地址栏
这里写图片描述
3.点击按钮就会出现结果
这里写图片描述
4.打印的日志:
这里写图片描述
通过打印的日志,发现我们此次是从网络获取,因为是第一次访问


二.演示从内存获取并显示:

  • 1.再次点击请求,或者点返回键回到手机主界面
  • 2.再进去,再同样请求
  • 3.打印的日志:
    这里写图片描述

通过日志发现,我们此次是从内存读取,因为刚刚网络显示后已经把缓存放到磁盘再到内存了


三.演示从内存存储(相当磁盘)获取并显示:

  • 1.点击程序任务键,滑动并关闭程序.
  • 2.再进去,再同样请求
  • 3.打印的日志:
    这里写图片描述
    重点内容

四.显示“获取错误图片”,即没有该图片,证明输入地址错误或者服务器有问题.

  • 输入错误的地址

    显示效果:
    这里写图片描述


优化和拓展

大家有没有发现,我们这里的内存,我所用的是HashMap,但是大家有没有想过,如果有成千上百张,如果我们都这么存的话,那么就出现大问题了,可能内存就会崩掉,说以内存这一块我们要做出改进,就要用到我们的LruCeche();

LruCeche():全称:Least Recently Used 最近最少使用缓存.他是一个缓存集合**.其实底层还是LinkedHashMap.
特点:

当缓存的图片达到了预先设定的值的时候,那么近期使用次数最少的图片就会被回收掉。

使用方法:

  • 1.我们先定义好字节变成kb.
  • 2.缓存集合对象创建的时候构造传入大小,并重写方法sizeOf
  • 3.返回获取的值的字节大小,或者自定义单位转换.

代码如下:

//改用缓存集合
    private static LruCache<String, Bitmap> mMCeche;

    private MyImageTool2() {
        //这个是定义大小
        //int maxSize = 1024*1024*4;
        //注意这个地方及其的容易出错,因为我写成了freeMemory()/4+0.5f,导致大半天都集合都无法存入东西,因为两者不一样大了,而用int maxSize = 1024*1024*4;里面出现效果
        int maxSize = (int) (Runtime.getRuntime().totalMemory());//四舍五入,用总内存就没错

        mMCeche = new LruCache<String,Bitmap>(maxSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //这个是对应上面的byte,注意单位对应,如果这里想作为M那么上面就写4,这里就写/1024/1024
                return value.getByteCount();
            }
        };
    }

好的下面给出大家所有的代码

  • 1.主界面代码:MainActivity.java
package goodjobtome.com.lrucechepic;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;


public class MainActivity extends AppCompatActivity {

    private EditText  mAdress;
    private Button    mGo;
    private ImageView mIv;
    private ImageView mIv2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init() {
        initView();
        initEvent();
    }

    private void initView() {
        mAdress = (EditText) findViewById(R.id.et);
        mGo = (Button) findViewById(R.id.bt);
        mIv = (ImageView) findViewById(R.id.iv);
        mIv2 = (ImageView) findViewById(R.id.iv2);
    }

    private void initEvent() {
        mGo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                String path = mAdress.getText().toString().trim();
                //使用框架
                //Picasso.with(MainActivity.this).load(path).into(mIv);
                UseTimeTool.getInstance().start();
                //我们自己做的: 上下文->路径->控件
                MyImageTool2.with(MainActivity.this).load(path).into(mIv);
                UseTimeTool.getInstance().stop();
            }
        });
    }
}
  • 2.清单文件:注册,并添加网络权限即可
  • 3.封装更好的控件代码:
package goodjobtome.com.lrucechepic;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * 做成单例,安全
 */
public class MyImageTool2 {

    private static MyImageTool2 myImageTool = new MyImageTool2();
    //改用缓存集合
    private static LruCache<String, Bitmap> mMCeche;

    private MyImageTool2() {
        //这个是定义大小
        //int maxSize = 1024*1024*4;
        //注意这个地方及其的容易出错,因为我写成了freeMemory()/4+0.5f,导致大半天都集合都无法存入东西,因为两者不一样大了,而用int maxSize = 1024*1024*4;里面出现效果
        int maxSize = (int) (Runtime.getRuntime().totalMemory());//四舍五入,用总内存就没错

        mMCeche = new LruCache<String,Bitmap>(maxSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //这个是对应上面的byte,注意单位对应,如果这里想作为M那么上面就写4,这里就写/1024/1024
                return value.getByteCount();
            }
        };
    }

    //1.上下文
    static Context mContext;

    public static MyImageTool2 with(Context context) {
        mContext = context;
        return myImageTool;
    }

    //2.路径
    public RequestCreator load(String path) {
        RequestCreator creator = new RequestCreator(path);
        return creator;
    }

    Handler mHandler = new Handler();

    class RequestCreator {
        String mUrl = null;
        ImageView mIv;

        RequestCreator(String path) {
            mUrl = path;
        }

        //3.控件
        public void into(ImageView iv) {
            mIv = iv;
            load();
        }
        private void load() {
            /*-从内存里读取图片    HashMap-*/
            if (loadFromCache()) return;

            /*--从磁盘读取"文件"存入磁盘的是文件file-MD5统一名字,在内存中才是图片bitmap-用url当做名字就行,所以从磁盘读取需要转换成图片;--*/
            if(loadFromDisk()) return;

            /*--从网络获取,开启线程,然后子线程显示即可,再存磁盘,存内存--*/
            loadFromNet();
        }
        //从内存读取
        private boolean loadFromCache() {
            Bitmap bitmapCeche = mMCeche.get(mUrl);
            if (bitmapCeche != null) {
                Log.d("RequestCreator", "显示的是内存的图");
                mIv.setImageBitmap(bitmapCeche);
                return true;
            }
            return false;
        }

        //从磁盘获取
        private boolean loadFromDisk() {

            File file = getFileName();
            try {
                if (file.exists()) {
                    Bitmap bitmapDisk = BitmapFactory.decodeStream(new FileInputStream(file));
                    if (bitmapDisk != null) {
                        Log.d("RequestCreator", "显示的是磁盘的图");
                        mIv.setImageBitmap(bitmapDisk);
                        //立马且存到内存
                        saveCeche(bitmapDisk);
                        return true;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return false;
        }

        //从网络获取,并存如磁盘和内存
        private void loadFromNet() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        HttpURLConnection conn = (HttpURLConnection) new URL(mUrl).openConnection();
                        conn.setConnectTimeout(4 * 1000);
                        conn.setReadTimeout(4 * 1000);

                        InputStream is = conn.getInputStream();
                        final Bitmap bitmap = BitmapFactory.decodeStream(is);
                        if (bitmap == null) {
                            //没有就显示错误图片
                            showErrorPic();
                            return;
                        }
                        //子线程->主线程显示
                        mHandler.post(new Runnable() {
                            @Override
                            public void run() {
                                Log.d("RequestCreator", "显示的是网络的图");
                                mIv.setImageBitmap(bitmap);
                            }
                        });
                        //存到磁盘是file格式,所以内部要处理
                        saveDisk(bitmap);
                        //存到内存的是纯bitmap格式.
                        saveCeche(bitmap);
                        conn.disconnect();
                        is.close();
                    } catch (IOException e) {
                        //异常显示错误图片
                        showErrorPic();
                        e.printStackTrace();
                    }
                }
            }).start();
        }
        //磁盘里的file文件名字
        @NonNull
        private File getFileName() {
            String packageName = mContext.getPackageName();
            File dir = new File("data/data/" + packageName + "/" + "myimage/");//自定义的
            if (!dir.exists()) {
                dir.mkdir();//没有就创建
            }
            String name = MD5Tool.getMD5(mUrl);
            return new File(dir, name);
        }

        //图片存到磁盘
        private void saveDisk(Bitmap bitmap) {
            File fileName = getFileName();
            //bitmap->file,图片转成文件
            try {
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(fileName));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }

        //图片存到内存的方法
        private void saveCeche(Bitmap bitmap) {
            mMCeche.put(mUrl, bitmap);
            System.out.println("putSize---------------------------------------" + mMCeche.size());
        }
        //显示错误图片
        private void showErrorPic() {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    Bitmap error = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.errorpic);
                    Toast.makeText(mContext, "Error", Toast.LENGTH_SHORT).show();
                    mIv.setImageBitmap(error);
                }
            });
        }
    }
}

至于布局代码,时间工具,MD5工具,错误图片,布局文件,都比较简单,大家自己添加即可.

总结

1.三级缓存:

  • 1.内存: HashMap或者LruCache(底层其实是linkedHaskmap)
    • 1.注意的是maxSize这个的大小,否则画一天都找不到原因所在.
    • 2.存放的是:存bitMap格式的,获取是根据key:ulr获取
  • 2.磁盘:使用的是data/data/包名/myimage自定义文件夹下

    • 1.存放的是:存file格式的文件,为了统一姓名用md5加密ulr后的key
    • 2.图片转成文件即:bitmap.copress方法进行压缩即可.
    • 3.取出的时候用图片工厂解析流即可.
  • 3.网络:获取成功就显示,不成功就显示本地错误提示图片,异常也显示

    • 1.存磁盘
    • 2.存网络
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值