This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
OutOfMemory Exception when handling images
I have a Bitmap creation within a AsyncTask, which goes like this:
private WeakReference<Bitmap> myBitmap;
private WeakReference<Bitmap> endResultBitmap;
private ImageView imv;
...
private class SendBitmap extends AsyncTask<Integer, Void, Bitmap> {
public SendBitmap(Bitmap bitmap) {
myBitmap = new WeakReference<Bitmap>(bitmap);
}
#Override
protected Bitmap doInBackground(Integer... params) {
Bitmap bm = null;
bm = getBitmapFromNet(params[0]);
return bm;
}
And then I want to create Bitmap on which the received Bitmap would appear twice (one next to another)
protected void onPostExecute(Bitmap result) {
endResultBitmap = new WeakReference<Bitmap>(Bitmap.createBitmap(result.getWidth() * 2, result.getHeight(), result.getConf()));
Canvas canvas = new Canvas(endResultBitmap.get());
canvas.drawBitmap(result, 0, 0, null);
canvas.drawBitmap(result, result.getWidth(), 0, null);
imv.setImageBitmap(endResultBitmap);
}
then I have my onCancelled() method:
#Override
protected void onCancelled(Bitmap result) {
if(endResultBitmap!=null) {
endResultBitmap.recycle();
endResultBitmap = null;
}
}
The thing is that if I execute this AsyncTask couple of times, the heap grows as mad.
I execute the AsyncTask when a button is pressed, but at first I do:
public void onClicked(View v) {
if(asyncTaskInstance != null)
asyncTaskInstance.cancel();
asynctaskInstance.execute(2);
}
But again, the Heap grows as mad and at some point it will crash with OutOfMemoryError.
Any idea? Do I have something wrong in my Design of the task?
Android has some memory limits for apps (16 MB if i remember correctly), and that image is too big in an uncompressed format. Theres some interesting discussion in this question.
To solve it, there are afaik only two ways:
1. Reduce the size of the image to consume less memory
2. Load the image in native code using the NDK.
To 1.: I don't know what exactly you are trying to do and can't tell if thats really a viable option. If it is, you may want download the image file from the net and open it with the BitmapFactory class. There are some static functions that take an BitmapFactory.Options object. Use inSampleSize from this Options object to reduce the image size by a certain factor at loading time (inSampleSize should be a power of two by the way).
To 2.: The memory limits that I mentioned above don't apply to native code. So you may be able to load the image and display in a native way. I don't have any experience with that, I just know that it's possible, but googling around should turn up a few results.
Related
Trying to perform some printings using JasperReport.
JasperReport provides a function to print a document to a BufferedImage, which i convert to a WritableImage to display it on an ImageView object (as shown in the code below).
By time i get a Java Heap space out of memory exception in the getImage(int pageNumber) function. My guess is that the old references to the images are not freed.
Is it possible to fix that ?
private void viewPage(int pageNumber) throws JRException {
this.resultViewer.setFitHeight(this.imageHeight * this.zoomFactor);
this.resultViewer.setFitWidth(this.imageWidth * this.zoomFactor);
this.resultViewer.setImage(this.getImage(pageNumber));
}
#FXML
private ImageView resultViewer;
private WritableImage getImage(int pageNumber) throws JRException {
return SwingFXUtils
.toFXImage((BufferedImage) JasperPrintManager.printPageToImage(this.jasperPrint, pageNumber, 2), null);
}
I fixed the problem by caling flush() function on the last displayed BufferedImage before displaying a next one
here's the interface:
public interface BitmapCallBackInterface {
void onCallBack(Bitmap bitmap);
}
and here's the method and calling it:
public void downloadImagesFromFireStorage(String imgName, final BitmapCallBackInterface bitmapCallBackInterface) {
StorageReference storageRef = FirebaseStorage.getInstance()
.getReferenceFromUrl("gs://fake-mc-app.appspot.com").child("imgs").child(imgName + ".png");
final long ONE_MEGABYTE = 1024 * 1024;
storageRef.getBytes(ONE_MEGABYTE).addOnSuccessListener(new OnSuccessListener<byte[]>() {
#Override
public void onSuccess(byte[] bytes) {
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
bitmapCallBackInterface.onCallBack(bitmap);
}
});
}
Edit
call it:
for (i = 0; i < nameList.size(); i++) {
String couponName = nameList.get(i);
storageHandler.downloadImagesFromFireStorage(imgName, new BitmapCallBackInterface() {
#Override
public void onCallBack(Bitmap bitmap) {
insertImg(couponName, bitmap); // store image into SQLite
}
});
}
getDataFromSQLite();
// and then display Bitmaps to the a list of ImageView
Edit
someone had taught me that the thread will continue after receive callback, so i write a for loop to download all the images from firebase-storage, store them all into SQlite database, then retrieve them out and display to ImageView. But it seems like the thread continues before the image download complete? how can i fix it? Maybe i am misunderstanding what i had learned?
what i exectly want to do is, download several images, after all downloading complete, refresh the listview
First, I hope you are using a CursorAdapter for your listview, and not pulling data from sqlite, to an arraylist, to an ArrayAdapter, etc.
Secondly, you ideally shouldn't be storing listview images as part of the database. You can store firebase links, and then in the adapter, you will request the image content (using an image loading library such as Glide, see below)
In any case, what you need to do is notify the adapter dataSetChanged for every call to onCallBack, after your image is inserted, then the list will update after the image is in sqlite.
If you want to wait for all images to be inserted before you notify the adapter, you can check the inserted image position against nameList.size()
You might also want to consider just using the OnSuccessListener<byte[]> interface for storing bytes into sqlite as a BLOB, not necessarily a Bitmap object. Under that scenario, you don't need that extra interface
However, the encouraged pattern on the Android documentation for image loading in general is to use Glide
Getting Image from Firebase Storage using Glide
I have come across a very interisting issue. I am using the bellow code to load bitmaps using picasso:
final Target target = new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
// loaded bitmap is here (bitmap)
Log.i(TAG, "bitmapLoaded");
imageView.setImageBitmap(bitmap);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
Log.i(TAG, "bitmapFailed");
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
};
imageView.setTag(target);
Picasso.with(this)
.load(photoUrl)
.into(target);
I know a lot of questions have being asked about picasso not loading images due to weak reference but I don't think that's the case, since I have followed the solutions suggested in many topics to reference target like above.
In my program, I use this same code in 3 different classes and in 3 distinct moments. What I have noticed is that whenever I call this method for the first time it doesn't work, but for the next times it works, doesn't matter which of the 3 calls is being used. I can say that because I print different messages to the log from this 3 different methods.
Any thoughts about what is going on or have I missed something?
Thank you in advance.
Try to achieve this with using async approach.
Picasso.with(context).load(URL).into(profile, new Callback() {
#Override
public void onSuccess() {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {//Use your "bitmap" here
Bitmap innerBitmap = ((BitmapDrawable) profile.getDrawable()).getBitmap();
}
}, 100);
}
Also you may try to use Glide https://github.com/bumptech/glide
Problem: The problem is Picasso holds a weak reference to the target class and it got GARBAGE COLLECTED.
Solution: Convert it to class field instead of using it as local reference.
The Picasso library allows one to load an image easily like:
Picasso.with(context).load(url).into(imageview);
The API also allows to specify an error image. But what can I do if I want the library to first try three or four different URLs before giving up and displaying the error image? Ideally these images would be tried sequentially, falling back to the next one if the previous wasn't loaded.
Natively there's no API for such functionality. But with some clever coded Picasso.Target you can easily achieve such functionality.
I'll add here a quick hack-untested code that should give you a rought idea on what to look for. You'll have to test and maybe fine tune, but that should be pretty OK.
private static final List<MultiFallBackTarget> TARGETS = new ArrayList<MultiFallBackTarget>();
public static class MultiFallBackTarget implements Picasso.Target {
private WeakReference<ImageView> weakImage;
private List<String> fallbacks;
public MultiFallBackTarget(ImageView image){
weakImage = new WeakReference<>(image);
fallbacks = new ArrayList<String>();
TARGETS.add(this);
}
public void addFallback(String fallbackUrl){
fallbacks.add(fallbackUrl);
}
public void onBitmapLoaded(Bitmap bitmap, LoadedFrom from){
removeSelf();
ImageView image = weakImage.get();
if(image == null) return;
image.setImageBitmap(bitmap);
}
public void onBitmapFailed(Drawable errorDrawable){
ImageView image = weakImage.get();
if(image == null) {
removeSelf();
return;
}
if(fallbacks.size() > 0){
String nextUrl = fallbacks.remove(0);
// here you call picasso again
Picasso.with(image.getContext()).load(nextUrl).into(this);
} else {
removeSelf();
}
}
public void onPrepareLoad(Drawable placeHolderDrawable){}
private void removeSelf(){
TARGETS.remove(this);
}
}
Remember that Picasso does not hold strong references to the Target you put inside into(object). That means, internally Picasso uses WeakReference to that.
So that means that you need that self reference in TARGETS to keep reference of all MultiFallBackTarget you create and allow them to self-remove when their job is done.
Can anybody point me at resources with best practice on clearing sensitive runtime images?
Consider a scenario where a sensitive image is downloaded from a server at runtime, loaded into a Bitmap object, and is then displayed in an ImageView in a Fragment.
When the user leaves that screen, or the app is exited/put in the background for a long time, then I want to clear that image data so that it isn't easy to recover.
I was wondering if there is a reliable way to zero out the bitmap data as soon as the Fragment containing the image is destroyed?
This feel tricky to me, as Bitmaps are usually returned as immutable objects, e.g. BitmapFactory.decodeByteArray says:
Decode an immutable bitmap from the specified byte array.
Presumably I would have to create a mutable Bitmap and then copy over its data?
It looks like recycle() won't help me, as that will just mark the data as available for garbage collection, it won't wipe it.
You can simply clear the Bitmap using
someBitmap.eraseColor(android.graphics.Color.TRANSPARENT);
It will fill the bitmap with TRANSPARENT color and erase everything on it.
However, if you have no any references to your bitmap (e.g. you've set null to ImageView that was containing your Bitmap like this
someImageView.setDrawable(null)
the garbage collector should collect it shortly.
Thanks to #IlyaGulya for the eraseColor suggestion. Below is the code I've written so far.
Creating the mutable Bitmap:
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inMutable = true;
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, bitmapOptions);
The code to clear the image data in my Fragment (I save the BitmapDrawable into a myBitmapDrawable field when the Fragment receives it):
#Override
public void onDestroy() {
myImageView.setImageDrawable(null);
try {
MyUtils.zeroOutBitmapData(myBitmapDrawable.getBitmap());
} catch (Exception e) {
loggingUtil.logHandledException(e);
}
super.onDestroy();
}
My utility for zeroing out a Bitmap:
public static void zeroOutBitmapData(Bitmap mutableBitmap) {
if (mutableBitmap.isMutable()) {
mutableBitmap.eraseColor(android.graphics.Color.TRANSPARENT);
} else {
logger.error("Expected bitmap to be mutable");
}
}
...and here is a unit test (well, an ApplicationTestCase since I want to test with a real Bitmap):
public void testZeroOutBitmap() throws Exception {
Resources resources = getContext().getResources();
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
bitmapOptions.inMutable = true;
Bitmap mutableBitmap = BitmapFactory.decodeResource(resources, R.drawable.an_example_image);
// Assert that some pixels start out with non zero colors
assertEquals(-789517, mutableBitmap.getPixel(0, 0));
assertEquals(-723724, mutableBitmap.getPixel(10, 10));
MyUtils.zeroOutBitmapData(mutableBitmap);
// Check that pixel data has now been cleared out
assertEquals(android.graphics.Color.TRANSPARENT, mutableBitmap.getPixel(0, 0));
assertEquals(android.graphics.Color.TRANSPARENT, mutableBitmap.getPixel(10, 10));
}