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

来源:互联网 发布:androidframework源码 编辑:程序博客网 时间:2024/05/17 07:22

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

开发中我们经常涉及到图片的处理,几乎每个开发者都会碰到这个问题,我们经常在开发工程中使用各种各样的第三方框架做图片缓存,比如最简单的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
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 小孩不爱写作业怎么办啊 孩子不爱看书怎么办如何教育 2岁宝宝不爱看书怎么办 儿媳妇比儿子年龄大我不喜欢怎么办 不喜欢儿子却生了儿子怎么办 静不下心来看书怎么办 孩子爱玩不爱学怎么办 孩子爱玩不爱学习怎么办 孩子爱玩手机不爱学习怎么办 照四维宝宝太活泼了怎么办 胎宝宝太活泼了怎么办 7个月宝宝太活泼怎么办 我是个初中生不想上学怎么办 3岁宝宝不肯说话怎么办 两周宝宝不爱吃饭怎么办 小孩不喜欢吃水果蔬菜怎么办 孩子对学习不感兴趣怎么办 幼儿园老师不喜欢我孩子怎么办 孩子数学不主动思考问题怎么办 孩子做事慢磨蹭家长应该怎么办 孩子不写作业怎么办啊 对孩子在校被欺怎么办 二年级的孩子不爱学习怎么办 孩子不爱学习怎么办二年级 二年级孩子不爱写作业怎么办 孩子喜欢的朋友家长不喜欢怎么办? 孩子不喜欢上幼儿园家长怎么办 孩子不喜欢家长学佛怎么办呢? 学生家长讨厌老师老师该怎么办 孩子不爱去幼儿园总是哭怎么办 孩子学习一点都不主动怎么办 孩子怕老师不想上学怎么办 幼儿园孩子怕老师不想上学怎么办 孩子在幼儿园怕老师怎么办 幼儿园老师对孩子有歧视怎么办 儿子在幼儿园受到老师歧视怎么办 我把老师骂了怎么办 孩子讨厌老师骂老师怎么办 幼儿园老师讨厌一个孩子怎么办 幼儿园老师对孩子不好怎么办 高中分班讨厌班主任怎么办