A riveting mystery: Android, Bitmaps (JPEGs), and the GridView of DEATH? - android

My app is exhibiting some very strange behavior. I have tried everything to resolve the issues I'm having, but, at this point, I'm at a total loss.
Some background information is in order (some details of the implementation that I'm providing might be overkill, but I want to make sure no stone is left unturned):
The app makes use of the device's camera, utilizing a SurfaceView with the necessary SurfaceHolder callbacks to open the device's Camera and take photos. That all works just fine.
The photos are then saved to the devices internal storage, using a FileOutPutStream. I then maintain a reference to the file path of each photo inside a plain old Java object, which is actually serialized and also written to the device's internal storage using an ObjectOutputStream.
The photos, which are in JPEG format, are later retrieved by grabbing the serialized ArrayList of my objects, grabbing the file path from those objects and passing it to my caching mechanism like so Bitmap bm = ImageCache.getSharedInstance().getBitmap(photoObject.filePath,getResources()); to return a Bitmap which I can use with an ImageView, whatever. I use this to for displaying the JPEGs in ListViews, GridViews, etc.
Here's the problem:
I have a View, which I've created programmatically, that has a sort of navigation bar at the top of it. All the navigation bar does is set the "main" part of my custom View class to display whatever other view I pass to it. So, my View class has a method like: setContent(View v) which simply removes all Views from the main part (or the area that I've carved out as the content area) and then adds the View you passed in to this content area.
So, if you click on nav button, it shows the photos along with some other data in a ListView, click another and it shows a GridView using the same photos. Right here is the problem. I like to call it THE GRIDVIEW OF DEATH. The GridView implementation is pretty straightforward and seems to work just fine when it's loaded.
mGalleryAdapter = new GalleryAdapter(MyActivity.this, list);
gridView.setAdapter(mGalleryAdapter);
Fine... but here's the issue. Once the GridView is loaded and displays the data from the adapter, if you try to start any other activity or navigate to any other are of the app, the activities in question seem to launch, but the Views inside of them never display. It is the strangest behavior. If you start with the ListView displaying and then navigate to some other tab (yea, I'm using tabs to sort of mimick iPhone style UITabs), everything is fine. The activity loads, the views display... everything is fine. But once you click to display the GridView, the app just seems to melt. Aaaaand, here's the catch. Whatever activity you've navigate to after displaying the GridView, will first display an empty, white activity (with no view). But if, for example, you long-press the home button and switch to some other running app and then come back to my app, the newly launched activities then display the views inside of them just fine and the app resumes normally. Strange indeed.
I must mention that I originally had the app crash several times because of "exceeding the VM budget." This was clearly related to issues with the JPEGs and their memory usage. From the stuff I've read online, the main fix to this is to crank up the BitmapFactory.Options sampleSize. Well, that's exactly what I've done. I've cranked it way up to reduce the image size:
public Bitmap getBitmap(String filepath, Resources resources){
SoftReference<Bitmap> reference;
if(resourcesLoaded.containsKey(filepath)){
reference = resourcesLoaded.get(filepath);
if(reference.get() == null){
int width = resources.getDisplayMetrics().widthPixels;
int height = (int) (42*((float)(((float)width)/320.0)));
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 28;
opts.outHeight = height;
opts.inTempStorage = new byte[16*1024];
opts.outWidth = width;
opts.inPurgeable = true;
Bitmap bit = BitmapFactory.decodeFile(filepath ,opts);
reference = new SoftReference<Bitmap>(bit);
resourcesLoaded.put(filepath, reference);
}
}else{
int width = resources.getDisplayMetrics().widthPixels;
int height = (int) (42*((float)(((float)width)/320.0)));
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.outHeight = height;
opts.outWidth = width;
opts.inTempStorage = new byte[16*1024];
opts.inSampleSize = 28;
opts.inPurgeable = true;
Bitmap bit = BitmapFactory.decodeFile(filepath ,opts);
reference = new SoftReference<Bitmap>(bit);
resourcesLoaded.put(filepath, reference);
}
return reference.get();
}
And, that's the weird thing, the app doesn't even crash now. Using caching and reducing the image size seems to have eliminated that crash (at least on some of the devices I've tested on), but this strange behavior, WHICH ONLY OCCURS ONCE LOADING THE GRIDVIEW OF DEATH and then navigating away from it, persists.
On a related note, I have used some third party app on my phone to see the current memory usage of my app and, at times, it creeps up to 100MB! Every time I take a photo with the app and save it to the internal storage, the memory usage seems to jump considerably (generally starts around 30-40MB and climbs from there). So clearly, there are memory issues going on. But again, the app won't crash with a "exceeded VM budget" error. On the other hand, it's hard to ignore the obvious memory issues with the app.
I fully realize that some of my implementation is not perfect, not super memory efficient, and that using singletons to hold the ArrayList of my objects, etc might not be the greatest way to go. But even still, this behavior seems inexplicable.
To summarize: the app will load completely blank activities ONCE AND ONLY ONCE you've clicked to display the images (that are stored as JPEGs on the device) in the GridView. If you never click to use the GridView, everything is fine.
What's with the Gridview of death?
(I'm happy to provide the code for any portions you think are relevant.)
UPDATE:
Thanks #CommonsWare for all your patience and advice. Really appreciate it and it's always nice to have the unofficial Android developer advocate on the case. Turns out that using Fragments did, in fact, solve all of my problems. For a moment, I thought I had fully implemented the Fragment solution, but quickly realized that I was still "setting the content" of some base View that was loaded in my Fragment to my GridView. By creating a new framgent containing the Grid View and loading it, all problems seem to be solved and the app is now stable!
UPDATE: Well, I spoke too soon :( Apparently this GridView is still causing problems. Now when I switch to a new tab, I can see that the button I'm using for the tab was clicked, but when it calsl setCurrentTab() nothing happens. App remains frozen in the GridView screen.

Related

Erratic errors loading image with Texture2d.LoadImage(byte[]) on Android devices

I have an app which has to create Sprite-instances on the fly based on data contained in byte arrays (PNGs and JPGs). The following code is used to create the sprites:
Texture2D texture = new Texture2D(2, 2, TextureFormat.RGBA32,false,false);
texture.LoadImage(data);
Vector2 pivot = new Vector2(0.5f, 0.5f);
Rect tRect = new Rect(0, 0, texture.width, texture.height);
return Sprite.Create(texture, tRect, pivot);
This works fine, however, depending on the device and the size of the images, after a random number of images, the app freezes and then will be shut down by the OS. Its always another image, which fails. Also, the data source is irrelevant.
Looking into the logs of the app via adb shows nothing. If I write to the debug log, I can see, that the last statement which gets called is texture.LoadImage. However, there is no exception or another information about the error. Catching the exception does also not work.
The error does not occur in the editor. The error occurs on the android devices (2) in development build and in production build.
Searching the web, I found the below entry, which states the very same problem, but no solution has been posted (they circled around the www-part, however the problem is not with that):
https://forum.unity.com/threads/android-crash-when-using-multiple-www.483941/
UPDATE
One interesting finding is, that if I set the markNonReadable-Parameter of the texture.LoadImage() method to true, the error occurs less frequently, but still is there.
texture.LoadImage(data,true);
Textures are not garbage collected. So if you create a texture using new Texture then you need to destroy the texture with Destroy(texture) when you no longer need it. I believe Sprite object also needs to be destroyed.
In your case, textures that were loaded stayed in memory until Android OS closed your app because of memory pressure.
UnloadUnusedAssets() should also destroy all the textures and sprites that are no longer referenced, but it takes a lot of time (about 1 second), so it only makes sense to call that when changing scenes.

Android cwac-camera to take multiple photos?

The title may be unclear, but I'm using this awesome library by CommonsWare(nice meeting you at DroidCon btw) to deal with the notorious issues with Android's fragmented camera api.
I want to take 5 photos, or frames..but not simultaneously. Each frame should capture another shot a few milliseconds apart, or presumably after the previous photo has been successfully captured. Can this be done?
I'm following the standalone implementation in the demos, and simply taking a photo using
mCapture.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
try {
takePicture(true, false);
}catch(Exception e){
e.printStackTrace();
}
}
});
Passing in true to takePicture() because I will need the resulting Bitmap. I also disabled single shot mode since I will want to take another photo right after the previous has be snapped, and the preview is resumed
By default, the result of taking a picture is to return the
CameraFragment to preview mode, ready to take the next picture.
If, instead, you only need the one picture, or you want to send the
user to some other bit of UI first and do not want preview to start up
again right away, override useSingleShotMode() in your CameraHost to
return true. Or, call useSingleShotMode() on your
SimpleCameraHost.Builder, passing in a boolean to use by default. Or,
call useSingleShotMode() on your PictureTransaction, to control this
for an individual picture.
I was looking for a callback like onPictureTaken() or something similar inside CameraHost, that would allow me to go ahead and snap another photo right away before releasing the camera, but I don't see anything like this. Anyone ever done something like this using this library? Can the illustious CommonsWare please shed some light on this as well(if you see this?)
Thank you!
Read past the quoted paragraph to the next one, which begins with:
You will then probably want to use your own saveImage() implementation in your CameraHost to do whatever you want instead of restarting the preview. For example, you could start another activity to do something with the image.
If what you want is possible, you would call takePicture() again in saveImage() of your CameraHost, in addition to doing something with the image you received.
However:
Even with large heap enabled, you may not have enough heap space for what you are trying to do. You may need to explicitly choose a lower resolution image for the pictures.
This isn't exactly within the scope of the library. It may work, and I don't have a problem with it working, but being able to take N pictures in M seconds isn't part of the library's itch that I am (very very slowly) scratching. In particular, I don't think I have tested taking a picture with the preview already off, and there may be some issues in my code in that area.
Long-term, you may be better served with preview frame processing, rather than actually taking pictures.

Android Bitmap too heavy error

I have the following problem that some of you must know on my android app :
3288-byte external allocation too large for this process.
Out of memory: Heap Size=5959KB, Allocated=3922KB, Bitmap Size=18614KB
VM won't let us allocate 3288 bytes
Facts :
I'm creating a bitmap of the screen (so quite huge) and I manipulate it (changing size etc ...) for doing a flipping page animation.
It crashes only on a desire HTC : on galaxy s2 and kindle fire, no problems.
I'm already desallocating the current Bitmap everytime I create a new one with the following code :
Bitmap old = this.bitmap;
this.bitmap = bitmap;
this.invalidate();
if(old != null)
old.recycle();
I also tryied to call this function :
public void recycle() {
if (this.bitmap!=null)
this.bitmap.recycle();
System.gc();
Runtime.getRuntime().gc();
}
Severals time in my code, and sometimes it gets slightly better (like it crashes a little later), but that's still not good.
I spent a lot of time on this problem, and I don't really get how to fix it. It's like on forum there is a lot of misinformation, so I'm kinda lost.
Thanks, ask for more precision.
Edit :
Here is a code called a lot :
//set the foreground image with the current day
Bitmap b = Bitmap.createBitmap(visibleLayout.getWidth(), visibleLayout.getHeight(),Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
visibleLayout.draw(c);
viewBitmapNext.setBitmap(b);
viewBitmapNext.setVisibility(View.VISIBLE);
Where viewBitmapNext is an overwritted element of the View class. The setBitmap function is described above.
About the resizement, I do this line of code :
viewBitmapPrevious.setLayoutParams(new RelativeLayout.LayoutParams((int) (iterator - ((totalWidth - iterator) - activity.getResources().getDimension(R.dimen.margin_right))/2), RelativeLayout.LayoutParams.WRAP_CONTENT));
Again, tell me if you you want to know more.
I found out what was the problem. It will not be interresting for anyone, because it's a dumb error closely related to my project, but I say it anyway.
I actually had 2 errors :
one loop creating elements infinitly.
Two big pictures I put as a background after a certain action performed on a cheap phone ( I'm still on it but it should be easy to solve). I'll edit this answer when it's done.
To everyone that helped me, you couldn't find out the problem's solution (wasn't related to the bitmap-screen I do), but still it was helpful on it's way.
Thanks.

Android - Picture callback data returns an image that it black or jumbled

So I am using the Android camera to take pictures within an Android app. About 90% of my users have no issues, but the other 10% get a picture that returns pure black or a weird jumbling of pixels.
Has anyone else seen this behavior? or have any ideas why it happens?
Examples:
Black:
Jumbled:
I've had similar problems like this.
The problem in short is: Missing data.
It occurs to a Bitmap/Stream if the datastream was interrupted for too long or it is accidentally no more available.
Another example where it may occur: Downloading and uploading images.
If the user disables all of a sudden Wifi/mobile network no more data can be transmitted.
You end up in a splattered image.
The image will appear/view okay(where okay means black/splattered, it's still viewable!) but is invalid internally (missing or corrupted information).
If it's not too critical you can try to move all the data into a Bitmap object (BitmapFactory.decode*) and test if the returned Bitmap is null. If yes the data possibly is corrupted.
This is just solving the consequences of the problem, as you can guess.
The better way would be to take the problem on the foot:
Ensure a good connection to your data source (Large enough, stout buffer).
Try to avoid unneccesary casts (e.g. from char to int)
Use the correct type of buffers (Either Reader/Writer for character streams or InputStream/OutputStream for byte streams).
From android 4.0 they put hardwareAcceleration set to true as default in the manifest. Hardwareaccelerated canvas does not support Pictures and you will get a black screen...
Please also check that whether you use BitmapFactory.Options object for generating the bitmap or not. Because few methods of this object also makes the bitmap corrupted.

java.lang.OutOfMemoryError: bitmap size exceeds VM budget - Android

I developed an application that uses lots of images on Android.
The app runs once, fills the information on the screen (Layouts, Listviews, Textviews, ImageViews, etc) and user reads the information.
There is no animation, no special effects or anything that can fill the memory.
Sometimes the drawables can change. Some are android resources and some are files saved in a folder in the SDCARD.
Then the user quits (the onDestroy method is executed and app stays in memory by the VM ) and then at some point the user enters again.
Each time the user enters to the app, I can see the memory growing more and more until user gets the java.lang.OutOfMemoryError.
So what is the best/correct way to handle many images?
Should I put them in static methods so they are not loaded all the time?
Do I have to clean the layout or the images used in the layout in a special way?
One of the most common errors that I found developing Android Apps is the “java.lang.OutOfMemoryError: Bitmap Size Exceeds VM Budget” error. I found this error frequently on activities using lots of bitmaps after changing orientation: the Activity is destroyed, created again and the layouts are “inflated” from the XML consuming the VM memory available for bitmaps.
Bitmaps on the previous activity layout are not properly de-allocated by the garbage collector because they have crossed references to their activity. After many experiments I found a quite good solution for this problem.
First, set the “id” attribute on the parent view of your XML layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/RootView"
>
...
Then, on the onDestroy() method of your Activity, call the unbindDrawables() method passing a reference to the parent View and then do a System.gc().
#Override
protected void onDestroy() {
super.onDestroy();
unbindDrawables(findViewById(R.id.RootView));
System.gc();
}
private void unbindDrawables(View view) {
if (view.getBackground() != null) {
view.getBackground().setCallback(null);
}
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
unbindDrawables(((ViewGroup) view).getChildAt(i));
}
((ViewGroup) view).removeAllViews();
}
}
This unbindDrawables() method explores the view tree recursively and:
Removes callbacks on all the background drawables
Removes children on every viewgroup
It sounds like you have a memory leak. The problem isn't handling many images, it's that your images aren't getting deallocated when your activity is destroyed.
It's difficult to say why this is without looking at your code. However, this article has some tips that might help:
http://android-developers.blogspot.de/2009/01/avoiding-memory-leaks.html
In particular, using static variables is likely to make things worse, not better. You might need to add code that removes callbacks when your application redraws -- but again, there's not enough information here to say for sure.
To avoid this problem you can use native method Bitmap.recycle() before null-ing Bitmap object (or setting another value). Example:
public final void setMyBitmap(Bitmap bitmap) {
if (this.myBitmap != null) {
this.myBitmap.recycle();
}
this.myBitmap = bitmap;
}
And next you can change myBitmap w/o calling System.gc() like:
setMyBitmap(null);
setMyBitmap(anotherBitmap);
I've ran into this exact problem. The heap is pretty small so these images can get out of control rather quickly in regards to memory. One way is to give the garbage collector a hint to collect memory on a bitmap by calling its recycle method.
Also, the onDestroy method is not guaranteed to get called. You may want to move this logic/clean up into the onPause activity. Check out the Activity Lifecycle diagram/table on this page for more info.
This explanation might help:
http://code.google.com/p/android/issues/detail?id=8488#c80
"Fast Tips:
1) NEVER call System.gc() yourself. This has been propagated as a fix here, and it doesn't work. Do not do it. If you noticed in my explanation, before getting an OutOfMemoryError, the JVM already runs a garbage collection so there is no reason to do one again (its slowing your program down). Doing one at the end of your activity is just covering up the problem. It may causes the bitmap to be put on the finalizer queue faster, but there is no reason you couldn't have simply called recycle on each bitmap instead.
2) Always call recycle() on bitmaps you don't need anymore. At the very least, in the onDestroy of your activity go through and recycle all the bitmaps you were using. Also, if you want the bitmap instances to be collected from the dalvik heap faster, it doesn't hurt to clear any references to the bitmap.
3) Calling recycle() and then System.gc() still might not remove the bitmap from the Dalvik heap. DO NOT BE CONCERNED about this. recycle() did its job and freed the native memory, it will just take some time to go through the steps I outlined earlier to actually remove the bitmap from the Dalvik heap. This is NOT a big deal because the large chunk of native memory is already free!
4) Always assume there is a bug in the framework last. Dalvik is doing exactly what its supposed to do. It may not be what you expect or what you want, but its how it works. "
I had the exact same problem. After a few testing I found that this error is appearing for large image scaling. I reduced the image scaling and the problem disappeared.
P.S. At first I tried to reduce the image size without scaling the image down. That did not stop the error.
Following points really helped me a lot. There might be other points too, but these are very crucial:
Use application context(instead of activity.this) where ever possible.
Stop and release your threads in onPause() method of activity
Release your views / callbacks in onDestroy() method of activity
I suggest a convenient way to solve this problem.
Just assign the attribute "android:configChanges" value as followed in the Mainfest.xml for your errored activity.
like this:
<activity android:name=".main.MainActivity"
android:label="mainActivity"
android:configChanges="orientation|keyboardHidden|navigation">
</activity>
the first solution I gave out had really reduced the frequency of OOM error to a low level. But, it did not solve the problem totally. And then I will give out the 2nd solution:
As the OOM detailed, I have used too much runtime memory. So, I reduce the picture size in ~/res/drawable of my project. Such as an overqualified picture which has a resolution of 128X128, could be resized to 64x64 which would also be suitable for my application. And after I did so with a pile of pictures, the OOM error doesn't occur again.
I too am frustrated by the outofmemory bug. And yes, I too found that this error pops up a lot when scaling images. At first I tried creating image sizes for all densities, but I found this substantially increased the size of my app. So I'm now just using one image for all densities and scaling my images.
My application would throw an outofmemory error whenever the user went from one activity to another. Setting my drawables to null and calling System.gc() didn't work, neither did recycling my bitmapDrawables with getBitMap().recycle(). Android would continue to throw the outofmemory error with the first approach, and it would throw a canvas error message whenever it tried using a recycled bitmap with the second approach.
I took an even third approach. I set all views to null and the background to black. I do this cleanup in my onStop() method. This is the method that gets called as soon as the activity is no longer visible. If you do it in the onPause() method, users will see a black background. Not ideal. As for doing it in the onDestroy() method, there is no guarantee that it will get called.
To prevent a black screen from occurring if the user presses the back button on the device, I reload the activity in the onRestart() method by calling the startActivity(getIntent()) and then finish() methods.
Note: it's not really necessary to change the background to black.
The BitmapFactory.decode* methods, discussed in the Load Large Bitmaps Efficiently lesson, should not be executed on the main UI thread if the source data is read from disk or a network location (or really any source other than memory). The time this data takes to load is unpredictable and depends on a variety of factors (speed of reading from disk or network, size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags your application as non-responsive and the user has the option of closing it (see Designing for Responsiveness for more information).
Well I've tried everything I found on the internet and none of them worked. Calling System.gc() only drags down the speed of app. Recycling bitmaps in onDestroy didn't work for me too.
The only thing that works now is to have a static list of all the bitmap so that the bitmaps survive after a restart. And just use the saved bitmaps instead of creating new ones every time the activity if restarted.
In my case the code looks like this:
private static BitmapDrawable currentBGDrawable;
if (new File(uriString).exists()) {
if (!uriString.equals(currentBGUri)) {
freeBackground();
bg = BitmapFactory.decodeFile(uriString);
currentBGUri = uriString;
bgDrawable = new BitmapDrawable(bg);
currentBGDrawable = bgDrawable;
} else {
bgDrawable = currentBGDrawable;
}
}
I had the same problem just with switching the background images with reasonable sizes. I got better results with setting the ImageView to null before putting in a new picture.
ImageView ivBg = (ImageView) findViewById(R.id.main_backgroundImage);
ivBg.setImageDrawable(null);
ivBg.setImageDrawable(getResources().getDrawable(R.drawable.new_picture));
FWIW, here's a lightweight bitmap-cache I coded and have used for a few months. It's not all-the-bells-and-whistles, so read the code before you use it.
/**
* Lightweight cache for Bitmap objects.
*
* There is no thread-safety built into this class.
*
* Note: you may wish to create bitmaps using the application-context, rather than the activity-context.
* I believe the activity-context has a reference to the Activity object.
* So for as long as the bitmap exists, it will have an indirect link to the activity,
* and prevent the garbaage collector from disposing the activity object, leading to memory leaks.
*/
public class BitmapCache {
private Hashtable<String,ArrayList<Bitmap>> hashtable = new Hashtable<String, ArrayList<Bitmap>>();
private StringBuilder sb = new StringBuilder();
public BitmapCache() {
}
/**
* A Bitmap with the given width and height will be returned.
* It is removed from the cache.
*
* An attempt is made to return the correct config, but for unusual configs (as at 30may13) this might not happen.
*
* Note that thread-safety is the caller's responsibility.
*/
public Bitmap get(int width, int height, Bitmap.Config config) {
String key = getKey(width, height, config);
ArrayList<Bitmap> list = getList(key);
int listSize = list.size();
if (listSize>0) {
return list.remove(listSize-1);
} else {
try {
return Bitmap.createBitmap(width, height, config);
} catch (RuntimeException e) {
// TODO: Test appendHockeyApp() works.
App.appendHockeyApp("BitmapCache has "+hashtable.size()+":"+listSize+" request "+width+"x"+height);
throw e ;
}
}
}
/**
* Puts a Bitmap object into the cache.
*
* Note that thread-safety is the caller's responsibility.
*/
public void put(Bitmap bitmap) {
if (bitmap==null) return ;
String key = getKey(bitmap);
ArrayList<Bitmap> list = getList(key);
list.add(bitmap);
}
private ArrayList<Bitmap> getList(String key) {
ArrayList<Bitmap> list = hashtable.get(key);
if (list==null) {
list = new ArrayList<Bitmap>();
hashtable.put(key, list);
}
return list;
}
private String getKey(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Config config = bitmap.getConfig();
return getKey(width, height, config);
}
private String getKey(int width, int height, Config config) {
sb.setLength(0);
sb.append(width);
sb.append("x");
sb.append(height);
sb.append(" ");
switch (config) {
case ALPHA_8:
sb.append("ALPHA_8");
break;
case ARGB_4444:
sb.append("ARGB_4444");
break;
case ARGB_8888:
sb.append("ARGB_8888");
break;
case RGB_565:
sb.append("RGB_565");
break;
default:
sb.append("unknown");
break;
}
return sb.toString();
}
}

Categories

Resources