在活动代码中查找内存泄漏,以释放内存使用量并避免OutOfMemory异常

时间:2019-08-12 14:01:02

标签: java android memory-leaks out-of-memory leakcanary

我有一个Activity和一个ConstraingLayout,其中有ImageView个(每张卡一张)。

获胜后,通过单击将出现的ImageView,Activity将被“重新加载”,显示一组新的卡牌。

问题在于,每次获胜后,Activity使用的内存就会增加,而不是返回到初始使用的金额。

这会在某些内存不足的设备(例如Nexus 7)上导致OutOfMemory Exception。 :(

逻辑是:

  • onCreate方法中,我将ConstraintLayout设置为30 ImageView s(rhe卡的正面)和其他30 ImageView s(卡)
  • 对于每个ImageView(正面和背面),我通过缩放可绘制资源来设置OnClickListener和图像
  • 每次用户点击ImageView时,我都会在卡片的两面设置Alpha以仅显示正确的一面
  • 如果用户找到所有匹配项,则将显示win视图:如果用户单击它,将调用win方法,该方法“重新加载活动”

GiocaMemory.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();
    }
}

GiocaMemory的第一次运行中,AndroidStudio的{​​{1}}是:

enter image description here

获胜后(因此在第二个profiler之后),已使用的内存为:

enter image description here

现在onCreate的内存使用量为Java,而不是返回到28,1 MB的初始值。

屏幕截图指的是带有16个框的布局。 采用30盒式布局,使用的内存增加了很多。 (例如,从49 MB到83 MB)

我可以说图像的大小已足够调整,以便使用较少的内存,因此也许不是问题。请告诉我我是否错。

  • 为什么每次胜利后25,2 MB使用的MB会增加?
  • 您能帮我找到代码中剩下的一些内存泄漏吗?
  • 我用来“重新加载” Java活动的方法是正确的,还是有另一种方法可以让我释放更多资源?

我很难找到它们,因为我对Android编程还比较陌生,尤其是因为我几乎从未面临过分使用内存的问题。

修改

以下是一些使用LeakCanary的信息:

enter image description here

通过单击3个“ GiocaMemory Leaked 21 Agosto 13:35”之一(所有3个都是相同的,只更改跟踪末尾的GiocaMemory

key =

official LeakCanary's documentation说:

  

如果节点未泄漏,则指向该节点的任何先前引用都不是泄漏的来源,也不会泄漏。同样,如果某个节点正在泄漏,则泄漏跟踪下的任何节点也将泄漏。由此,我们可以推断出泄漏是由最后一个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) 之后和第一个Leaking: NO之前的引用引起的。

但是在我的泄漏跟踪中,只有Leaking: YES个泄漏(最后一个UNKNOWN除外)。

如何在我的代码中发现YES泄漏(如果可能是泄漏)?

非常感谢您的帮助!

1 个答案:

答案 0 :(得分:2)

您是否从AudioManager正确放弃了焦点监听器?

AudioManager#abandonAudioFocus(OnAudioFocusChangeListener listener)

实际的OOM可能是如上所述的非回收列表的结果,但这可能是导致内存泄漏的原因。