MediaPlayer原始碼存在的記憶體洩漏問題,釋放資源的正確方式

weixin_33890499發表於2017-09-26

最近完成了一個聯網的視訊播放器Demo,閒來無聊,嘗試了一下LeakCanary,一款Android檢視記憶體洩漏的工具。使用方式

https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/
這個是LeakCanary的中文使用文件,很簡單。

無意間發現應用存在記憶體洩漏問題。
LeakCanary提供的Log資訊:

 D/LeakCanary: In com.shuyu.video.clean.debug:2.0.0:30305.
  * com.shuyu.video.activity.PlayActivity has leaked:
  * GC ROOT static com.shuyu.video.MyApplication.mApplication
  * references com.shuyu.video.MyApplication.mLoadedApk
  * references android.app.LoadedApk.mReceivers
  * references android.util.ArrayMap.mArray
  * references array java.lang.Object[].[3]
  * references android.util.ArrayMap.mArray
  * references array java.lang.Object[].[2]
  * references android.media.MediaPlayer$ProxyReceiver.this$0
  * references android.media.MediaPlayer.mProxyContext
  * leaks com.shuyu.video.activity.PlayActivity instance
                                                                         

可以看到,這個工具很強大很方便,直接指出了記憶體洩漏的地方,上面寫的很清楚,倒數第二行說明了Mediaplayer存在一個代理引用,導致了PlayActivity無法回收,造成記憶體洩漏。

按理說,既然工具已經這麼詳細的說明了記憶體洩漏出現問題的地方,問題應該很好解決,但是,我檢視了我的程式碼,mediaplayer容易出現記憶體洩漏的地方無非就是在和Activity進行生命週期的時候,需要自己進行釋放資源,不然會造成記憶體洩漏。
附上我的關鍵程式碼吧:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ReleasePlayer();

    }

    /**
     * 釋放播放器資源
     */
    private void ReleasePlayer() {
        if (player != null) {
            player.stop();
            player.release();
            player = null;
        }

    }

可以看到我重寫了Activity的生命週期,在OnDestroy方法中釋放了Mediaplayer的資源,釋放Mediaplayer的資源的方法也是網路上常用的釋放Meidplayer的步驟。但是問題來了,這樣怎麼會造成記憶體洩漏哪?

最後,還是老外厲害啊,在stackoverflow上找到一篇文章,問題一模一樣,原文連結

7866586-6bdc51c7c3caf7b4
這裡寫圖片描述

大概意思是,他檢視了Mediaplayer的原始碼,發現存在一個引用,應該回收,但是在release方法中,並沒有沒處理,只有在reset方法中,這個引用才被消除。
所以結論來了:釋放資源的正確方式:

    /**
     * 釋放播放器資源
     */
    private void ReleasePlayer() {
        if (player != null) {
            player.stop();
            
            //關鍵語句
            player.reset();
            
            player.release();
            player = null;
        }

    }

具體到原始碼,是哪一個沒有回收,大概看了一下
Mediaplayer的release原始碼:

    public void release() {
        baseRelease();
        stayAwake(false);
        updateSurfaceScreenOn();
        mOnPreparedListener = null;
        mOnBufferingUpdateListener = null;
        mOnCompletionListener = null;
        mOnSeekCompleteListener = null;
        mOnErrorListener = null;
        mOnInfoListener = null;
        mOnVideoSizeChangedListener = null;
        mOnTimedTextListener = null;
        if (mTimeProvider != null) {
            mTimeProvider.close();
            mTimeProvider = null;
        }
        mOnSubtitleDataListener = null;
        _release();
    }

reset的原始碼:

public void reset() {
        mSelectedSubtitleTrackIndex = -1;
        synchronized(mOpenSubtitleSources) {
            for (final InputStream is: mOpenSubtitleSources) {
                try {
                    is.close();
                } catch (IOException e) {
                }
            }
            mOpenSubtitleSources.clear();
        }
        if (mSubtitleController != null) {
            mSubtitleController.reset();
        }
        if (mTimeProvider != null) {
            mTimeProvider.close();
            mTimeProvider = null;
        }

        stayAwake(false);
        _reset();
        // make sure none of the listeners get called anymore
        if (mEventHandler != null) {
            mEventHandler.removeCallbacksAndMessages(null);
        }

        synchronized (mIndexTrackPairs) {
            mIndexTrackPairs.clear();
            mInbandTrackIndices.clear();
        };
    }

我查了reset中的物件,發現mSubtitleController 這個物件在release方法中沒有被處理,只在reset方法中被reset了,並且mSubtitleController 的構造方法會存在context物件,我猜應該就是這個物件,導致記憶體洩漏這個問題存在吧

mSubtitleController = new SubtitleController(context, mTimeProvider, MediaPlayer.this);

問題的正確與否,具體原始碼原理和原因待求大神解釋。

相關文章