For readibility, I posted the code examples that my solutions refer to first, and thenI listed the explanations of my solutions in a numerical list.
I have been struggling with this for a while now. I have done much reading, asked questions on here, and experimented; but have not come up with a decent solution. I need to read variously sized images from input streams, and display them with as high of quality as my memory constraints allow. Below are the options I have considered, none of which seem great to me. Any help or input would be greatly appreciated.
public class NativeTest extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
double nativeUsage = Debug.getNativeHeapAllocatedSize();
Log.i("memory", nativeUsage+"");
}
}
double getAvailableMemory()
{
//current heap size
double heapSize = Runtime.getRuntime().totalMemory();
//amount available in heap
double heapRemaining = Runtime.getRuntime().freeMemory();
double nativeUsage = Debug.getNativeHeapAllocatedSize();
double memoryAvailable = Runtime.getRuntime().maxMemory() - (heapSize - heapRemaining) - nativeUsage;
return memoryAvailable;
}
Bitmap createImageTrialAndError(InputStream stream)
{
Bitmap image = null;
int dowsample = 1;
while(image == null)
{
try
{
Options opts = new Options();
opts.inSampleSize = downsample;
image = BitmapFactory.decodeStream(imgStream, null, opts);
}
catch (OutOfMemoryError ome)
{
downsample = downsample * 2;
Log.i("out of mem", "try using: " + downsample);
}
}
return image;
}
- The ideal solution would be if Bitmap had a Bitmap.drawBitmap(inputstream...) method. This would allow me to draw from the inputstream without having to allocate memory for the Bitmap. Alas, this is not an option.
- Scale the Bitmap according to memory available. This involves retrieving the width and height of the Bitmap, calculating the number of bytes Bitmap requires as
width * height * 4
, calculating the available memory, and thenBitmapFactory.Options.inSampleSize
such that the Bitmap will use less memory than what is available. However, this option fails because I have not been able to find a remotely reliable way to calculate the available memory. The getAvailableMemory() method below seems like it should work: it calculates available memory as maximum memory - memory used in the java heap - memory used in the native heap. Unfortunately, this formula gives very unreliable results. Mainly becauseDebug.getNativeHeapAllocatedSize()
does not appear to be an accurate representation of Bitmap memory usage. One obvious example of its inaccuracy is the NativeTest Activity, below. On my Samsung Galaxy tablet, the log statement outputted is: 3759416.0. 3.75 mb of native allocation for an empty activity, clearly not a reliable way to determine bitmap scaling. - Start with a scaling factor of one, and try to initialize the Bitmap; if the initialization fails due to memory, double the scaling factor and try again; and repeat this process until successful. This is illustrated by
createBitmapTrialAndError()
. This is actually surprisingly effective and not terribly slow. However, it is very undesirable because I use SoftReferences elsewhere in my application, and running out of memory forces the collection of these SoftReferences, which has a significant performance impact. It would be much more desirable to know the proper scaling factor initially, which would avoid the unnecessary collection of these SoftReferences. - My final solution seems a bit questionable. Basically it combines 2 & 3, but doesn’t use
Debug.getNativeAllocatedSize()
. Instead, I track my own Bitmap memory allocation, by tracking anywhere I allocate Bitmaps and tallying their memory usage, and then subtracting the memory usage of any Bitmaps that I recycle. I use this value in place of the nativeUsage in getAvailableMemory(), in order to calculate the proper scaling factor for the Bitmaps. And in the event that an Out Of Memory exception occurs in using this method, I use solution 3 as a fa开发者_高级运维llback way to calculate an acceptable scale. The obvious problem with this is the massive sketchyness of trying to track my own native memory usage but, to me, it seems to be the best solution.
The right approach is to decode for the size you need to show and subsample the image in tiles when you need to zoom.
There's a library that should do exactly this, check it out: https://github.com/davemorrissey/subsampling-scale-image-view
The problem of the android heap is, that you actually don't know how much of the heap you may use, because any background service could at any time ruin everything for you, if you overstep the memory constraints.
Why don't you just keep one Bitmap the size of the canvas you are always drawing on, and a stack of downsampled bitmaps? You could then render all image in native solution to your canvas, and always draw your downsampled bitmaps for any change that occurs. Once the change is over, or it is clear which image is most important, redraw that one in native resolution to the canvas (by accessing the disk again).
A simple and straightforward way is to use "inJustDecodeBounds" property of Options. Set this property to true for options object you created, then continue to decode stream.
The bitmap returned will be null, which means no memory is allocated to it, but you can read the dimensions of the bitmap and thus determine its size and adjust the inSampleSize ratio.
Later reset inJustDecodeBounds to false, by now you know the scale down factor, thus bitmap of required size can now be generated.
Hope this helps.
精彩评论