全屏浏览
缩小浏览
回到页首

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("\n");
    writer.write(VERSION_1);
    writer.write("\n");
    writer.write(Integer.toString(appVersion));
    writer.write("\n");
    writer.write(Integer.toString(valueCount));
    writer.write("\n");
    writer.write("\n");

    for (Entry entry : lruEntries.values()) {
        if (entry.currentEditor != null) {
            writer.write(DIRTY + ' ' + entry.key + '\n');
        } else {
            writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
        }
    }

    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 + '\n'内容。最后返回包含这个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 + '\n');
    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() + '\n');
        if (success) {
            entry.sequenceNumber = nextSequenceNumber++;
        }
    } else {
        lruEntries.remove(entry.key);
        journalWriter.write(REMOVE + ' ' + entry.key + '\n');
    }

    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 + '\n');  // 在日志文件中写入内容
  ....
return new Snapshot(key, entry.sequenceNumber, ins); // 返回一个Snapshot对象

 

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

 

 友情链接

posted @ 2016-04-13 08:26  huhx  阅读(4356)  评论(0编辑  收藏  举报