Android监听铃声和通知音量变化问题求助
我之前能完美监听媒体音量变化,但现在需要检测用户通过手机「声音和振动」设置界面的触摸滑块调整Ring & notification volume的操作。我用ContentObserver实现了如下代码:
import android.content.Context; import android.database.ContentObserver; import android.media.AudioManager; import android.net.Uri; import android.os.Handler; public class VolumeObserver extends ContentObserver { private final Context context; private int lastRingVolume, lastNotificationVolume; public VolumeObserver(Handler handler, Context context) { super(handler); this.context = context; AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); lastRingVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING); lastNotificationVolume = audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION); } @Override public void onChange(boolean selfChange, Uri uri) { super.onChange(selfChange); if (uri != null && uri.toString().contains("volume")) { AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); int currentRingVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING); int currentNotificationVolume = audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION); if (currentRingVolume != lastRingVolume || currentNotificationVolume != lastNotificationVolume) { Log.v("TAG", "ringVolume: " + lastRingVolume + "-->" + currentRingVolume + ", notificationVolume: " + lastNotificationVolume + "-->" + currentNotificationVolume); lastRingVolume = currentRingVolume; lastNotificationVolume = currentNotificationVolume; } else { Log.w("TAG", "ringVolume: " + lastRingVolume + "===" + currentRingVolume + ", notificationVolume: " + lastNotificationVolume + "===" + currentNotificationVolume); } } } }
我已经在主Activity里完成了正确的注册/注销,但现在只有按下音量上下键(对应KEYCODE_VOLUME_UP和KEYCODE_VOLUME_DOWN)时能收到回调,而且此时铃声音量并没有变化;真正在设置界面调整滑块改变铃声音量的时候,却触发不了事件。我试过用BroadcastReceiver实现,结果一样——只响应硬件按键操作。现在只能用轮询AudioManager.getStreamVolume(AudioManager.STREAM_RING)的方式,效率很低。
想问下我是不是漏掉了什么?轮询真的是监听这种滑块音量变化的唯一办法吗?
更新(9天后):我没找到类似监听媒体音量的方法来监听铃声和通知音量的变化,暂时放弃用VolumeObserver监听STREAM_RING的思路了。
解决方案
1. 精准监听设置项Uri
系统铃声和通知音量的设置项对应Settings.System中的特定字段,需要直接监听这两个字段的Uri,而不是泛过滤含"volume"的Uri:
- 铃声音量Uri:
Settings.System.getUriFor(Settings.System.RING_VOLUME) - 通知音量Uri:
Settings.System.getUriFor(Settings.System.NOTIFICATION_VOLUME)
注册Observer时,同时监听这两个Uri即可。
2. 优化ContentObserver逻辑
去掉Uri的模糊判断,直接根据回调的Uri匹配对应的设置项,简化逻辑:
import android.content.Context; import android.database.ContentObserver; import android.media.AudioManager; import android.net.Uri; import android.os.Handler; import android.provider.Settings; import android.util.Log; public class VolumeObserver extends ContentObserver { private final Context context; private int lastRingVolume, lastNotificationVolume; public VolumeObserver(Handler handler, Context context) { super(handler); this.context = context; AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); lastRingVolume = audioManager.getStreamVolume(AudioManager.STREAM_RING); lastNotificationVolume = audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION); } @Override public void onChange(boolean selfChange, Uri uri) { super.onChange(selfChange); AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); if (Settings.System.RING_VOLUME.equals(Settings.System.getSettingName(uri))) { int current = audioManager.getStreamVolume(AudioManager.STREAM_RING); if (current != lastRingVolume) { Log.v("TAG", "铃声音量变化: " + lastRingVolume + "-->" + current); lastRingVolume = current; } } else if (Settings.System.NOTIFICATION_VOLUME.equals(Settings.System.getSettingName(uri))) { int current = audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION); if (current != lastNotificationVolume) { Log.v("TAG", "通知音量变化: " + lastNotificationVolume + "-->" + current); lastNotificationVolume = current; } } } }
3. 确保权限配置
在AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.READ_SETTINGS" />
Android 6.0及以上版本,需要动态申请该权限才能正常读取设置变化。
4. 轮询的优化方案
如果上述方法在部分定制ROM上不生效,轮询仍是可选方案。可以用Handler.postDelayed实现周期性检查,建议将间隔设置在500ms-1s之间,平衡实时性和性能消耗:
private Handler pollHandler = new Handler(); private Runnable pollRunnable = new Runnable() { @Override public void run() { AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); int currentRing = audioManager.getStreamVolume(AudioManager.STREAM_RING); if (currentRing != lastRingVolume) { // 处理音量变化逻辑 lastRingVolume = currentRing; } pollHandler.postDelayed(this, 1000); // 1秒轮询一次 } }; // 启动轮询 pollHandler.post(pollRunnable); // 停止轮询 pollHandler.removeCallbacks(pollRunnable);
内容的提问来源于stack exchange,提问作者sfinja

