当查阅 MediaPlayer 文档时 你会发现这个方法setOnCompletionListener,这里的说明指出该方法允许你注册一个回调。当媒体资源或音频文件到达结束位置时会回调该方法,注意该方法的输入是OnCompletionListener
部分代码如下:
代码语言:javascript复制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后代码如下:
代码语言:javascript复制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 播放会中断。