Bitmap相关问题总结
recycle方法
recycle方法什么意思,我们知道bitmap是存在于java内存和native内存当中的。
所以说当它被回收的时候,需要分两部分来回收,一是java内存一是native内存。
在android 3.0以前bitmap像素数据和bitmap对象是一起存放在堆中的。这时候你只需要回收堆中的内存即可。
3.0 内存是不稳定的,官方建议我们调用recycle方法回收内存。
/**
* Free the native object associated with this bitmap, and clear the
* reference to the pixel data. This will not free the pixel data synchronously;
* it simply allows it to be garbage collected if there are no other references.
* The bitmap is marked as "dead", meaning it will throw an exception if
* getPixels() or setPixels() is called, and will draw nothing. This operation
* cannot be reversed, so it should only be called if you are sure there are no
* further uses for the bitmap. This is an advanced call, and normally need
* not be called, since the normal GC process will free up this memory when
* there are no more references to this bitmap.
*/
public void recycle() {
if (!mRecycled) {
nativeRecycle(mNativePtr);
mNinePatchChunk = null;
mRecycled = true;
}
}
通过官方的注释可以看出:
bitmap的recycle方法,释放的是和bitmap对象关联的native内存。同时会清理像素数据的引用。但是这里处理像素对象的引用并不是立即执行。
如果没有别的引用与像素数据关联时,允许垃圾回收器进行回收。此时bitmap被标记为dead状态,再去执行getPixels和setPixels方法就会抛出异常。
recycle操作是不可逆的,必须确认程序不能再使用到bitmap是才可以调用。
而且通常不建议主动调用,因为没有别的引用与这bitmap关联,垃圾回收执行时会主动清理内存
LRU 缓存
最近最久未使用的会被优先移除缓存。
LRUCache实现原理
LRU实现原理非常简单
LinkedHashMap 充当缓存,具体可以看下力扣上LRU的实现
public class LruCache<K, V> {
@UnsupportedAppUsage
private final LinkedHashMap<K, V> map;
...
}
trimToSize方法:
在看它的trimToSize方法,简单说就是判断当前的元素个数如果超过maxSize就删掉最近最久未使用的元素。
/**
* Remove the eldest entries until the total of remaining entries is at or
* below the requested size.
*
* @param maxSize the maximum size of the cache before returning. May be -1
* to evict even 0-sized elements.
*/
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize) {
break;
}
Map.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
put方法:
其实就是将数据放到缓存中,同时将该数据移到队头
/**
* Caches {@code value} for {@code key}. The value is moved to the head of
* the queue.
*
* @return the previous value mapped by {@code key}.
*/
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
get方法:
get方法就是把数据添加到队头,再把原数据删掉,并返回数据的值
/**
* Returns the value for {@code key} if it exists in the cache or can be
* created by {@code #create}. If a value was returned, it is moved to the
* head of the queue. This returns null if a value is not cached and cannot
* be created.
*/
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
/*
* Attempt to create a value. This may take a long time, and the map
* may be different when create() returns. If a conflicting value was
* added to the map while create() was working, we leave that value in
* the map and release the created value.
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// There was a conflict so undo that last put
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
计算inSampleSize
缩略图
根据inSampleSize算出来的值,来相应的保存bitmap到内存当中。
inJustDecodeBounds:
首先inJustDecodeBounds设置为true,在通过decodeFile加载到内存中,这时候不是把原图加载到内存,而是会计算一个比例。
再将inJustDecodeBounds设置为false后,在通过缩放比例,将bitmap放到内存当中。计算出缩略图的大小。
三级缓存
现在的app有很多图片是通过网络获取,如果每次加载图片都通过网络,那么是很耗费流量的。
为了减少网络请求的数据量,减少用户流量消耗,提出了三级缓存的概念。
三级缓存有哪三级:
粗略的可以分为网络,本地,内存;
原理很简单,比如首次打开app需要加载图片,这时候只能从网络上获取。
当从网络加载完图片之后,把图片保存在本地和内存中各一份。
此时再次请求同样URL的bitmap时,就不用从网络上获取,此时直接从本地或者内存中获取即可。
网络:
速度慢,浪费流量,相同url的bitmap应该只有第一次启动时需要从网络获取。
本地:
本地没什么好说的
内存:
内存缓存优先加载,同时速度也是最快的。