Android 큰 그림 불 러 오기 최적화 - LRU 알고리즘 기반 로 컬 파일 캐 시

머리말
블 로그: Android 큰 그림 로 딩 메모리 최적화 (OutOf Memory 방지) 에 서 는 그림 을 불 러 올 때 원본 그림 을 완전히 불 러 오지 않 거나 예상 그림 의 크기 를 설명 합 니 다. 적당 한 사 이 즈 를 불 러 올 때 OOM 을 방지 합 니 다.다음은 그림 파일 의 로 컬 캐 시 를 설명 합 니 다. 네트워크 그림 은 로 컬 캐 시 를 거 쳐 야 자원 의 접근 속 도 를 높 일 수 있 습 니 다. 메모리 의 캐 시 는 SDCard 캐 시 에 맞 춰 야 장점 을 발휘 할 수 있 습 니 다.본 고 는 LRU 로 컬 캐 시 정책 을 사 용 했 습 니 다. 본 고 는 파일 의 캐 시 에 중심 을 두 었 기 때문에 메모리 의 캐 시 를 도입 하지 않 았 고 이전 블 로그 에서 내 려 온 이미지 로 딩 장점 도 발휘 하지 못 했 습 니 다. 그러나 후속 블 로그 에서 저 는 전체 프로젝트 를 계속 보완 하여 제3자 이미지 로 딩 라 이브 러 리 를 공개 하 겠 습 니 다.
LRU 알고리즘
그림 을 불 러 오 는 데 중요 한 절차 가 있 습 니 다. 네트워크 그림 의 로 컬 캐 시 입 니 다. 캐 시 된 그림 이 언제 삭 제 될 지 모 르 는 경우 가 많 습 니 다. 이 럴 때 합 리 적 인 로 컬 이미지 캐 시 정책 이 필요 합 니 다. 그림 파일 이 저장 공간 을 제한 없 이 차지 하지 않도록 저장 공간 이 부족 하고 자원 의 낭 비 를 초래 하지 않도록 해 야 합 니 다.컴퓨터 운영 체제 에서 작업 스케줄 링 에 LRU 알고리즘 을 도입 하 였 습 니 다. 。쉽게 말 하면 가장 오 랜 시간 동안 사용 되 지 않 은 자원 의 우선 순 위 를 최소 화하 고 사용 빈도 가 높 은 자원 을 우선 보장 하 는 것 이다.
그림 로 컬 캐 시 핵심 코드
설정 클래스 를 추가 합 니 다. 이 클래스 에 따라 그림 파일 의 캐 시 위치, 크기 등 매개 변 수 를 설정 할 수 있 습 니 다. 나중에 필요 할 수 있 는 설정 은 이 안에서 확장 해 야 합 니 다.
/**
 * Created by CJstar on 15/8/24.
 */
public final class FileCacheOptions {
    /**
     * the file cache root path
     */
    private String cacheRootPath;
    /**
     * file cache count
     */
    private int maxFileCount;
    /**
     * file cache max size: byte
     */
    private int maxCacheSize;
    /**
     * if it is false, will not cache files
     */
    private boolean isUseFileCache = true;

    public String getCacheRootPath() {
        return cacheRootPath;
    }

    public void setCacheRootPath(String cacheRootPath) {
        this.cacheRootPath = cacheRootPath;
    }

    public int getMaxFileCount() {
        return maxFileCount;
    }

    public void setMaxFileCount(int maxFileCount) {
        this.maxFileCount = maxFileCount;
    }

    /**
     * cache size in bytes
     * @return
     */
    public int getMaxCacheSize() {
        return maxCacheSize;
    }

    public void setMaxCacheSize(int maxCacheSize) {
        this.maxCacheSize = maxCacheSize;
    }

    public boolean isUseFileCache() {
        return isUseFileCache;
    }

    public void setIsUseFileCache(boolean isUseFileCache) {
        this.isUseFileCache = isUseFileCache;
    }

    private FileCacheOptions(Builder builder){
        setCacheRootPath(builder.getCacheRootPath());
        setIsUseFileCache(builder.isUseFileCache());
        setMaxCacheSize(builder.getMaxCacheSize());
        setMaxFileCount(builder.getMaxFileCount());
    }

    /**
     * This is the options set builder, we can create the options by this method
     */
    public static class Builder{
        private String cacheRootPath;
        private int maxFileCount;
        private int maxCacheSize;
        private boolean isUseFileCache;

        public Builder(){
        }

        public String getCacheRootPath() {
            return cacheRootPath;
        }

        public Builder setCacheRootPath(String cacheRootPath) {
            this.cacheRootPath = cacheRootPath;
            return this;
        }

        public int getMaxFileCount() {
            return maxFileCount;
        }

        public Builder setMaxFileCount(int maxFileCount) {
            this.maxFileCount = maxFileCount;
            return this;
        }

        public int getMaxCacheSize() {
            return maxCacheSize;
        }

        public Builder setMaxCacheSize(int maxCacheSize) {
            this.maxCacheSize = maxCacheSize;
            return this;
        }

        public boolean isUseFileCache() {
            return isUseFileCache;
        }

        public Builder setIsUseFileCache(boolean isUseFileCache) {
            this.isUseFileCache = isUseFileCache;
            return this;
        }

        public FileCacheOptions builder(){
            return new FileCacheOptions(this);
        }
    }
}

다음은 핵심 처리 류:
/**
 * Created by CJstar on 15/8/24.
 */
public class LRUFileCache implements FileCache {

    /**
     * cache config
     */
    private FileCacheOptions options;
    /**
     * cache file suffix
     */
    private static final String WHOLESALE_CONV = ".cach";
    /**
     * mini free space on SDCard
     */
    private static final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10*1024*1024;

    private static LRUFileCache mLRUFileCache;

    public static LRUFileCache getInstance(){
        if(mLRUFileCache==null){
            synchronized (LRUFileCache.class){
                if(mLRUFileCache==null){
                    mLRUFileCache = new LRUFileCache();
                }
            }
        }

        return mLRUFileCache;
    }

    public void setFileLoadOptions(FileCacheOptions options) {
        this.options = options;
    }

    /**
     * use default options
     */
    private LRUFileCache() {
        this.options = new FileCacheOptions.Builder()
                .setCacheRootPath("FileCache")
                .setIsUseFileCache(true)
                .setMaxCacheSize(10 * 1024 * 1024)//10MB
                .setMaxFileCount(100)
                .builder();
    }

    @Override
    public void addDiskFile(String key, InputStream inputStream) {
        if (TextUtils.isEmpty(key) || inputStream == null) {
            return;
        }

        String filename = convertUrlToFileName(key);
        String dir = options.getCacheRootPath();
        File dirFile = new File(dir);
        if (!dirFile.exists())
            dirFile.mkdirs();
        File file = new File(dir + "/" + filename);
        OutputStream outStream;
        try {
            if(file.exists()){
                file.delete();
            }
            
            file.createNewFile();
            outStream = new FileOutputStream(file);
            while (inputStream.available()!=0){
                outStream.write(inputStream.read());
            }
            outStream.flush();
            outStream.close();
            inputStream.close();
        } catch (Throwable e) {
            Log.w("LRUFileCache", e.getMessage());
        }

        // free the space at every time to add a new file
        freeSpaceIfNeeded();
    }

    @Override
    public File getDiskFile(String key) {
        File file = new File(getFilePathByKey(key));

        if(file!=null&&file.exists()){
            updateFileTime(file);

        }else{
            file = null;
        }

        return file;
    }

    @Override
    public boolean isExist(String key) {
        if (URLUtil.isNetworkUrl(key)) {
            return new File(options.getCacheRootPath() + "/" + convertUrlToFileName(key)).exists();

        } else if (URLUtil.isFileUrl(key)) {
            return new File(key).exists();

        } else {
            return false;
        }
    }

    @Override
    public void removeDiskFile(String key) {
        File file = getDiskFile(key);
        if (file != null &&file.exists()) {
            file.delete();
        }
    }

    @Override
    public void removeAllDiskFiles() {
        new File(options.getCacheRootPath()).delete();
    }

    /**
     * This method will free the files which had not been used at a long time
     */
    private void freeSpaceIfNeeded(){
        File dir = new File(options.getCacheRootPath());
        File[] files = dir.listFiles();
        if(files==null){
            return;
        }

        int dirSize = 0;
        for (int i = 0; i < files.length; i++) {
            if (files[i].getName().contains(WHOLESALE_CONV)) {
                dirSize += files[i].length();
            }
        }
        // if the dir size larger than max size or the free space on SDCard is less than 10MB
        //free 40% space for system
        if (dirSize > options.getMaxCacheSize()
                || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            // delete 40% files by LRU
            int removeFactor = (int) ((0.4 * files.length) + 1);
            // sort the files by modify time
            Arrays.sort(files, new FileLastModifSort());
            // delete files
            for (int i = 0; i < removeFactor; i++) {
                if (files[i].getName().contains(WHOLESALE_CONV)) {
                    files[i].delete();
                }
            }
        }

        //if file count is larger than max count, delete the last
        if(files.length>options.getMaxFileCount()){
            Arrays.sort(files, new FileLastModifSort());
            // delete files
            for (int i = options.getMaxFileCount(); i < files.length; i++) {
                if (files[i].getName().contains(WHOLESALE_CONV)) {
                    files[i].delete();
                }
            }
        }
    }

    /**
     * Modify the file time
     *
     * @param file the file which need to update time
     */
    public void updateFileTime(File file) {
        if(file!=null&&file.exists()){
            long newModifiedTime = System.currentTimeMillis();
            file.setLastModified(newModifiedTime);
        }
    }

    /**
     * get the free space on SDCard
     *
     * @return free size in MB
     */
    private int freeSpaceOnSd() {
        StatFs stat = new StatFs(Environment.getExternalStorageDirectory()
                .getPath());
        double sdFreeMB = ((double) stat.getAvailableBlocks() * (double) stat
                .getBlockSize());
        return (int) sdFreeMB;
    }

    /**
     * Get the file name by file url
     *
     * @param url
     * @return file name
     */
    private String convertUrlToFileName(String url) {
        String[] strs = url.split("/");
        return strs[strs.length - 1] + WHOLESALE_CONV;
    }

    public String getFilePathByKey(String key){
        if(URLUtil.isFileUrl(key)){
            return key;

        }else if(URLUtil.isNetworkUrl(key)){
            return options.getCacheRootPath()+"/"+convertUrlToFileName(key);

        }else {
            return null;
        }
    }

    /**
     * The comparator for the file modify, sort the files by modify time.
     */
    private class FileLastModifSort implements Comparator {
        public int compare(File arg0, File arg1) {
            if (arg0.lastModified() > arg1.lastModified()) {
                return 1;
            } else if (arg0.lastModified() == arg1.lastModified()) {
                return 0;
            } else {
                return -1;
            }
        }
    }
}

전체 코드 주 소 는:https://github.com/CJstar/Android-ImageFileCache
다음은 Memory Cache 도 LRU 알고리즘 으로 이 루어 진 캐 시 이지 만 파일 캐 시 보다 복잡 합 니 다.

좋은 웹페이지 즐겨찾기