private int mCacheRefCount = 0; private int mDisplayRefCount = 0; ... // Notify the drawable that the displayed state has changed. // Keep a count to determine when the drawable is no longer displayed. 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. // Keep a count to determine when the drawable is no longer being cached. publicvoid setIsCached(boolean isCached){ synchronized(this){ if(isCached){ mCacheRefCount++; }else{ mCacheRefCount--; } } // Check to see if recycle() can be called. checkState(); } privatesynchronizedvoid checkState(){ // If the drawable cache and display ref counts = 0, and this drawable // has been displayed, then recycle. if(mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed && hasValidBitmap()){ getBitmap().recycle(); } } private synchronized boolean hasValidBitmap(){ Bitmap bitmap = getBitmap(); return bitmap != null && !bitmap.isRecycled(); }
Set<SoftReference<Bitmap>> mReusableBitmaps; private LruCache<String, BitmapDrawable> mMemoryCache; // If you're running on Honeycomb or newer, create a // synchronized HashSet of references to reusable bitmaps. if(Utils.hasHoneycomb()){ mReusableBitmaps = Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>()); } mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize){ // Notify the removed entry that is no longer being cached. @Override protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldValue, BitmapDrawable newValue){ if(RecyclingBitmapDrawable.class.isInstance(oldValue)){ // The removed entry is a recycling drawable, so notify it // that it has been removed from the memory cache. ((RecyclingBitmapDrawable) oldValue).setIsCached(false); }else{ // The removed entry is a standard BitmapDrawable. if(Utils.hasHoneycomb()){ // We're running on Honeycomb or later, so add the bitmap // to a SoftReference set for possible use with inBitmap later. mReusableBitmaps.add (new SoftReference<Bitmap>(oldValue.getBitmap())); } } } .... }
public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, int reqHeight, ImageCache cache){ final BitmapFactory.Options options = new BitmapFactory.Options(); ... BitmapFactory.decodeFile(filename, options); ... // If we're running on Honeycomb or newer, try to use inBitmap. if(Utils.hasHoneycomb()){ addInBitmapOptions(options, cache); } ... return BitmapFactory.decodeFile(filename, options); }
public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, int reqHeight, ImageCache cache){ final BitmapFactory.Options options = new BitmapFactory.Options(); ... BitmapFactory.decodeFile(filename, options); ... // If we're running on Honeycomb or newer, try to use inBitmap. if(Utils.hasHoneycomb()){ addInBitmapOptions(options, cache); } ... return BitmapFactory.decodeFile(filename, options); }
private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache){ // inBitmap only works with mutable bitmaps, so force the decoder to // return mutable bitmaps. options.inMutable = true; if(cache != null){ // Try to find a bitmap to use for inBitmap. Bitmap inBitmap = cache.getBitmapFromReusableSet(options); if(inBitmap != null){ // If a suitable bitmap has been found, set it as the value of // inBitmap. options.inBitmap = inBitmap; } } } // This method iterates through the reusable bitmaps, looking for one // to use for inBitmap: protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options){ Bitmap bitmap = null; if(mReusableBitmaps != null && !mReusableBitmaps.isEmpty()){ synchronized(mReusableBitmaps){ final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator(); Bitmap item; while(iterator.hasNext()){ item = iterator.next().get(); if(null != item && item.isMutable()){ // Check to see it the item can be used for inBitmap. if(canUseForInBitmap(item, options)){ bitmap = item; // Remove from reusable set so it can't be used again. iterator.remove(); break; } }else{ // Remove from the set if the reference has been cleared. iterator.remove(); } } } } return bitmap; }
inBitmap
:
static boolean canUseForInBitmap( Bitmap candidate, BitmapFactory.Options targetOptions){ if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.KITKAT){ // From Android 4.4 (KitKat) onward we can re-use if the byte size of // the new bitmap is smaller than the reusable bitmap candidate // allocation byte count. int width = targetOptions.outWidth / targetOptions.inSampleSize; int height = targetOptions.outHeight / targetOptions.inSampleSize; int byteCount = width * height * getBytesPerPixel(candidate.getConfig()); return byteCount <= candidate.getAllocationByteCount(); } // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1 return candidate.getWidth() == targetOptions.outWidth && candidate.getHeight() == targetOptions.outHeight && targetOptions.inSampleSize == 1; } /** * A helper function to return the byte usage per pixel of a bitmap based on its configuration. */ static int getBytesPerPixel(Config config){ if(config == Config.ARGB_8888){ return 4; }elseif(config == Config.RGB_565){ return 2; }elseif(config == Config.ARGB_4444){ return 2; }elseif(config == Config.ALPHA_8){ return 1; } return1; }
Images come in all shapes and sizes. In many cases they are larger than required for a typical application user interface (UI). For example, the system Gallery application displays photos taken using your Android devices's camera which are typically much higher resolution than the screen density of your device.
Given that you are working with limited memory, ideally you only want to load a lower resolution version in memory. The lower resolution version should match the size of the UI component that displays it. An image with a higher resolution does not provide any visible benefit, but still takes up precious memory and incurs additional performance overhead due to additional on the fly scaling.
This lesson walks you through decoding large bitmaps without exceeding the per application memory limit by loading a smaller subsampled version in memory.
The BitmapFactory
class provides several decoding methods (decodeByteArray()
, decodeFile()
,decodeResource()
, etc.) for creating a Bitmap
from various sources. Choose the most appropriate decode method based on your image data source. These methods attempt to allocate memory for the constructed bitmap and therefore can easily result in an OutOfMemory
exception. Each type of decode method has additional signatures that let you specify decoding options via the BitmapFactory.Options
class. Setting the inJustDecodeBounds
property to true
while decoding avoids memory allocation, returning null
for the bitmap object but setting outWidth
, outHeight
and outMimeType
. This technique allows you to read the dimensions and type of the image data prior to construction (and memory allocation) of the bitmap.
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType;
To avoid java.lang.OutOfMemory
exceptions, check the dimensions of a bitmap before decoding it, unless you absolutely trust the source to provide you with predictably sized image data that comfortably fits within the available memory.
Now that the image dimensions are known, they can be used to decide if the full image should be loaded into memory or if a subsampled version should be loaded instead. Here are some factors to consider:
ImageView
or UI component that the image is to be loaded into. For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be displayed in a 128x96 pixel thumbnail in an ImageView
.
To tell the decoder to subsample the image, loading a smaller version into memory, set inSampleSize
to true
in your BitmapFactory.Options
object. For example, an image with resolution 2048x1536 that is decoded with aninSampleSize
of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image (assuming a bitmap configuration of ARGB_8888
). Here’s a method to calculate a sample size value that is a power of two based on a target width and height:
publicstaticint calculateInSampleSize( BitmapFactory.Options options,int reqWidth,int reqHeight){ // Raw height and width of image finalint height = options.outHeight; finalint width = options.outWidth; int inSampleSize =1; if(height > reqHeight || width > reqWidth){ finalint halfHeight = height /2; finalint halfWidth = width /2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while((halfHeight / inSampleSize)> reqHeight &&(halfWidth / inSampleSize)> reqWidth){ inSampleSize *=2; } } return inSampleSize; }
Note: A power of two value is calculated because the decoder uses a final value by rounding down to the nearest power of two, as per the inSampleSize
documentation.
To use this method, first decode with inJustDecodeBounds
set to true
, pass the options through and then decode again using the new inSampleSize
value and inJustDecodeBounds
set to false
:
public static Bitmap decodeSampledBitmapFromResource(Resources res,int resId, int reqWidth,int reqHeight){ // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); }
ImageView
that displays a 100x100 pixel thumbnail, as shown in the following example code:
mImageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage,100,100));
You can follow a similar process to decode bitmaps from other sources, by substituting the appropriate BitmapFactory.decode*
method as needed.
处理 Bitmap 的参考 Demo 下载链接