Android - how to avoid memory over load using bitmaps? - android

My application is using bitmaps and every time the user come to the specific activity where it shows an image the second time it stops working.
Bitmap bm = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+"//Pics/"Image.jpg");
I have tried using things like...
BitmapFactory.Options options = new BitmapFactory.Options();
options.inTempStorage = new byte[16*1024];
Not sure what to set it too. But this doesnt help. Once the user leaves this activity is there not a way to clear the bitmap etc? thanks

Call Bitmap.recycle() when you are done using the Bitmap to free the memory.

Besides using Bitmap.recycle() as suggested (which is not suitable for all situations and it's a pain in the neck to be asking: "do I still need this bitmap?"), I always use this technique which works really fine:
// 1. create a cache map
private WeakHashMap<String, SoftReference<Bitmap>> mCache;
As you can see, it's a hash map of WeakReferences with a SoftReference as the values.
//2. when you need a bitmap, ask for it:
public Bitmap get(String key){
if( key == null ){
return null;
}
if( mCache.containsKey(key) ){
SoftReference<Bitmap> reference = mCache.get(key);
Bitmap bitmap = reference.get();
if( bitmap != null ){
return bitmap;
}
return decodeFile(key);
}
// the key does not exists so it could be that the
// file is not downloaded or decoded yet...
File file = new File(Environment.getExternalStorageDirectory(), key);
if( file.exists() ){
return decodeFile(key);
} else{
throw new RuntimeException("Boooom!");
}
}
This will check the cache map. If the file was already decoded, it will be returned; otherwise it will be decoded and cached.
//3. the decode file will look like this in your case
private Bitmap decodeFile(String key) {
Bitmap bitmap = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory()+"//Pics/"+key);
mCache.put(key, new SoftReference<Bitmap>(bitmap));
return bitmap;
}
Working with soft references is nice because you shift the responsibility of removing bitmaps from memory to the OS.

Be aware.
When we think about softreferences we think that the OS will remove the softreferenced objects from memrory before reporting an outofmemory exception.
In android this is not always true. I had to implement my own caching system for images and I can assure you softreferenced objects were not removed from memory when memory was almost full.
Finally I had to switch to hard references (the normal ones) but used android.support.v4.util.LruCache for managing the cached objects. I would call recycle on the onRemoved callback from the lru cache. Its definetely more convenient.
Cheers.

Related

Displaying Coverart (from id3) in Recyclerview in background

I am working on an app that uses a Recyclerview to display mp3 files, providing its cover art image along with other info. It works but is slow once it starts dealing with a dozen or more cover arts to retrieve, as I am currently doing this from the id3 on the main thread, which I know is not a good idea.
Ideally, I would work with placeholders so that the images can be added as they become available. I've been looking into moving the retrieval to a background thread and have looked at different options: AsyncTask, Service, WorkManager. AsyncTask seems not to be the way to go as I face memory leaks (I need context to retrieve the cover art through MetadataRetriever). So I am leaning away from that. Yet I am struggling to figure out which approach is best in my case.
From what I understand I need to find an approach that allows multithreading and also a means to cancel the retrieval in case the user has already moved on (scrolling or navigating away). I am already using Glide, which I understand should help with the caching.
I know I could rework the whole approach and provide the cover art as images separately, but that seems a last resort to me, as I would rather not weigh down the app with even more data.
The current version of the app is here (please note it will not run as I cannot openly divulge certain aspects). I am retrieving the cover art as follows (on the main thread):
static public Bitmap getCoverArt(Uri medUri, Context ctxt) {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(ctxt, medUri);
byte[] data = mmr.getEmbeddedPicture();
if (data != null) {
return BitmapFactory.decodeByteArray(data, 0, data.length);
} else {
return null;
}
}
I've found many examples with AsyncTask or just keeping the MetaDataRetriever on the main thread, but have yet to find an example that enables a dozen or more cover arts to be retrieved without slowing down the main thread. I would appreciate any help and pointers.
It turns out it does work with AsyncTask, as long as it is not a class onto itself but setup and called from a class with context. Here is a whittled down version of my approach (I am calling this from within my Adapter.):
//set up titles and placeholder image so we needn't wait on the image to load
titleTv.setText(selectedMed.getTitle());
subtitleTv.setText(selectedMed.getSubtitle());
imageIv.setImageResource(R.drawable.ic_launcher_foreground);
imageIv.setAlpha((float) 0.2);
final long[] duration = new long[1];
//a Caching system that helps reduce the amount of loading needed. See: https://github.com/cbonan/BitmapFun?files=1
if (lruCacheManager.getBitmapFromMemCache(selectedMed.getId() + position) != null) {
//is there an earlier cached image to reuse? imageIv.setImageBitmap(lruCacheManager.getBitmapFromMemCache(selectedMed.getId() + position));
imageIv.setAlpha((float) 1.0);
titleTv.setVisibility(View.GONE);
subtitleTv.setVisibility(View.GONE);
} else {
//time to load and show the image. For good measure, the duration is also queried, as this also needs the setDataSource which causes slow down
new AsyncTask<Uri, Void, Bitmap>() {
#Override
protected Bitmap doInBackground(Uri... uris) {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
mmr.setDataSource(ctxt, medUri);
byte[] data = mmr.getEmbeddedPicture();
Log.v(TAG, "async data: " + Arrays.toString(data));
String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
duration[0] = Long.parseLong(durationStr);
if (data != null) {
InputStream is = new ByteArrayInputStream(mmr.getEmbeddedPicture());
return BitmapFactory.decodeStream(is);
} else {
return null;
}
}
#Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
durationTv.setVisibility(View.VISIBLE);
durationTv.setText(getDisplayTime(duration[0], false));
if (bitmap != null) {
imageIv.setImageBitmap(bitmap);
imageIv.setAlpha((float) 1.0);
titleTv.setVisibility(View.GONE);
subtitleTv.setVisibility(View.GONE);
} else {
titleTv.setVisibility(View.VISIBLE);
subtitleTv.setVisibility(View.VISIBLE);
}
lruCacheManager.addBitmapToMemCache(bitmap, selectedMed.getId() + position);
}
}.execute(medUri);
}
I have tried working with Glide for the caching, but I haven't been able to link the showing/hiding of the TextViews to whether there is a bitmap. In a way though, this is sleeker as I don't need to load the bulk of the Glide-library. So I am happy with this for now.

Android ImageManager Google Game Services

We are trying to download avatars from Google Play Game Services at URIs like content :/ / ... through android-class ImageManager, but for unknown reasons, the class stops loading on same avatars, simply ceases to come in response OnImageLoadedListener. Each picture is a separate instance of the manager.
All avatars are valid (for the tests, each at least once loaded) no exceptions catch, the context in create manager is valid, tried everything, from the current Activity to getApplicationContext().
Tried to make loading and asynchronously and synchronously.
What is remarkable: after the last loader's action is always line in log with garbage collector and then do not continue to load (the application continues its work), although all references to objects are stored.
More often than not finish loading the last avatar.
Has anyone encountered a similar problem? Appears on versions from 2.3 to 4.2
Code in the alternate boot:
static ImageManager imageManager = null;
static int id = 0;
static Uri uri = null;
...
// ...
// change id & uri
// ...
imageManager = ImageManager.create(context);
Log.v("app", "LoadImageFromGoogle Manager created for Image #" + id);
imageManager.loadImage(new OnImageLoadedListener() {
#Override
public void onImageLoaded(Uri arg0, Drawable arg1) {
try {
Log.v("app", "LoadImageFromGoogle Image loaded #" + id);
Bitmap bm = ((BitmapDrawable)arg1).getBitmap();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();
// ... some code
Log.v("app", "LoadImageFromGoogle Image added #" + id);
} catch (Exception e) {
Log.v("app", "LoadImageFromGoogle after loading Exception = " + e.toString());
}
}
}, uri, android.R.drawable.btn_star);
the last line in the log is always the case:
Log.v("app", "LoadImageFromGoogle Manager created for Image #" + id);
and the following message GC.
Hmm.... I've never seen this issue. Are you sure you are feeding it a valid URI? My first guess would be that it might be an off-by-one error somewhere in your code where you're passing an invalid URI the last time you call imageManager.loadImage(). I'd try making the above code print the URIs it's trying to load as well as the image numbers, to see if something weird comes up.
If not, can you make a simple repro case and post it to our public bug tracker?
http://code.google.com/p/play-games-platform
Thanks!
I had the same problem. It looks like garbage collector can "clean" the listener before it can retrieve the image. The way to solve the problem is to implement the ImageManager.OnImageLoadedListener directly on your activity and not creating a new object on the moment.
Here you can find a related issue on google code:
https://code.google.com/p/play-games-platform/issues/detail?id=14&can=1&sort=-status&colspec=ID%20Type%20Status%20Priority%20Platform%20Summary%20Stars
Never is to late ;)

Android: Bitmap Decode java.lang.OutofMemoryError error

My current android applications main functionality is to pull the frames (10fps) from a recorded video using the built in camera. Whenever the user selects the video I call my class "FrameCollector" which loops through the video and pulls the frames out and stores them into an ArrayList of Bitmaps. Having this work well for a number of days had me thinking that I was on the right track, but now I'm getting the dreaded "java.lang.OutofMemoryError"
My code is as follows:
Here it is setting the MediaMetaDataRetriever to select 10 frames per second from the video path which was passed from the main class
public class FrameCollector {
MediaMetadataRetriever _mmr;
double _fps;
double _duration;
long _counter = 0;
long _incrementer;
public FrameCollector(String path, Context context)
{
try
{
_mmr = new MediaMetadataRetriever();
_mmr.setDataSource(path);
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
String fpsString = pref.getString("prefFPS", "10");
_fps = Double.parseDouble(fpsString);
_incrementer = (long) (1000000 / _fps);
String stringDuration = _mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
_duration = Double.parseDouble(stringDuration) * 1000;
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
The below method is then adding the frame at that time to a bitmap and adding the bitmap to an arraylist of bitmaps "bitFrames"
public ArrayList<Bitmap> getBitmaps()
{
try
{
ArrayList<Bitmap> bitFrames = new ArrayList<Bitmap>();
Bitmap b = _mmr.getFrameAtTime(_counter);
while (_counter < _duration && b != null)
{
bitFrames.add(b);
_counter += _incrementer;
b = _mmr.getFrameAtTime(_counter);
}
return bitFrames;
}
catch (Exception ex)
{
ex.printStackTrace();
return null;
}
}
I'm thinking my issue lies with in this method. I believe I need to decode the bitmaps before I store them into the ArrayList but I'm unsure how as
Bitmap b = BitmapFactory.decode____(_mmr.getFrameAtTime(_counter));
-- Whether it be decodeStream, decodeResource, decodeFile all bring errors.
Any help would be much appreciated
Many Thanks,
Never store Bitmaps in an ArrayList unless the number of bitmaps are very small. Bitmaps take up large amounts of memory.
I've not worked with video recording before, but I would suggest you try to get a native codec library like ffmpeg to do it for you.
If you have to grab the frames yourself, however, I suggest storing them in a fixed size buffer in memory from the camera and have a background thread which pulls bitmaps from the buffer and stores it to the disk in parallel.
It's not good to store ArrayList of Bitmaps in RAM.
Anyway if you need more than default heap size to store bitmaps in ArrayList, you can try this hack.
http://habrahabr.ru/post/139717/
It's in Russian, but I guess code is quite clear to understand.

Custom image viewing slows down

The problem
Hi there,
I'm developing an application where the user specifies some pictures and how long they are going to be on the screen.So sometimes he wants to create something like a small animation or viewing the images for a small amount of time.The problem is that after some time the images are not previewed when they should and we have a few ms of error.In the application that i'm developing time matters so I would like some help on what the problem might be.
The code
So let me explain how it works.I take the pictures from my web app and then I save them in a HashMap
Bitmap image = ImageOperations(url,String.valueOf(frameNum) + ".jpg");
ImageMap.put(String.valueOf(frameNum), image);
where the mathod ImageOperations is like that:
private Bitmap ImageOperations(String url, String saveFilename) {
try {
Display display = getWindowManager().getDefaultDisplay();
InputStream is = (InputStream) this.fetch(url);
Bitmap theImage = BitmapFactory.decodeStream(is);
if (theImage.getHeight() >= 700 || theImage.getWidth() >= 700) {
theImage = Bitmap.createScaledBitmap(theImage,
display.getWidth(), display.getHeight() - 140, true);
}
return theImage;
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
So later I run a thread that updates the UI when the user specified.The method that updates it is this one.
public void setPictures(int NumOfFrame) {
if (frameArray.get(NumOfFrame - 1).frame_pic.contains("n/a") != true) {
ImagePlace.setImageBitmap(ImageMap.get(String.valueOf(NumOfFrame)));
} else {
ImagePlace.setImageDrawable(null);
}
}
After we update the image we put the thread for sleep and when runs again it updates the thread.Is there something that creates the problem?Does it have to do with Garbage collection?
Thank you in advance
Probably the issue is in increasing heap size when it loads additional images. I would suggest You to do some profiling so things will be much clearer and You'll get full picture of timings for the app.
First you are missing a null check at here:
ImageMap.get(String.valueOf(NumOfFrame))
And you do not recycle the old bitmap at here:
theImage.recycle(); // missing line
theImage = Bitmap.createScaledBitmap(theImage,
display.getWidth(), display.getHeight() - 140, true);
It may lead to outofmemory exceptions, with is most likely from your description of the problem.
Also I am not sure if BitmapFactory.decodeStream throw exception when he fails. You need to add a null point check there too.

Android HashMap Serialization / Deserialization

Hello I have a Hasmap of bitmaps which I need to store on the Android device to be used when next the application starts.
My hashmap looks like this, and contains up to 1000 Bitmaps:
private static HashMap <String, Bitmap> cache = new HashMap<String, Bitmap>();
You might want to consider create extension of Map (by using AbstractMap) and override the related functions. In general the structure of the extension should have:
An in memory hard cache using regular Map. This should be a size bound cache object. You could leverage LinkedHashMap and override removeEldesEntry() to check if the size is exceeded
this.objectMap = Collections.synchronizedMap(new LinkedHashMap() {
#Override
protected boolean removeEldestEntry(LinkedHashMap.Entry eldest) {
if (size() > HARD_CACHE_CAPACITY) {
// remove from cache, pass to secondary SoftReference cache or directly to the disk
}
}
});
If the cache is exceeded, then put it to disk
Override the get function to do the following : On initial get, load the bitmap from disk based on certain naming convention (related to the key) and store it in memory. Roughly something like (please forgive any syntax error)
#Override
public Bitmap get(Object key) {
if(key != null) {
// first level, hard cache
if(objectMap.containsKey(key)) {
return objectMap.get(key);
}
// soft reference cache
if(secondaryCache.containsKey(key)) {
return secondaryCache.get(key);
}
// get from disk if it is not in hard or soft cache
String fileName = "Disk-" + key + ".txt";
File f = new File(cacheDir, fileName);
if(f.exists()) {
// put this back to the hard cache
Bitmap object = readFromReader(f);
if(object != null) {
objectMap.put((String)key, object);
return object;
}
}
}
return null; // unable to get from any data source
}
Similarly your put has to be override to put to the disk for later use, so when you reinitialize your app you could just create an instance of the map extension. If you want, you could also preload the hashmap by most recently used items in the app. Basically, by extending the AbstractMap, you get the flexibilities without killing your memory with that 1000 bitmaps. Hope this helps

Categories

Resources