【发布时间】:2012-04-26 14:26:50
【问题描述】:
我知道如何在活动中收听音量按钮。但是我可以在后台服务中做到这一点吗?如果是,该怎么做?
【问题讨论】:
-
您需要听音量按钮,还是音量变化?
-
请注意,这目前不适用于 Android 12(在后台):issuetracker.google.com/issues/201546605
我知道如何在活动中收听音量按钮。但是我可以在后台服务中做到这一点吗?如果是,该怎么做?
【问题讨论】:
这是可能的。使用以下代码(对于较新的 Android 版本,尤其是 Marshmallow,请参阅答案底部):
public class SettingsContentObserver extends ContentObserver {
int previousVolume;
Context context;
public SettingsContentObserver(Context c, Handler handler) {
super(handler);
context=c;
AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
previousVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC);
}
@Override
public boolean deliverSelfNotifications() {
return super.deliverSelfNotifications();
}
@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
int currentVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC);
int delta=previousVolume-currentVolume;
if(delta>0)
{
Logger.d("Ściszył!"); // volume decreased.
previousVolume=currentVolume;
}
else if(delta<0)
{
Logger.d("Zrobił głośniej!"); // volume increased.
previousVolume=currentVolume;
}
}
}
然后在你的服务 onCreate 中注册它:
mSettingsContentObserver = new SettingsContentObserver(this,new Handler());
getApplicationContext().getContentResolver().registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, mSettingsContentObserver );
然后在 onDestroy 中注销:
getApplicationContext().getContentResolver().unregisterContentObserver(mSettingsContentObserver);
注意这个例子是根据媒体音量的变化来判断的,如果你想使用其他的音量,就改变吧!
更新:
上面的方法据说在 Marshmallow 上不起作用,但是自从 MediaSession 被引入以来,现在有了更好的方法!所以首先你必须将你的代码迁移到 MediaController/MediaSession 模式,然后使用这个代码:
private VolumeProviderCompat myVolumeProvider = null;
myVolumeProvider = new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, maxVolume, currentVolume) {
@Override
public void onAdjustVolume(int direction) {
// <0 volume down
// >0 volume up
}
};
mSession.setPlaybackToRemote(myVolumeProvider);
即使屏幕关闭也能以某种方式检测到音量按钮按下(只要确保注册适当的媒体按钮意图接收器,如果适用于您的平台!)
更新 2,因为 GalDude 要求提供有关获取媒体 MediaSession/MediaController 的更多信息。抱歉,由于我停止使用 Java,它将使用 Kotlin:
lateinit var mediaSession: MediaSessionCompat // you have to initialize it in your onCreate method
val kontroler: MediaControllerCompat
get() = mediaSession.controller // in Java it's just getController() on mediaSession
// in your onCreate/start method:
mediaSession = MediaSessionCompat(this, "YourPlayerName", receiver, null)
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
mediaSession.isActive = true
if (ratingIsWorking) // note: rating crashes on some machines you have to check it!
mediaSession.setRatingType(RatingCompat.RATING_5_STARS)
mediaSession.setCallback(object : MediaSessionCompat.Callback() {
...
// here you have to implement what happens with your player when play/pause/stop/ffw etc. is requested - see exaples elsewhere
})
// onDestroy/exit method:
mediaSession.isActive = false
mediaSession.release()
【讨论】:
VolumeProviderCompat 仅适用于 API 21(+)。在我的 Nexus 5X 上,观察系统设置确实确实有效,就像在旧平台上一样。
AOSP 音乐应用有一个服务 (MediaPlaybackService),它通过注册一个 BroadcastReceiver (MediaButtonIntentReceiver) 来响应音量键事件。
这是注册接收器的代码sn-p:
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
ComponentName rec = new ComponentName(getPackageName(),
MediaButtonIntentReceiver.class.getName());
mAudioManager.registerMediaButtonEventReceiver(rec);
另外,不要忘记清单:
<receiver android:name="com.android.music.MediaButtonIntentReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
即使音乐应用程序不在前台,此功能也有效。这不是你想要的吗?
【讨论】:
我能够使用MediaSession 在 android 5+ 设备上运行它。但是,@ssuukk 建议的ContentObserver 在 4.4 和 7.0 设备上都不适用于我(至少在我一直在测试的 ROM 上)。
这是一个适用于 android 5+ 的完整示例。
服务:
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.v4.media.VolumeProviderCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
public class PlayerService extends Service {
private MediaSessionCompat mediaSession;
@Override
public void onCreate() {
super.onCreate();
mediaSession = new MediaSessionCompat(this, "PlayerService");
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 0) //you simulate a player which plays something.
.build());
//this will only work on Lollipop and up, see https://code.google.com/p/android/issues/detail?id=224134
VolumeProviderCompat myVolumeProvider =
new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, /*max volume*/100, /*initial volume level*/50) {
@Override
public void onAdjustVolume(int direction) {
/*
-1 -- volume down
1 -- volume up
0 -- volume button released
*/
}
};
mediaSession.setPlaybackToRemote(myVolumeProvider);
mediaSession.setActive(true);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
mediaSession.release();
}
}
在AndroidManifest.xml:
<application ...>
...
<service android:name=".PlayerService"/>
</application>
在你的活动中:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
startService(new Intent(this, PlayerService.class));
}
有几点需要注意:
Settings->Applications,找到该应用并强制停止它才能恢复音量按钮。【讨论】:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { mediaSession.setCallback(new MediaSessionCompat.Callback(){ }); }
不幸的是,这是 Android 的另一个领域,其中有五种不同的方法可以“解决问题”,但大多数都不能很好地工作。为了我自己的理智,我将尝试在下面列出所有不同的方法。
MediaSession(来自服务)Denis Kniazhev 的回答:https://stackoverflow.com/a/43304591/2441655
缺点:
- 需要 Android API 级别 21+ (Android 5.0+)。
android.media.VOLUME_CHANGED_ACTION(来自服务)Nikhil 的回答:https://stackoverflow.com/a/44040282/2441655
缺点:
- 不是 SDK 的官方部分:https://stackoverflow.com/a/8974510/2441655
- 忽略第一次按音量键(因为它只显示音量条)。
- 在 100% 时忽略音量增大键,在 0% 时忽略音量减小键。
ContentObserver(来自服务)ssuukk 回答:https://stackoverflow.com/a/15292255/2441655(第一部分)
缺点:
- 不适用于较新版本的 Android:comment by dsemi
- 忽略第一次按音量键(因为它只显示音量条)。
- 在 100% 时忽略音量增大键,在 0% 时忽略音量减小键。
AudioManager.registerMediaButtonEventReceiver(来自服务)乔回答:https://stackoverflow.com/a/11510564/2441655
缺点:
- 不适用于大多数 rom:comment by elgui
onKeyDown(来自活动)dipali 的回答:https://stackoverflow.com/a/21086563/2441655
缺点:
- 如果屏幕关闭、在不同的应用程序中等,则不起作用。
dispatchKeyEvent(来自活动)莫里斯·加文的回答:https://stackoverflow.com/a/11462962/2441655
缺点:
- 如果屏幕关闭、在不同的应用程序中等,则不起作用。
我目前使用的解决方案是#1,因为:
如果您发现任何其他问题 - 或者您是否发现其中一些问题的更多缺点,请告诉我!
【讨论】:
【讨论】:
您需要从服务中播放空白声音,然后才能收听音量变化。以下为我工作
private MediaPlayer mediaPlayer;
public MyService() {
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
........
mediaPlayer = MediaPlayer.create(this, R.raw.blank);
mediaPlayer.setLooping(true);
mediaPlayer.start();
.......
return START_STICKY;
}
@Override
public void onDestroy() {
mediaPlayer.stop();
mediaPlayer.release();
super.onDestroy();
}
int volumePrev = 0;
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if ("android.media.VOLUME_CHANGED_ACTION".equals(intent.getAction())) {
int volume = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_VALUE",0);
Log.i(TAG, "volume = " + volume);
if (volumePrev < volume) {
Log.i(TAG, "You have pressed volume up button");
} else {
Log.i(TAG, "You have pressed volume down button");
}
volumePrev = volume;
}
}
};
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
.....
IntentFilter filter = new IntentFilter();
filter.addAction("android.media.VOLUME_CHANGED_ACTION");
registerReceiver(broadcastReceiver, filter);
....
return START_STICKY;
}
@Override
public void onDestroy() {
.....
unregisterReceiver(broadcastReceiver);
.....
super.onDestroy();
}
【讨论】:
我的目标是通过服务调整系统音量。不过,任何操作都可以在媒体上采取。
public class VolumeKeyController {
private MediaSessionCompat mMediaSession;
private final Context mContext;
public VolumeKeyController(Context context) {
mContext = context;
}
private void createMediaSession() {
mMediaSession = new MediaSessionCompat(mContext, KeyUtil.log);
mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mMediaSession.setPlaybackState(new Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 0)
.build());
mMediaSession.setPlaybackToRemote(getVolumeProvider());
mMediaSession.setActive(true);
}
private VolumeProviderCompat getVolumeProvider() {
final AudioManager audio = mContext.getSystemService(Context.AUDIO_SERVICE);
int STREAM_TYPE = AudioManager.STREAM_MUSIC;
int currentVolume = audio.getStreamVolume(STREAM_TYPE);
int maxVolume = audio.getStreamMaxVolume(STREAM_TYPE);
final int VOLUME_UP = 1;
final int VOLUME_DOWN = -1;
return new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, maxVolume, currentVolume) {
@Override
public void onAdjustVolume(int direction) {
// Up = 1, Down = -1, Release = 0
// Replace with your action, if you don't want to adjust system volume
if (direction == VOLUME_UP) {
audio.adjustStreamVolume(STREAM_TYPE,
AudioManager.ADJUST_RAISE, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
else if (direction == VOLUME_DOWN) {
audio.adjustStreamVolume(STREAM_TYPE,
AudioManager.ADJUST_LOWER, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
setCurrentVolume(audio.getStreamVolume(STREAM_TYPE));
}
};
}
// Call when control needed, add a call to constructor if needed immediately
public void setActive(boolean active) {
if (mMediaSession != null) {
mMediaSession.setActive(active);
return;
}
createMediaSession();
}
// Call from Service's onDestroy method
public void destroy() {
if (mMediaSession != null) {
mMediaSession.release();
}
}
}
【讨论】:
在这种情况下,Android 不会记录与音量按钮交互的 API。所以我猜答案是否定的……
【讨论】:
checkout Controlling Your App’s Volume and Playback ...这将有助于解决您的问题...多个应用程序可能希望从后台侦听按钮按下,这可能是 KeyEvents 只能由活动处理的原因,因为它们是接口给用户按键。
【讨论】:
注意:这仅适用于活动,不适用于问题所述的服务。
根据需要回调的上下文,可能会有替代解决方案。
为了能够检测到音量按钮,Activity 需要覆盖 dispatchKeyEvent 函数。为了让这个出现在多个活动中,可以编写一个包含被所有后续活动扩展的重写函数的超类。
这是检测音量增大/减小按键的代码:
// Over-ride this function to define what should happen when keys are pressed (e.g. Home button, Back button, etc.)
@Override
public boolean dispatchKeyEvent(KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_DOWN)
{
switch (event.getKeyCode())
{
case KeyEvent.KEYCODE_VOLUME_UP:
// Volume up key detected
// Do something
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
// Volume down key detected
// Do something
return true;
}
}
return super.dispatchKeyEvent(event);
}
【讨论】: