memory leak while handling bitmaps - android

I'm trying to free memory when I don't need it any more but that doesn't seem to be easy. I create a ListView containing some images (Bitmaps). Every time I do that, the memory monitor shows increase of memory used by my app. This is how I do it:
private ArrayList<ImgMdl> populateList(){
ArrayList<ImgMdl> list = new ArrayList<>();
File folder = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + settingsFileDir + "/" + imgLogFileDir);
if(folder.exists()) {
File[] listOfFiles = folder.listFiles();
listOfFiles = sortFileList(listOfFiles);
for (int i = 0; i < listOfFiles.length; i++) {
String imgFilePath = listOfFiles[i].toString();
Bitmap bmp = BitmapFactory.decodeFile(imgFilePath);
ImgMdl imgMdl = new ImgMdl();
imgMdl.set_itm_txt(getDateString(listOfFiles[i].getName()));
imgMdl.set_itm_img(bmp);
list.add(imgMdl);
}
}
return list;
}
public class MainGate_ImgMdl {
private String itm_txt;
private Bitmap itm_img;
public String get_itm_txt() {
return itm_txt;
}
public void set_itm_txt(String itm_txt) {
this.itm_txt = itm_txt;
}
public Bitmap get_itm_img() {
return itm_img;
}
public void set_itm_img(Bitmap itm_img) {
this.itm_img = itm_img;
}
}
If I open the activity containing that list several times, I'll sometime get an outOfMemoryException. How can I free memory if I don't need it any more? finish() doesn't help.

First, do not load images until they are needed. Suppose that you have 100 images in that directory. Most likely, the user cannot see all 100 on the screen — they would have to scroll to do that. If the user does not scroll, you have loaded the unseen images for no reason.
Second, do not load images that you already loaded. If you "open the activity containing that list several times", and you are loading all of the images each time, that is a waste, as you loaded many of those images previously.
Overall, do not load images yourself. There are plenty of image-loading libraries that are available, such as Picasso and Glide. A decent image-loading library will have a configurable cache (addressing my second point) and know how to load images on-demand in the background, even from rows in a ListView or RecyclerView (addressing my first point).

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.

How to load image (icons) of apps faster in gridView?

I am displaying all apps installed in a gridView. When loading a lot of apps, lets say 30 or more, the icons will display at the default Android icon and then several seconds later update to the correct icon. I am wondering about improvements I can make to my code to make the icon images display faster.
Load the following with: new LoadIconsTask().execute(mApps.toArray(new AppsInstalled[]{}));
Here is what I do.
private class LoadIconsTask extends AsyncTask<AppsInstalled, Void, Void>{
#Override
protected Void doInBackground(AppsInstalled... params) {
// TODO Auto-generated method stub
Map<String, Drawable> icons = new HashMap<String, Drawable>();
PackageManager manager = getApplicationContext().getPackageManager();
// match package name with icon, set Adapter with loaded Map
for (AppsInstalled app : params) {
String pkgName = app.getAppUniqueId();
Drawable ico = null;
try {
Intent i = manager.getLaunchIntentForPackage(pkgName);
if (i != null) {
ico = manager.getActivityIcon(i);
}
} catch (NameNotFoundException e) {
Log.e(TAG, "Unable to find icon match based on package: " + pkgName
+ " : " + e.getMessage());
}
icons.put(app.getAppUniqueId(), ico);
}
mAdapter.setIcons(icons);
return null;
}
Also populate my listing of apps before I loadIconsTask() with
private List<App> loadInstalledApps(boolean includeSysApps) {
List<App> apps = new ArrayList<App>();
// the package manager contains the information about all installed apps
PackageManager packageManager = getPackageManager();
List<PackageInfo> packs = packageManager.getInstalledPackages(0); // PackageManager.GET_META_DATA
for (int i = 0; i < packs.size(); i++) {
PackageInfo p = packs.get(i);
ApplicationInfo a = p.applicationInfo;
// skip system apps if they shall not be included
if ((!includeSysApps)
&& ((a.flags & ApplicationInfo.FLAG_SYSTEM) == 1)) {
continue;
}
App app = new App();
app.setTitle(p.applicationInfo.loadLabel(packageManager).toString());
app.setPackageName(p.packageName);
app.setVersionName(p.versionName);
app.setVersionCode(p.versionCode);
CharSequence description = p.applicationInfo
.loadDescription(packageManager);
app.setDescription(description != null ? description.toString()
: "");
apps.add(app);
}
return apps;
}
In regards to my Adapter class it is standard. My getView() looks like the following:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
AppViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.row, null);
// creates a ViewHolder and stores a reference to the children view
// we want to bind data to
holder = new AppViewHolder();
holder.mTitle = (TextView) convertView.findViewById(R.id.apptitle);
holder.mIcon = (ImageView) convertView.findViewById(R.id.appicon);
convertView.setTag(holder);
} else {
// reuse/overwrite the view passed assuming that it is castable!
holder = (AppViewHolder) convertView.getTag();
}
App app = mApps.get(position);
holder.setTitle(app.getTitle());
if (mIcons == null || mIcons.get(app.getPackageName()) == null) {
holder.setIcon(mStdImg);
} else {
holder.setIcon(mIcons.get(app.getPackageName()));
}
return convertView;
}
Is there a better way? Can I somehow store the images of the icons in a data structure and when I return back to this Activity I can skip the loadIconsTask? Is that possible? Thank you in advance.
You can use Picasso library with a custom RequestHandler to load the icons in the background.
First create a RequestHandler which will handle the specific case where an app icon needs to be loaded.
public class AppIconRequestHandler extends RequestHandler {
/** Uri scheme for app icons */
public static final String SCHEME_APP_ICON = "app-icon";
private PackageManager mPackageManager;
public AppIconRequestHandler(Context context) {
mPackageManager = context.getPackageManager();
}
/**
* Create an Uri that can be handled by this RequestHandler based on the package name
*/
public static Uri getUri(String packageName) {
return Uri.fromParts(SCHEME_APP_ICON, packageName, null);
}
#Override
public boolean canHandleRequest(Request data) {
// only handle Uris matching our scheme
return (SCHEME_APP_ICON.equals(data.uri.getScheme()));
}
#Override
public Result load(Request request, int networkPolicy) throws IOException {
String packageName = request.uri.getSchemeSpecificPart();
Drawable drawable;
try {
drawable = mPackageManager.getApplicationIcon(packageName);
} catch (PackageManager.NameNotFoundException ignored) {
return null;
}
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
return new Result(bitmap, Picasso.LoadedFrom.DISK);
}
}
In your adapter, create a Picasso instance and add your RequestHandler.
// field variable
private Picasso mPicasso;
// in constructor
Picasso.Builder builder = new Picasso.Builder(context);
builder.addRequestHandler(new AppIconRequestHandler(context));
mPicasso = builder.build();
In your adapter's getView() load the icon using Picasso.
mPicasso.load(AppIconRequestHandler.getUri(app.packageName)).into(holder.mIcon);
it's surprising the system takes that much time in getting these lists, you may want to add some logs with timestamping to see which one is the demanding operation.
I don't know if that procedure can be further optimized, I haven't used these system API's very much, but what you can certainly do is to cache this list
Create it in onResume / onCreate as a static list, and (for the sake of correctness) destroy it in onPause / onStop if you want to consider the case where the user may install an application while in your app (onPause will be called), but you can certainly skip this step.
You may want to also permanently cache the list in the sdcard and find some simple and fast heuristic to decide if the list has changed in order to recreate it. Something like maybe the number of installed packages together with something else (to discard the case when the user uninstalls 3 apps and install 3 different apps, the number of packages will be the same and you have to detect this somehow).
EDIT- To recommend a caching mechanism, you should identify which one is the slow operation. Just guessing, and from your question "the icons take some seconds to appear" it looks like that the slow operation is:
ico = manager.getActivityIcon(i);
but I might be wrong. Let's suppose I'm right, so a cheap caching can be:
1) Move the Map<String, Drawable> icons = new HashMap<String, Drawable>(); outside of doInBackground to the root of the class and make it static, like:
private static Map<String, Drawable> sIcons = new HashMap<String, Drawable>()
2) In your loadIconsTask consider the case you already have this icon:
for (AppsInstalled app : params) {
String pkgName = app.getAppUniqueId();
if (sIcons.containsKey(pkgName) continue;
.
.
.
}
This is because sIcons is now static and will be alive as long as your application is alive.
3) As a classy thing, you may want to change sIcons from Drawable to Bitmap. Why? Because a Drawable may keep inside references to Views and Context and it's a potential memory leak. You can get the Bitmap from a Drawable very easily, calling drawable.getBitmap() , (Assuming drawable is a BitmapDrawable, but it will obviously be because it's an app icon), so suming up you'll have:
// the static icon dictionary now stores Bitmaps
static Map<String, Bitmap> sIcons = new HashMap<String, Bitmap>();
.
.
// we store the bitmap instead of the drawable
sIcons.put(app.getAppUniqueId(), ((BitmapDrawable)ico).getBitmap());
.
.
// when setting the icon, we create the drawable back
holder.setIcon(new BitmapDrawable(mIcons.get(app.getPackageName())));
This way your static hashmap will never leak any memory.
4) You may want to check if it's worth to store those bitmaps on disk. Mind this is some additional work and it might not be worth if the time to load the icon from disk is similar to the time to load the icon calling ico = manager.getActivityIcon(i);. It may be (i don't know if manager.getActivityIcon() extracts the icon from the APK) but it certainly may be not.
If you check out it's worth, when you create the list, you can save the bitmaps to the sdcard like this:
// prepare a file to the application cache dir.
File cachedFile=new File(context.getCacheDir(), "icon-"+app.getPackageName());
// save our bitmap as a compressed JPEG with the package name as filename
myBitmap.compress(CompressFormat.JPEG, quality, new FileOutputStream(cachedFile);
... then when loading the icons, check if the icon exists and load from the sdcard instead:
String key=app.getPackageName();
File localFile=new File(context.getCacheDir(), "icon-"+key);
if (localFile.exists()) {
// the file exists in the sdcard, just load it
Bitmap myBitmap = BitmapFactory.decodeStream(new FileInputStream(localFile));
// we have our bitmap from the sdcard !! Let's put it into our HashMap
sIcons.put(key, myBitmap)
} else {
// use the slow method
}
Well as you see it's just a matter of identifying the slow operation. If our above assumption is correct, your stored bitmaps will survive your application destroy and it will hopefully optimize the icon loading.
You can use Glide for automatic loading and caching and the URI of each application icon:
final RequestManager mGlide = Glide.with(activity);
final Uri appIconUri = applicationInfo.icon != 0 ?
Uri.parse("android.resource://" + packageName + "/" + applicationInfo.icon) :
null;
if (appIconUri != null) mGlide.load(appIconUri).into(holder.appIconImgView);
else {
mGlide.clear(holder.appIconImgView); // as suggested here: https://bumptech.github.io/glide/doc/getting-started.html
mGlide.load(android.R.drawable.sym_def_app_icon).into(holder.appIconImgView);
}
The reason I suggest Glide and not other image loading libraries is that Glide supports XML drawable (or dynamic/adaptive or vector icons) loading while others don't (see https://github.com/facebook/fresco/issues/2173)

Caching ListFragment data

My application generates ListFragments based on user type (determined by the DeviceID) and then fetch images from a web server. These images will then be displayed as a list.
My question is how can I cache those images as well as fragments (generated dynamically) to be displayed in offline mode. For instance when user opens the application without having an active internet connection, it should display the images within the fragments generated dynamically last time.
At the moment my app just download the images from a web service each time.
code for generating fragments dynamically in the MainActivity each time when the application loads.
//generating the views based on JSON data
try {
JSONObject resultObject = new JSONObject(result.toString());
boolean success = resultObject.getBoolean("success");
JSONArray jArray = resultObject.getJSONArray("data");
if (success == true) {
//save menu
for (int i = 0; i < jArray.length(); i++) {
postObject = jArray.getJSONObject(i);
if (postObject.has("ev_count")) {
categoriesSet.put("Events", "Events");
mTabsAdapter.addTab(bar.newTab().setText("Events"), EventsFragment.class, null);
}
if (postObject.has("pl_count")) {
categoriesSet.put("Places", "Places");
mTabsAdapter.addTab(bar.newTab().setText("Places"), PlacesFragment.class, null);
}
if (postObject.has("gn_count")) {
categoriesSet.put("General", "General");
mTabsAdapter.addTab(bar.newTab().setText("General"), GeneralFragment.class, null);
}
}
}
//saving values to the shared preferences (hashmap as a string)
categoriesPreferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = categoriesPreferences.edit();
editor.putString("categories", categoriesSet.toString());
editor.commit();
}catch (JSONException e) {
Log.e("ALLOCATE_DAT_ERROR", e.toString());
}
I would just advice to use some image downloading library which also often handles caching for you..
Here is a short list of some of them:
Volley - directly from google, its more like a whole networking stack, but it allows you to easily download Images (see NetworkImageView) and also to cache them (you need to provide a cache implemtantion - google will help you)
Picasso - nice library from square with very straightforward API - I would advice you to go with it, it might be the easiest way to go
Universal Image Loader - another option, it has really a lot of options and is also easy to use
Here is a simple sample code which should advice you how to use volley to load and cache images.
NetworkImageView mage = (NetworkImageView) view.findViewById(...);
image.setImageUrl("http://someurl.com/image.png",mImageLoader);
You need an ImageLoader instance then..
public ImageLoader getImageLoader() {
if (mImageLoader == null) {
mImageLoader = new ImageLoader(getImageRequestQueue(), new DiskBitmapCache(getCacheDir(), 50 * 1024 * 1024));
}
return mImageLoader;
}
imageRequestQueue is standard queue you should have already initialised somewhere in your app if already using volley for networking stuff
As a DiskCache you can use this

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.

HashMap De-Serialization

I have a server/client app where I retrieve data from the server via Hessian/hessdroid. The data is very complex with HashMaps containing other HashMaps and images stored in byte arrays. I can display the data perfectly.
To not always query the server, I´m using a data structure as a cache. This data object I save to SD card using ObjectOutputStream when closing the app. When I restart it, I read it back to memory with an ObjectInputStream.
I´m having problems with the app only after reading the data from SD card. LogCat gives me the following output (100 times):
DEBUG/dalvikvm(4150): GetFieldID: unable to find field Ljava/util/HashMap;.loadFactor:F
and this in between the other messages:
INFO/dalvikvm-heap(4150): Grow heap (frag case) to 10.775MB for 281173-byte allocation
When the heap grows upon ~17 MB the app crashes.
I read several threads about HashMap Serialization and that there seems to be a bug when serializing between architectures, but for me the data transfer via Hessian works perfectly and I´m having the described problems only when reading the HashMaps from disk.
Any ideas?
The problem is not directly related to the HashMap Deserialization, as cyber-monk commented. There is indeed some kind of bug in Android or it´s HashMap implementation, but I don´t think it´s why the app crashes.
By now I solved the problem, using less images in the app. I had a gallery for example in which you could swipe from one image to the next in a flipper and loaded all the images at once. At a certain amount of images, there is not enough heap space.
My solution to this is, to not keep all the decoded images at once.
It´s done like this:
1) Hold the binary image data in memory (not a problem as long as the images are not that big)
2) Don´t load the binary image data into the ImageViews when creating the views of the flipper.
3) Set the binary image data into the ImageView that is displayed.
4) Keep the binary image data of the next and the last ImageView for better user experience)
5) "Unload" the ImageViews that are not displayed by setting its resource to the transparent color.
Here´s some code:
// initialize the viewFlipper by creating blank views
for (ComponentImageDto listElement : images) {
LinearLayout view = renderView();
flipper.addView(view);
}
showImage(flipper.getCurrentView());
renderView() just returns a LinearLayout containing an ImageView
Then I wrote some methods to show the next/previous image in which I set the binary data to the ImageView:
private void showNextElement() {
// show next flipper view
flipper.showNext();
// get current view
int displayedChild = flipper.getDisplayedChild();
View currentView = flipper.getCurrentView();
// load the binary data
showImage(currentView);
// get the next to last view index (if keeping max. 3 images at a time in memory)
int otherChild = (displayedChild - 2);
if (otherChild < 0) {
otherChild = otherChild + flipper.getChildCount();
}
// .. and remove it
removeImage(flipper.getChildAt(otherChild));
}
private void showPreviousElement() {
flipper.showPrevious();
int displayedChild = flipper.getDisplayedChild();
View currentView = flipper.getCurrentView();
showImage(currentView);
setTitle((CharSequence) currentView.getTag());
int otherChild = (displayedChild + 2) % flipper.getChildCount();
removeImage(flipper.getChildAt(otherChild));
}
private void removeImage(View view) {
ImageView imageView = (ImageView) view.findViewById(R.id.gallery_image);
if (imageView != null) {
imageView.setImageResource(R.color.transparent);
System.gc();
}
}
private void showImage(View view) {
ImageView imageView = (ImageView) view.findViewById(R.id.gallery_image);
if (imageView != null) {
bm = BitmapHelper.decodeByteArray(images.get(flipper.getDisplayedChild()).getImage().getBinaryObject());
imageView.setImageBitmap(bm);
}
}
To furtherly improve memory handling I´m using some code in BitmapHelper class that I found on stackoverflow, that helps to save memory for images by reducing their size.

Categories

Resources