音量变化是否有广播操作?

58

我正在编写一个小型小部件,需要在用户更改铃声音量或振动设置时进行更新。

对于振动设置,捕获 android.media.VIBRATE_SETTING_CHANGED 可以正常工作,但是我还没有找到任何一种方法可以在铃声音量更改时得到通知。虽然我可以尝试捕获用户按下音量加/减实体键的情况,但是有许多其他选项可以在不使用这些键的情况下更改音量。

你知道是否有专门用于此的广播操作,或者有没有其他解决问题的方法,无需通过这种方式实现吗?

9个回答

86

没有广播操作,但我发现你可以连接内容观察器以在设置更改时收到通知,其中流音量是这些设置之一。注册android.provider.Settings.System.CONTENT_URI以得到所有设置更改的通知:

mSettingsContentObserver = new SettingsContentObserver( new Handler() ); 
this.getApplicationContext().getContentResolver().registerContentObserver( 
    android.provider.Settings.System.CONTENT_URI, true, 
    mSettingsContentObserver );

内容观察者可能看起来像这样:

public class SettingsContentObserver extends ContentObserver {

   public SettingsContentObserver(Handler handler) {
      super(handler);
   } 

   @Override
   public boolean deliverSelfNotifications() {
      return super.deliverSelfNotifications(); 
   }

   @Override
   public void onChange(boolean selfChange) {
      super.onChange(selfChange);
      Log.v(LOG_TAG, "Settings change detected");
      updateStuff();
   }
}

确保在某个时候注销内容观察者。


谢谢你的回答,对于我要做的事情来说有点极端,但确实看起来是最接近的选择... - luthier
1
@NathanTotura:当屏幕关闭时,音量键是否会被检测到?还是你的解决方案仅适用于屏幕开启的情况? - Basher51
2
由于Content_URI是通用URI,我会多次收到onChange回调。我只想监视铃声音量的变化。尝试使用不同的铃声URI,但它们都无法正常工作。有人可以帮忙吗? - Prashant
在哪里放置(添加),注销观察者? - Codelaby
@Waboodoo 你可以在Settings.System源代码中找到它们,分别为Settings.System.VOLUME_NOTIFICATION和Settings.System.VOLUME_RING。它们已从公共API中删除,但似乎我们仍然可以使用这些相应的字符串。 - vrfloppa
显示剩余2条评论

66

Nathan的代码可行,但每次更改系统设置会产生两个通知。为了避免这种情况,请使用以下代码:

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("Decreased");
            previousVolume=currentVolume;
        }
        else if(delta<0)
        {
            Logger.d("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);

1
由于Content_URI是通用URI,我会多次收到onChange回调。我只想监视铃声音量的变化。尝试使用不同的铃声URI,但它们都无法正常工作。有人可以帮忙吗? - Prashant
2
请注意:如果音量是通过代码或按钮更改的话,观察者将会被触发。 - cwgso
@cwgso 怎么修复这个问题?使得当代码改变时,观察者不会被触发。 - Jazib Khan

14

是的,你可以为音量变化注册一个接收器(这有点像一种黑客方式,但有效),我就是用这种方式实现的(不涉及ContentObserver): 在manifest xml文件中:

<receiver android:name="com.example.myproject.receivers.MyReceiver" >
     <intent-filter>
          <action android:name="android.media.VOLUME_CHANGED_ACTION" />
     </intent-filter>
</receiver>

BroadcastReceiver:

public class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("android.media.VOLUME_CHANGED_ACTION")) {
            Log.d("Music Stream", "has changed");       
        }
    }
}

希望能对你有所帮助!


对我有用。谢谢。 - Joubert Vasconcelos
1
请注意,此方法不受支持,并且在未来版本中可能停止工作:https://dev59.com/AV_Va4cB1Zd3GeqPUpbW#8974510 - Hugh Jeffner
当音量达到限制(例如高音量或静音)时,请停止在接收器上发送回调。这在一些LG设备上发生。 - VasFou
1
这个代码可以运行,但为什么它会触发 onRecieve 函数多次?日志打印了五次! - Biswas Khayargoli

11

基于 Nathanadiswooby 的代码,我创建了一个完整的工作示例,并进行了一些小的改进。

查看 AudioFragment 类,我们可以看到使用自定义 ContentObserver 轻松地监听音量变化

public class AudioFragment extends Fragment implements OnAudioVolumeChangedListener {

    private AudioVolumeObserver mAudioVolumeObserver;

    @Override
    public void onResume() {
        super.onResume();
        // initialize audio observer
        if (mAudioVolumeObserver == null) {
            mAudioVolumeObserver = new AudioVolumeObserver(getActivity());
        }
        /*
         * register audio observer to identify the volume changes
         * of audio streams for music playback.
         *
         * It is also possible to listen for changes in other audio stream types:
         * STREAM_RING: phone ring, STREAM_ALARM: alarms, STREAM_SYSTEM: system sounds, etc.
         */
        mAudioVolumeObserver.register(AudioManager.STREAM_MUSIC, this);
    }

    @Override
    public void onPause() {
        super.onPause();
        // release audio observer
        if (mAudioVolumeObserver != null) {
            mAudioVolumeObserver.unregister();
        }
    }

    @Override
    public void onAudioVolumeChanged(int currentVolume, int maxVolume) {
        Log.d("Audio", "Volume: " + currentVolume + "/" + maxVolume);
        Log.d("Audio", "Volume: " + (int) ((float) currentVolume / maxVolume) * 100 + "%");
    }
}


public class AudioVolumeContentObserver extends ContentObserver {

    private final OnAudioVolumeChangedListener mListener;
    private final AudioManager mAudioManager;
    private final int mAudioStreamType;
    private int mLastVolume;

    public AudioVolumeContentObserver(
            @NonNull Handler handler,
            @NonNull AudioManager audioManager,
            int audioStreamType,
            @NonNull OnAudioVolumeChangedListener listener) {

        super(handler);
        mAudioManager = audioManager;
        mAudioStreamType = audioStreamType;
        mListener = listener;
        mLastVolume = audioManager.getStreamVolume(mAudioStreamType);
    }

    /**
     * Depending on the handler this method may be executed on the UI thread
     */
    @Override
    public void onChange(boolean selfChange, Uri uri) {
        if (mAudioManager != null && mListener != null) {
            int maxVolume = mAudioManager.getStreamMaxVolume(mAudioStreamType);
            int currentVolume = mAudioManager.getStreamVolume(mAudioStreamType);
            if (currentVolume != mLastVolume) {
                mLastVolume = currentVolume;
                mListener.onAudioVolumeChanged(currentVolume, maxVolume);
            }
        }
    }

    @Override
    public boolean deliverSelfNotifications() {
        return super.deliverSelfNotifications();
    }
}


->


public class AudioVolumeObserver {

    private final Context mContext;
    private final AudioManager mAudioManager;
    private AudioVolumeContentObserver mAudioVolumeContentObserver;

    public AudioVolumeObserver(@NonNull Context context) {
        mContext = context;
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    }

    public void register(int audioStreamType, 
                         @NonNull OnAudioVolumeChangedListener listener) {

        Handler handler = new Handler();
        // with this handler AudioVolumeContentObserver#onChange() 
        //   will be executed in the main thread
        // To execute in another thread you can use a Looper
        // +info: https://dev59.com/vWEi5IYBdhLWcg3wWLAS#35261443

        mAudioVolumeContentObserver = new AudioVolumeContentObserver(
                handler,
                mAudioManager,
                audioStreamType,
                listener);

        mContext.getContentResolver().registerContentObserver(
                android.provider.Settings.System.CONTENT_URI,
                true,
                mAudioVolumeContentObserver);
    }

    public void unregister() {
        if (mAudioVolumeContentObserver != null) {
            mContext.getContentResolver().unregisterContentObserver(mAudioVolumeContentObserver);
            mAudioVolumeContentObserver = null;
        }
    }
}


:空段落。
public interface OnAudioVolumeChangedListener {

    void onAudioVolumeChanged(int currentVolume, int maxVolume);
}

希望它仍对某人有用!:)


1
很好的答案,总结了如何处理各种流量变化的方法。然而,在给出的示例(onAudioVolumeChanged())中有一个小错误。在maxVolume后的右括号应该放在值100之后。所以:(int)((float) currentVolume / maxVolume * 100)。否则,您将始终获得0%(如果currentVolume!= maxVolume)或100%(如果currentVolume == maxVolume)的音量百分比。我冒昧改正了这篇文章,希望您不介意... - GeertVc
我不能只更改一个字符(至少要更改6个字符),所以我也添加了一些注释... - GeertVc
AudioVolumeObserver的代码片段存在不准确之处。注释中说“使用此处理程序,将在主线程中执行AudioVolumeContentObserver#onChange()”,但实际上使用了new Handler(),因此它将使用与调用register(...)方法相同的线程。 - AlexeyGorovoy
@AlexeyGorovoy 这段代码片段是准确的,因为问题是关于观察音量变化而不是线程。为了简单起见,我只是使用了 new Handler(),它会为当前线程的 looper 创建一个 handler(正如您可以在我在评论中引用的另一个 SO 回答中所读到的)。 - Ryan Amaral
@RyanAmaral 是的,我同意你所说的“我只是使用了new Handler(),它为当前线程的looper创建了一个handler”,并且相信这明显与你在代码片段中的评论“使用此处理程序,onChange()将在主线程中执行”相矛盾。 - AlexeyGorovoy

10

Nathan和Adi的代码可以工作,但可以进行优化并自包含:

public class AudioStreamVolumeObserver
{
    public interface OnAudioStreamVolumeChangedListener
    {
        void onAudioStreamVolumeChanged(int audioStreamType, int volume);
    }

    private static class AudioStreamVolumeContentObserver
            extends ContentObserver
    {
        private final AudioManager                       mAudioManager;
        private final int                                mAudioStreamType;
        private final OnAudioStreamVolumeChangedListener mListener;

        private int mLastVolume;

        public AudioStreamVolumeContentObserver(
                @NonNull
                Handler handler,
                @NonNull
                AudioManager audioManager, int audioStreamType,
                @NonNull
                OnAudioStreamVolumeChangedListener listener)
        {
            super(handler);

            mAudioManager = audioManager;
            mAudioStreamType = audioStreamType;
            mListener = listener;

            mLastVolume = mAudioManager.getStreamVolume(mAudioStreamType);
        }

        @Override
        public void onChange(boolean selfChange)
        {
            int currentVolume = mAudioManager.getStreamVolume(mAudioStreamType);

            if (currentVolume != mLastVolume)
            {
                mLastVolume = currentVolume;

                mListener.onAudioStreamVolumeChanged(mAudioStreamType, currentVolume);
            }
        }
    }

    private final Context mContext;

    private AudioStreamVolumeContentObserver mAudioStreamVolumeContentObserver;

    public AudioStreamVolumeObserver(
            @NonNull
            Context context)
    {
        mContext = context;
    }

    public void start(int audioStreamType,
                      @NonNull
                      OnAudioStreamVolumeChangedListener listener)
    {
        stop();

        Handler handler = new Handler();
        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

        mAudioStreamVolumeContentObserver = new AudioStreamVolumeContentObserver(handler, audioManager, audioStreamType, listener);

        mContext.getContentResolver()
                .registerContentObserver(System.CONTENT_URI, true, mAudioStreamVolumeContentObserver);
    }

    public void stop()
    {
        if (mAudioStreamVolumeContentObserver == null)
        {
            return;
        }

        mContext.getContentResolver()
                .unregisterContentObserver(mAudioStreamVolumeContentObserver);
        mAudioStreamVolumeContentObserver = null;
    }
}

1
为什么没有评论就点踩呢?这样别人怎么能知道为什么会被点踩呢? - swooby

4
如果只是更改铃声模式,您可以使用广播接收器,并将"android.media.RINGER_MODE_CHANGED"作为操作。这样会更容易实现。

你能说得更具体一些吗? - Noor Hossain

3

你好,我尝试了上面的代码,但它对我不起作用。但当我尝试添加这行代码时

getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);

并放置

mSettingsContentObserver = new SettingsContentObserver(this,new Handler());
getApplicationContext().getContentResolver().registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, mSettingsContentObserver );

现在已经可以正常工作了。我的担心是如何在更改时隐藏音量对话框。请参见此图像


2
  private const val EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE"
  private const val VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION"

    val filter = IntentFilter(VOLUME_CHANGED_ACTION)
    filter.addAction(RINGER_MODE_CHANGED_ACTION)

      val receiver = object : BroadcastReceiver() {
            override fun onReceive(context1: Context, intent: Intent) {

                val stream = intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, UNKNOWN)
                val mode = intent.getIntExtra(EXTRA_RINGER_MODE, UNKNOWN)
                val volumeLevel = audioManager.getStreamVolume(stream)
            }
        }

1

百分之百的解决方案,在所有情况下都有效

  public class SettingsContentObserver extends ContentObserver {

        SettingsContentObserver(Handler handler) {
            super(handler);
        }

        @Override
        public boolean deliverSelfNotifications() {
            return super.deliverSelfNotifications();
        }

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            volumeDialogContract.updateMediaVolume(getMediaVolume());


    }

    int getMediaVolume() {
        return audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
    }



    void unRegisterVolumeChangeListener() {
        volumeDialogContract.getAppContext().getApplicationContext().getContentResolver().
                unregisterContentObserver(settingsContentObserver);
    }

    void registerVolumeChangeListener() {
        settingsContentObserver = new VolumeDialogPresenter.SettingsContentObserver(new Handler());
        volumeDialogContract.getAppContext().getApplicationContext().getContentResolver().registerContentObserver(
                android.provider.Settings.System.CONTENT_URI, true,
                settingsContentObserver);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接