安卓多媒体框架支持多种的通用多媒体类型的播放,所以你能轻易的整合音频、视频和图片到你的应用中。使用MediaPlayer APIs 你能够播放在你应用的raw资源中、在文件系统中和网络上的多媒体文件。
这个文档将告诉你如何写一个与用户和系统交互并获得好的性能和用户体验的APK。注意:只能使用标准的输出设备播放音频文件,你不能够在通话中播放音频。
基本要素
MediaPlayer:这个类是播放音频和视频的主要的API。
AudioManager:这个类管理一个设备的音频资源和音频输出。
Manifest中的声明
在使用MediaPlayer开始开发你的应用之前,请确保你的manifest有合适的声明。
internet permission:如果你需要使用网络上的资源,那么你必须要声明网络权限。
<uses-permission android:name="android.permission.INTERNET" />
Wake Lock Permission:如果你的app需要阻止屏幕变暗或者处理器休眠,或者使用MediaPlayer.setScreenOnWhilePlaying()或者MediaPlayer.setWakeMode()方法,你必须要声明如下权限。
<uses-permission android:name="android.permission.WAKE_LOCK" />
使用MediaPlayer
在多媒体framework的一个重要组件是MediaPlayer类。这个类的对象仅需简单设置就可以获得、解码、然后播放音频和视频文件。它支持多种多媒体源:
1、本地资源
2、internal URIS
3、External URLS
这有一个使用APK raw 资源的例子
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
在这个例子中,一个raw资源是一个系统不用特别解析的资源。然而,这个资源的内容不一定是raw audio,它应该是一个合理编码和格式的
多媒体文件。
这有一个使用本地uri地址的例子
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
从一个远程url的例子:
String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();
注意:当使用setDataSource的时候,必须要捕获或者抛出IllegalArgumentException和IOException,因为有可能引用的资源不存在。
Asynchronous Preparation
使用MediaPlayer原则上是可以很简单的。然而,有一些重要的事情是你必须要正确整合到你的apk中。例如,当你调用prepare方法的时候,有可能需要花费很长时间去执行,因为它要获得和解码多媒体资源。因此,一定不能再app的UI线程中调用这些方法。在UI线程中调用这些方法会引起非常不好的用户体验和引起ANR。即使你认为你的资源加载的很快,但是任何操作1/10秒的反映在UI中都会引起一个显而易见的暂停,用户会有一个应用卡顿的感觉。
为了避免挂起你的ui线程,产生另一个线程去准备MediaPlayer然后当准备好了之后去提示主线程。你在新线程中可以按照自己的逻辑去写,不过安卓框架提供了一个非常方便的方法——prepareAsync。这个方法在后台准备多媒体资源,当资源准备好之后会立即通知在MediaPlayer.OnPreparedListener的onPrepared方法。
Managing State
MediaPlayer的另外一个方面是你必须谨记状态。MediaPlayer有一个内部状态,这个状态觉得了你如何编写你的代码。因为一些操作仅仅在一些特定状态下有效。如果你在一个错误的状态下进行操作,那么系统可能抛出异常或者引起一些期待之外的行为。
MediaPlayer的api中有一个状态图明确指出了从一个状态到另外一个状态所使用的方法。例如,当你创建一个新的MediaPlayer的时候,它是在idle状态。在这个时候,你应该来调用setDataSource方法来初始化,使得MediaPlayer进入initialized状态。在此之后,你应该使用prepare或者prepareAsync方法来进入prepare,当准备接受的时候,它将进入prepared状态。这个时候你应该调用start方法来播放多媒体。根据状态图阐释,你能够通过调用start、pause、seekto来移动started、paused和playbackcompleted状态。当你调用stop方法的时候,注意你不能再次调用start方法,只到你再次准备好MediaPlayer。
将状态图谨记在心,当你编写代码的时候才能不因为在一些状态下调用错误的方法而引起一些bug。
释放MediaPlayer
一个MediaPlayer会消耗掉宝贵的系统资源。因此,你应该常常防范不在不需要的时候过多的闲置你的MediaPlayer。当你使用完毕,你就应该调用release方法来释放掉这些系统资源。例如,当你的receivers调用了一个onstop方法,你必须释放MediaPlayer,因为在后台挂起是没有意义的。除非在后台与用户交互(比如后台播放音乐。)释放MediaPlayer的方法:
mediaPlayer.release();
mediaPlayer = null;
有这么一种情况,如果你忘了释放掉MediaPlayer当你activity停止的时候,当时创建了一个新的对象在你activity再次启动的时候。这是一个很常见的情况,在自动旋转开启的时候,当你不停的旋转屏幕那么就会不停的创建MediaPlayer对象造成资源浪费。
使用服务来控制MediaPlayer
如果你想你的多媒体文件在后台播放,你必须要开启一个service来控制MediaPlayer。你应该注意这个设置,因为用户和系统希望能够按照期望服务,如果你不能够满足期望那么将会有一个非常不好的用户体验。
首先,像一个activity一样service是在一个单独的线程中的。服务需要快速响应intent,并且不进行大量的计算。如果有任何的繁重工作在service中执行,那么可以采用异步的方式。当使用一个MediaPlayer从你的主线程中的时候,你应该调用prepareAsync方法会比prepare方法要好。然后实现MediaPlayer.OnPreparedListener来提醒。
public class MyService extends Service implements MediaPlayer.OnPreparedListener {
private static final String ACTION_PLAY = "com.example.action.PLAY";
MediaPlayer mMediaPlayer = null;
public int onStartCommand(Intent intent, int flags, int startId) {
...
if (intent.getAction().equals(ACTION_PLAY)) {
mMediaPlayer = ... // initialize it here
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.prepareAsync(); // prepare async to not block main thread
}
}
/** Called when MediaPlayer is ready */
public void onPrepared(MediaPlayer player) {
player.start();
}
}
当异步的时候遇到一些错误的时候我们可以通过一些方法来处理错误。
public class MyService extends Service implements MediaPlayer.OnErrorListener {
MediaPlayer mMediaPlayer;
public void initMediaPlayer() {
// ...initialize the MediaPlayer here...
mMediaPlayer.setOnErrorListener(this);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// ... react appropriately ...
// The MediaPlayer has moved to the Error state, must be reset!
}
}
使用WakeLock
当在后台播放音乐的时候,设备可能在服务运行的过程中休眠。主要是为了续航时间。
为了确保你的服务能够一直运行,那么你需要使用wakelock。请注意:只要能够释放的wakelock的时候请立即释放,因为长时间持有会影响续航。
mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
如果需要使用wifi的话,也可以取得wifilock
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();
获取当前的音频焦点
有的时候我们在播放音乐的时候,如果打开了一个视频或者其他什么东西,那么音频焦点可能会变化。这个时候如果在播放其他东西的时候你的音乐还在播放会改用户一个非常糟糕的体验。
首先判断音频焦点
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// could not get audio focus.
}
当焦点改变的时候,我们可以通过监听一些改变来得到当前的状态:
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// resume playback
if (mMediaPlayer == null) initMediaPlayer();
else if (!mMediaPlayer.isPlaying()) mMediaPlayer.start();
mMediaPlayer.setVolume(1.0f, 1.0f);
break;
case AudioManager.AUDIOFOCUS_LOSS:
// Lost focus for an unbounded amount of time: stop playback and release media player
if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// Lost focus for a short time, but we have to stop
// playback. We don't release the media player because playback
// is likely to resume
if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// Lost focus for a short time, but it's ok to keep playing
// at an attenuated level
if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);
break;
}
}
操作AUDIO_BECOMING_NOISY intent
比如你的耳机插在手机中,但是突然被拔出了。这个时候你不希望声音还在一直播放,所以这个时候要用到这个intent。
注册一个广播,当接收到这样情况发生的时候你可以把你的播放关掉,也可以让他声音变小之类的。
public class MusicIntentReceiver extends android.content.BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent intent) {
if (intent.getAction().equals(
android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
// signal your service to stop playback
// (via an Intent, for instance)
}
}
}
<receiver android:name=".MusicIntentReceiver">
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>