当查阅 MediaPlayer 文档时 你会发现这个方法setOnCompletionListener,这里的说明指出该方法允许你注册一个回调。当媒体资源或音频文件到达结束位置时会回调该方法,注意该方法的输入是OnCompletionListener

部分代码如下:

public class NumbersActivity extends AppCompatActivity {
    /**
     * Handles playback of all the sound files
     */
    private MediaPlayer mMediaPlayer;
    private static final String TAG = "NumbersActivity";


    /**
     * This listener gets triggered when the {@link MediaPlayer} has completed
     * playing the audio file.
     */
    private MediaPlayer.OnCompletionListener mCompletionListener = new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mediaPlayer) {
            // Now that the sound file has finished playing, release the media player resources.
            releaseMediaPlayer();
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.word_list);

        // Create a list of words
        final ArrayList<Word> words = new ArrayList<Word>();
        words.add(new Word("one", "lutti", R.drawable.number_one, R.raw.number_one));
        words.add(new Word("two", "otiiko", R.drawable.number_two, R.raw.number_two));
        words.add(new Word("three", "tolookosu", R.drawable.number_three, R.raw.number_three));
        words.add(new Word("four", "oyyisa", R.drawable.number_four, R.raw.number_four));
        words.add(new Word("five", "massokka", R.drawable.number_five, R.raw.number_five));
        words.add(new Word("six", "temmokka", R.drawable.number_six, R.raw.number_six));
        words.add(new Word("seven", "kenekaku", R.drawable.number_seven, R.raw.number_seven));
        words.add(new Word("eight", "kawinta", R.drawable.number_eight, R.raw.number_eight));
        words.add(new Word("nine", "wo’e", R.drawable.number_nine, R.raw.number_nine));
        words.add(new Word("ten", "na’aacha", R.drawable.number_ten, R.raw.number_ten));

        // Create an {@link WordAdapter}, whose data source is a list of {@link Word}s. The
        // adapter knows how to create list items for each item in the list.
        final WordAdapter adapter = new WordAdapter(this, words, R.color.category_numbers);

        // Find the {@link ListView} object in the view hierarchy of the {@link Activity}.
        // There should be a {@link ListView} with the view ID called list, which is declared in the
        // word_list.xml layout file.
        ListView listView = (ListView) findViewById(R.id.list);

        // Make the {@link ListView} use the {@link WordAdapter} we created above, so that the
        // {@link ListView} will display list items for each {@link Word} in the list.
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                // Release the media player if it currently exists because we are about to
                // play a different sound file
                // 当用户很快的点击播放不同的音频时,就先释放,否则快速点击音频会有声音重叠
                releaseMediaPlayer();
                // Get the {@link Word} object at the given position the user clicked on
                Word word = words.get(position);

                // Create and setup the {@link MediaPlayer} for the audio resource associated
                // with the current word
                mMediaPlayer = MediaPlayer.create(NumbersActivity.this, word.getAudioResourceId());

                // Start the audio file
                mMediaPlayer.start();

                // Setup a listener on the media player, so that we can stop and release the
                // media player once the sound has finished playing.
                // 播放完成可以释放资源
                mMediaPlayer.setOnCompletionListener(mCompletionListener);
            }
        });
    }

    /**
     * Clean up the media player by releasing its resources.
     */
    private void releaseMediaPlayer() {
        // If the media player is not null, then it may be currently playing a sound.
        if (mMediaPlayer != null) {
            // Regardless of the current state of the media player, release its resources
            // because we no longer need it.
            mMediaPlayer.release();

            // Set the media player back to null. For our code, we've decided that
            // setting the media player to null is an easy way to tell that the media player
            // is not configured to play an audio file at the moment.
            mMediaPlayer = null;
        }
    }
}

MediaPlayer.OnCompletionListener 是个接口,意味着你需要实现该方法,并为其 onCompletion() 方法提供实现代码。
当 MediaPlayer 播放完成时,onCompletionListener 对象的onCompletion() 方法将被调用。在 MusicPlayer 开始后,我们需要设置 completionListener,当 mediaPlayer.start() 被调用后,我们来调用 setOnCompletionListener 方法,使用了一个异步回调,当 MediaPlayer 播放完歌曲后,我会获得通知,在此期间,我可以执行其他操作,例如对用户的其他按钮点击操作做出响应,并等待着获得回调。
 

当音频文件完成播放时,要调用刚刚添加的这个 releaseMediaPlayer() 方法,这意味着需要对 MediaPlayer注册一个 onCompletionListener,注意,在 MediaPlayer对象执行start()后需要作出这一设置,将代码添加到 mMediaPlayer.start() 这行的下面,在 onCompletion 方法回调中,我可以调用releaseMediaPlayer 方法。

文档显示:建议一旦不再使用MediaPlayer对象,立即调用release()以便可以立即释放与MediaPlayer对象关联的内部播放器引擎使用的资源。资源可能包括单一资源(如硬件加速组件),没有调用release()可能导致后续的MediaPlayer实例回退到软件实现或完全失败。一旦MediaPlayer对象处于End状态,就无法再使用它,也无法将其恢复到任何其他状态。

我们可以创建一个该 onCompletionListener 的实例,并且每次都重复使用它,而不用每次点击某个列表项时都创建一个新的 onCompletionListener,这么做会更高效,因为我们就不用每次都创建新的对象并占用新的资源,我将这段用来实现 onCompletionListener 接口的代码段移走用一个全局变量来保存,并用mCompletionListener 的全局变量指向这个实例,每次我创建一个新的 MediaPlayer 对象,并开始该 MediaPlayer 时,我可以将 CompletionListener 设置为该全局变量传入 mCompletionListener。当它播放完声音文件后,它就会释放该 MediaPlayer 资源。

在 MediaPlayer 被创建初始化以便播放不同的声音前,也要释放 MediaPlayer 资源这么做是为了配置播放不同的音频文件而准备的。出现这一情况可能是比如用户连续快速点按了多个列表项,设备可能没有足够的时间播放完每个音频文件,因此 onCompletionListener 可能未被触发,如果我们正在播放某个音频,用户点击了完全不同的音频文件,那么我们需要停止播放并释放该 MediaPlayer 资源,然后立即为当前的歌曲创建一个新的 MediaPlayer 对象。如果初始化前不释放MediaPlayer资源,快速点击会出现音频重叠播放的情况。

我们再来看看关于 MediaPlayer 类中的release 方法的文档

public void release ()

释放与此MediaPlayer对象关联的资源。使用MediaPlayer后调用此方法被认为是一种好习惯。特别是每当应用程序的Activity暂停(调用其onPause()方法)或停止(调用其onStop()方法)时,应调用此方法以释放MediaPlayer对象,除非应用程序具有特殊功能需要保持对象。除了持有不必要的资源(例如内存和编解码器实例)之外,当不再需要MediaPlayer对象时,若没有立即调用此方法也可能导致移动设备的电池持续消耗如果设备上不支持同一编解码器的多个实例,没有调用release()则会导致其他应用程序的播放失败。即使支持同一编解码器的多个实例,当同时使用不必要的多个实例时,可能会出现一些性能下降。


文档在这里指出 当 Activity 通过 onPause 方法被暂停后或通过 onStop 方法被停止后,需要调用release方法,以便释放 MediaPlayer 对象,要么是在onPause方法,要么是在onStop方法,因为只需要释放一次,我们将选择在 onStop 方法中释放我们的资源,当 Activity 完全针对用户隐藏后,即使没有播放完当前的音频文件,也将释放媒体资源。

现在重写 onStop Activity 生命周期回调方法。注意,每次要重写 Activity 生命周期方法,我们都应该调用该方法的超级类版本,即super.onStop(),因为该方法知道如何停止 Activity,并在后台清空资源,我们不需要操心这些。当我们注释掉这行代码,应用将出现异常崩溃。

添加onStop后代码如下:

public class NumbersActivity extends AppCompatActivity {
    private MediaPlayer mMediaPlayer;
    private static final String TAG = "NumbersActivity";

    private MediaPlayer.OnCompletionListener mCompletionListener = new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mediaPlayer) {
            releaseMediaPlayer();
        }
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.word_list);

        // Create a list of words
        .......
    }

    @Override
    protected void onStop() {
        super.onStop();
        // When the activity is stopped, release the media player resources because we won't
        // be playing any more sounds.
        releaseMediaPlayer();
    }


    /**
     * Clean up the media player by releasing its resources.
     */
    private void releaseMediaPlayer() {
        // If the media player is not null, then it may be currently playing a sound.
        if (mMediaPlayer != null) {
            // Regardless of the current state of the media player, release its resources
            // because we no longer need it.
            mMediaPlayer.release();

            // Set the media player back to null. For our code, we've decided that
            // setting the media player to null is an easy way to tell that the media player
            // is not configured to play an audio file at the moment.
            mMediaPlayer = null;
        }
    }
}

这样无论是该 Activity 完成音频文件的播放还是被停止了,我们都可以释放该 Activity 中的MediaPlayer 资源。现在如果我播放某个发音,然后通过点按主屏幕按钮立即离开该应用,会立即停止播放发音,因为我添加了这段 onStop 代码,如果没有onStop()里面添加的逻辑代码,那么离开该 Activity时,每个单词的发音还会继续播放,现在离开该 Activity 播放会中断。

 

====================Talk is cheap, show me the code====================