我知道如何在一个Activity中监听音量按钮。但是我能在后台服务中做到吗?如果可以,应该怎么做?
这是可能的。使用下面的代码(对于较新的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);
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上,观察系统设置确实起作用,就像在旧平台上一样。 - MH.receiver
变量? - Denys Kniazhev-Support Ukraine很遗憾,这是 Android 中另一个有五种不同方法“解决问题”的领域之一,但它们中的大多数效果都不太好。为了保持自己的理智,我将尝试列出下面所有不同的方法。
MediaSession
(来自 Service)由 Denis Kniazhev 提供答案: https://dev59.com/o2kw5IYBdhLWcg3wE2dA#43304591
缺点:
- 需要 Android API 级别 21+ (Android 5.0+)。
android.media.VOLUME_CHANGED_ACTION
(来自 Service)由 Nikhil 提供答案: https://dev59.com/AaHia4cB1Zd3GeqPZtez#44040282
缺点:
- 不是 SDK 的官方部分:https://dev59.com/AV_Va4cB1Zd3GeqPUpbW#8974510
- 忽略音量键的第一次按下(因为它只显示音量条)。
- 当音量为 100% 时忽略音量加键,当音量为 0% 时忽略音量减键。
ContentObserver
(来自 Service)由ssuukk回答: https://dev59.com/o2kw5IYBdhLWcg3wE2dA#15292255 (第一部分)
缺点:
- 在较新版本的Android中不起作用:dsemi的评论
- 忽略音量键的第一次按下(因为它只显示音量条)。
- 当音量为100%时,会忽略音量增加键,当音量为0%时,会忽略音量减小键。
AudioManager.registerMediaButtonEventReceiver
(来自服务)由Joe回答:https://dev59.com/o2kw5IYBdhLWcg3wE2dA#11510564
缺点:
- 在大多数rom上不起作用:elgui的评论
onKeyDown
(来自Activity)由dipali回答:https://dev59.com/QGEi5IYBdhLWcg3wpdnJ#21086563
缺点:
- 如果屏幕关闭、在其他应用程序中等,则无法工作。
dispatchKeyEvent
(来自Activity)Maurice Gavin的答案:https://dev59.com/o2kw5IYBdhLWcg3wE2dA#11462962
缺点:
- 如果屏幕关闭、在其他应用程序中等,则无法工作。
我目前正在使用的解决方案是#1,因为:
如果您找到任何其他解决方案,请告诉我-或者如果您发现某些解决方案的更多缺点!
AOSP音乐应用具有服务(MediaPlaybackService),通过注册BroadcastReceiver来响应音量键事件(MediaButtonIntentReceiver)。
以下代码片段显示了注册接收器的位置:
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
在安卓5+设备上使其正常工作。然而,@ssuukk建议的ContentObserver
在我测试的4.4和7.0设备上都不起作用(至少在ROM上是这样的)。
以下是一个完整的示例,可在安卓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));
}
需要注意以下几点:
设置->应用
,找到该应用并强制停止才能恢复音量按钮。 });
}` - VasFouprivate 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();
}
@venryx: 解决方案1在Android 12中不再适用
@ssuukk: 我可以确认@venryx的评论,如果音量已经达到最小或最大值,则SettingsContentObserver不会被触发。
@bikram: 我创建了一个VolumeButtonHelper类,使用了这种方法。虽然它使用了一个未记录的SDK功能,但它仍然在2022年起作用。我对这个主题进行了广泛的研究,这是我能找到的唯一解决方案。
class VolumeButtonHelper(private var context: Context,
private var stream: Int? = null,
enabledScreenOff: Boolean)
{
companion object
{
const val VOLUME_CHANGE_ACTION = "android.media.VOLUME_CHANGED_ACTION"
const val EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE"
}
enum class Direction
{
Up,
Down,
Release
}
private lateinit var mediaPlayer: MediaPlayer
private var volumeBroadCastReceiver: VolumeBroadCastReceiver? = null
private var volumeChangeListener: VolumeChangeListener? = null
private val audioManager: AudioManager? =
context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
private var priorVolume = -1
private var volumePushes = 0.0
private var longPressReported = false
var doublePressTimeout = 350L
var buttonReleaseTimeout = 100L
var minVolume = -1
private set
var maxVolume = -1
private set
var halfVolume = -1
private set
var currentVolume = -1
private set
init
{
if (audioManager != null)
{
minVolume = audioManager.getStreamMinVolume(STREAM_MUSIC)
maxVolume = audioManager.getStreamMaxVolume(STREAM_MUSIC)
halfVolume = (minVolume + maxVolume) / 2
/*************************************
* BroadcastReceiver does not get triggered for VOLUME_CHANGE_ACTION
* if the screen is off and no media is playing.
* Playing a silent media file solves that.
*************************************/
if (enabledScreenOff)
{
mediaPlayer =
MediaPlayer.create(context,
R.raw.silence)
.apply {
isLooping = true
setWakeMode(context, PARTIAL_WAKE_LOCK)
start()
}
}
}
else
Log.e(TAG, "Unable to initialize AudioManager")
}
fun registerVolumeChangeListener(volumeChangeListener: VolumeChangeListener)
{
if (volumeBroadCastReceiver == null)
{
this.volumeChangeListener = volumeChangeListener
volumeBroadCastReceiver = VolumeBroadCastReceiver()
if (volumeBroadCastReceiver != null)
{
val filter = IntentFilter()
filter.addAction(VOLUME_CHANGE_ACTION)
context.registerReceiver(volumeBroadCastReceiver, filter)
}
else
Log.e(TAG, "Unable to initialize BroadCastReceiver")
}
}
fun unregisterReceiver()
{
if (volumeBroadCastReceiver != null)
{
context.unregisterReceiver(volumeBroadCastReceiver)
volumeBroadCastReceiver = null
}
}
fun onVolumePress(count: Int)
{
when (count)
{
1 -> volumeChangeListener?.onSinglePress()
2 -> volumeChangeListener?.onDoublePress()
else -> volumeChangeListener?.onVolumePress(count)
}
}
interface VolumeChangeListener
{
fun onVolumeChange(direction: Direction)
fun onVolumePress(count: Int)
fun onSinglePress()
fun onDoublePress()
fun onLongPress()
}
inner class VolumeBroadCastReceiver : BroadcastReceiver()
{
override fun onReceive(context: Context, intent: Intent)
{
if (stream == null ||
intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1) == stream)
{
currentVolume = audioManager?.getStreamVolume(STREAM_MUSIC) ?: -1
if (currentVolume != -1)
{
if (currentVolume != priorVolume)
{
if (currentVolume > priorVolume)
volumeChangeListener?.onVolumeChange(Up)
else if (currentVolume < priorVolume)
volumeChangeListener?.onVolumeChange(Down)
priorVolume = currentVolume
}
volumePushes += 0.5 // For some unknown reason (to me), onReceive gets called twice for every button push
if (volumePushes == 0.5)
{
CoroutineScope(Dispatchers.Main).launch {
delay(doublePressTimeout - buttonReleaseTimeout)
buttonDown()
}
}
}
}
}
private fun buttonDown()
{
val startVolumePushes = volumePushes
CoroutineScope(Dispatchers.Main).launch {
delay(buttonReleaseTimeout)
val currentVolumePushes = volumePushes
if (startVolumePushes != currentVolumePushes)
{
if (volumePushes > 2 && !longPressReported)
{
longPressReported = true
volumeChangeListener?.onLongPress()
}
buttonDown()
}
else
{
onVolumePress(volumePushes.toInt())
volumeChangeListener?.onVolumeChange(Release)
volumePushes = 0.0
longPressReported = false
}
}
}
}
}
在一个服务中实例化该类(使用适当的唤醒锁):
class ForegroundService : Service()
{
private lateinit var volumeButtonHelper: VolumeButtonHelper
companion object
{
var wakeLock: WakeLock? = null
const val TAG = "VolumeButtonHelper"
const val ACTION_FOREGROUND_WAKELOCK = "com.oliverClimbs.volumeButtonHelper.FOREGROUND_WAKELOCK"
const val ACTION_FOREGROUND = "com.oliverClimbs.volumeButtonHelper.FOREGROUND"
const val WAKELOCK_TAG = "com.oliverClimbs.volumeButtonHelper:wake-service"
const val CHANNEL_ID = "Running in background"
}
override fun onBind(p0: Intent?): IBinder?
{
return null
}
override fun onCreate()
{
super.onCreate()
volumeButtonHelper = VolumeButtonHelper(this,
STREAM_MUSIC,
enabledScreenOff = true)
volumeButtonHelper.registerVolumeChangeListener(
object : VolumeButtonHelper.VolumeChangeListener
{
override fun onVolumeChange(direction: VolumeButtonHelper.Direction)
{
Log.i(TAG, "onVolumeChange: $direction")
}
override fun onVolumePress(count: Int)
{
Log.i(TAG, "onVolumePress: $count")
}
override fun onSinglePress()
{
Log.i(TAG, "onSinglePress")
}
override fun onDoublePress()
{
Log.i(TAG, "onDoublePress")
}
override fun onLongPress()
{
Log.i(TAG, "onLongPress")
}
})
}
@SuppressLint("WakelockTimeout")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int
{
super.onStartCommand(intent, flags, startId)
if (intent?.action == ACTION_FOREGROUND || intent?.action == ACTION_FOREGROUND_WAKELOCK)
startForeground(R.string.foreground_service_started,
Notification.Builder(this, CHANNEL_ID).build())
if (intent?.action == ACTION_FOREGROUND_WAKELOCK)
{
if (wakeLock == null)
{
wakeLock = getSystemService(PowerManager::class.java)?.newWakeLock(
PARTIAL_WAKE_LOCK,
WAKELOCK_TAG)
wakeLock?.acquire()
}
else
{
releaseWakeLock()
}
}
return START_STICKY
}
private fun releaseWakeLock()
{
wakeLock?.release()
wakeLock = null
}
override fun onDestroy()
{
super.onDestroy()
releaseWakeLock()
stopForeground(STOP_FOREGROUND_REMOVE)
volumeButtonHelper.unregisterReceiver()
}
}
从您的Activity启动服务:
class MainActivity : AppCompatActivity()
{
private var configurationChange = false
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (!configurationChange)
startService(Intent(ForegroundService.ACTION_FOREGROUND_WAKELOCK).setClass(this,
ForegroundService::class.java))
}
override fun onDestroy()
{
Log.d(TAG, "MainActivity: onDestroy")
configurationChange =
if (isChangingConfigurations)
true
else
{
stopService(Intent(this, ForegroundService::class.java))
false
}
super.onDestroy()
}
}
我已经在github.com/oliverClimbs/VolumeButtonDemo上分享了完整的项目。
我的目标是从服务中调整系统音量。但是,任何操作都可以在按下时执行。
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();
}
}
}
MyService.java
public class MyService extends Service {
private BroadcastReceiver vReceiver;
@Override
public void onCreate() {
super.onCreate();
vReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
FileLog.e("Something just happens");
}
};
registerReceiver(vReceiver, new IntentFilter("android.media.VOLUME_CHANGED_ACTION"));
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
unregisterReceiver(vReceiver);
}
}
AndroidManifest.xml
<application>
...
<service android:name=".MyService" android:exported="true"/>
</application>
onCreate || onStartActivity
public void onCreate(){
....
startService(new Intent(this, MyService.class));
}
那个解决方案有点类似于Answer
但是适用于Android 33及以上版本
对于我来说,辅助功能服务只能按预期工作
class KeyService : AccessibilityService() {
override fun onServiceConnected() {}
override fun onAccessibilityEvent(event: AccessibilityEvent) {}
override fun onKeyEvent(event: KeyEvent): Boolean {
when (event.keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> {
when (event.action) {
KeyEvent.ACTION_DOWN -> {
}
KeyEvent.ACTION_UP -> {
}
}
}
KeyEvent.KEYCODE_VOLUME_DOWN -> {
when (event.action) {
KeyEvent.ACTION_DOWN -> {
}
KeyEvent.ACTION_UP -> {
}
}
}
}
return super.onKeyEvent(event)
}
override fun onInterrupt() {}
}
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityFlags="flagRequestFilterKeyEvents"
android:canRequestFilterKeyEvents="true"
android:description="@string/app_name" />
<service
android:name=".KeySrvice"
android:exported="true"
android:label="@string/app_name"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/key" />
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
</service>
inline fun <reified T : Service> Context.hasAccessibility(): Boolean {
var enabled = 1
try {
enabled = Secure.getInt(contentResolver, Secure.ACCESSIBILITY_ENABLED)
} catch (ignored: Throwable) {
}
if (enabled == 1) {
val name = ComponentName(applicationContext, T::class.java).flattenToString()
val services = Secure.getString(contentResolver, Secure.ENABLED_ACCESSIBILITY_SERVICES)
return services?.contains(name) ?: false
}
return false
}
if (!hasAccessibility<KeyService>()) {
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
}