Android-缓存网络图片(MVP模式)学习笔记

Android之缓存网络图片到内存中学习笔记

访问网络图片是很普遍的事了,在前面的学习中,我也写过了几次异步网上请求网络图片,但是没有缓存图片,那么我们也都知道,有时候访问一些经常访问的网络图片,如果不采取缓存的形式,那么对流量的消耗会非常大,所以,有必要的时候我们可以采取缓存图片的方式来解决流量消耗问题,下面就通过一个MVP模式的简单设计来这里写代码片讲解一下缓存网络图片。

整体的结构如下:

这里写图片描述

首先对于缓存图片来说,我们需要掌握一下几个小点:

1. 当图片没有缓存的时候,我们就要去通过网络异步加载图片
2. 当存在缓存的时候,我们就不能去通过网络异步加载图片,直接获取缓存中的图片
3. 当保存缓存图片时如何确定唯一命名规则,由于网络下载图片地址是不同的,所以可根据地址命名
4. 考虑到有些模拟器保存到内存卡的缓存中时,出现在cache中找到不缓存的图片时,我们将图片地址
中的非单词字符(除了a-z A-Z 0 - 9) 以外,全部去掉
5. 考虑缓存的内存是否够用,当缓存不够用时我们就要删除部分(这里我的代码是删除全部)已缓存的图片
6. 考虑在GridView中复用控件时,出现图片的闪烁已经图片错乱问题

model层:

ModelInter.java

//所有的获取数据功能都抽取到接口中,让其子类实现
public interface ModelInter {
    void getData(OnCompleteListener listener, String path);
    public interface OnCompleteListener {
        void onComplete(byte[] bytes, String path);
    }
}

ModelImp.java

//实现获取数据
public class ModelImp implements ModelInter {
    @Override
    public void getData(OnCompleteListener listener, String path) {
        new MyAsyncTask(listener).execute(path);
    }
}
view层:

ViewInter.java

//所有更新界面,以及得到数据的功能
public interface ViewInter<T> {
    void getAdapterData(List<T> data);
}

MainActivity.java

//主界面启动,展示数据等。
public class MainActivity extends AppCompatActivity implements ViewInter<Bean.NewslistBean> {

    private String path = "http://apis.baidu.com/txapi/mvtp/meinv?num=10";
    private GridView mGrid;
    private MyAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        new Presenter(this).load(path);
    }

    private void init() {
        mGrid = (GridView) findViewById(R.id.mGrid);
    }

    private void showListData(List<Bean.NewslistBean> data) {
        if (adapter == null) {
            adapter = new MyAdapter(data, R.layout.list_item);
            mGrid.setAdapter(adapter);
        } else {

        }
    }
    //通过presenter代理类为我们提供数据
    @Override
    public void getAdapterData(List<Bean.NewslistBean> data) {
        showListData(data);
    }
}
presenter层:

presenter.java

//为视图层提供所需要的数据
public class Presenter {
    private ViewInter viewInter;
    private ModelInter modelInter;
    public Presenter(ViewInter viewInter){
        this.viewInter = viewInter;
        modelInter  = new ModelImp();
    }
    //当view需要数据时,调用该方法就可以获取数据了
    public void load(final String path){
        //传递了匿名内部类,实现获取数据的方法
        modelInter.getData(new ModelInter.OnCompleteListener() {
            @Override
            public void onComplete(byte[] bytes, String path) {
                //通过gson解析数据
                Bean bean = new Gson().fromJson(new String(bytes), Bean.class);
                if(bean != null)
                    viewInter.getAdapterData(bean.getNewslist());
            }
        },path);
    }
}
adapter:

MyAdapter.java

//自定义适配器类,不详细解说,需要了解的查看我相关知识点即可
public class MyAdapter extends MyBaseAdapter<Bean.NewslistBean> {

    public MyAdapter(List<Bean.NewslistBean> data, int layout) {
        super(data,layout);
    }

    @Override
    public void setData(ViewHolder holder, Bean.NewslistBean t) {
        //初始化上下文
        if(MyUtils.context == null)
            MyUtils.context = holder.getContext();
         //如果不存在缓存,就加载网络图片
        if(!MyUtils.isCacheImage(t.getPicUrl())){
            if(MyUtils.isEnoughForCache())//判断缓存空间是否足够
                //加载请求网络图片
                holder.setImageURL(R.id.mImg,t.getPicUrl());
            else{
                Toast.makeText(MyUtils.context,"空间不足了,缓存不了图片了,正在请求删除图片...",Toast.LENGTH_SHORT).show();
                //删除已经缓存的图片
                MyUtils.deletePic(MyUtils.context.getExternalCacheDir());
            }
        }else {
            //这里可以通过log日志判断数据是怎样获取的
            Log.i("IT_Real", "setData: 直接拿数据了,没用通过缓存拿数据");
            //得到缓存路径下的文件
            String fileName = t.getPicUrl().replaceAll("\\W","");
            fileName = fileName + t.getPicUrl().substring(t.getPicUrl().lastIndexOf("."),t.getPicUrl().length());
            //通过文件位置获取一个Bitmap
            Bitmap bm = BitmapFactory.decodeFile(holder.getContext().getExternalCacheDir() + "/" + fileName);
            //设置图片
            holder.setImageBitmap(R.id.mImg,bm);
        }
    }
}

MyBaseAdapter.java

public abstract class MyBaseAdapter<T> extends BaseAdapter {
    protected List<T> data;
    protected int layout;
    public MyBaseAdapter(List<T> data,int layout){
        this.data = data;
        this.layout = layout;
    }
    @Override
    public int getCount() {
        return data == null ? 0 : data.size();
    }

    @Override
    public Object getItem(int position) {
        return data.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = ViewHolder.getHolder(convertView,parent,position, R.layout.list_item);
        setData(holder,data.get(position));
        return holder.getConvertView();
    }
    public abstract void setData(ViewHolder holder,T t);
}

ViewHolder.java

public class ViewHolder {
    private int position;
    private SparseArray<View> array;
    private View convertView;
    private Context context;

    private ViewHolder(ViewGroup parent, int position, int layout) {
        this.position = position;
        this.context = parent.getContext();
        convertView = LayoutInflater.from(parent.getContext()).inflate(layout, null);
        convertView.setTag(this);
        array = new SparseArray<>();
    }

    public static ViewHolder getHolder(View convertView, ViewGroup parent, int position, int layout) {
        if (convertView == null) {
            return new ViewHolder(parent, position, layout);
        } else {
            ViewHolder holder = (ViewHolder) convertView.getTag();
            holder.position = position;
            return holder;
        }
    }

    public <T extends View> T getView(int viewId) {
        View view = array.get(viewId);
        if (view == null) {
            view = convertView.findViewById(viewId);
            array.put(viewId, view);
        }
        return (T) view;
    }

    public Context getContext() {
        return context;
    }

    public View getConvertView() {
        return convertView;
    }

    public ViewHolder setText(int viewId, String data) {
        TextView tv = getView(viewId);
        tv.setText(data);
        return this;
    }

    public ViewHolder setImageResource(int viewId, int resId) {
        ImageView img = getView(viewId);
        img.setImageResource(resId);
        return this;
    }
    public ViewHolder setImageURL(int viewId, String url){
        final ImageView image = getView(viewId);
        //防止图片闪烁,每次加载GridView时,设置为空白界面
        image.setImageBitmap(null);
        new MyAsyncTask(new ImageCallBack(image,url,this.context)).execute(url);
        Log.i("IT_Real", "setImageURL: 网络请求了");
        return this;
    }
    public ViewHolder setImageBitmap(int viewId, Bitmap bm) {
        ImageView img = getView(viewId);
        img.setImageBitmap(bm);
        return this;
    }
}
utils:

MyAsyncTask.java

//异步网络请求数据
public class MyAsyncTask extends AsyncTask<String,Void,byte[]> {
    private String path;
    private ModelInter.OnCompleteListener listener;
    public MyAsyncTask(ModelInter.OnCompleteListener listener){
        this.listener = listener;
    }

    /**
    * 异步请求网络数据
    */
    @Override
    protected byte[] doInBackground(String... params) {
        path = params[0];
        return getBytes(path);
    }
    /**
    * 获取到数据,然后接口回调传递数据
    */
    @Override
    protected void onPostExecute(byte[] bytes) {
        super.onPostExecute(bytes);
        if(listener != null) {
            listener.onComplete(bytes,path);
        }
    }

    /**
    * 网络请求的数据
    */
    private byte[] getBytes(String path){
        try {
            URL url = new URL(path);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
                connection.setRequestProperty("apikey","百度APIStore的个人apikey,没有的可以自己去注册一个,然后获取美女图片json数据");
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(5000);
            connection.connect();
            if(connection.getResponseCode() == HttpURLConnection.HTTP_OK){
                BufferedInputStream bis = new BufferedInputStream(connection.getInputStream());
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                int len = -1;
                byte[] bytes = new byte[1024 * 8];
                while(-1 != (len = bis.read(bytes))){
                    baos.write(bytes,0,len);
                }
                byte[] result = baos.toByteArray();
                bis.close();
                baos.close();
                return result;
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

MyUtils.java
//所有自己需要实现的功能工具类

public class MyUtils {
    public static  Context context;
    private static StatFs statFs;

    /**
     *  判断是否存在缓存图片
     * @param path 图片路径
     * @return
     */
    public static boolean isCacheImage(String path){
        String fileName = path.replaceAll("\\W","");
        fileName = fileName + path.substring(path.lastIndexOf("."),path.length());
        File file = new File(context.getExternalCacheDir(),fileName);
        Log.i("IT_Real", "isCacheImage: " + "file is exists" + file.exists() + "  ->" + isMount());
        return isMount() && file.exists();
    }

    /**
     * 判断内存卡是否具有读写权限
     * @return true有,false没有
     */
    public static boolean isMount(){
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    }

    /**
     * 判断缓存空间是否足够
     * @return
     */
    public static boolean isEnoughForCache(){
        if(statFs == null)
            statFs = new StatFs(Environment.getExternalStorageDirectory().getPath());
        int availableBlocks = statFs.getAvailableBlocks();
        int blockSize = statFs.getBlockSize();
        //如果可用空间小于10兆,就提示用户内存不足
        return availableBlocks  * blockSize>= 10 * 1024 * 1024;
    }

    /**
     * 递归删除缓存的文件
     * @param file 文件路径
     */
    public static void deletePic(File file){
        if(file == null) return;
        if(!file.isFile()){//如果不是文件就遍历
            File[] files = file.listFiles();
            if(file != null){
                for (File file1 : files) {
                    deletePic(file1);
                }
            }
        }else {
            file.delete();
            Log.i("IT_Real", "deletePic: 删除了..");
        }

    }
}

callback:

ImageCallBack.java
//图片回调接口实现类

public class ImageCallBack implements ModelInter.OnCompleteListener {
    private ImageView image;
    private String path;
    private Context context;

    public ImageCallBack(ImageView image, String path, Context context) {
        this.image = image;
        this.path = path;
        this.context = context;
    }

    /**
     * 得到网络请求图片后的图片数据
     * @param bytes
     * @param path
     */
    @Override
    public void onComplete(byte[] bytes, String path) {
        //判断每次加载的网络数据是否和当前图片地址的加载数据,不相同则不设置此图片,防止图片数据错乱
        if (bytes != null && this.path.equals(path)) {
            Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
            image.setImageBitmap(bm);
            //开始保存图片
            saveCache(context.getExternalCacheDir(), bm, path);

        }
    }

    /**
     * 保存缓存的图片
     * @param cacheDir 缓存的路径
     * @param bm 
     * @param path 文件的名字
     */
    private void saveCache(File cacheDir, Bitmap bm, String path) {
        //去掉所有非单词的字符
        String fileName = path.replaceAll("\\W","");
        fileName = fileName + path.substring(path.lastIndexOf("."),path.length());
        try {
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(cacheDir, fileName)));
            bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);
            bos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

整体的功能已经实现了,这个小案例中,我使用了MVP模式、万能通用适配器,关于MVP模式和万能适配器,这里都不做介绍了,不了解的可用查看我博客中的相关知识点,所有涉及到的主要问题都已经注释了,如果有不懂的可用留言下方,我会在第一时间回复。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值