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

image

缩略图

根据inSampleSize算出来的值,来相应的保存bitmap到内存当中。
image

inJustDecodeBounds:
首先inJustDecodeBounds设置为true,在通过decodeFile加载到内存中,这时候不是把原图加载到内存,而是会计算一个比例。
再将inJustDecodeBounds设置为false后,在通过缩放比例,将bitmap放到内存当中。计算出缩略图的大小。

三级缓存

现在的app有很多图片是通过网络获取,如果每次加载图片都通过网络,那么是很耗费流量的。
为了减少网络请求的数据量,减少用户流量消耗,提出了三级缓存的概念。
image

三级缓存有哪三级:

粗略的可以分为网络,本地,内存;
原理很简单,比如首次打开app需要加载图片,这时候只能从网络上获取。
当从网络加载完图片之后,把图片保存在本地和内存中各一份。
此时再次请求同样URL的bitmap时,就不用从网络上获取,此时直接从本地或者内存中获取即可。

网络:

速度慢,浪费流量,相同url的bitmap应该只有第一次启动时需要从网络获取。

本地:

本地没什么好说的

内存:

内存缓存优先加载,同时速度也是最快的。

posted @ 2022-07-09 20:31  cfdroid  阅读(119)  评论(0编辑  收藏  举报