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);方法得到緩存在硬盤的圖片。

 

 友情鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM