使用服务的MediaPlayer
如果想要的媒体在后台播放,即使应用程序不在屏幕上,也就是说在用户跟其他应用程序交互时,媒体文件也能继续播放,那么就必须启动一个服务(Service),并且在服务中控制MediaPlayer实例。你应该关注这种情况,因为用户和系统都希望运行后台服务的应用程序应该跟系统的空闲时间交互。如果应用程序不能满足这种期望,那么用户可能会有很坏的体验。本节会向你介绍这些主要的问题,并提供解决它们的方法。
异步运行
首先,实际上默认情况下,Activity以及在Service中的所有工作都是在一个单一线程中执行的,如果在同一个应用程序中运行一个Activity和Service,那么默认情况下,它们都会使用同一个线程(main线程)。因此,服务需要快速的处理输入的请求,并且在响应请求时,不要处理长时的计算处理。如果有繁重的工作或阻塞调用,就必须采用异步的方式:既可以采用另一个线程来执行你的处理,也可以使用很多由框架提供的便利的异步处理。
例如,在main线程中使用MediaPlayer时,应该调用prepareAsync()方法,而不是prepare(),并且为了在准备完成时便于通知你启动播放处理,你要实现MediaPlayer.OnPrepare监听器,例如:
public class MyService extends Service implements MediaPlayer.OnPreparedListener {
private static final 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();
}
}
处理异步错误
在同步操作上,通常错误会发出一个异常信号或错误代码,但是在使用异步资源时,应该确保应用程序获取正确的错误通知。在MediaPlayer场景中,通过实现MediaPlayer.OnError监听器,并把它设置给MediaPlayer实例来达到目的:
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!
}
}
重要的是要记住:在错误发生时,MediaPlayer会转移到Error状态,在你能够在再次使用它之前,你必须要对它进行重置。
使用唤醒锁(wake lock)
在设计在后台播放媒体的应用程序时,在服务还在运行时,设备可能进入休眠状态。因为Android系统会在设备休眠时来尝试保存电量,所以系统会尝试关掉所有不需要的电话功能,包括CPU和WiFi等硬件。但是,如果你的服务正在播放或流化音乐,就要防止系统对你的播放动作的干扰。
为了确保你的服务能够在这些条件下能够继续运行,就必须使用“wake locks”。唤醒锁是一种以信号的方式,把应用程序正在使用的功能发送给系统,要求系统即使是在系统空闲的时候也要保留这些功能的有效性。
注意:应该尽量少的使用wake locks,只在真正需要的时候才持有它们,因为它们会直接减少电池的使用时间。
在MediaPlayer播放过程中要保证CPU继续运行,就要在MediaPlayer初始化时,调用setWakeMode()方法。一旦你调用了这个方法,MediaPlayer对象就在播放时持有了这个特定的锁,并且要在挂起或终止时释放这个锁:
mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
但是,在这个例子中所获得的唤醒锁只保证CPU保持工作状态。如果正在播放的流媒体是基于网络的,并且你正在使用Wi-Fi功能,那么还要持有WifiLock,这个锁必须要手动的获取和释放。因此在使用远程的URL来开始准备MediaPlayer时,应该创建和获取Wi-Fi锁。例如:
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();
在服务挂起或终止媒体播放时,或在不在需要网络时,你就应该释放这个锁:
wifiLock.release();