收听后台服务中的音量按钮?

Posted

技术标签:

【中文标题】收听后台服务中的音量按钮?【英文标题】:Listen to volume buttons in background service? 【发布时间】:2012-04-26 14:26:50 【问题描述】:

我知道如何在活动中收听音量按钮。但是我可以在后台服务中做到这一点吗?如果是,该怎么做?

【问题讨论】:

您需要听音量按钮,还是音量变化? 请注意,这目前不适用于 android 12(在后台):issuetracker.google.com/issues/201546605 【参考方案1】:

这是可能的。使用以下代码(对于较新的 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()

【讨论】:

@ssuukk:屏幕关闭时会检测到音量键吗?或者您的解决方案是否仅适用于屏幕打开的情况? 此代码在应用程序处于暂停状态时有效。应用程序退出运行状态后如何检测音量变化? 这不再适用于 Android Marshmallow,所有音量设置已从 android.provider.Settings.System 中删除。 - developer.android.com/sdk/api_diff/23/changes/… 还是有可能的。我马上更新我的答案! 旁注:VolumeProviderCompat 仅适用于 API 21(+)。在我的 Nexus 5X 上,观察系统设置确实确实有效,就像在旧平台上一样。【参考方案2】:

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>

即使音乐应用程序不在前台,此功能也有效。这不是你想要的吗?

【讨论】:

音量按钮不被视为股票 ROM 中的“媒体按钮”。我猜这个应用程序能够获得音量按钮,因为它使用自定义的 android rom 运行...... @elgui 您能否提供支持您的话的文档链接? 好吧,使用stock rom,创建一个接收媒体按钮的接收器,按下音量按钮并观察没有任何反应...... 这里是文档链接androidxref.com/4.4.4_r1/xref/frameworks/base/media/java/… Google Play 音乐怎么样?它不是股票播放器,但它也管理媒体按钮,即使不在前台时也是如此......【参考方案3】:

我能够使用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-&gt;Applications,找到该应用并强制停止它才能恢复音量按钮。

【讨论】:

在 onAdjustVolume 中一无所获。我在 nexus 5 上运行 如果您添加此行代码,它确实有效。 :if (Build.VERSION.SDK_INT &gt;= Build.VERSION_CODES.LOLLIPOP) mediaSession.setCallback(new MediaSessionCompat.Callback() ); 这个解决方案很好,但是在屏幕关闭时不起作用,有什么解决方案吗?谢谢 @Daniele 当屏幕关闭时它对我有用,就像答案作者(丹尼斯)一样。 (我的手机:Android 9,小米 Mix 3) 如果您的 IDE 无法识别 MediaSessionCompat 类 ***.com/a/52019860/7953908,请检查此答案【参考方案4】:

不幸的是,这是 Android 的另一个领域,其中有五种不同的方法可以“解决问题”,但大多数都不能很好地工作。为了我自己的理智,我将尝试在下面列出所有不同的方法。

解决方案

1) MediaSession(来自服务)

Denis Kniazhev 的回答:https://***.com/a/43304591/2441655

缺点:

    需要 Android API 级别 21+ (Android 5.0+)。

2) android.media.VOLUME_CHANGED_ACTION(来自服务)

Nikhil 的回答:https://***.com/a/44040282/2441655

缺点:

    不是 SDK 的官方部分:https://***.com/a/8974510/2441655 忽略第一次按音量键(因为它只显示音量条)。 在 100% 时忽略音量增大键,在 0% 时忽略音量减小键。

3) ContentObserver(来自服务)

ssuukk 回答:https://***.com/a/15292255/2441655(第一部分)

缺点:

    不适用于较新版本的 Android:comment by dsemi 忽略第一次按音量键(因为它只显示音量条)。 在 100% 时忽略音量增大键,在 0% 时忽略音量减小键。

4) AudioManager.registerMediaButtonEventReceiver(来自服务)

乔回答:https://***.com/a/11510564/2441655

缺点:

    不适用于大多数 rom:comment by elgui

5) onKeyDown(来自活动)

dipali 的回答:https://***.com/a/21086563/2441655

缺点:

    如果屏幕关闭、在不同的应用程序中等,则不起作用。

6) dispatchKeyEvent(来自活动)

莫里斯·加文的回答:https://***.com/a/11462962/2441655

缺点:

    如果屏幕关闭、在不同的应用程序中等,则不起作用。

结论

我目前使用的解决方案是#1,因为:

    它是 SDK 的官方部分。 可以从服务中使用。 (即,无论您使用什么应用) 无论当前音量/用户界面状态如何,它都会捕获每次音量键按下。 它在屏幕关闭时工作。

如果您发现任何其他问题 - 或者您是否发现其中一些问题的更多缺点,请告诉我!

【讨论】:

#1 缺点:从另一个应用程序启动另一个媒体会话时,由于会话不再处于活动状态,因此您会松开音量监听器。如果您找到一种方法来保持当前媒体会话处于活动状态,我很乐意接受。我已经测试了上述所有解决方案,并且可以说直到今天这仍然是正确的..【参考方案5】:

从关于这个主题的其他几个问题来看,没有。

Other question 1, Other question 2

服务根本不接收 KeyEvent 回调。

【讨论】:

查看 my answer here 了解 AOSP 音乐应用程序是如何实现它的 :) 有一些方法可以从服务接收音量按钮事件。其实有四种不同的方式!有关不同方法的摘要,请参阅 my answer here。【参考方案6】:

您需要从服务中播放空白声音,然后才能收听音量变化。以下为我工作

步骤

1。将blank.mp3放入原始文件夹(从here下载)

2。在 onStartCommand() 处启动媒体

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;

3。您必须选择停止并释放媒体播放器。最好在 onDestroy() 中这样做

@Override
public void onDestroy() 

    mediaPlayer.stop();
    mediaPlayer.release();

    super.onDestroy();

4。创建将监听音量变化的广播接收器

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;
        
    
;

5。在 onStartCommand() 中注册广播接收器

@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;

6。在 onDestroy() 中注销广播接收器

 @Override
public void onDestroy() 
    .....

    unregisterReceiver(broadcastReceiver);

    .....

    super.onDestroy();

就是这样

【讨论】:

请注意,此解决方案/操作虽然有效,但并不是 SDK 的“正式”部分。见这里:***.com/a/8974510/2441655 这个解决方案很好用【参考方案7】:

这需要 Lollipop (v5.0/API 21) 或更高版本

我的目标是通过服务调整系统音量。不过,任何操作都可以在媒体上采取。

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();
        
    

【讨论】:

【参考方案8】:

在这种情况下,Android 不会记录与音量按钮交互的 API。所以我猜答案是否定的……

【讨论】:

【参考方案9】:

checkout Controlling Your App’s Volume and Playback ...这将有助于解决您的问题...多个应用程序可能希望从后台侦听按钮按下,这可能是 KeyEvents 只能由活动处理的原因,因为它们是接口给用户按键。

【讨论】:

【参考方案10】:

注意:这仅适用于活动,不适用于问题所述的服务。

根据需要回调的上下文,可能会有替代解决方案。

为了能够检测到音量按钮,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);
    

【讨论】:

这个仅在活动中运行良好!服务不会像活动那样列出关键事件。 这个回调只包含在Activity类中,不包含在Service中 这不是答案,因为问题是关于服务,而不是活动

以上是关于收听后台服务中的音量按钮?的主要内容,如果未能解决你的问题,请参考以下文章

在后台服务中拦截 Android 蓝牙相机快门

如何在 Android 应用程序后台监听音量键事件?

如何在无需打开应用的情况下后台启动 Flutter 服务?

当应用不在前台时,Ionic / Cordova 触发应用操作

从后台返回时隐藏音量 HUD

Android - 为视频创建后台服务