I have an Activity
with a ConstraingLayout
with lots of ImageView
s (one per card).
After a win, by clicking on the ImageView that will appear, the Activity
will be "reloaded" showing a new set of cards to play.
The problem is that after each win the memory used by the Activity
raises instead of returning at the initial amount used.
This cause an OutOfMemory Exception
on some devices with low memory (e.g. on Nexus 7). :(
The logic items are:
onCreate
method I set the ConstraintLayout
made of 30 ImageView
s (the front side of the cards) and others 30 ImageView
s (the back side of the cards)ImageView
(front and back sides) I set the OnClickListener
and the image by scaling the drawable resourceImageView
, I set the alpha for the two sides of the card to show only the proper sideGiocaMemory.java:
package ...
import ...
public class GiocaMemory extends AppCompatActivity
{
...
MediaPlayer mediaPlayer;
MediaPlayer.OnCompletionListener onCompletionListenerReleaseMediaPlayer;
private AudioManager audioManager;
private AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener;
...
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
...
onCompletionListenerReleaseMediaPlayer = new MediaPlayer.OnCompletionListener()
{
@Override
public void onCompletion(MediaPlayer mp)
{
releaseMediaPlayer();
}
};
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
onAudioFocusChangeListener = new AudioManager.OnAudioFocusChangeListener()
{
@Override
public void onAudioFocusChange(int focusChange)
{
if(mediaPlayer != null)
{
switch (focusChange)
{
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
mediaPlayer.pause();
mediaPlayer.seekTo(0);
break;
case AudioManager.AUDIOFOCUS_GAIN:
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
mediaPlayer.start();
break;
case AudioManager.AUDIOFOCUS_LOSS:
releaseMediaPlayer();
break;
}
}
}
};
...
}
private void win()
{
showView(textViewWin);
}
public void reloadActivity()
{
finish();
startActivity(getIntent());
}
public void playSound(int idElement)
{
String name = idElement + "_name";
playAudio(name, onCompletionListenerReleaseMediaPlayer);
}
public void playAudioName(int idElement)
{
String name = idElement + "_sound";
playAudio(name, onCompletionListenerReleaseMediaPlayer);
}
public void onClickHome(View view)
{
finish();
}
public void stopAudio()
{
releaseMediaPlayer();
}
private void playAudio(String audioName, MediaPlayer.OnCompletionListener onCompletionListener)
{
stopAudio();
if(!audioName.isEmpty())
{
int result = audioManager.requestAudioFocus(onAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
{
int resID = res.getIdentifier(audioName, "raw", getPackageName());
if (resID == 0)
{
return;
}
releaseMediaPlayer();
startMediaPlayerWithRes(this, resID, audioName);
mediaPlayer.setOnCompletionListener(onCompletionListener);
}
}
}
private void startMediaPlayerWithRes(Context context, int resID, String audioName)
{
mediaPlayer = MediaPlayer.create(context, resID);
if(mediaPlayer != null) mediaPlayer.start();
else mediaPlayer = new MediaPlayer();
}
private void releaseMediaPlayer()
{
if(mediaPlayer != null) mediaPlayer.release();
mediaPlayer = null;
}
private void loadContents()
{
...
}
@Override
protected void onPause()
{
super.onPause();
releaseMediaPlayer();
}
public void freeRes()
{
...
mediaPlayer = null;
onCompletionListenerReleaseMediaPlayer = null;
audioManager = null;
onAudioFocusChangeListener = null;
res = null;
releaseMediaPlayer();
}
@Override
protected void onDestroy() {
super.onDestroy();
freeRes();
}
}
At the first run of GiocaMemory
the AndroidStudio
's profiler
is:
After a win (so after the second onCreate
) the used memory is:
Now the Java memory usage is 28,1 MB
, instead of returning at the initial value of 25,2 MB
.
The screenshots refer to the layout with 16 boxes. With the 30 box layout the memory used increases a lot more. (E.g. from 49 MB to 83 MB)
I may say that the images are enough resized in order to use the less memory possible, so maybe they cannot be the problem. Of course, I would be keen to know if I'm wrong.
Java
will increase?GiocaMemory
Activity is correct or there is an other way which let me free more resources?I'm find very very hard to find them because I'm relatively new to Android programming, especially since I almost never had to face problems related to excessive memory usage.
These are some info using LeakCanary:
By clicking on one of the 3 "GiocaMemory Leaked 21 Agosto 13:35" (all 3 are the same, changing only the key =
at the end of the trace)
ApplicationLeak(className=app.myapp.GiocaMemory, leakTrace=
┬
├─ android.media.AudioManager$1
│ Leaking: UNKNOWN
│ Anonymous subclass of android.media.IAudioFocusDispatcher$Stub
│ GC Root: Global variable in native code
│ ↓ AudioManager$1.this$0
│ ~~~~~~
├─ android.media.AudioManager
│ Leaking: UNKNOWN
│ ↓ AudioManager.mAudioFocusIdListenerMap
│ ~~~~~~~~~~~~~~~~~~~~~~~~
├─ java.util.HashMap
│ Leaking: UNKNOWN
│ ↓ HashMap.table
│ ~~~~~
├─ java.util.HashMap$HashMapEntry[]
│ Leaking: UNKNOWN
│ ↓ array HashMap$HashMapEntry[].[0]
│ ~~~
├─ java.util.HashMap$HashMapEntry
│ Leaking: UNKNOWN
│ ↓ HashMap$HashMapEntry.value
│ ~~~~~
├─ app.myapp.GiocaMemory$2
│ Leaking: UNKNOWN
│ Anonymous class implementing android.media.AudioManager$OnAudioFocusChangeListener
│ ↓ GiocaMemory$2.this$0
│ ~~~~~~
╰→ app.myapp.GiocaMemory
Leaking: YES (Activity#mDestroyed is true and ObjectWatcher was watching this)
key = dfa0d5fe-0c50-4c64-a399-b5540eb686df
watchDurationMillis = 380430
retainedDurationMillis = 375425
, retainedHeapByteSize=470627)
The official LeakCanary's documentation says:
If a node is not leaking, then any prior reference that points to it is not the source of the leak, and also not leaking. Similarly, if a node is leaking then any node down the leak trace is also leaking. From that, we can deduce that the leak is caused by a reference that is after the last
Leaking: NO
and before the firstLeaking: YES
.
But in my leakTrace there are only UNKNOWN
leaks except the last YES
.
How can I find that YES
leak in my code, if it maybe a leak?
Are you properly abandoning the focus listener from AudioManager?
AudioManager#abandonAudioFocus(OnAudioFocusChangeListener listener)
The actual OOM might be the result of non recycling lists as mentioned, but that could be the cause of your memory leak.