Android:图片缓存

来源:互联网 发布:java工程师等级 编辑:程序博客网 时间:2024/05/01 12:45

如果每次加载同一张图片都要从网络获取,那代价实在太大了。所以同一张图片只要从网络获取一次就够了,然后在本地缓存起来,之后加载同一张图片时就从缓存中加载就可以了。从内存缓存读取图片是最快的,但是因为内存容量有限,所以最好再加上文件缓存。文件缓存空间也不是无限大的,容量越大读取效率越低,因此可以设置一个限定大小比如10M,或者限定保存时间比如一天。

因此,加载图片的流程应该是:

1、先从内存缓存中获取,取到则返回,取不到则进行下一步;

2、从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行下一步;

3、从网络下载图片,并更新到内存缓存和文件缓存。

 

接下来看内存缓存类:ImageMemoryCache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
publicclass ImageMemoryCache {
    /**
     * 从内存读取数据速度是最快的,为了更大限度使用内存,这里使用了两层缓存。
     * 硬引用缓存不会轻易被回收,用来保存常用数据,不常用的转入软引用缓存。
     */
    privatestatic final int SOFT_CACHE_SIZE = 15//软引用缓存容量
    privatestatic LruCache<String, Bitmap> mLruCache;  //硬引用缓存
    privatestatic LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache;  //软引用缓存
                                                                                          
    publicImageMemoryCache(Context context) {
        intmemClass = ((ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();
        intcacheSize = 1024* 1024* memClass / 4//硬引用缓存容量,为系统可用内存的1/4
        mLruCache = newLruCache<String, Bitmap>(cacheSize) {
            @Override
            protectedint sizeOf(String key, Bitmap value) {
                if(value != null)
                    returnvalue.getRowBytes() * value.getHeight();
                else
                    return0;
            }
                                                                                          
            @Override
            protectedvoid entryRemoved(booleanevicted, String key, Bitmap oldValue, Bitmap newValue) {
                if(oldValue != null)
                    // 硬引用缓存容量满的时候,会根据LRU算法把最近没有被使用的图片转入此软引用缓存
                    mSoftCache.put(key,newSoftReference<Bitmap>(oldValue));
            }
        };
        mSoftCache = newLinkedHashMap<String, SoftReference<Bitmap>>(SOFT_CACHE_SIZE, 0.75f,true) {
            privatestatic final long serialVersionUID = 6040103833179403725L;
            @Override
            protectedboolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
                if(size() > SOFT_CACHE_SIZE){   
                    returntrue
                
                returnfalse;
            }
        };
    }
                                                                                  
    /**
     * 从缓存中获取图片
     */
    publicBitmap getBitmapFromCache(String url) {
        Bitmap bitmap;
        //先从硬引用缓存中获取
        synchronized(mLruCache) {
            bitmap = mLruCache.get(url);
            if(bitmap != null) {
                //如果找到的话,把元素移到LinkedHashMap的最前面,从而保证在LRU算法中是最后被删除
                mLruCache.remove(url);
                mLruCache.put(url, bitmap);
                returnbitmap;
            }
        }
        //如果硬引用缓存中找不到,到软引用缓存中找
        synchronized(mSoftCache) {
            SoftReference<Bitmap> bitmapReference = mSoftCache.get(url);
            if(bitmapReference != null) {
                bitmap = bitmapReference.get();
                if(bitmap != null) {
                    //将图片移回硬缓存
                    mLruCache.put(url, bitmap);
                    mSoftCache.remove(url);
                    returnbitmap;
                }else{
                    mSoftCache.remove(url);
                }
            }
        }
        returnnull;
    }
                                                                                  
    /**
     * 添加图片到缓存
     */
    publicvoid addBitmapToCache(String url, Bitmap bitmap) {
        if(bitmap != null) {
            synchronized(mLruCache) {
                mLruCache.put(url, bitmap);
            }
        }
    }
                                                                                  
    publicvoid clearCache() {
        mSoftCache.clear();
    }
}

 

文件缓存类:ImageFileCache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
publicclass ImageFileCache {
    privatestatic final String CACHDIR = "ImgCach";
    privatestatic final String WHOLESALE_CONV = ".cach";
                                                            
    privatestatic final int MB = 1024*1024;
    privatestatic final int CACHE_SIZE = 10;
    privatestatic final int FREE_SD_SPACE_NEEDED_TO_CACHE = 10;
                                                                
    publicImageFileCache() {
        //清理文件缓存
        removeCache(getDirectory());
    }
                                                                
    /** 从缓存中获取图片 **/
    publicBitmap getImage(finalString url) {   
        finalString path = getDirectory() + "/"+ convertUrlToFileName(url);
        File file = newFile(path);
        if(file.exists()) {
            Bitmap bmp = BitmapFactory.decodeFile(path);
            if(bmp == null) {
                file.delete();
            }else{
                updateFileTime(path);
                returnbmp;
            }
        }
        returnnull;
    }
                                                                
    /** 将图片存入文件缓存 **/
    publicvoid saveBitmap(Bitmap bm, String url) {
        if(bm == null) {
            return;
        }
        //判断sdcard上的空间
        if(FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            //SD空间不足
            return;
        }
        String filename = convertUrlToFileName(url);
        String dir = getDirectory();
        File dirFile = newFile(dir);
        if(!dirFile.exists())
            dirFile.mkdirs();
        File file = newFile(dir +"/"+ filename);
        try{
            file.createNewFile();
            OutputStream outStream = newFileOutputStream(file);
            bm.compress(Bitmap.CompressFormat.JPEG,100, outStream);
            outStream.flush();
            outStream.close();
        }catch(FileNotFoundException e) {
            Log.w("ImageFileCache","FileNotFoundException");
        }catch(IOException e) {
            Log.w("ImageFileCache","IOException");
        }
    }
                                                                
    /**
     * 计算存储目录下的文件大小,
     * 当文件总大小大于规定的CACHE_SIZE或者sdcard剩余空间小于FREE_SD_SPACE_NEEDED_TO_CACHE的规定
     * 那么删除40%最近没有被使用的文件
     */
    privateboolean removeCache(String dirPath) {
        File dir = newFile(dirPath);
        File[] files = dir.listFiles();
        if(files == null) {
            returntrue;
        }
        if(!android.os.Environment.getExternalStorageState().equals(
                android.os.Environment.MEDIA_MOUNTED)) {
            returnfalse;
        }
                                                            
        intdirSize = 0;
        for(inti = 0; i < files.length; i++) {
            if(files[i].getName().contains(WHOLESALE_CONV)) {
                dirSize += files[i].length();
            }
        }
                                                            
        if(dirSize > CACHE_SIZE * MB || FREE_SD_SPACE_NEEDED_TO_CACHE > freeSpaceOnSd()) {
            intremoveFactor = (int) ((0.4* files.length) + 1);
            Arrays.sort(files,newFileLastModifSort());
            for(inti = 0; i < removeFactor; i++) {
                if(files[i].getName().contains(WHOLESALE_CONV)) {
                    files[i].delete();
                }
            }
        }
                                                            
        if(freeSpaceOnSd() <= CACHE_SIZE) {
            returnfalse;
        }
                                                                    
        returntrue;
    }
                                                                
    /** 修改文件的最后修改时间 **/
    publicvoid updateFileTime(String path) {
        File file = newFile(path);
        longnewModifiedTime = System.currentTimeMillis();
        file.setLastModified(newModifiedTime);
    }
                                                                
    /** 计算sdcard上的剩余空间 **/
    privateint freeSpaceOnSd() {
        StatFs stat = newStatFs(Environment.getExternalStorageDirectory().getPath());
        doublesdFreeMB = ((double)stat.getAvailableBlocks() * (double) stat.getBlockSize()) / MB;
        return(int) sdFreeMB;
    }
                                                                
    /** 将url转成文件名 **/
    privateString convertUrlToFileName(String url) {
        String[] strs = url.split("/");
        returnstrs[strs.length - 1] + WHOLESALE_CONV;
    }
                                                                
    /** 获得缓存目录 **/
    privateString getDirectory() {
        String dir = getSDPath() + "/"+ CACHDIR;
        returndir;
    }
                                                                
    /** 取SD卡路径 **/
    privateString getSDPath() {
        File sdDir = null;
        booleansdCardExist = Environment.getExternalStorageState().equals(
                android.os.Environment.MEDIA_MOUNTED); //判断sd卡是否存在
        if(sdCardExist) {
            sdDir = Environment.getExternalStorageDirectory();  //获取根目录
        }
        if(sdDir != null) {
            returnsdDir.toString();
        }else{
            return"";
        }
    }
                                                            
    /**
     * 根据文件的最后修改时间进行排序
     */
    privateclass FileLastModifSort implementsComparator<File> {
        publicint compare(File arg0, File arg1) {
            if(arg0.lastModified() > arg1.lastModified()) {
                return1;
            }elseif (arg0.lastModified() == arg1.lastModified()) {
                return0;
            }else{
                return-1;
            }
        }
    }
                                                            
}

 

从网络获取图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
publicclass ImageGetFromHttp {
    privatestatic final String LOG_TAG = "ImageGetFromHttp";
                                                           
    publicstatic Bitmap downloadBitmap(String url) {
        finalHttpClient client = newDefaultHttpClient();
        finalHttpGet getRequest = newHttpGet(url);
                                                               
        try{
            HttpResponse response = client.execute(getRequest);
            finalint statusCode = response.getStatusLine().getStatusCode();
            if(statusCode != HttpStatus.SC_OK) {
                Log.w(LOG_TAG,"Error " + statusCode + " while retrieving bitmap from " + url);
                returnnull;
            }
                                                                   
            finalHttpEntity entity = response.getEntity();
            if(entity != null) {
                InputStream inputStream = null;
                try{
                    inputStream = entity.getContent();
                    FilterInputStream fit = newFlushedInputStream(inputStream);
                    returnBitmapFactory.decodeStream(fit);
                }finally{
                    if(inputStream != null) {
                        inputStream.close();
                        inputStream = null;
                    }
                    entity.consumeContent();
                }
            }
        }catch(IOException e) {
            getRequest.abort();
            Log.w(LOG_TAG,"I/O error while retrieving bitmap from " + url, e);
        }catch(IllegalStateException e) {
            getRequest.abort();
            Log.w(LOG_TAG,"Incorrect URL: " + url);
        }catch(Exception e) {
            getRequest.abort();
            Log.w(LOG_TAG,"Error while retrieving bitmap from " + url, e);
        }finally{
            client.getConnectionManager().shutdown();
        }
        returnnull;
    }
                                                       
    /*
     * An InputStream that skips the exact number of bytes provided, unless it reaches EOF.
     */
    staticclass FlushedInputStream extendsFilterInputStream {
        publicFlushedInputStream(InputStream inputStream) {
            super(inputStream);
        }
                                                       
        @Override
        publiclong skip(longn) throwsIOException {
            longtotalBytesSkipped = 0L;
            while(totalBytesSkipped < n) {
                longbytesSkipped = in.skip(n - totalBytesSkipped);
                if(bytesSkipped == 0L) {
                    intb = read();
                    if(b < 0) {
                        break// we reached EOF
                    }else{
                        bytesSkipped = 1;// we read one byte
                    }
                }
                totalBytesSkipped += bytesSkipped;
            }
            returntotalBytesSkipped;
        }
    }
}

 

最后,获取一张图片的流程就如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*** 获得一张图片,从三个地方获取,首先是内存缓存,然后是文件缓存,最后从网络获取 ***/
publicBitmap getBitmap(String url) {
    // 从内存缓存中获取图片
    Bitmap result = memoryCache.getBitmapFromCache(url);
    if(result == null) {
        // 文件缓存中获取
        result = fileCache.getImage(url);
        if(result == null) {
            // 从网络获取
            result = ImageGetFromHttp.downloadBitmap(url);
            if(result != null) {
                fileCache.saveBitmap(result, url);
                memoryCache.addBitmapToCache(url, result);
            }
        }else{
            // 添加到内存缓存
            memoryCache.addBitmapToCache(url, result);
        }
    }
    returnresult;
}