I have an Activity with a ConstraingLayout with lots of ImageViews (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 logics are:
- in the
onCreatemethod I set theConstraintLayoutmade of 30ImageViews (the front side of rhe cards) and others 30ImageViews (the back side of the cards) - for each
ImageView(front and back sides) I set theOnClickListenerand the image by scaling the drawable resource - every time the user clicks on an
ImageView, I set the alpha for the two sides of the card to show only the proper side - if the user finds all matches, the win View will appear: if the user clicks it, will be invoked the win method, which "reload the activity"
GiocaMemory.java:
package ...
import ...
public class GiocaMemory extends AppCompatActivity
{
int qtyElements = 16;
Map<Integer, MyElement> mapElements = new LinkedHashMap<>();
Map<Integer, Integer> mapPosMyElements = new LinkedHashMap<>();
MediaPlayer mediaPlayer;
MediaPlayer.OnCompletionListener onCompletionListenerReleaseMediaPlayer;
private AudioManager audioManager;
private AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener;
SeekBar seekBar_volume;
Resources res;
ImageView imageView_volume;
int metrics_widthPixels = 0;
int metrics_heightPixels = 0;
TextView textViewWin;
Set<Integer> setIdElementsToFind = new HashSet<>();
Map<Integer, String> mapViewNameElements = new HashMap<>();
Map<Integer, ImageView> mapImageViewElements = new HashMap<>();
Map<Integer, ImageView> mapImageViewElementsBack = new HashMap<>();
int idElement1;
int posElement1;
int idElement2;
int posElement2;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
qtyElements = getIntent().getExtras().getInt("qtyElements", 16);
int idContentView;
if(qtyElements == 30) {
idContentView = R.layout.activity_play_memory_30;
}
else if(qtyElements == 16) {
idContentView = R.layout.activity_play_memory_16;
}
setContentView(idContentView);
res = getResources();
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;
}
}
}
};
setVolumeControlStream(AudioManager.STREAM_MUSIC);
initVolumeControls();
textViewWin = findViewById(R.id.textViewWin);
textViewWin.setOnClickListener(v -> {
finish();
startActivity(getIntent());
});
hideView(textViewWin);
imageView_volume = findViewById(R.id.imageView_volume);
metrics_widthPixels = MyUtils.getDisplayMetrics_widthPixels(res);
metrics_heightPixels = MyUtils.getDisplayMetrics_heightPixels(res);
loadContents();
if(preferenza_fullScreen)
{
hideSystemUI();
}
}
public void onClickElement(int pos)
{
hideView(seekBar_volume);
stopAudio();
if(idElement1 > 0 && idElement2 > 0) {
showTileBack(posElement1);
showTileBack(posElement2);
idElement1 = 0;
idElement2 = 0;
posElement1 = 0;
posElement2 = 0;
}
showTile(pos);
int idElementChoosen = mapPosMyElements.get(pos);
playAudioNameThenSound(idElementChoosen);
if(idElement1 > 0) {
idElement2 = idElementChoosen;
posElement2 = pos;
}
else {
idElement1 = idElementChoosen;
posElement1 = pos;
}
if(idElement2 > 0)
{
if(idElement1 == idElement2)
{
if (setIdElementsToFind.contains(idElementChoosen))
{
setIdElementsToFind.remove(idElementChoosen);
idElement1 = 0;
idElement2 = 0;
posElement1 = 0;
posElement2 = 0;
if (setIdElementsToFind.isEmpty())
{
win();
}
}
}
}
}
private void win()
{
showView(textViewWin);
}
private void showTile(int pos)
{
mapImageViewElements.get(pos).setAlpha(1f);
mapImageViewElementsBack.get(pos).setAlpha(0f);
}
private void showTileBack(int pos)
{
mapImageViewElementsBack.get(pos).setAlpha(1f);
mapImageViewElements.get(pos).setAlpha(0f);
}
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 playAudioNameThenSound(int idElement)
{
String name = idElement + "_name";
String sound = idElement + "_sound";
play2Audio(name, sound);
}
public void playSoundThenName(int idElement)
{
String name = idElement + "_name";
String sound = idElement + "_sound";
play2Audio(sound, name);
}
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 play2Audio(final String audioName1, final String audioName2)
{
stopAudio();
final int resID1 = !audioName1.isEmpty() ? res.getIdentifier(audioName1, "raw", getPackageName()) : 0;
final int resID2 = !audioName2.isEmpty() ? res.getIdentifier(audioName2, "raw", getPackageName()) : 0;
if(resID1 > 0)
{
int result = audioManager.requestAudioFocus(onAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
{
releaseMediaPlayer();
startMediaPlayerWithRes(this, resID1, audioName1);
if(resID2 == 0)
{
mediaPlayer.setOnCompletionListener(onCompletionListenerReleaseMediaPlayer);
}
else
{
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener()
{
@Override
public void onCompletion(MediaPlayer mp)
{
int result = audioManager.requestAudioFocus(onAudioFocusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED)
{
if (resID2 > 0)
{
releaseMediaPlayer();
startMediaPlayerWithRes(getApplicationContext(), resID2, audioName2);
mediaPlayer.setOnCompletionListener(onCompletionListenerReleaseMediaPlayer);
}
}
}
});
}
}
}
else if(resID2 > 0)
{
playAudio(audioName2, onCompletionListenerReleaseMediaPlayer);
}
}
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()
{
int numTotalElements = qtyElements;
for(int n = 1; n <= numTotalElements; n++)
{
mapViewNameElements.put(n, "imageViewElement" + n);
ImageView imageView = findViewById(res.getIdentifier("imageViewElement" + n, "id", getPackageName()));
ImageView imageViewRetro = findViewById(res.getIdentifier("imageViewElementBack" + n, "id", getPackageName()));
if(imageView != null) {
mapImageViewElements.put(n, imageView);
}
if(imageViewRetro != null) {
mapImageViewElementsBack.put(n, imageViewRetro);
}
}
int qtaPerSide = (int) Math.sqrt(numTotalElements);
List<EnumElements> listElementsEnum = new ArrayList<>(Arrays.asList(EnumElements.values()));
for(int posizion = 1; posizion <= numTotalElements; posizion++)
{
if(mapPosMyElements.containsKey(posizion)) {
continue;
}
Collections.shuffle(listElementsEnum);
EnumElements e = listElementsEnum.get(new Random().nextInt(listElementsEnum.size()-1));
mapElements.put(e.idElement, new MyElement(e.idElement, e.name, e.idElementAudioName, e.idElementAudioSound));
mapPosMyElements.put(posizion, e.idElement);
setIdElementsToFind.add(e.idElement);
int posizion2 = MyUtils.randomIntRangeWithExcludedNumbers(posizion+1, numTotalElements, new ArrayList<>(mapPosMyElements.keySet()));
mapPosMyElements.put(posizion2, e.idElement);
String nameRes = "_" + e.idElement;
int imageId = res.getIdentifier(nameRes, "drawable", getPackageName());
if(imageId > 0) {
int reqWidth = metrics_widthPixels / qtaElementiPerLato;
int reqHeight = metrics_heightPixels / qtaElementiPerLato;
Bitmap bitmapResized = generateBitmapResized(res, imageId, reqWidth, reqHeight);
final int posForLambda = posizion;
mapImageViewElements.get(posizion).setImageBitmap(bitmapResized);
mapImageViewElements.get(posizion).setOnClickListener(v -> onClickElement(posForLambda));
mapImageViewElementsBack.get(posizion).setOnClickListener(v -> onClickElement(posForLambda));
final int pos2ForLambda = posizion2;
mapImageViewElements.get(posizion2).setImageBitmap(bitmapResized);
mapImageViewElements.get(posizion2).setOnClickListener(v -> onClickElement(pos2ForLambda));
mapImageViewElementsBack.get(posizion2).setOnClickListener(v -> onClickElement(pos2ForLambda));
showTileBack(posizion);
showTileBack(posizion2);
if(bitmapResized != null)
{
bitmapResized = null;
}
}
listElementsEnum.remove(e);
}
listElementsEnum = null;
}
public void onClickImgVolume(View view)
{
toggleView(R.id.seekBar_volume);
setIconVolume();
}
public void setIconVolume()
{
if(viewVisibile(R.id.seekBar_volume)) {
imageView_volume.setColorFilter(Color.BLUE);
imageView_volume.setAlpha(1f);
}
else {
imageView_volume.setColorFilter(Color.GRAY);
imageView_volume.setAlpha(0.25f);
}
}
public void toggleView(int idView)
{
if(viewVisibile(idView)) {
hideView(idView);
}
else {
showView(idView);
}
}
public void showView(int idLayout)
{
View view = findViewById(idLayout);
view.setVisibility(View.VISIBLE);
}
public void showView(View view)
{
int idView = view != null ? view.getId() : 0;
if(idView > 0) showView(idView);
}
public void hideView(int idLayout)
{
View view = findViewById(idLayout);
view.setVisibility(View.GONE);
}
public void hideView(View view)
{
int idView = view != null ? view.getId() : 0;
if(idView > 0) hideView(idView);
}
public boolean viewVisibile(int idView)
{
return findViewById(idView).getVisibility() == View.VISIBLE;
}
public void initVolumeControls()
{
try {
seekBar_volume = findViewById(R.id.seekBar_volume);
seekBar_volume.setMax(audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC));
seekBar_volume.setProgress(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC));
seekBar_volume.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, progress, 0);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
catch (Exception e)
{
e.printStackTrace();
}
}
@Override
public void onWindowFocusChanged(boolean hasFocus)
{
super.onWindowFocusChanged(hasFocus);
if (hasFocus)
{
hideSystemUI();
}
}
private void hideSystemUI()
{
if (Build.VERSION.SDK_INT >= 19)
{
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility
(
View.SYSTEM_UI_FLAG_IMMERSIVE
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
);
}
}
@Override
protected void onPause()
{
super.onPause();
releaseMediaPlayer();
}
public void freeRes()
{
textViewWin.setOnClickListener(null);
textViewWin = null;
seekBar_volume = null;
imageView_volume = null;
mapPosMyElements.clear();
mapPosMyElements = null;
setIdElementsToFind.clear();
setIdElementsToFind = null;
mapViewNameElements.clear();
mapViewNameElements = null;
for(Map.Entry<Integer, ImageView> entry : mapImageViewElements.entrySet()) {
ImageView imageView = entry.getValue();
Drawable drawable = imageView.getDrawable();
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
bitmap.recycle();
bitmapDrawable = null;
bitmap = null;
}
imageView.setOnClickListener(null);
imageView.setImageDrawable(null);
imageView.setImageBitmap(null);
imageView = null;
drawable = null;
}
mapImageViewElements.clear();
mapImageViewElements = null;
for(Map.Entry<Integer, ImageView> entry : mapImageViewElementsBack.entrySet()) {
ImageView imageView = entry.getValue();
Drawable drawable = imageView.getDrawable();
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
bitmap.recycle();
bitmapDrawable = null;
bitmap = null;
}
imageView.setOnClickListener(null);
imageView.setImageDrawable(null);
imageView.setImageBitmap(null);
imageView = null;
drawable = null;
}
mapImageViewElementsBack.clear();
mapImageViewElementsBack = null;
mapElements.clear();
mapElements = null;
mediaPlayer = null;
onCompletionListenerReleaseMediaPlayer = null;
audioManager = null;
onAudioFocusChangeListener = null;
res = null;
releaseMediaPlayer();
}
@Override
protected void onDestroy() {
super.onDestroy();
freeRes();
}
}
MyUtils.java:
public class MyUtils {
public static int getDisplayMetrics_widthPixels(Resources res) {
return res.getDisplayMetrics().widthPixels;
}
public static int getDisplayMetrics_heightPixels(Resources res) {
return res.getDisplayMetrics().heightPixels;
}
public static int randomIntRange(int min, int max)
{
return (new Random().nextInt(max-min+1))+min;
}
public static int randomIntRangeWithExcludedNumbers(int min, int max, List<Integer> excl)
{
int maxTry = 1000;
int num = -1;
while(maxTry > 0)
{
maxTry--;
int numTemp = MyUtils.randomIntRange(min, max);
if(!excl.contains(numTemp)) {
num = numTemp;
break;
}
}
return num;
}
}
MyImg.java:
public class MyImg {
public static Bitmap generateBitmapResized(Resources res, int imageId, int reqWidthPixels, int reqHeightPixels)
{
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, imageId, options);
return decodeSampledBitmapFromResource(res, imageId, reqWidthPixels, reqHeightPixels);
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
}
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. Please tell me if I'm wrong.
- Why after every win the MB used by
Javawill increase? - Can you please help me find some memory leaks I left in the code?
- The way I'm using to "reload" the
GiocaMemoryActivity 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.
Edit:
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: NOand before the firstLeaking: YES.
But in my leakTrace there are only UNKNOWN leaks exept the last YES.
How can I find that YES leak in my code, if it maybe a leak?
Thank you very much for your help.
from Find memory leaks in the Activity code to free memory usage and avoid OutOfMemory Exception



No comments:
Post a Comment