异步准备 - Asynchronous Preparation
使用MediaPlayer
为了避免你的UI线程挂起,产生另一个线程准备MediaPlayer
4.
状态管理 - Managing State
关于MediaPlayer
MediaPlayer
在编写代码与MediaPlayer
释放MediaPlayer-Releasing the MediaPlayer
MediaPlayer
下面是释放和取消你的MediaPlayer的方法:
mediaPlayer.release();
mediaPlayer = null;
作为思考题,考虑一下如果当活动停止的时候你忘了释放MediaPlayer,活动重启后新建一个MediaPlayer,可能会发生的问题。正如你可能知道的,当用户更改屏幕的方向(或以另一种方式更改设备配置),该系统通过重启活动处理(通过默认方式),所以当用户频繁在纵向和横向之间切换时,你可能会很快消耗掉所有的系统资源,原因是你没有释放方向变化时各个方向上创建的新MediaPlayer。(更多关于运行时重启的资料,请查看HandlingRuntime Changes)。
你可能会想知道在用户离开活动时后台继续播放媒体是如何实现的,采用同样的方式实现的,如内置的音乐应用程序的行为。在这种情况下,你需要通过一个Service来控制MediaPlayer,所以我们开始学习Usinga Service with MediaPlayer。
5.
使用服务控制MediaPlayer - Using a Service with MediaPlayer
如果你希望后台播放媒体,你希望用户操作其他应用时继续播放,你必须开始一个Service并且从那里控制MediaPlayer实例。你必须慎重考虑这个设置,因为用户与系统期望应用程序运行的后台服务应该与系统的其余部分相互作用。如果应用程序不满足这些预期,就不能有良好的用户体验。本节介绍的主要内容是:告诉你相关知识,并提供建议如何接触它们。
异步运行 - Running asynchronously
首先,如一个Activity,服务里的所有任务默认在单一线程中完成。如果你从同一个应用程序里运行一个Activity和一个Service,它们默认使用相同的线程(“主线程”)。因此,Service需要迅速处理传入的意图并且响应它们的时候从不执行冗长的计算。如果预计调用一些复杂的任务或阻塞,你必须异步处理这些任务:由另一个线程自己实现自己,或使用框架处理异步。
例如,当你从主要线程使用一个MediaPlayer
代码如下:
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
}
}
public void onPrepared(MediaPlayer player)
{
player.start();
}
}
处理异步错误 - Handling asynchronous errors
在同步操作中,错误通常会出现异常或错误代码信息。但当你使用异步资源时,您需要确保您的应用程序有错误提示,在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
使用唤醒锁 - Using wake locks
应用程序在后台播放媒体,其服务在运行期间,设备可能会进入休眠状态。因为Android系统希望在设备休眠时节省电池。系统试图关闭手机的应用程序,是没有必要的,包括CPU和WiFi硬件。但是,如果你的服务正在运行或播放着音乐,你希望防止系统干扰你的回放。
为了确保您的服务在这些条件下能继续运行,你需要使用“wakelocks”。唤醒锁是一种信号系统,它发出信号,显示:应用程序正在使用或可用的功能,或手机闲置。
注意:你应该尽量少用唤醒锁,只有在必要时候才使用它们。它们会使设备的电池寿命大大降低。
你MediaPlayer
mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
在这个例子中获得唤醒锁是指在保证CPU在唤醒状态。当你通过网络获取媒体和您正在使用WiFi时,你可能希望有个WifiLock,可以手动获取并释放。当你开始通过远程URL准备MediaPlayer,你应该创建并获得wi- fi锁。 代码如下:
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
.createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();
当你暂停或停止你的媒体时,或当你不再需要这样的网络,你应该释放该锁: 代码如下:
wifiLock.release();
作为前景服务运行 - Running as a foreground service
服务通常用于执行后台任务,例如获取电子邮件,同步数据,下载内容,或其他。在这些情况下,用户不会意识到这个服务的执行,甚至可能不会注意到这些服务被打断,后来重新启动。毫无疑问,后台播放音乐是一个服务,用户能意识到,任何中断都会严重影响到用户体验。此外,用户可能会希望在这个服务执行期间作用于它。这种情况,服务应该运行一个“前景服务”。前台服务在系统中持有一个更高水平的重要性,系统几乎从未将服务扼杀,因为它对用户有着直接的重要性。当应用在前台运行,该服务还必须提供一个状态栏来通知用户意识有服务正在运行同时允许他们打开一个活动,可以与服务进行交互。
为了把你的服务变为前景服务,您必须为状态栏创建一个Notification,并且从Service调用startForeground()方法。
代码如下:
String songName;
// assign the song name to songNamePendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,
new Intent(getApplicationContext(), MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification();
notification.tickerText = text;
notification.icon = R.drawable.play0;
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample",
"Playing: " + songName, pi);
startForeground(NOTIFICATION_ID, notification);
通知区域可见的设备告诉你,服务在前台运行。如果用户选择了这个通知,系统将调用你提供的PendingIntent。在上面的例子中,它打开了一个Activity。(MainActivity)
图1显示了如何将通知呈现给用户:
图1:界面的一个前景服务通知,如上图,显示通知图标(在左)、扩展视图(在右)。
实际执行一些用户能够意识到的服务时,你应该保留“foregroundservice”的状态。相反情况下,你应该调用stopForeground()方法来释放它。 代码如下:
stopForeground(true);
更多信息,请参考Service和
处理音频焦点 - Handling audio focus=
在给定的时间尽管只有一个活动可以运行,但Android是一个多任务环境。这对应用程序使用音频造成了一个特别大的难度,由于只有一个音频输出,可能会有好几个媒体服务争夺使用它。Android2.2之前,没有内置机制来解决这个问题,这可能在某些情况下导致糟糕的用户体验。例如,一个用户正在听音乐,同时,另一个应用程序有很重要的事需要通知用户,由于吵闹的音乐用户可能不会听到提示音。从Android2.2开始,Android平台为应用程序提供了一个方式来协商设备的音频输出。这个机制被称为音频焦点。
当您的应用程序需要输出音频如音乐或一个通知,这时你就必须请求音频焦点。一旦得到焦点,它就可以自由的使用声音输出设备,同时它会不断监听焦点的更改。如果它被通知已经失去了音频焦点,它会要么立即杀死音频或立即降低到一个安静的水平(被称为“ducking”——有一个标记,指示哪一个是适当的)当它再次接收焦点时,继续不断播放。
音频焦点是自然的合作。应用程序都期望(强烈鼓励)遵守音频焦点指南,但规则并不是系统强制执行的。如果应用程序失去音频焦点后想要播放嘈杂的音乐,在系统中没有什么会阻止他。然而,这样可能会让用户有更糟糕的体验,并可能卸载这运行不当的应用程序。
请求音频焦点,您必须从AudioManager调用requestAudioFocus()方法,下面展示一个例子:
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.}
requestAudioFocus()的第一个参数是AudioManager.OnAudioFocusChangeListen
代码如下:
class MyService extends Service
implements AudioManager.OnAudioFocusChangeListener {
// ....
public void onAudioFocusChange(int focusChange) {
// Do something based on focus change...
}
}
focusChange
- AUDIOFOCUS_GAIN: 你已经得到了音频焦点。
- AUDIOFOCUS_LOSS:你已经失去了音频焦点很长时间了。你必须停止所有的音频播放。因为你应该不希望长时间等待焦点返回,这将是你尽可能清除你的资源的一个好地方。例如,你应该释放MediaPlayer。
- AUDIOFOCUS_LOSS_TRANSIENT:你暂时失去了音频焦点,但很快会重新得到焦点。你必须停止所有的音频播放,但是你可以保持你的资源,因为你可能很快会重新获得焦点。
- AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:你暂时失去了音频焦点,但你可以小声地继续播放音频(低音量)而不是完全扼杀音频。
下面是一个示例实现:
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;
}
}
记住,音频焦点APIs在API级别8(Android2.2)及以上才有效。所以如果你想要支持的以前版本的Android,(如果有的话)你应该采取一种向后兼容性策略,允许您使用该特性,(如果没有的话),只能选择8以后的版本。
通过反射调用音频焦点方法或通过在一个单独类中实现所有的音频焦点特性,您可以实现向后兼容性(AudioFocusHelper
public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
AudioManager mAudioManager;
// other fields here, you'll probably hold a reference to an interface
// that you can use to communicate the focus changes to your Service
public AudioFocusHelper(Context ctx, ) {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
// ...
}
public boolean requestFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC,
AudioManager.AUDIOFOCUS_GAIN);
}
public boolean abandonFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED ==
mAudioManager.abandonAudioFocus(this);
}
@Override
public void onAudioFocusChange(int focusChange) {
// let your service know about the focus change
}
}
当你发现系统运行时API级别在8级或以上时,您可以创建AudioFocusHelper
if (android.os.Build.VERSION.SDK_INT >= 8) {
mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
} else {
mAudioFocusHelper = null;
}
执行清理 - Performing cleanup
正如前面所提到的,MediaPlayer
代码如下:
public class MyService extends Service {
MediaPlayer mMediaPlayer;
// ...
@Override
public void onDestroy() {
if (mMediaPlayer != null) mMediaPlayer.release();
}
}
你最好始终寻找其它机会来释放你的MediaPlayer,关闭的时候就释放掉。例如,如果你期望较长的一段时间不能够播放媒体(例如,失去音频焦点后),你肯定得先释放你现有的MediaPlayer
处理AUDIO_BECOMING_NOISY意图 - Handling the AUDIO_BECOMING_NOISYIntent
许多编写良好的应用程序有以下特点,当一个事件导致音频变得聒噪时,自动停止音频播放。(通过外部扬声器输出)。例如,一个用户戴着耳机听音乐,可能会不小心切断耳机和设备的链接。虽然,这种行为不会自动发生。如果您没有实现这个特性,设备的外部扬声器会将音频播放出来,这可能是用户不希望发生的。
这些情况下通过处理ACTION_AUDIO_BECOMING_NOISY意图,可以让你的应用程序停止播放音乐,通过在你的manifest里添加以下代码,你可以注册一个接收器:
".MusicIntentReceiver">
"android.media.AUDIO_BECOMING_NOISY" />
注册MusicIntentReceiver
public class MusicIntentReceiver implements 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)
}
}
}
从内容解析器检索媒体 - Retrieving Media from a Content Resolver
在媒体播放器应用程序中,另一个可能有用的特性是用户可以在设备上检索音乐。你可以通过为外部媒体查询ContentResolver,代码如下:
ContentResolver contentResolver = getContentResolver();
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = contentResolver.query(uri, null, null, null, null);
if (cursor == null) {
// query failed, handle error.
} else if (!cursor.moveToFirst()) {
// no media on the device
} else {
int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
do {
long thisId = cursor.getLong(idColumn);
String thisTitle = cursor.getString(titleColumn);
// ...process entry...
} while (cursor.moveToNext());
}
要和MediaPlayer一起使用,你可以这样做,代码如下:
long id = ;
Uri contentUri = ContentUris.withAppendedId(
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(getApplicationContext(), contentUri);
// ...prepare and start...
原文地址:http://blog.sina.com.cn/s/blog_6324d6990101dg3g.html