/* * Copyright (C) 2013 Peng fei Pan <sky@xiaopan.me> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package me.xiaopan.sketch.cache; import android.content.Context; import android.text.format.Formatter; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.locks.ReentrantLock; import me.xiaopan.sketch.Configuration; import me.xiaopan.sketch.SLog; import me.xiaopan.sketch.SLogType; import me.xiaopan.sketch.util.DiskLruCache; import me.xiaopan.sketch.util.SketchMD5Utils; import me.xiaopan.sketch.util.NoSpaceException; import me.xiaopan.sketch.util.SketchUtils; import me.xiaopan.sketch.util.UnableCreateDirException; import me.xiaopan.sketch.util.UnableCreateFileException; public class LruDiskCache implements DiskCache { protected String logName = "LruDiskCache"; private int maxSize; private int appVersionCode; private File cacheDir; private Context context; private DiskLruCache cache; private Configuration configuration; private boolean closed; private boolean disabled; private Map<String, ReentrantLock> editLockMap; public LruDiskCache(Context context, Configuration configuration, int appVersionCode, int maxSize) { context = context.getApplicationContext(); this.context = context; this.maxSize = maxSize; this.appVersionCode = appVersionCode; this.configuration = configuration; this.cacheDir = SketchUtils.getDefaultSketchCacheDir(context, DISK_CACHE_DIR_NAME, true); } /** * 检查磁盘缓存器是否可用 */ protected boolean checkDiskCache() { return cache != null && !cache.isClosed(); } /** * 检查缓存目录是否存在并可用 */ protected boolean checkCacheDir() { return cacheDir != null && cacheDir.exists(); } /** * 安装磁盘缓存 */ protected synchronized void installDiskCache() { if (closed) { return; } // 旧的要关闭 if (cache != null) { try { cache.close(); } catch (IOException e) { e.printStackTrace(); } cache = null; } // 创建缓存目录,然后检查空间并创建个文件测试一下 try { cacheDir = SketchUtils.buildCacheDir(context, DISK_CACHE_DIR_NAME, true, DISK_CACHE_RESERVED_SPACE_SIZE, true, true, 10); } catch (NoSpaceException e) { e.printStackTrace(); configuration.getMonitor().onInstallDiskCacheError(e, cacheDir); return; } catch (UnableCreateDirException e) { e.printStackTrace(); configuration.getMonitor().onInstallDiskCacheError(e, cacheDir); return; } catch (UnableCreateFileException e) { e.printStackTrace(); configuration.getMonitor().onInstallDiskCacheError(e, cacheDir); return; } SLog.d(SLogType.CACHE, logName, "diskCacheDir: %s", cacheDir.getPath()); try { cache = DiskLruCache.open(cacheDir, appVersionCode, 1, maxSize); } catch (IOException e) { e.printStackTrace(); configuration.getMonitor().onInstallDiskCacheError(e, cacheDir); } } // 这个方法性能优先,因此不加synchronized @Override public boolean exist(String uri) { if (closed) { return false; } if (disabled) { SLog.w(SLogType.CACHE, logName, "Disabled. Unable judge exist, uri=%s", uri); return false; } // 这个方法性能优先,因此不检查缓存目录 if (!checkDiskCache()) { installDiskCache(); if (!checkDiskCache()) { return false; } } try { return cache.exist(uriToDiskCacheKey(uri)); } catch (DiskLruCache.ClosedException e) { e.printStackTrace(); return false; } catch (IOException e) { e.printStackTrace(); return false; } } @Override public synchronized Entry get(String uri) { if (closed) { return null; } if (disabled) { SLog.w(SLogType.CACHE, logName, "Disabled. Unable get, uri=%s", uri); return null; } if (!checkDiskCache() || !checkCacheDir()) { installDiskCache(); if (!checkDiskCache()) { return null; } } DiskLruCache.SimpleSnapshot snapshot = null; try { snapshot = cache.getSimpleSnapshot(uriToDiskCacheKey(uri)); } catch (IOException e) { e.printStackTrace(); } catch (DiskLruCache.ClosedException e) { e.printStackTrace(); } return snapshot != null ? new LruDiskCacheEntry(uri, snapshot) : null; } @Override public synchronized Editor edit(String uri) { if (closed) { return null; } if (disabled) { SLog.w(SLogType.CACHE, logName, "Disabled. Unable edit, uri=%s", uri); return null; } if (!checkDiskCache() || !checkCacheDir()) { installDiskCache(); if (!checkDiskCache()) { return null; } } DiskLruCache.Editor diskEditor = null; try { diskEditor = cache.edit(uriToDiskCacheKey(uri)); } catch (IOException e) { e.printStackTrace(); // 发生异常的时候(比如SD卡被拔出,导致不能使用),尝试重装DiskLruCache,能显著提高遇错恢复能力 installDiskCache(); if (!checkDiskCache()) { return null; } try { diskEditor = cache.edit(uriToDiskCacheKey(uri)); } catch (IOException e1) { e1.printStackTrace(); } catch (DiskLruCache.ClosedException e1) { e1.printStackTrace(); } } catch (DiskLruCache.ClosedException e) { e.printStackTrace(); // 旧的关闭了,必须要重装DiskLruCache installDiskCache(); if (!checkDiskCache()) { return null; } try { diskEditor = cache.edit(uriToDiskCacheKey(uri)); } catch (IOException e1) { e1.printStackTrace(); } catch (DiskLruCache.ClosedException e1) { e1.printStackTrace(); } } return diskEditor != null ? new LruDiskCacheEditor(diskEditor) : null; } @Override public synchronized File getCacheDir() { return cacheDir; } @Override public long getMaxSize() { return maxSize; } @Override public String uriToDiskCacheKey(String uri) { // 由于DiskLruCache会在uri后面加序列号,因此这里不用再对apk文件的名称做特殊处理了 // if (SketchUtils.checkSuffix(uri, ".apk")) { // uri += ".icon"; // } return SketchMD5Utils.md5(uri); } @Override public synchronized long getSize() { if (closed) { return 0; } if (!checkDiskCache()) { return 0; } return cache.size(); } @Override public boolean isDisabled() { return disabled; } @Override public void setDisabled(boolean disabled) { this.disabled = disabled; if (disabled) { SLog.w(SLogType.CACHE, logName, "setDisabled. %s", true); } else { SLog.i(SLogType.CACHE, logName, "setDisabled. %s", false); } } @Override public synchronized void clear() { if (closed) { return; } if (cache != null) { try { cache.delete(); } catch (IOException e) { e.printStackTrace(); } cache = null; } installDiskCache(); } @Override public synchronized boolean isClosed() { return closed; } @Override public synchronized void close() { if (closed) { return; } closed = true; if (cache != null) { try { cache.close(); } catch (IOException e) { e.printStackTrace(); } cache = null; } } @Override public synchronized ReentrantLock getEditLock(String uri) { if (editLockMap == null) { synchronized (this) { if (editLockMap == null) { editLockMap = new WeakHashMap<String, ReentrantLock>(); } } } ReentrantLock lock = editLockMap.get(uri); if (lock == null) { lock = new ReentrantLock(); editLockMap.put(uri, lock); } return lock; } @Override public String getKey() { return String.format("%s(maxSize=%s,appVersionCode=%d,cacheDir=%s)", logName, Formatter.formatFileSize(context, maxSize), appVersionCode, cacheDir.getPath()); } public static class LruDiskCacheEntry implements Entry { private String uri; private DiskLruCache.SimpleSnapshot snapshot; public LruDiskCacheEntry(String uri, DiskLruCache.SimpleSnapshot snapshot) { this.uri = uri; this.snapshot = snapshot; } @Override public InputStream newInputStream() throws IOException { return snapshot.newInputStream(0); } @Override public File getFile() { return snapshot.getFile(0); } @Override public String getUri() { return uri; } @Override public boolean delete() { try { snapshot.getDiskLruCache().remove(snapshot.getKey()); return true; } catch (IOException e) { e.printStackTrace(); return false; } catch (DiskLruCache.ClosedException e) { e.printStackTrace(); return false; } } } public static class LruDiskCacheEditor implements Editor { private DiskLruCache.Editor diskEditor; public LruDiskCacheEditor(DiskLruCache.Editor diskEditor) { this.diskEditor = diskEditor; } @Override public OutputStream newOutputStream() throws IOException { return diskEditor.newOutputStream(0); } @Override public void commit() throws IOException, DiskLruCache.EditorChangedException, DiskLruCache.ClosedException, DiskLruCache.FileNotExistException { diskEditor.commit(); } @Override public void abort() { try { diskEditor.abort(); } catch (IOException e) { e.printStackTrace(); } catch (DiskLruCache.EditorChangedException e) { e.printStackTrace(); } catch (DiskLruCache.FileNotExistException e) { e.printStackTrace(); } } } }