0
votes

I am working on an application which consists of different screens, which I am designing as different activities with different layouts. For each screen, there are two, large (1536x2048) png files, used as overlay backgrounds (they are alpha blended). For now, the layout xml files of the activities are something like that:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/backgroundPic0" 
tools:context=".TakvimActivity" >

    <ImageView
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@drawable/backgroundPic1" 
     />

</RelativeLayout>

For the time being, I am able to travel from the main activity to three different activities. I am doing this simply with

    Intent intent = new Intent(this, TakvimActivity.class);
    startActivity(intent);

I am aware that the background images consume approximately 24 MBs of memory each time a new Activity is started and the setContentView method is called. When I travel to a new screen for the first time, return back to the main screen and travel to a second,new activity, the application crashes due to Out of Memory exception, saying "Out of memory on a blabla-byte allocation." (Note that it does not crash exactly at the second Activity transition, sometimes at the third one, or so.) It seems to me that the application does not release the resources (most importantly, the image files) when I travel back from an Activity. I checked that whether the current activity is properly destroyed by overriding the onDestroy method and I saw that it is properly called. Shouldn't the GC clear all UI related memory of an Activity as it is being destroyed, since the reference to its view hierarchy gets erased? Is there something I am missing, for example, is there an explicit way to clear an Activity's memory which I did not include in my code?

2
AFAIK, resources remain in your process once loaded. Rather than using android:background, you may need to explicitly manage your large images using BitmapFactory, preferably using inBitmap with your BitmapFactory.Options to reuse the same Bitmap memory buffer. Bear in mind that `Out of memory" errors do not mean that you are out of heap space, but rather that there is no single contiguous block big enough for your request. Android's garbage collector is non-compacting, so you can fragment your memory such that you cannot load massive images, even with lots of free heap.CommonsWare
@CommonsWare Does that mean that everytime I start an Activity for the first time, the resources it uses are allocated and persist during the lifetime of the application? Is there an explicit way to delete resources once they are loaded?Ufuk Can Bicici
"Does that mean that everytime I start an Activity for the first time, the resources it uses are allocated and persist during the lifetime of the application?" -- AFAIK, they are lazy-loaded. Regardless, once loaded, they then remain in memory for the duration of your process. See stackoverflow.com/a/10135306/115145 "Is there an explicit way to delete resources once they are loaded?" -- not that I am aware of.CommonsWare

2 Answers

1
votes

If the imageview itself is GC'ed then the problem goes away for me. Its not enough to call unbindDrawables on the imageview itself.

See also my answer here

unbindDrawables at PagerAdapter Android Lollipop don't work

and my similar but Android Studio focused question here

Leaked unreferenced byte[] originally from bitmap but recycled() causing memory-leak (until activity stopped)

0
votes

Try adding the following method to your application and call it from your activities as such:

private void unbindDrawables(View view) {
    try
    {
        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();
          }         
        }
    catch(Exception e)
    {
        e.printStackTrace();            
    }

}

@Override
protected void onDestroy()
{
super.onDestroy();
//R.id.LayoutId is the id of the root layout of your activity   
unbindDrawables(findViewById(R.id.LayoutId)); 
    System.gc();

}