Android利用DiskLruCache和LruCache实现简单的照片墙

现在很多App的某些功能会把图片以及内容缓存在本地,即使是没有网络的情况下也还是可以加载出之前浏览过的内容来,这些功能就是使用了DiskLruCache技术;

LruCache负责管理内存中图片的存储与释放,如果图片从内存中被移除的话,那么又需要从网络上重新加载一次图片,这显然非常耗时,所以Google又提供了一套硬盘缓存的解决方案:DiskLruCache;
DiskLruCache并没有限制数据的缓存位置,可以自由地进行设定,不过一般来说会存储在SD卡中, 当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取缓存路径,否则就调用getCacheDir()方法来获取缓存路径。 前者获取到的就是 /sdcard/Android/data//cache 这个路径 而后者获取到的是 data/data/<application - package>/cache这个路径,接下来就用一个简单的照片墙demo来阐述DiskLruCache以及LruCache缓存技术吧;

先附上我的项目结构
在这里插入图片描述

DiskLruCache这个类并没有在android api中,所以需要从网上下载下来,然后加在项目中就可以了,这里我就附上链接了
https://download.csdn.net/download/qq_42889476/11485240

因为图片的话是需要机密处理的,所以我们需要一个工具类----MD5转换工具类

package com.example.disklrucache_demo;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5Utils {
    //使用MD5加密算法
    public static String md5(String plainText){
        byte[] secretBytes = null;

        try {
            secretBytes = MessageDigest.getInstance("md5").digest(plainText.getBytes());
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("没有md5这个算法!");
        }
        //16进制
        String md5Code = new BigInteger(1,secretBytes).toString(16);
        // 如果生成数字未满32位,需要前面补0
        for (int i = 0; i < 32 - md5Code.length(); i++) {
            md5Code = "0" + md5Code;
        }
        return md5Code;
    }
}

然后是数据集合

package com.example.disklrucache_demo;

public class Images {
    public final static String[] imageThumbUrls = new String[]{
            "http://image.biaobaiju.com/uploads/20180211/01/1518283483-ZSaWgidtGK.jpg",
            "http://img.51ztzj.com/upload/image/20131024/dn201310242017_670x419.jpg",
            "http://image.biaobaiju.com/uploads/20180803/20/1533300593-nYwpoTMUEs.jpg",
            "http://img.juimg.com/tuku/yulantu/130316/267856-1303160U31010.jpg",
            "http://pic1.win4000.com/wallpaper/2017-10-18/59e7096016774.jpg",
            "http://gss0.baidu.com/-Po3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/b64543a98226cffcad22c094ba014a90f603ea88.jpg",
            "http://img.pconline.com.cn/images/photoblog/1/0/1/5/10159318/200911/4/1257320021267_mthumb.jpg",
            "http://img3.duitang.com/uploads/item/201605/18/20160518232450_zxyFi.thumb.700_0.jpeg",
            "http://img.tukexw.com/img/252557a08a4ae4d1.jpg",
            "http://img4q.duitang.com/uploads/blog/201409/18/20140918211053_zaFRk.thumb.700_0.jpeg",
            "http://www.zhutizhijia.net/uploads/120323/24-120323155524102.jpg",
            "http://image.biaobaiju.com/uploads/20180706/04/1530821556-qYtMwiolae.jpg",
            "http://b-ssl.duitang.com/uploads/item/201302/17/20130217212232_xAK5C.jpeg",
            "http://b-ssl.duitang.com/uploads/item/201201/23/20120123195204_A8CRN.thumb.700_0.jpg",
            "http://image.yy.com/yywebalbumbs2bucket/28e43cdd19324941b73fe57c09046ddd_1487870799169.jpeg",
            "http://img5.duitang.com/uploads/blog/201508/10/20150810145410_5xEQJ.thumb.700_0.jpeg",
            "http://photocdn.sohu.com/20120704/Img347284777.jpg",
            "http://img4q.duitang.com/uploads/item/201411/03/20141103115630_HMhnU.jpeg",
            "http://img1.doubanio.com/pview/event_poster/raw/public/6eec72f07eb8c98.jpg",
            "http://img4q.duitang.com/uploads/item/201306/23/20130623230957_fmVV4.thumb.700_0.jpeg",
            "http://img2.ph.126.net/RJc77dzeQTv8z2CHGmzzgQ==/1828179973736115230.jpg",
            "http://s10.sinaimg.cn/middle/950ebfd0nb5be42e39a09&690",
            "http://g.hiphotos.baidu.com/zhidao/pic/item/ae51f3deb48f8c54fa5fd5673a292df5e1fe7fed.jpg",
            "http://img21.mtime.cn/mg/2010/09/14/162329.60324301.jpg",
            "http://i1.szhomeimg.com/n/2013/04/10/0410171600540.JPG",
            "http://b-ssl.duitang.com/uploads/item/201609/18/20160918141907_WBG2A.jpeg",
            "http://k.zol-img.com.cn/sjbbs/6319/a6318625_s.jpg",
            "http://b-ssl.duitang.com/uploads/item/201205/16/20120516125715_AfAiz.jpeg",
            "http://b-ssl.duitang.com/uploads/item/201206/15/20120615095518_UB3CS.jpeg",
            "http://i1.sinaimg.cn/edu/2012/0413/U7516P42DT20120413140544.jpg",
            "http://www.cnr.cn/newscenter/gjxw/list/201305/W020130516373227379082.jpg",
            "http://b-ssl.duitang.com/uploads/item/201204/12/20120412130056_kKxcd.jpeg",
            "http://img.boqiicdn.com/Data/BK/A/1301/23/img84111358931890_y.jpg",
            "http://img3.100bt.com/upload/ttq/20130323/1364004564120.png",
            "http://img01.tooopen.com/Downs/images/2011/7/19/sy_2011071921084512107.jpg",
            "http://img2.shangxueba.com/img/lady_Img/20140808/17/E7EACCFD9928B5B0064022F8E0E7DB36.jpg"
    };
}

注:(如果程序跑起来了是默认的图片的话,建议自己去网上找一点图片的链接,然后再跑一遍就OK了)
activity_main.xml

<?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:orientation="vertical"
    tools:context=".MainActivity">

    <GridView
        android:id="@+id/grid_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:numColumns="3"
        android:verticalSpacing="10dp"
        android:horizontalSpacing="10dp"
        android:columnWidth="90dp"
        android:stretchMode="columnWidth"
        android:gravity="center">

    </GridView>

</RelativeLayout>

item_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <ImageView
        android:id="@+id/img_photo"
        android:layout_width="120dp"
        android:layout_height="120dp" />

</LinearLayout>

网格布局有了,子item也有了,接下来就需要一个适配器了
流程:继承ArrayAdatper,在构造函数传入必要参数后,然后在getView方法中来设置图片源,首先先设置成一张默认的图片,然后根据图片的URL去缓存中找是否有相关联的资源,如果没找到在磁盘缓存中找,如果还是没找到再去网络上下载,然后保存在磁盘缓存里,下载资源的话是一个耗时的操作,所以开启了一个内部类Async异步类去完成,把所有的耗时操作都安排在doInBackground里执行,然后下载好了会缓存在内存和磁盘中
这个适配器的其他功能我都用代码注释好了,就不一一讲解了

package com.example.disklrucache_demo;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;

import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import android.util.LruCache;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.ImageView;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;

import static com.example.disklrucache_demo.MainActivity.TAG;


public class ImageAdapter extends ArrayAdapter {

    //声明LruCache缓存对象
    private LruCache<String, Bitmap> lruCache;
    // 声明DiskLruCache硬盘缓存对象
    private DiskLruCache diskLruCache;
    //任务列表
    private Set<LoadImageAsync> tasks;
    //声明GridView对象
    private GridView gridView;

    public ImageAdapter(Context context, int textViewResourceId,String[] objects,GridView gridView) {
        super(context, textViewResourceId,objects);
        this.gridView = gridView;
        tasks = new HashSet<LoadImageAsync>();
        //初始化内存缓存LruCache,获取应用最大可占内存
        int MaxMemory = (int) Runtime.getRuntime().maxMemory();
        //设置最大内存的八分之一作为图片资源的缓存大小
        int LruMemory = MaxMemory / 8;
        lruCache = new LruCache<String, Bitmap>(LruMemory){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //返回Bitmap对象所占大小,单位:kb
                return value.getByteCount();
            }
        };

        //初始化硬盘缓存DiskLruCache,获取硬盘的缓存路径,参数2为所在缓存路径的文件夹的名称
        File directory = getDiskCacheDir(getContext(),"bitmap");
        if (!directory.exists()){
            //若文件不存在,则创建新的文件夹
            directory.mkdirs();
        }
        //获取App版本号
        int appVersion = getAppVersion(getContext());
        //缓存1:缓存文件路径,参数2:系统版本号,参数3:一个缓存路径对几个文件,缓存4:缓存的空间大小
        try {
            diskLruCache = DiskLruCache.open(directory,appVersion,1,1024*1024*10);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * @param context
     * @param uniqueName
     * @return
     *         当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取缓存路径,否则就调用getCacheDir
     *         ()方法来获取缓存路径。 前者获取到的就是 /sdcard/Android/data/<application-
     *         package>/cache 这个路径 而后者获取到的是 data/data/application - package>/cache
     *         这个路径。
     */

    public File getDiskCacheDir(Context context,String uniqueName){
        String cachePath;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()){
            cachePath = context.getExternalCacheDir().getPath();
        }else {
            cachePath = context.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator + uniqueName);
    }

    /**
     * @param context
     * @return 获取系统版本号
     */
    public int getAppVersion(Context context){
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(),0);
            return info.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return 1;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //获取图片资源URL地址
        String path = (String) getItem(position);
        View view = null;
        if (convertView == null){
            //加载子布局
            view = LayoutInflater.from(getContext()).inflate(R.layout.item_activity,null);
        }else {
            view = convertView;
        }
        //获取控件实例
        ImageView imageView = view.findViewById(R.id.img_photo);
        //设置唯一一个标识符,避免异步加载时错位
        imageView.setTag(path);
        //设置默认显示图片
        imageView.setImageResource(R.drawable.timg);
        // 根据图片URL到缓存中去找图片资源并设置
        setImageFromLruCache(path, imageView);
        return view;
    }
    /**
     * 根据图片URL地址获取缓存中图片,若不存在去磁盘缓存中查找,磁盘中没有就进行网络下载
     *
     * @param path
     * @param imageView
     */
    private void setImageFromLruCache(String path,ImageView imageView){
        //获取图片的地址
        Bitmap bitmap = lruCache.get(path);
        if (bitmap != null){
            //有缓存,直接取出图片
            Log.d(TAG, "setImageFromLruCache: 在内存缓存中找到图片" );
            imageView.setImageBitmap(bitmap);
        }else {
            // 缓存不存在,先找硬盘缓存,没有的话就去网络下载(开启异步任务)
            LoadImageAsync loadImageAsync = new LoadImageAsync();
            loadImageAsync.execute(path);
            //添加在任务列表
            tasks.add(loadImageAsync);
        }
    }
    /**
     * 取消队列中准备下载和正在下载的任务
     */
    public void cancelTask(){
        for (LoadImageAsync task : tasks){
            task.cancel(false);
        }
    }


    /**
     * 同步内存操作到journal文件
     */
    public void flushCache() {
        if (diskLruCache != null) {
            try {
                diskLruCache.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    class LoadImageAsync extends AsyncTask<String,Void,Bitmap>{

        String path = null;
        @Override
        protected Bitmap doInBackground(String... strings) {
            //图片的下载地址
            this.path = strings[0];
            DiskLruCache.Snapshot snapshot = null;
            OutputStream outputStream = null;
            Bitmap bitmap = null;
            String pathMd5 = MD5Utils.md5(path);
            //根据图片的URL查找图片是否存在于硬盘
            try {
                snapshot = diskLruCache.get(pathMd5);
            if (snapshot == null){
                //在磁盘缓存里没有找到对应的图片资源
                DiskLruCache.Editor editor = diskLruCache.edit(pathMd5);
                if (editor != null){
                    outputStream = editor.newOutputStream(0);
                    // 开启异步网络任务获取图片,并存入磁盘缓存
                    if (DownLoadToStream(path,outputStream)){
                        //下载成功
//                        Log.d(TAG, "doInBackground: DownLoad Image Suc" );
                        editor.commit();
                    }
                }
            }
            // 图片写入磁盘缓存后,再一次的查找磁盘缓存
            snapshot = diskLruCache.get(pathMd5);
            if (snapshot != null){
                // 若查找到,获取图片,并把图片资源写入内存缓存
                bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));
                Log.d(TAG, "doInBackground: 在磁盘缓存中找到");
            }
                if (bitmap != null) {
                    // 将Bitmap对象添加到内存缓存当中
                    lruCache.put(path,bitmap);
                }
                return bitmap;
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (outputStream != null) {
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return null;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            //根据Tag获取控件对象并设置图片
            ImageView imageView = gridView.findViewWithTag(path);
            if (imageView != null && bitmap != null) {
                //加载图片
                imageView.setImageBitmap(bitmap);
            }
            tasks.remove(this);
        }
        /**
         * 根据图片URL地址下载图片,成功返回true,失败false
         *
         * @param urlString
         * @param outputStream
         * @return
         */
        private boolean DownLoadToStream(String urlString,OutputStream outputStream){
            HttpURLConnection connection = null;
            BufferedInputStream inputStream = null;
            BufferedOutputStream outputStream1 = null;

            try {
                final URL url = new URL(urlString);
                connection = (HttpURLConnection) url.openConnection();
                inputStream = new BufferedInputStream(connection.getInputStream(),8*1024);
                outputStream = new BufferedOutputStream(outputStream,8*1024);
                int b;
                //读
                while ((b = inputStream.read()) != -1){
                    //写
                    outputStream.write(b);
                }
                return true;
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if (connection != null) {
                    connection.disconnect();
                }
                try{
                    if (outputStream != null) {
                        outputStream.close();
                    }
                    if (inputStream != null) {
                        inputStream.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
    }
}

MainActivity类
在onPause方法里进行flush操作(更新磁盘缓存操作日志,磁盘缓存之所以能够被读取取决于日志文件

package com.example.disklrucache_demo;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.jakewharton.disklrucache.DiskLruCache;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

public class MainActivity extends AppCompatActivity {

    public static final String TAG = "MainActivity";
    private GridView gv_photo;
    private ImageAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        gv_photo= findViewById(R.id.grid_view);
        adapter = new ImageAdapter(MainActivity.this,0,Images.imageThumbUrls,gv_photo);
        gv_photo.setAdapter(adapter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        adapter.flushCache();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        adapter.cancelTask();
    }
}

附上效果图
在这里插入图片描述

最后的话附上一个Demo下载链接

https://download.csdn.net/download/qq_42889476/11485297

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值