android 图片加载优化,避免oom问题产生
1,及时回收bitmap,在activity的onstop()和onDestory()里面调用如下代码进行bitmap的回收:
// 先判断是否已经回收 if(bitmap != null && !bitmap.isRecycled()){ // 回收并且置为null bitmap.recycle(); bitmap = null; } System.gc();
2,对oom异常的捕获:出现异常不能让程序就那么崩掉吧,所以对程序中中设计bitmap的操作都要检测oom异常进而进行处理:
Bitmap bitmap = null; try { // 实例化Bitmap bitmap = BitmapFactory.decodeFile(path); } catch (OutOfMemoryError e) { //此处主要处理bitmap加载到内存出现oom的问题,可以在此处加载默认的图片去处理oom的问题。 } if (bitmap == null) { // 如果实例化失败 返回默认的Bitmap对象 return defaultBitmapMap; }
3,通过option相关参数的设置,对图片尺寸进行相应缩小,对图片质量进行降低来减少图片加载到内存中对内存的占用大小:
比如从res中获取图片:
/** * 获取压缩之后的图片 * * @param context * @param resId res下资源图片的id * @param reqWidth 期望的宽度 * @param reqHeight 期望的高度 * @return */ public static Bitmap getImageCompress(Context context, int resId, int reqWidth, int reqHeight) { Bitmap bitmap = null; try { bitmap = ImageUtils.decodeSampledBitmapFromResource(context.getResources(), resId, reqWidth, reqHeight); } catch (OutOfMemoryError e) { //此处监听oom的问题,出现oom问题就返回默认的图片 bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher); e.printStackTrace(); } return bitmap; }
再看decodeSampledBitmapFromResource方法:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小 final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // 调用上面定义的方法计算inSampleSize值 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // 使用获取到的inSampleSize值再次解析图片 options.inJustDecodeBounds = false;
//降低了图片的质量,减少内存消耗 options.inPreferredConfig = Bitmap.Config.RGB_565; return BitmapFactory.decodeResource(res, resId, options); }
inSampleSize参数通过下面的calculateInSampleSize()方法获取:
/** * 获取一个合适的压缩比例:InSampleSize 根据需要(传入的宽高设置),与原图片的宽高进行比例的调节,获取一个合适的InSampleSize也就是一个合适的压缩比例! * * @param options BitmapFactory的设置选项 * @param reqWidth 期望压缩后的宽度 * @param reqHeight 期望压缩后的高度 * @return 压缩后的图片 */ private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // 源图片的高度和宽度 final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // 计算出实际宽高和目标宽高的比率 final int heightRatio = Math.round((float)height / (float)reqHeight); final int widthRatio = Math.round((float)width / (float)reqWidth); // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高 // 一定都会大于等于目标的宽和高。 inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } return inSampleSize; }
还有可以参考使用可回收的Imageview,可以根据图片显示状态进行bitmap的回收,进而避免oom的问题
代码来自google官方的bitmapfun的代码:
/** * Sub-class of ImageView which automatically notifies the drawable when it is * being displayed. */ public class RecyclingImageView extends ImageView { public RecyclingImageView(Context context) { super(context); } public RecyclingImageView(Context context, AttributeSet attrs) { super(context, attrs); } /** * @see android.widget.ImageView#onDetachedFromWindow() */ @Override protected void onDetachedFromWindow() { // This has been detached from Window, so clear the drawable setImageDrawable(null); super.onDetachedFromWindow(); } /** * @see android.widget.ImageView#setImageDrawable(android.graphics.drawable.Drawable) */ @Override public void setImageDrawable(Drawable drawable) { // Keep hold of previous Drawable final Drawable previousDrawable = getDrawable(); // Call super to set new Drawable super.setImageDrawable(drawable); // Notify new Drawable that it is being displayed notifyDrawable(drawable, true); // Notify old Drawable so it is no longer being displayed notifyDrawable(previousDrawable, false); } /** * Notifies the drawable that it's displayed state has changed. * * @param drawable * @param isDisplayed */ private static void notifyDrawable(Drawable drawable, final boolean isDisplayed) { if (drawable instanceof RecyclingBitmapDrawable) { // The drawable is a CountingBitmapDrawable, so notify it ((RecyclingBitmapDrawable) drawable).setIsDisplayed(isDisplayed); } else if (drawable instanceof LayerDrawable) { // The drawable is a LayerDrawable, so recurse on each layer LayerDrawable layerDrawable = (LayerDrawable) drawable; for (int i = 0, z = layerDrawable.getNumberOfLayers(); i < z; i++) { notifyDrawable(layerDrawable.getDrawable(i), isDisplayed); } } } }
关联另外一个类:
/** * A BitmapDrawable that keeps track of whether it is being displayed or cached. * When the drawable is no longer being displayed or cached, * {@link android.graphics.Bitmap#recycle() recycle()} will be called on this drawable's bitmap. */ public class RecyclingBitmapDrawable extends BitmapDrawable { static final String LOG_TAG = "CountingBitmapDrawable"; //缓存引用计数器 private int mCacheRefCount = 0; //显示引用计数器 private int mDisplayRefCount = 0; //判断是否已经显示过了 private boolean mHasBeenDisplayed; public RecyclingBitmapDrawable(Resources res, Bitmap bitmap) { super(res, bitmap); } /** * Notify the drawable that the displayed state has changed. Internally a * count is kept so that the drawable knows when it is no longer being * displayed. * 在RecyclingImageView setImageDrawable的时候,这个方法调用,相关计数发 * 生变化,checkState通过计数值的判断决定是否回收bitmap * @param isDisplayed - Whether the drawable is being displayed or not */ public void setIsDisplayed(boolean isDisplayed) { synchronized (this) { if (isDisplayed) { mDisplayRefCount++; mHasBeenDisplayed = true; } else { mDisplayRefCount--; } } // Check to see if recycle() can be called checkState(); } /** * Notify the drawable that the cache state has changed. Internally a count * is kept so that the drawable knows when it is no longer being cached. * * @param isCached - Whether the drawable is being cached or not */ public void setIsCached(boolean isCached) { synchronized (this) { if (isCached) { mCacheRefCount++; } else { mCacheRefCount--; } } // Check to see if recycle() can be called checkState(); } private synchronized void checkState() { // If the drawable cache and display ref counts = 0, and this drawable // has been displayed, then recycle //如果图片缓存和显示引用计数为0,并且已经显示过了,回收掉 if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed && hasValidBitmap()) { getBitmap().recycle(); } } private synchronized boolean hasValidBitmap() { Bitmap bitmap = getBitmap(); return bitmap != null && !bitmap.isRecycled(); } }
可以下载bitmap自己去参考写适合当前业务的RecyclingImageView,下面给出下载地址:http://download.csdn.net/detail/syy81k816/6811575
关于LRU算法来对内存大小进行限制来避免OOM的问题产生:
内存缓存之LRU算法: LRU算法其实内部是LinkedHashMap LinkedHashMap 先进先出 链表结构,能够记住存储顺序,存储的时候会判断缓存是否超出构造方法传进去的缓存大小,如果超出了,会删除最早插入的内容;这个算法可以配合在Volley的图片加载中使用,避免oom的问题出现在volley图片加载中也可以使用,可参考前面写过的博客:http://www.cnblogs.com/androidsuperman/p/8a157b18ede85caa61ca5bc04bba43d0.html 下面下看下代码再说: 1,lruche的构造方法里面初始化创建linkedHashMap对象,指定缓存大小maxSize 2,lruche的put()方法:首先算出占用多大字节,safeSizeOf(key,value);内容在内存中占用的字节 3 , lruche的trimToSize()判断图片的大小是否超出指定的缓存大小,如果超出会调用entryRemove()的方法
其实LRU算法存在于support_v4包下面,具体查看源码:
/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.v4.util; import java.util.LinkedHashMap; import java.util.Map; /** * Static library version of {@link android.util.LruCache}. Used to write apps * that run on API levels prior to 12. When running on API level 12 or above, * this implementation is still used; it does not try to switch to the * framework's implementation. See the framework SDK documentation for a class * overview. */ public class LruCache<K, V> { private final LinkedHashMap<K, V> map; /** Size of this cache in units. Not necessarily the number of elements. */ private int size; private int maxSize; private int putCount; private int createCount; private int evictionCount; private int hitCount; private int missCount; /** * @param maxSize for caches that do not override {@link #sizeOf}, this is * the maximum number of entries in the cache. For all other caches, * this is the maximum sum of the sizes of the entries in this cache. *构造方法里面初始化创建linkedHashMap对象,指定缓存大小maxSize */ public LruCache(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } this.maxSize = maxSize; this.map = new LinkedHashMap<K, V>(0, 0.75f, true); } /** * Sets the size of the cache. * * @param maxSize The new maximum size. */ public void resize(int maxSize) { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } synchronized (this) { this.maxSize = maxSize; } trimToSize(maxSize); } /** * 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; } } /** * Caches {@code value} for {@code key}. The value is moved to the head of * the queue. *lruche的put方法:首先算出占用多大字节,safeSizeOf(key,value);内容在内存中占用的字节 * @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; } /** * Remove the eldest entries until the total of remaining entries is at or * below the requested size. *判断图片的大小是否超出指定的缓存大小,如果超出会调用entryRemove()的方法 * @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 || map.isEmpty()) { break; } Map.Entry<K, V> toEvict = map.entrySet().iterator().next(); key = toEvict.getKey(); value = toEvict.getValue(); map.remove(key); size -= safeSizeOf(key, value); evictionCount++; } entryRemoved(true, key, value, null); } } /** * Removes the entry for {@code key} if it exists. * * @return the previous value mapped by {@code key}. */ public final V remove(K key) { if (key == null) { throw new NullPointerException("key == null"); } V previous; synchronized (this) { previous = map.remove(key); if (previous != null) { size -= safeSizeOf(key, previous); } } if (previous != null) { entryRemoved(false, key, previous, null); } return previous; } /** * Called for entries that have been evicted or removed. This method is * invoked when a value is evicted to make space, removed by a call to * {@link #remove}, or replaced by a call to {@link #put}. The default * implementation does nothing. * * <p>The method is called without synchronization: other threads may * access the cache while this method is executing. * * @param evicted true if the entry is being removed to make space, false * if the removal was caused by a {@link #put} or {@link #remove}. * @param newValue the new value for {@code key}, if it exists. If non-null, * this removal was caused by a {@link #put}. Otherwise it was caused by * an eviction or a {@link #remove}. */ protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {} /** * Called after a cache miss to compute a value for the corresponding key. * Returns the computed value or null if no value can be computed. The * default implementation returns null. * * <p>The method is called without synchronization: other threads may * access the cache while this method is executing. * * <p>If a value for {@code key} exists in the cache when this method * returns, the created value will be released with {@link #entryRemoved} * and discarded. This can occur when multiple threads request the same key * at the same time (causing multiple values to be created), or when one * thread calls {@link #put} while another is creating a value for the same * key. */ protected V create(K key) { return null; } private int safeSizeOf(K key, V value) { int result = sizeOf(key, value); if (result < 0) { throw new IllegalStateException("Negative size: " + key + "=" + value); } return result; } /** * Returns the size of the entry for {@code key} and {@code value} in * user-defined units. The default implementation returns 1 so that size * is the number of entries and max size is the maximum number of entries. * * <p>An entry's size must not change while it is in the cache. */ protected int sizeOf(K key, V value) { return 1; } /** * Clear the cache, calling {@link #entryRemoved} on each removed entry. */ public final void evictAll() { trimToSize(-1); // -1 will evict 0-sized elements } /** * For caches that do not override {@link #sizeOf}, this returns the number * of entries in the cache. For all other caches, this returns the sum of * the sizes of the entries in this cache. */ public synchronized final int size() { return size; } /** * For caches that do not override {@link #sizeOf}, this returns the maximum * number of entries in the cache. For all other caches, this returns the * maximum sum of the sizes of the entries in this cache. */ public synchronized final int maxSize() { return maxSize; } /** * Returns the number of times {@link #get} returned a value that was * already present in the cache. */ public synchronized final int hitCount() { return hitCount; } /** * Returns the number of times {@link #get} returned null or required a new * value to be created. */ public synchronized final int missCount() { return missCount; } /** * Returns the number of times {@link #create(Object)} returned a value. */ public synchronized final int createCount() { return createCount; } /** * Returns the number of times {@link #put} was called. */ public synchronized final int putCount() { return putCount; } /** * Returns the number of values that have been evicted. */ public synchronized final int evictionCount() { return evictionCount; } /** * Returns a copy of the current contents of the cache, ordered from least * recently accessed to most recently accessed. */ public synchronized final Map<K, V> snapshot() { return new LinkedHashMap<K, V>(map); } @Override public synchronized final String toString() { int accesses = hitCount + missCount; int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", maxSize, hitCount, missCount, hitPercent); } }
最后一点:最后就是通过图片加载的框架,灵活的处理缓存,避免多个地方引用同一图片时的内存中多余的加载和避免不必要的流量消耗和电量消耗,从而避免个人应用影响设备性能等。