Android OkHttp与物理存储介质缓存:DiskLruCache(2)

简介: Android OkHttp与物理存储介质缓存:DiskLruCache(2)本文在附录文章8,9的基础之上,把Android OkHttp与DiskLruCache相结合,综合此两项技术,实现基于OkHttp的物理存储介质缓存DiskLruCache。


Android OkHttp与物理存储介质缓存:DiskLruCache(2)

本文在附录文章8,9的基础之上,把Android OkHttp与DiskLruCache相结合,综合此两项技术,实现基于OkHttp的物理存储介质缓存DiskLruCache。
用一个完整的例子加以说明。该例子的代码要实现这样的过程:代码启动后,要往一个ImageView里面加载一张网络图片,首先检查DiskLruCache是否已经存在该图片的缓存,如果存在,则直接复用缓存,如果不存在则使用OkHttp把图片异步从网络加载,当OkHttp异步加载网络图片成功后,要做两件事情:
一, 毫无疑问,要把该图片设置到目标ImageView里面。代码启动后首先要检查本地的DiskLruCache物理存储介质上是否已经有特定图片的缓存,如果有,则直接复用,不再浪费网络资源重复加载。
二,把该图片的数据写入DiskLruCache缓存中,为以后的缓存使用。此情况是当DiskLruCache不存在特定资源(本例是图片)缓存时候,要从网络加载。我使用OkHttp网络驱动加载,当OkHttp加载图片成功后,一方面要把图片设置到ImageView,另外一方面要把图片缓存到DiskLruCache以备后续使用。

完整代码:

package zhangphil.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.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Callback;

import com.jakewharton.disklrucache.DiskLruCache;

public class MainActivity extends AppCompatActivity {

    private String TAG = "zhangphil_tag";

    private String UNIQUENAME = "zhangphil_cache";

    private DiskLruCache mDiskLruCache = null;

    //缓存大小
    private int DISK_CACHE_MAX_SIZE = 10 * 1024 * 1024;


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

        //初始化DiskLruCache
        makeDiskLruCache();

        //在布局里面放一个ImageView,放网络请求后的图片
        final ImageView image = (ImageView) findViewById(R.id.imageView);

        //我的博客头像
        String image_url = "http://avatar.csdn.net/9/7/A/1_zhangphil.jpg";

        Bitmap bmp = readBitmapFromDiskLruCache(image_url);

        //首先检查DiskLruCache是否已经缓存了特定资源,如果有则直接复用。
        //如果没有则从网路加载。
        if (bmp != null) {
            image.setImageBitmap(bmp);
        } else {
            downloadBitmapFromNetwork(image, image_url);
        }
    }

    //从DiskLruCache中读取缓存
    private Bitmap readBitmapFromDiskLruCache(String url) {
        DiskLruCache.Snapshot snapShot = null;
        try {
            //把url转换成一个md5字符串,然后以这个md5字符串作为key
            String key = urlToKey(url);

            snapShot = mDiskLruCache.get(key);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (snapShot != null) {
            Log.d(TAG, "发现缓存:" + url);
            InputStream is = snapShot.getInputStream(0);
            Bitmap bitmap = BitmapFactory.decodeStream(is);
            Log.d(TAG, "从缓存中读取Bitmap.");

            return bitmap;
        } else
            return null;
    }

    //把byte字节写入缓存DiskLruCache
    private void writeToDiskLruCache(String url, byte[] buf) throws Exception {
        Log.d(TAG, url + " : 开始写入缓存...");

        //DiskLruCache缓存需要一个key,我先把url转换成md5字符串,
        //然后以md5字符串作为key键
        String key = urlToKey(url);
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);

        OutputStream os = editor.newOutputStream(0);
        os.write(buf);
        os.flush();
        editor.commit();

        mDiskLruCache.flush();

        Log.d(TAG, url + " : 写入缓存完成.");
    }

    private void makeDiskLruCache() {
        try {
            File cacheDir = getDiskCacheDir(this, UNIQUENAME);

            if (!cacheDir.exists()) {
                Log.d(TAG, "缓存目录不存在,创建之...");
                cacheDir.mkdirs();
            } else
                Log.d(TAG, "缓存目录已存在,不需创建.");

            //第二个参数我选取APP的版本code。DiskLruCache如果发现第二个参数version不同则销毁缓存
            //第三个参数为1,在写缓存的流时候,newOutputStream(0),0为索引,类似数组的下标
            mDiskLruCache = DiskLruCache.open(cacheDir, getVersionCode(this), 1, DISK_CACHE_MAX_SIZE);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void downloadBitmapFromNetwork(final ImageView image, final String image_url) {
        Log.d(TAG, "从网络中加载图片资源 ...  @ " + image_url);

        //初始化OkHttpClient
        final OkHttpClient client = new OkHttpClient();

        //创建OkHttpClient针对某个url的数据请求
        Request request = new Request.Builder().url(image_url).build();

        Call call = client.newCall(request);

        //请求加入队列
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                //此处处理请求失败的业务逻辑
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                //如果response响应成功则继续,否则返回
                if (!response.isSuccessful())
                    return;

                //我写的这个例子是请求一个图片
                //response的body是图片的byte字节
                byte[] bytes = response.body().bytes();

                //已经获得图片数据,记到要写入硬盘缓存
                //出于性能考虑,此处可以放到后台或者放到一个线程里面处理
                //简单期间,我就在这儿直接写缓存了。
                try {
                    writeToDiskLruCache(image_url, bytes);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                //把byte字节组装成图片
                final Bitmap bmp = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

                //回调是运行在非ui主线程,
                //数据请求成功后,在主线程中更新
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //网络图片请求成功,更新到主线程的ImageView
                        image.setImageBitmap(bmp);
                    }
                });
            }
        });
    }


    /*
    *
    * 当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取缓存路径,
    * 否则就调用getCacheDir()方法来获取缓存路径。
    * 前者获取到的就是 /sdcard/Android/data/<application package>/cache
    * 而后者获取到的是 /data/data/<application package>/cache 。
    *
    * */
    public File getDiskCacheDir(Context context, String uniqueName) {
        String cachePath = null;
        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
            cachePath = context.getExternalCacheDir().getPath();
        } else {
            cachePath = context.getCacheDir().getPath();
        }

        File dir = new File(cachePath + File.separator + uniqueName);
        Log.d(TAG, "缓存目录:" + dir.getAbsolutePath());

        return dir;
    }


    //版本名
    public static String getVersionName(Context context) {
        return getPackageInfo(context).versionName;
    }

    //版本号
    public static int getVersionCode(Context context) {
        return getPackageInfo(context).versionCode;
    }

    private static PackageInfo getPackageInfo(Context context) {
        PackageInfo pi = null;

        try {
            PackageManager pm = context.getPackageManager();
            pi = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_CONFIGURATIONS);

            return pi;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return pi;
    }


    public static String urlToKey(String url) {
        return getMD5(url);
    }

    /*
    * 传入一个字符串String msg,返回Java MD5加密后的16进制的字符串结果。
    * 结果形如:c0e84e870874dd37ed0d164c7986f03a
    */
    public static String getMD5(String msg) {
        MessageDigest md = null;
        try {
            md = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        md.reset();
        md.update(msg.getBytes());
        byte[] bytes = md.digest();

        String result = "";
        for (byte b : bytes) {
            // byte转换成16进制
            result += String.format("%02x", b);
        }

        return result;
    }
}


涉及到网络和读写存储,不要忘记加权限:

 <!-- SDCard中创建与删除文件权限 -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    <!-- 向SDCard写入数据权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <uses-permission android:name="android.permission.INTERNET"></uses-permission>


附录文章:
1,《Android第三方异步网路加载库AsyncHttpClient内部实现缓存策略了吗?》链接:http://blog.csdn.net/zhangphil/article/details/48595817 
2,《Android图片加载与缓存开源框架:Android Glide》链接:http://blog.csdn.net/zhangphil/article/details/45535693
3,《Android获取App版本号和版本名》链接:http://blog.csdn.net/zhangphil/article/details/43795099
4,《基于Java LinkedList,实现Android大数据缓存策略》链接:http://blog.csdn.net/zhangphil/article/details/44116885
5,《使用新式LruCache取代SoftReference缓存图片,Android异步加载图片》链接:http://blog.csdn.net/zhangphil/article/details/43667415
6,《使用Android新式LruCache缓存图片,基于线程池异步加载图片》链接:http://blog.csdn.net/zhangphil/article/details/44082287
7,《Java MD5(字符串)》链接:http://blog.csdn.net/zhangphil/article/details/44152077
8,《Android OkHttp(1)》链接:http://blog.csdn.net/zhangphil/article/details/51861503
9,《Android二级缓存之物理存储介质上的缓存DiskLruCache》链接:http://blog.csdn.net/zhangphil/article/details/51888974

相关文章
|
4月前
|
存储 缓存 Android开发
安卓Jetpack Compose+Kotlin, 使用ExoPlayer播放多个【远程url】音频,搭配Okhttp库进行下载和缓存,播放完随机播放下一首
这是一个Kotlin项目,使用Jetpack Compose和ExoPlayer框架开发Android应用,功能是播放远程URL音频列表。应用会检查本地缓存,如果文件存在且大小与远程文件一致则使用缓存,否则下载文件并播放。播放完成后或遇到异常,会随机播放下一首音频,并在播放前随机设置播放速度(0.9到1.2倍速)。代码包括ViewModel,负责音频管理和播放逻辑,以及UI层,包含播放和停止按钮。
|
2月前
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
34 2
|
2月前
|
存储 缓存 NoSQL
【Azure Redis 缓存】关于Azure Cache for Redis 服务在传输和存储键值对(Key/Value)的加密问题
【Azure Redis 缓存】关于Azure Cache for Redis 服务在传输和存储键值对(Key/Value)的加密问题
|
16天前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
39 8
|
5月前
|
安全 Android开发
Android之OKHttp基本使用和OKHttp发送https请求安全认证
Android之OKHttp基本使用和OKHttp发送https请求安全认证
126 0
|
4月前
|
存储 缓存
使用tp5内cache缓存,存储手机短信验证码
使用tp5内cache缓存,存储手机短信验证码
95 1
|
4月前
|
缓存 Android开发 Kotlin
【安卓app开发】kotlin Jetpack Compose框架 | 先用OKhttp下载远程音频文件再使用ExoPlayer播放
使用 Kotlin 的 Jetpack Compose 开发安卓应用时,可以结合 OkHttp 下载远程音频文件和 ExoPlayer 进行播放。在 `build.gradle` 添加相关依赖后,示例代码展示了如何下载音频并用 ExoPlayer 播放。代码包括添加依赖、下载文件、播放文件及简单的 Compose UI。注意,示例未包含完整错误处理和资源释放,实际应用需补充这些内容。
|
4月前
|
存储 Android开发 Kotlin
开发安卓app OKhttp下载后使用MediaPlayer播放
在Android Jetpack Compose应用程序中,要使用OkHttp下载远程音频文件并在本地播放,你需要完成以下几个步骤: 1. **添加依赖**:确保`build.gradle`文件包含OkHttp和Jetpack Compose的相关依赖。 2. **下载逻辑**:创建一个`suspend`函数,使用OkHttp发起网络请求下载音频文件到本地。 3. **播放逻辑**:利用`MediaPlayer`管理音频播放状态。 4. **Compose UI**:构建用户界面,包含下载和播放音频的按钮。
|
3月前
|
缓存 编解码 安全
Android经典面试题之Glide的缓存大揭秘
Glide缓存机制包括内存和硬盘缓存。内存缓存使用弱引用的ActiveResources和LRU策略,硬盘缓存利用DiskLruCache。Engine.load方法首先尝试从内存和弱引用池加载,然后从LRU缓存中加载图片,增加引用计数并移出LRU。若缓存未命中,启动新任务或加入现有任务。内存大小根据设备内存动态计算,限制在0.4以下。DiskLruCache使用自定义读写锁,保证并发安全,写操作通过锁池管理,确保高效。
62 0
|
4月前
|
存储 缓存 NoSQL
了解Redis,第一弹,什么是RedisRedis主要适用于分布式系统,用来用缓存,存储数据,在内存中存储那么为什么说是分布式呢?什么叫分布式什么是单机架构微服务架构微服务的本质
了解Redis,第一弹,什么是RedisRedis主要适用于分布式系统,用来用缓存,存储数据,在内存中存储那么为什么说是分布式呢?什么叫分布式什么是单机架构微服务架构微服务的本质