當前位置: 首頁>>編程語言>>正文


內存回收之Android MediaPlayer音頻播放異常中斷分析

MediaPlayer是android官方提供的一組音頻/視頻播放Api, 可以滿足簡單的、大部分音頻/視頻文件的播放需求。由於最近項目需要音頻播放,第一次在Android APP引入了MediaPlayer。初步開發實現過程還算順利,現在最終上線測試過程中,發現音頻播放大概率出現中斷不播完的問題,但是有時候又正常。經過反複的調試與問題追蹤,最終發現每次播放中斷,日誌都會輸出以下內容:

04-06 15:56:14.713 11324-11332/com.ivan.ivanapp W/MediaPlayer-JNI: MediaPlayer finalized without being released
04-06 15:56:35.819 11324-11332/com.ivan.ivanapp W/MediaPlayer-JNI: MediaPlayer finalized without being released

從日誌內容可以看出,MediaPlayer jni底層調用出現了警告信息,大概意思是:MediaPlayer資源沒有釋放就結束了。為了進一步分析問題原因,在嘗試各種方案和查詢官方和stackoverflow論壇資料後,終於發現有價值的解決方案。以下引用自stackoverflow

I think this is because you create the media player within the scope of the method, therefore, when the method completes, it goes out of scope. This means there are no references, so is ok for garbage collection.
This means, it can be free’d by the GC before it has even called onCompletion, hence won’t release before cleared. Instead, you need to store a reference to the media player as a member variable in your class.

stackoverflow有人提出了相同的播放中斷問題:MediaPlayer finalized without being released,根據答題者@T. Kiley的回答,大部分人都解決了這個問題。@T.kiley的大概意思是:

當在方法內部創建MediaPlayer對象實例(局部變量)時,一旦方法執行完成,局部變量將不再被引用所持有。而根據Java JVM內存回收機製,不再被引用持有的對象,執行gc的
時候該對象就會被回收。這意味著,MediaPlayer對象實例在播放完成、釋放之前是可以被gc回收的,因此會導致音頻/視頻播放中斷。解決方案是:應當將MediaPlayer對象實例
聲明為class類成員變量,而不是聲明為方法的局部變量。

另外,Android MediaPlayer官方文檔說明如下:

It is also recommended that once a MediaPlayer object is no longer being used, call release() immediately so that resources used by the internal player engine associated with the MediaPlayer object can be released immediately. Resource may include singleton resources such as hardware acceleration components and failure to call release() may cause subsequent instances of MediaPlayer objects to fallback to software implementations or fail altogether. Once the MediaPlayer object is in the End state, it can no longer be used and there is no way to bring it back to any other state.

含義如下:

推薦在MediaPlayer對象在不再使用的時候,應當立即調用release()方法以便於所有和MediaPlayer關聯的內部播放引擎資源能及時釋放。資源可能包含單例資源,比如硬件
加速組件;release()調用失敗可能會導致MediaPlayer後續的對象實例回退軟件實現或者執行一起失敗。一旦MediaPlayer對象處在結束狀態,該對象就不能被使用,也沒有
任何方法回到任何其他狀態。

雖然根據以上方法,將MediaPlayer局部變量修改為類成員後,項目問題已經修複,測試也未發現問題;但是為了驗證此問題是否由於局部變量引起,本著嚴謹的學習研究態度,寫了一段測試代碼進一步證實了這個問題的原因:

1.局部變量實現
# 為了簡潔便於閱讀,省略大部分結構性代碼,隻保留核心部分
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onPostResume() {

        new Thread(new Runnable() {
            @Override
            public void run() {
                player();
            }
        }).start();
    }

    private void player(){
        MediaPlayer player = MediaPlayer.create(getApplicationContext(), R.raw.xxx_mp3);
        player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mediaPlayer) {
                mediaPlayer.start();
            }
        });
        player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mediaPlayer) {
                MediaPlayer.release();
            }
        });

        player.start();
    }
}

通過Android Studio連接真機調試,利用Android Studio monitor窗口Initiate GC操作主動回收內存,操作界麵如下:

image

真機運行demo代碼,每次點擊Initiate GC時,播放就會停止,日誌輸出如下日誌:

04-06 15:56:35.819 11324-11332/com.ivan.ivanapp W/MediaPlayer-JNI: MediaPlayer finalized without being released
2.類成員變量實現
# 為了簡潔便於閱讀,省略大部分結構性代碼,隻保留核心部分
public class MainActivity extends AppCompatActivity {

    private MediaPlayer player;
    @Override
    protected void onPostResume() {

        new Thread(new Runnable() {
            @Override
            public void run() {
                player2();
            }
        }).start();
    }

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

        if (player != null) {
            player.release();
        }
    }

    private void player2(){
        player = MediaPlayer.create(getApplicationContext(), R.raw.liren);
        player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mediaPlayer) {
                mediaPlayer.start();
            }
        });
        player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mediaPlayer) {
                mediaPlayer.release();
            }
        });

        player.start();
    }
}

同樣,和局部變量實現的調試方法一樣,使用Android Studio monitor窗口Initiate GC操作主動回收內存,無論點擊多少次回收操作,音頻播放也沒有中斷,日誌業務警告日誌輸出。至此,通過前後2段demo代碼運行結果的對比,說明MediaPlayer聲明為類成員變量的方案更好,實際應用中運行更穩定。#在挖坑填坑的路上越走越遠,希望對你有所幫助#。

Android MediaPlayer

參考資料:
1. http://stackoverflow.com/questions/15023037/garbage-collection-causes-mediaplayer-finalized-without-being-released
2. https://developer.android.com/reference/android/media/MediaPlayer.html
本文由《純淨天空》出品。文章地址: https://vimsky.com/zh-tw/article/3303.html,未經允許,請勿轉載。