Android Bitmap大量使用不产生OOM之使用缓存机制
2015-08-10 10:27
621 查看
转载请注明 /article/1462864.html
加载一张图到你的界面,很轻松。但是,如果是在一瞬间要加载很多图的情况呢?比方说像listview、gridview、或者viewpager。或许你已经想到了,是的,用我上篇讲到Android Bitmap大量使用不产生OOM之多线程并发加载Bitmap的处理方式。虽然上篇能解决一瞬间加载很多图片,但是,这样一来不是每次都在请求数据吗?当我把一部分items滑出了界面,这部分加载的bitmap就没用了,然后会被垃圾回收器给回收了。等下次滑进来的时候,又要重新去加载一次。这样虽然内存使用上是没问题的,但是在图片显示性能上显然不好。为了保证一个流畅又美好的界面,你必须得做点什么来保护那些可能下次还会被使用的bitmap数据。
这一篇会介绍如何使用内存和本地缓存bitmap机制,在需要不断加载大量图片数据的情况下,来提高响应速度和ui的流畅。
题外话:以前,一个很有名的内存缓存是用SoftReference(软引用)和WeakReference(弱引用)来管理bitmap的。但是这种方式已经不被推荐了。因为从Android2.3(API Level 9)开始垃圾回收器回收软引用和弱引用的积极性有了很大的提升,这样一来,先前的方案就显得没什么意义了。还有就是,在Android3.0(API Level 11)之前,bitmap的数据是存储在native内存区(这一块区域不受垃圾回收器限制,是底层c和c++管辖的区域),然后它的释放时不可预见的,潜在的会导致很容易超出一个应用的内存限制而崩溃。
要选择一个合适的LruCache大小,有一些因素需要被考虑,比方说:
1.你给其余的activity或者应用剩多少内存。
2.一次要加载多少图片?有多少需要马上显示到界面?
3.当前屏幕的尺寸和屏幕像素密度是多少?一个很高屏幕像素密度的手机像Galaxy Nexus(xhdpi)在缓存相同数量图片时,所需的缓存大小肯定比像素密度稍微低点的像Nexus S(hdpi)来的大。
4.要知道bitmaps的尺寸和结构类型,从而确定每个要消耗多少。
5.这些图片使用频繁吗?有没有一些图片比其他图片使用的更频繁?如果是,你可能需要让它们一直保存在缓存,或者在多建些LruCache,用来分批保存这些图片。
6.你能在质量和数量之间找到一个平衡点吗?有时候,保存大量低质量的图片会更加有效,潜在的在后台加载高质量版本的图片。
对于所有应用来说,指定LruCache的大小是没有规定的,也无法给个明确值,这取决与你对你内存的使用情况分析。如果缓存太小,没太大意义,还要浪费缓存的维护开销;如果缓存太大,那么留给你应用的其他内存就少,也会更容易发生OOM。
这里举个例子,看看怎么使用LruCache:
在这个例子里,我们给LruCache分配了1/8的允许最大内存。在一般配置或者hdpi的手机,这个最小大概在4M(32/4)。一个满屏的GridView,然后在800x480的分辩率下,大概要消耗1.5M(800*480*4),这样一来,LruCache能缓存大概2.5页的数据。
在ImageView加载Bitmap之前,我们应该先尝试从LruCache获取试试。如果找到了,那么就可以直接被使用了。如果找不到,那就新建个task重新下载。
我们也要重新修改下BitmapWorkerTask部分代码,把LruCache给加进去:
DiskLruCache就很适合处理刚才描述的情况,它会储存这些图片到本地,然后我们下次就不用去网上下载,可以直接从本地获取到。
关于这部分内容,Android DOC里讲述的内容不全面,所以,我会自己总结一下。
我们先来看看官方给的一个使用DiskLruCache的例子:
我们可以看到,DiskLruCache的使用和LruCache几乎是差不多的。先是初始化,然后再可以写入和读出。
我通过官方给的链接,把DiskLruCache下载下来,发现代码和上面的例子很不一样了。所以,我们不能参考官方的doc去熟悉DiskCache,我们只能通过自己去探索并结合网上能找到的信息。
DiskLruCache的官网链接是:
https://android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/io/DiskLruCache.java
所以,我们使用它的时候,也要创一个这个package。
初始化调用的方法和官方给的是一样的,只是参数不一样了。
我们可以看到,需要提供四个参数。这四个参数分别是:
1.File directory 缓存的文件位置
2.int appVersion 应用的版本
3.int valueCount 指定同一个key能对应的缓存文件的数量,这里一般设置1。
4.long maxSize 这个参数指定缓存大小。
缓存的文件位置,大部分开发者都会设定在sd卡。当然,我们可以选择sd卡的任意能访问到的位置,但是,一般对于这种本地缓存,有很好的地方可以放置,那就是/sdcard/Android/data/application package/cache。指定在这里,有一个好处,那就是当指定的应用被卸载后,在这里的文件也会被系统给清理掉。从用户的角度出发,这种方式显然是最好的。
官方给了获取该地址的方案了:
它写得很到位,先是判断sd卡能不能获取到,如果能,就获取/sdcard/Android/data/application package/cache的路径;如果不能,退而求其次,获取/data/data/application package/cache的路径,该位置也受系统的管制,当应用卸载了,数据也会清理掉,只是那里的使用空间有限。
得到了缓存位置,然后是获取应用版本。这里为啥要获取应用版本呢?是这样的,每次初始化的时候,它都会把当前获取到的应用版本和DiskLruCache已记录的应用版本进行比较,如果不一样,会把缓存数据清光。这样一来,你的应用升级,数据就会没了!当然如果你不喜欢这样,可以把它源码改动一下。
我们先贴个获取应用版本的代码:
然后,看看为啥DiskLruCache会因为版本不同而清空数据:
第11行是创建了cache,然后12行是判断是否已经有journalFile创建过。那什么是cache.journalFile呢?我直接贴关键的了:
这里,directory的位置就是先前定义的缓存位置。也就是说先读取/data/application package/cache/journal这个文件是否存在。
这个journal文件其实就是DiskLruCache的日志,记录了每一笔的操作情况,也记录了DiskLruCache的初始配置值。
我们再来看看第14行的
可以看到,先是获取记录的各种配置信息,包括了应用版本,然后和当前传进来的进行对比,如果不一致,就直接抛异常了。
再回到DiskLruCache的open,它捕获异常之后,就直接
再说DiskLruCache的open的第三个参数int valueCount,这个参数的含义是,我们传入数据的时候,会用key去对应这个数据,然后它指定了key能对应的数据的个数。
一般来说,我们是希望一一对应,所以,我们就传个1。
第四个参数我就不说了。
官方给的初始化是在子线程进行的,这样是最好的,不过,你要控制一下流程,别还没初始化完就去读取或者写入。当然写在主线程一般也没啥太大问题,除非遇到特殊情况了(sd卡好好的,初始化的时候突然松了$#&@)。
这里,如果你觉得这种方式不爽,你也可以改源码。
key不用说了,通过key获取editor,然后通过editor获取stream。这里
写入成功后,就会在对应的地方产生一条数据:
我创建的缓存位置在sd卡,所以会到Android/data下,缓存文件名是bitmap,这里的key,我做了处理。其实只要保证唯一性就行了。
我这里直接贴示例代码:
可以看到,读取时,先要获得DiskLruCache.Snapshot对象,然后要先判空,再从里面获取Inputstream,就可以读入了。当然,如果你觉得这种方式不适合你,你可以改代码。
一句话调用:
很简单吧,但是我们一般用不到,因为本身它有管理容量,超过容量,它会自动选择最近最少使用的把它删除,所以不用我们去多此一举。
好了,关于bitmap的缓存部分讲完了。如果你对DiskLruCache有兴趣,可以自己查看它的代码,就一个文件,大部分是文件操作,不难。
加载一张图到你的界面,很轻松。但是,如果是在一瞬间要加载很多图的情况呢?比方说像listview、gridview、或者viewpager。或许你已经想到了,是的,用我上篇讲到Android Bitmap大量使用不产生OOM之多线程并发加载Bitmap的处理方式。虽然上篇能解决一瞬间加载很多图片,但是,这样一来不是每次都在请求数据吗?当我把一部分items滑出了界面,这部分加载的bitmap就没用了,然后会被垃圾回收器给回收了。等下次滑进来的时候,又要重新去加载一次。这样虽然内存使用上是没问题的,但是在图片显示性能上显然不好。为了保证一个流畅又美好的界面,你必须得做点什么来保护那些可能下次还会被使用的bitmap数据。
这一篇会介绍如何使用内存和本地缓存bitmap机制,在需要不断加载大量图片数据的情况下,来提高响应速度和ui的流畅。
使用内存缓存
内存缓存机制可以让你快速的得到bitmaps,但是相应的,需要消耗一定的应用内存。我们不需要自己去实现内存缓存,在support-v4里已经有现成的了。那就是LruCache,这个类很适合用来缓存bitmaps。它会把最近经常使用的objects保存到一个强大的数据结构LinkedHashMap,然后当缓存区满的时候,把最近最少使用的objects丢掉。题外话:以前,一个很有名的内存缓存是用SoftReference(软引用)和WeakReference(弱引用)来管理bitmap的。但是这种方式已经不被推荐了。因为从Android2.3(API Level 9)开始垃圾回收器回收软引用和弱引用的积极性有了很大的提升,这样一来,先前的方案就显得没什么意义了。还有就是,在Android3.0(API Level 11)之前,bitmap的数据是存储在native内存区(这一块区域不受垃圾回收器限制,是底层c和c++管辖的区域),然后它的释放时不可预见的,潜在的会导致很容易超出一个应用的内存限制而崩溃。
要选择一个合适的LruCache大小,有一些因素需要被考虑,比方说:
1.你给其余的activity或者应用剩多少内存。
2.一次要加载多少图片?有多少需要马上显示到界面?
3.当前屏幕的尺寸和屏幕像素密度是多少?一个很高屏幕像素密度的手机像Galaxy Nexus(xhdpi)在缓存相同数量图片时,所需的缓存大小肯定比像素密度稍微低点的像Nexus S(hdpi)来的大。
4.要知道bitmaps的尺寸和结构类型,从而确定每个要消耗多少。
5.这些图片使用频繁吗?有没有一些图片比其他图片使用的更频繁?如果是,你可能需要让它们一直保存在缓存,或者在多建些LruCache,用来分批保存这些图片。
6.你能在质量和数量之间找到一个平衡点吗?有时候,保存大量低质量的图片会更加有效,潜在的在后台加载高质量版本的图片。
对于所有应用来说,指定LruCache的大小是没有规定的,也无法给个明确值,这取决与你对你内存的使用情况分析。如果缓存太小,没太大意义,还要浪费缓存的维护开销;如果缓存太大,那么留给你应用的其他内存就少,也会更容易发生OOM。
这里举个例子,看看怎么使用LruCache:
private LruCache<String, Bitmap> mMemoryCache; @Override protected void onCreate(Bundle savedInstanceState) { ... // 获取该应用虚拟机允许的最大内存,超过该内存量,会OOM。用kb单位保存。 final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // 获取最大内存的1/8. final int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // 缓存的单位大小用kb表示。 return bitmap.getByteCount() / 1024; } }; ... } public void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } public Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); }
在这个例子里,我们给LruCache分配了1/8的允许最大内存。在一般配置或者hdpi的手机,这个最小大概在4M(32/4)。一个满屏的GridView,然后在800x480的分辩率下,大概要消耗1.5M(800*480*4),这样一来,LruCache能缓存大概2.5页的数据。
在ImageView加载Bitmap之前,我们应该先尝试从LruCache获取试试。如果找到了,那么就可以直接被使用了。如果找不到,那就新建个task重新下载。
public void loadBitmap(int resId, ImageView imageView) { final String imageKey = String.valueOf(resId); final Bitmap bitmap = getBitmapFromMemCache(imageKey); if (bitmap != null) { mImageView.setImageBitmap(bitmap); } else { mImageView.setImageResource(R.drawable.image_placeholder); BitmapWorkerTask task = new BitmapWorkerTask(mImageView); task.execute(resId); } }
我们也要重新修改下BitmapWorkerTask部分代码,把LruCache给加进去:
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { ... // Decode image in background. @Override protected Bitmap doInBackground(Integer... params) { final Bitmap bitmap = decodeSampledBitmapFromResource( getResources(), params[0], 100, 100)); addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); return bitmap; } ... }
使用DiskCache
内存缓存适合解决最近常常浏览图片的快速获取,但是我想说,朋友,你不能只依靠这个。像GridView,它需要大量数据,于是,很快就会充满整个LruCache。此时如果你的应用被其他应用给中断了呢?比方说,突然进来一个电话。最坏的情况,你的应用被系统回收释放了,此时,保存在缓存的数据也就全没了。然后,你苦逼的回到原先的应用,发现这些图片必须重新加载了。DiskLruCache就很适合处理刚才描述的情况,它会储存这些图片到本地,然后我们下次就不用去网上下载,可以直接从本地获取到。
关于这部分内容,Android DOC里讲述的内容不全面,所以,我会自己总结一下。
我们先来看看官方给的一个使用DiskLruCache的例子:
private DiskLruCache mDiskLruCache; private final Object mDiskCacheLock = new Object(); private boolean mDiskCacheStarting = true; private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB private static final String DISK_CACHE_SUBDIR = "thumbnails"; @Override protected void onCreate(Bundle savedInstanceState) { ... // Initialize memory cache ... // Initialize disk cache on background thread File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR); new InitDiskCacheTask().execute(cacheDir); ... } class InitDiskCacheTask extends AsyncTask<File, Void, Void> { @Override protected Void doInBackground(File... params) { synchronized (mDiskCacheLock) { File cacheDir = params[0]; mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE); mDiskCacheStarting = false; // Finished initialization mDiskCacheLock.notifyAll(); // Wake any waiting threads } return null; } } class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { ... // Decode image in background. @Override protected Bitmap doInBackground(Integer... params) { final String imageKey = String.valueOf(params[0]); // Check disk cache in background thread Bitmap bitmap = getBitmapFromDiskCache(imageKey); if (bitmap == null) { // Not found in disk cache // Process as normal final Bitmap bitmap = decodeSampledBitmapFromResource( getResources(), params[0], 100, 100)); } // Add final bitmap to caches addBitmapToCache(imageKey, bitmap); return bitmap; } ... } public void addBitmapToCache(String key, Bitmap bitmap) { // Add to memory cache as before if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } // Also add to disk cache synchronized (mDiskCacheLock) { if (mDiskLruCache != null && mDiskLruCache.get(key) == null) { mDiskLruCache.put(key, bitmap); } } } public Bitmap getBitmapFromDiskCache(String key) { synchronized (mDiskCacheLock) { // Wait while disk cache is started from background thread while (mDiskCacheStarting) { try { mDiskCacheLock.wait(); } catch (InterruptedException e) {} } if (mDiskLruCache != null) { return mDiskLruCache.get(key); } } return null; } // Creates a unique subdirectory of the designated app cache directory. Tries to use external // but if not mounted, falls back on internal storage. public static File getDiskCacheDir(Context context, String uniqueName) { // Check if media is mounted or storage is built-in, if so, try and use external cache dir // otherwise use internal cache dir final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() : context.getCacheDir().getPath(); return new File(cachePath + File.separator + uniqueName); }
我们可以看到,DiskLruCache的使用和LruCache几乎是差不多的。先是初始化,然后再可以写入和读出。
我通过官方给的链接,把DiskLruCache下载下来,发现代码和上面的例子很不一样了。所以,我们不能参考官方的doc去熟悉DiskCache,我们只能通过自己去探索并结合网上能找到的信息。
DiskLruCache的官网链接是:
https://android.googlesource.com/platform/libcore/+/jb-mr2-release/luni/src/main/java/libcore/io/DiskLruCache.java
使用DiskLruCache
我们可以看到,/src/main/java/libcore/io/DiskLruCache.java,也就是说DiskLruCache的package是package libcore.io;
所以,我们使用它的时候,也要创一个这个package。
初始化DiskLruCache
我们先来看看怎么初始化DiskLruCache。初始化调用的方法和官方给的是一样的,只是参数不一样了。
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
我们可以看到,需要提供四个参数。这四个参数分别是:
1.File directory 缓存的文件位置
2.int appVersion 应用的版本
3.int valueCount 指定同一个key能对应的缓存文件的数量,这里一般设置1。
4.long maxSize 这个参数指定缓存大小。
缓存的文件位置,大部分开发者都会设定在sd卡。当然,我们可以选择sd卡的任意能访问到的位置,但是,一般对于这种本地缓存,有很好的地方可以放置,那就是/sdcard/Android/data/application package/cache。指定在这里,有一个好处,那就是当指定的应用被卸载后,在这里的文件也会被系统给清理掉。从用户的角度出发,这种方式显然是最好的。
官方给了获取该地址的方案了:
// Creates a unique subdirectory of the designated app cache directory. Tries to use external // but if not mounted, falls back on internal storage. public static File getDiskCacheDir(Context context, String uniqueName) { // Check if media is mounted or storage is built-in, if so, try and use external cache dir // otherwise use internal cache dir final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() : context.getCacheDir().getPath(); return new File(cachePath + File.separator + uniqueName); }
它写得很到位,先是判断sd卡能不能获取到,如果能,就获取/sdcard/Android/data/application package/cache的路径;如果不能,退而求其次,获取/data/data/application package/cache的路径,该位置也受系统的管制,当应用卸载了,数据也会清理掉,只是那里的使用空间有限。
得到了缓存位置,然后是获取应用版本。这里为啥要获取应用版本呢?是这样的,每次初始化的时候,它都会把当前获取到的应用版本和DiskLruCache已记录的应用版本进行比较,如果不一样,会把缓存数据清光。这样一来,你的应用升级,数据就会没了!当然如果你不喜欢这样,可以把它源码改动一下。
我们先贴个获取应用版本的代码:
public int getAppVersion(Context context) { try { PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); return info.versionCode; } catch (NameNotFoundException e) { e.printStackTrace(); } return 1; }
然后,看看为啥DiskLruCache会因为版本不同而清空数据:
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) { // System.logW("DiskLruCache " + directory + " is corrupt: " // + journalIsCorrupt.getMessage() + ", removing"); cache.delete(); } } // create a new empty cache directory.mkdirs(); cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); cache.rebuildJournal(); return cache; }
第11行是创建了cache,然后12行是判断是否已经有journalFile创建过。那什么是cache.journalFile呢?我直接贴关键的了:
static final String JOURNAL_FILE = "journal"; this.journalFile = new File(directory, JOURNAL_FILE);
这里,directory的位置就是先前定义的缓存位置。也就是说先读取/data/application package/cache/journal这个文件是否存在。
这个journal文件其实就是DiskLruCache的日志,记录了每一笔的操作情况,也记录了DiskLruCache的初始配置值。
我们再来看看第14行的
cache.readJournal();
private void readJournal() throws IOException { InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE); try { String magic = readAsciiLine(in); String version = readAsciiLine(in); String appVersionString = readAsciiLine(in); String valueCountString = readAsciiLine(in); String blank = readAsciiLine(in); if (!MAGIC.equals(magic) || !VERSION_1.equals(version) || !Integer.toString(appVersion).equals(appVersionString) || !Integer.toString(valueCount).equals(valueCountString) || !"".equals(blank)) { throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " + valueCountString + ", " + blank + "]"); } while (true) { try { readJournalLine(readAsciiLine(in)); } catch (EOFException endOfJournal) { break; } } } finally { closeQuietly(in); } }
可以看到,先是获取记录的各种配置信息,包括了应用版本,然后和当前传进来的进行对比,如果不一致,就直接抛异常了。
再回到DiskLruCache的open,它捕获异常之后,就直接
cache.delete();。
再说DiskLruCache的open的第三个参数int valueCount,这个参数的含义是,我们传入数据的时候,会用key去对应这个数据,然后它指定了key能对应的数据的个数。
一般来说,我们是希望一一对应,所以,我们就传个1。
第四个参数我就不说了。
官方给的初始化是在子线程进行的,这样是最好的,不过,你要控制一下流程,别还没初始化完就去读取或者写入。当然写在主线程一般也没啥太大问题,除非遇到特殊情况了(sd卡好好的,初始化的时候突然松了$#&@)。
缓存写入
怎么写入缓存呢?有点类似于SharedPreferece,先获取DiskLruCache.Editor,然后再获取OutputStream,通过Stream得到数据,再执行commit()。DiskLruCache.Editor editor = mDiskLruCache.edit(key); if (editor != null) { OutputStream outputStream = editor.newOutputStream(0); if (dataToStream(key, outputStream)) { editor.commit(); } else { editor.abort(); } }
这里,如果你觉得这种方式不爽,你也可以改源码。
key不用说了,通过key获取editor,然后通过editor获取stream。这里
editor.newOutputStream(0);的0是指key对应的第一个数据,我们先前定义key对应一个数据,所以这里只要获取第一个数据就行。
写入成功后,就会在对应的地方产生一条数据:
我创建的缓存位置在sd卡,所以会到Android/data下,缓存文件名是bitmap,这里的key,我做了处理。其实只要保证唯一性就行了。
缓存读取
缓存读取是通过DiskLruCache的get方法:public synchronized Snapshot get(String key) throws IOException
我这里直接贴示例代码:
DiskLruCache.Snapshot snapshot; try { snapshot = diskLruCache.get(key); if (snapshot != null) { fileInputStream = (FileInputStream) snapshot.getInputStream(0); fileDescriptor = fileInputStream.getFD(); }
可以看到,读取时,先要获得DiskLruCache.Snapshot对象,然后要先判空,再从里面获取Inputstream,就可以读入了。当然,如果你觉得这种方式不适合你,你可以改代码。
缓存删除
缓存移除通过调用remove方法:public synchronized boolean remove(String key) throws IOException
一句话调用:
diskLruCache.remove(key);
很简单吧,但是我们一般用不到,因为本身它有管理容量,超过容量,它会自动选择最近最少使用的把它删除,所以不用我们去多此一举。
其他操作
还有一些其他操作,这里稍微讲下。flush()
看到这个名称,大部分就知道这个干嘛用的了。没错,它就是用来把文件操作的缓存内容写到journal,这个是针对日志的写入的。一般我们只要在activity的onPause()调用下就行。close()
这个是关闭DiskLruCache缓存。delete()
这个是删除所有缓存文件,包含了close():public void delete() throws IOException { close(); deleteContents(directory); }
public static void deleteContents(File dir) throws IOException { File[] files = dir.listFiles(); if (files == null) { throw new IllegalArgumentException("not a directory: " + dir); } for (File file : files) { if (file.isDirectory()) { deleteContents(file); } if (!file.delete()) { throw new IOException("failed to delete file: " + file); } } }
size()
这个是当前缓存的数据大小。好了,关于bitmap的缓存部分讲完了。如果你对DiskLruCache有兴趣,可以自己查看它的代码,就一个文件,大部分是文件操作,不难。
相关文章推荐
- Android 中使用的数学运算
- Android Handler 异步消息处理机制的妙用 创建强大的图片加载类
- Android studio 安装中遇到一些问题的解决办法,分享一下
- android:layout_weight的理解
- android两种圆形进度条
- facebook的Android调试工具Stetho介绍
- Android利用Intent与其他应用交互
- android停止应用不能接收广播
- Android 第三方应用广告拦截实现
- Android Data Binding简单介绍
- Android getDecorView用途——屏幕截图 (转)
- Java4Android-接口的应用
- Android Studio从SVN检出代码
- Android利用activity启动模式退出整个应用
- Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系
- Junit单元测试AndroidManifest.xml中的权限添加
- Android fill_parent、wrap_content和match_parent的区别
- Android实现的仿淘宝购物车demo示例
- android数据管理DataCleanManager
- android 图片上传