zoukankan      html  css  js  c++  java
  • android基础---->DiskLruCache的使用及原理

      DiskLruCache是谷歌推荐的用来实现硬盘缓存的类,今天我们开始对于DiskLruCache的学习。DiskLruCache的测试代码: DiskLruCache的测试代码下载。关于FidkLruCache的使用,请参见我的博客:android基础---->LruCache的使用及原理

    目录导航

    1.   DiskLruCache缓存的代码实例
    2.   DiskLruCache的原理分析
    3.   友情链接

    DiskLruCache缓存的代码实例

    我们通过一个案例来体会DiskLruCache的使用及执行的流程,在我们的项目中包含DiskLruCache文件:http://pan.baidu.com/s/1slR6pg5。项目结构如下:

    一、 DiskLruCache的是一个final类,不能继承。如果我们要创建一个DiskLruCache的实例,则需要调用它的open()方法,如下:

    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)  

    在AndroidManifest.xml加入网络权限和sd卡写入权限:

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

    项目中创建并打开缓存:

    try {
        File cacheDir = getDiskCacheDir(this, "bitmap");
        if (!cacheDir.exists()) {
            cacheDir.mkdirs();
        }
        mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(this), 1, 10 * 1024 * 1024);
    } catch (Exception e) {
        e.printStackTrace();
    }

    二、 从网络得到图片资源,并写入缓存:

    private void saveCache() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    String key = hashKeyForDisk(imageUrl);
                    DiskLruCache.Editor editor = null;
    
                    editor = mDiskLruCache.edit(key);
                    if (editor != null) {
                        OutputStream outputStream = editor.newOutputStream(0);
                        if (downloadUrlToStream(imageUrl, outputStream)) {
                            editor.commit();
                        } else {
                            editor.abort();
                        }
                    }
                    //频繁的flush
                    mDiskLruCache.flush();
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            textView.setText("saveCache done,the bitmap is ready");
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    downloadUrlToStream方法用于从网络上获取图片资源:

    private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
        HttpURLConnection urlConnection = null;
        BufferedOutputStream out = null;
        BufferedInputStream in = null;
        try {
            final URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
            out = new BufferedOutputStream(outputStream, 8 * 1024);
            int b;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            return true;
        } catch (final IOException e) {
            e.printStackTrace();
        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    三、 由于涉及到key的因素,我们写一个MD5对key进行编码:

    public String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }
    
    private String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(0xFF & bytes[i]);
            if (hex.length() == 1) {
                sb.append('0');
            }
            sb.append(hex);
        }
        return sb.toString();
    }

    四、 从缓存中读取数据:

    private void readCache() {
        //读取缓存
        try {
            DiskLruCache.Snapshot snapshot = mDiskLruCache.get(hashKeyForDisk(imageUrl));
            if (snapshot != null) {
                InputStream is = snapshot.getInputStream(0);
                Bitmap bitmap = BitmapFactory.decodeStream(is);
                imageView.setImageBitmap(bitmap);
            } else {
                imageView.setImageBitmap(null);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    五、 移除缓存:

    private void removeCache() {
        try {
            mDiskLruCache.remove(hashKeyForDisk(imageUrl));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    六、 清空缓存:

    private void deleteCache() {
        try {
            //delete()方法内部会调用close()
            mDiskLruCache.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    七、 得到缓存的大小:

    private void getCacheSize() {
        textView.setText("cache size : " + mDiskLruCache.size() + "B");
    }

    八、 在onDesctory方法中关闭缓存:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            //关闭DiskLruCache,与open对应
            mDiskLruCache.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    DiskLruCache的原理分析

    经过上述实例的说明,我们对DiskLruCache的使用已经有了一些认识。现在我们开始DiskLruCache的原理分析:

    创建打开缓存

    一、 当创建打开缓存:mDiskLruCache = DiskLruCache.open(cacheDir, getAppVersion(this), 1, 10 * 1024 * 1024);

    创建日志journal文件,并且初始化journalWriter:

    public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
            throws IOException {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        if (valueCount <= 0) {
            throw new IllegalArgumentException("valueCount <= 0");
        }
    
        // prefer to pick up where we left off
        DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
        if (cache.journalFile.exists()) {
            try {
                cache.readJournal();
                cache.processJournal();
                cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),
                        IO_BUFFER_SIZE);
                return cache;
            } catch (IOException journalIsCorrupt) {
                cache.delete();
            }
        }
    
        // create a new empty cache
        directory.mkdirs();
        cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
        cache.rebuildJournal();
        return cache;
    }

    在DiskLruCache的构造方法中,创建journal和journal.tmp文件。

    private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
        this.directory = directory;
        this.appVersion = appVersion;
        this.journalFile = new File(directory, JOURNAL_FILE);
        this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);
        this.valueCount = valueCount;
        this.maxSize = maxSize;
    }

    在rebuildJournal方法中,在journalFileTmp临时文件中,写入一些数据,最后重命名为journalFile:

    private synchronized void rebuildJournal() throws IOException {
        if (journalWriter != null) {
            journalWriter.close();
        }
    
        Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);
        writer.write(MAGIC);
        writer.write("
    ");
        writer.write(VERSION_1);
        writer.write("
    ");
        writer.write(Integer.toString(appVersion));
        writer.write("
    ");
        writer.write(Integer.toString(valueCount));
        writer.write("
    ");
        writer.write("
    ");
    
        for (Entry entry : lruEntries.values()) {
            if (entry.currentEditor != null) {
                writer.write(DIRTY + ' ' + entry.key + '
    ');
            } else {
                writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '
    ');
            }
        }
    
        writer.close();
        journalFileTmp.renameTo(journalFile);
        journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);
    }

    二、 创建完成之后 ,在sd中存在journal文件:内容如下:

    libcore.io.DiskLruCache  // MAGIC
    1                        // VERSION_1
    1                        // appVersion
    1                        // valueCount

    写入缓存

    二、 然后是这个代码:DiskLruCache.Editor editor  = mDiskLruCache.edit(key);其中的lruEntries是一个LinkedHashMap,用于记录资源的key与value。

    当第一次edit时,lruEntries.get(key)返回的是空。这里会创建一个Entry对象,并在日志文件中写入DIRTY + ' ' + key + ' '内容。最后返回包含这个entry的Editor。

    private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
        checkNotClosed();
        validateKey(key);
        Entry entry = lruEntries.get(key);
        if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
                && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
            return null; // snapshot is stale
        }
        if (entry == null) {
            entry = new Entry(key);
            lruEntries.put(key, entry);
        } else if (entry.currentEditor != null) {
            return null; // another edit is in progress
        }
    
        Editor editor = new Editor(entry);
        entry.currentEditor = editor;
    
        // flush the journal before creating files to prevent file leaks
        journalWriter.write(DIRTY + ' ' + key + '
    ');
        journalWriter.flush();
        return editor;
    }

    三、 OutputStream outputStream = editor.newOutputStream(0);downloadUrlToStream(imageUrl, outputStream);得到文件输出流,将从网络上请求到的资源写入到文件中。

    这里关注下outputStream,它是由newOutputStream方法得到的:

    public OutputStream newOutputStream(int index) throws IOException {
        synchronized (DiskLruCache.this) {
            if (entry.currentEditor != this) {
                throw new IllegalStateException();
            }
            return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));
        }
    }

    getDirtyFile文件是一个临时文件,也就是网络文件写入到这个临时文件当中:

    public File getDirtyFile(int i) {
        return new File(directory, key + "." + i + ".tmp");
    }

    四、 接着执行到了editor.commit()方法,如果没有出现错误的话:
    创建一个key + "." + i的文件,将上述的dirty文件重命名为key + "." + i的文件。更新已经缓存的大小,并且删除上述的dirty文件。

    private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
        Entry entry = editor.entry;
        if (entry.currentEditor != editor) {
            throw new IllegalStateException();
        }
    
        // if this edit is creating the entry for the first time, every index must have a value
        if (success && !entry.readable) {
            for (int i = 0; i < valueCount; i++) {
                if (!entry.getDirtyFile(i).exists()) {
                    editor.abort();
                    throw new IllegalStateException("edit didn't create file " + i);
                }
            }
        }
    
        for (int i = 0; i < valueCount; i++) {
            File dirty = entry.getDirtyFile(i);
            if (success) {
                if (dirty.exists()) {
                    File clean = entry.getCleanFile(i);
                    dirty.renameTo(clean);
                    long oldLength = entry.lengths[i];
                    long newLength = clean.length();
                    entry.lengths[i] = newLength;
                    size = size - oldLength + newLength;
                }
            } else {
                deleteIfExists(dirty);
            }
        }
    
        redundantOpCount++;
        entry.currentEditor = null;
        if (entry.readable | success) {
            entry.readable = true;
            journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '
    ');
            if (success) {
                entry.sequenceNumber = nextSequenceNumber++;
            }
        } else {
            lruEntries.remove(entry.key);
            journalWriter.write(REMOVE + ' ' + entry.key + '
    ');
        }
    
        if (size > maxSize || journalRebuildRequired()) {
            executorService.submit(cleanupCallable);
        }
    }

    五、 当执行完写入之后,日志文件如下内容:abb7d9d7add6b9fba5314aec6e60c9e6就是上述MD5生成的key,15417是代表缓存的大小。并生成一个abb7d9d7add6b9fba5314aec6e60c9e6.0文件.

    libcore.io.DiskLruCache
    1
    1
    1
    
    DIRTY abb7d9d7add6b9fba5314aec6e60c9e6
    CLEAN abb7d9d7add6b9fba5314aec6e60c9e6 15417

    从缓存中读取

    一、 mDiskLruCache.get(key,以下是关键代码,中间省略了代码);

    Entry entry = lruEntries.get(key); // 根据key从map中得到相应的value
      ....
    InputStream[] ins = new InputStream[valueCount];
    try {
        for (int i = 0; i < valueCount; i++) {
            ins[i] = new FileInputStream(entry.getCleanFile(i)); // 得到clearn文件输入流
        }
    } catch (FileNotFoundException e) {
        // a file must have been deleted manually!
        return null;
    }
    journalWriter.append(READ + ' ' + key + '
    ');  // 在日志文件中写入内容
      ....
    return new Snapshot(key, entry.sequenceNumber, ins); // 返回一个Snapshot对象

    二、 snapshot.getInputStream(0)得到clean文件的输入流,然后通过Bitmap bitmap = BitmapFactory.decodeStream(is);方法得到缓存在硬盘的图片。

     友情链接

  • 相关阅读:
    对象直接量
    js学习类
    jquery.js与sea.js综合使用
    拥抱模块化的JavaScript
    匿名函数与闭包
    js对象如何合并?
    Web.config配置文件详解
    javascipt自定义命名空间、静态类、实例对象
    jQuery源码的基础知识
    企业架构/企业开发 [Enterprise architecture / Enterprise Development]
  • 原文地址:https://www.cnblogs.com/huhx/p/useDiskLruCache.html
Copyright © 2011-2022 走看看