MediaPlayer原始碼存在的記憶體洩漏問題,釋放資源的正確方式
最近完成了一個聯網的視訊播放器Demo,閒來無聊,嘗試了一下LeakCanary,一款Android檢視記憶體洩漏的工具。使用方式
無意間發現應用存在記憶體洩漏問題。
LeakCanary提供的Log資訊:
03-03 14:31:06.281 20734-27495/com.shuyu.video.clean.debug 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上找到一篇文章,問題一模一樣,原文連結
大概意思是,他查看了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);
問題的正確與否,具體原始碼原理和原因待求大神解釋。