Android 监控 MediaSession 播放状态并打印包名的 Java 实现
下面是一个完整的 Java 示例,展示如何系统级监控所有应用的 MediaSession 播放状态,并打印当前正在播放的应用包名。
📦 一、核心原理
通过 MediaSessionManager 获取所有活跃的 MediaSession,监听其状态变化,并提取对应的包名。
🛠️ 二、完整实现代码
- 权限配置 (AndroidManifest.xml)
<?xml version="1.0" encoding="utf-8"?>
<!-- 🔑 必须权限:监听媒体按键和会话 -->
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
<!-- ⚠️ 仅系统应用可获得 MEDIA_CONTENT_CONTROL 权限 -->
<!-- 普通应用需用户手动授权或使用无障碍服务 -->
<application>
<service
android:name=".GlobalMediaMonitorService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
</application>
- 全局媒体监控服务 (GlobalMediaMonitorService.java)
import android.app.Service;
import android.content.Context;
import android.media.MediaController;
import android.media.MediaSessionManager;
import android.media.session.MediaController.Callback;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class GlobalMediaMonitorService extends Service {
private static final String TAG = "MediaMonitor";
private MediaSessionManager mMediaSessionManager;
private Set<MediaController> mActiveControllers = new HashSet<>();
private final List<MediaController.Callback> mControllerCallbacks = new ArrayList<>();
// 记录最后一次播放状态,避免重复打印
private String mLastPlayingPackage = "";
private int mLastKnownState = PlaybackState.STATE_NONE;
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "全局媒体监控服务启动");
mMediaSessionManager = (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE);
startMonitoring();
}
/**
* 🔥 核心:开始监控所有活跃会话
*/
private void startMonitoring() {
try {
// 获取所有活跃的 MediaSession
List<MediaController> controllers = mMediaSessionManager.getActiveSessions(null);
for (MediaController controller : controllers) {
if (controller != null) {
registerControllerCallback(controller);
mActiveControllers.add(controller);
// 初始检查
checkInitialState(controller);
}
}
// 监听会话变化(Android 5.0+)
mMediaSessionManager.addOnActiveSessionsChangedListener(this::onActiveSessionsChanged, null);
} catch (SecurityException e) {
Log.e(TAG, "权限不足,无法访问 MediaSession: " + e.getMessage());
}
}
/**
* 活跃会话列表变化回调
*/
private void onActiveSessionsChanged(List<MediaController> controllers) {
Log.d(TAG, "活跃会话数量变化: " + controllers.size());
// 清理旧的
for (MediaController oldController : mActiveControllers) {
if (!controllers.contains(oldController)) {
unregisterControllerCallback(oldController);
}
}
// 添加新的
for (MediaController newController : controllers) {
if (!mActiveControllers.contains(newController)) {
registerControllerCallback(newController);
mActiveControllers.add(newController);
}
}
}
/**
* 为单个控制器注册回调
*/
private void registerControllerCallback(MediaController controller) {
String pkg = controller.getPackageName();
Log.d(TAG, "开始监控应用: " + pkg);
MediaController.Callback callback = new MediaController.Callback() {
@Override
public void onPlaybackStateChanged(PlaybackState state) {
super.onPlaybackStateChanged(state);
handlePlaybackStateChange(pkg, state);
}
@Override
public void onSessionDestroyed() {
super.onSessionDestroyed();
Log.d(TAG, "会话销毁: " + pkg);
mActiveControllers.remove(controller);
}
};
controller.registerCallback(callback);
mControllerCallbacks.add(callback);
}
/**
* 取消回调注册
*/
private void unregisterControllerCallback(MediaController controller) {
controller.unregisterCallback(mControllerCallbacks.get(mActiveControllers.indexOf(controller)));
}
/**
* 🔥 处理播放状态变化
*/
private void handlePlaybackStateChange(String packageName, PlaybackState state) {
if (state == null) return;
int stateCode = state.getState();
String stateName = getStateName(stateCode);
// 过滤:只有状态变化或包名变化时才打印
boolean shouldPrint = !packageName.equals(mLastPlayingPackage) ||
stateCode != mLastKnownState;
if (shouldPrint) {
Log.i(TAG, String.format(
"🎵 [%s] %s - %s (位置: %d)",
packageName,
getAppNameFromPackage(packageName),
stateName,
state.getPosition()
));
// 记录最后状态
mLastPlayingPackage = packageName;
mLastKnownState = stateCode;
}
// 特别关注正在播放的应用
if (stateCode == PlaybackState.STATE_PLAYING) {
onAppStartedPlaying(packageName);
} else if (stateCode == PlaybackState.STATE_STOPPED ||
stateCode == PlaybackState.STATE_NONE) {
onAppStoppedPlaying(packageName);
}
}
/**
* 初始状态检查
*/
private void checkInitialState(MediaController controller) {
PlaybackState state = controller.getPlaybackState();
if (state != null && state.getState() == PlaybackState.STATE_PLAYING) {
String pkg = controller.getPackageName();
Log.d(TAG, "发现正在播放的应用: " + pkg);
onAppStartedPlaying(pkg);
}
}
/**
* 应用开始播放的处理
*/
private void onAppStartedPlaying(String packageName) {
// 这里可以加入你的业务逻辑
Log.w(TAG, "⚠️ 检测到播放开始: " + packageName);
// 示例:限制某些应用播放
if (isRestrictedApp(packageName)) {
Log.e(TAG, "受限应用正在播放: " + packageName);
}
}
/**
* 应用停止播放的处理
*/
private void onAppStoppedPlaying(String packageName) {
Log.w(TAG, "⏹️ 检测到播放停止: " + packageName);
}
/**
* 获取状态名称
*/
private String getStateName(int state) {
switch (state) {
case PlaybackState.STATE_NONE: return "无状态";
case PlaybackState.STATE_STOPPED: return "已停止";
case PlaybackState.STATE_PAUSED: return "已暂停";
case PlaybackState.STATE_PLAYING: return "正在播放";
case PlaybackState.STATE_FAST_FORWARDING: return "快进中";
case PlaybackState.STATE_REWINDING: return "快退中";
case PlaybackState.STATE_BUFFERING: return "缓冲中";
case PlaybackState.STATE_ERROR: return "错误";
case PlaybackState.STATE_CONNECTING: return "连接中";
case PlaybackState.STATE_SKIPPING_TO_PREVIOUS: return "切上一首";
case PlaybackState.STATE_SKIPPING_TO_NEXT: return "切下一首";
default: return "未知状态(" + state + ")";
}
}
/**
* 根据包名获取应用名称
*/
private String getAppNameFromPackage(String packageName) {
try {
return getPackageManager().getApplicationLabel(
getPackageManager().getApplicationInfo(packageName, 0)
).toString();
} catch (Exception e) {
return packageName;
}
}
/**
* 判断是否为受限应用(示例)
*/
private boolean isRestrictedApp(String packageName) {
// 示例:黑名单
String[] restrictedApps = {"com.example.restricted1", "com.example.restricted2"};
for (String restricted : restrictedApps) {
if (restricted.equals(packageName)) return true;
}
return false;
}
@Override
public IBinder onBind(Intent intent) {
return new MonitorBinder();
}
public class MonitorBinder extends Binder {
public GlobalMediaMonitorService getService() {
return GlobalMediaMonitorService.this;
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "监控服务销毁");
// 清理资源
for (MediaController controller : mActiveControllers) {
unregisterControllerCallback(controller);
}
mActiveControllers.clear();
mControllerCallbacks.clear();
}
}
- 启动监控的 Activity (MonitorActivity.java)
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MonitorActivity extends AppCompatActivity {
private static final String TAG = "MonitorActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_monitor);
// 启动监控服务
startMonitorService();
// 检查权限
checkPermissions();
}
private void startMonitorService() {
try {
Intent serviceIntent = new Intent(this, GlobalMediaMonitorService.class);
startService(serviceIntent);
Log.d(TAG, "媒体监控服务已启动");
} catch (SecurityException e) {
Log.e(TAG, "启动服务失败,可能需要系统权限: " + e.getMessage());
}
}
private void checkPermissions() {
// 检查必要的权限
if (!hasMediaControlPermission()) {
Log.w(TAG, "缺少 MEDIA_CONTENT_CONTROL 权限,监控可能受限");
// 引导用户到设置页面
openSettingsForPermission();
}
}
private boolean hasMediaControlPermission() {
// 检查权限的逻辑
return checkSelfPermission("android.permission.MEDIA_CONTENT_CONTROL")
== PackageManager.PERMISSION_GRANTED;
}
private void openSettingsForPermission() {
// 打开系统设置页面
Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
startActivity(intent);
}
@Override
protected void onDestroy() {
super.onDestroy();
// 可选:停止服务
// stopService(new Intent(this, GlobalMediaMonitorService.class));
}
}
- 简化的无障碍服务方案 (普通应用备用方案)
如果无法获取 MEDIA_CONTENT_CONTROL 权限,可以使用无障碍服务间接监控:
public class MediaAccessibilityService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
// 通过窗口变化推断播放状态
String packageName = event.getPackageName() != null
? event.getPackageName().toString() : "";
// 检查是否有媒体通知
if (hasMediaNotification(packageName)) {
Log.d("AccessibilityMedia", "检测到媒体应用: " + packageName);
}
}
}
private boolean hasMediaNotification(String packageName) {
// 这里实现通知扫描逻辑
return false; // 简化实现
}
@Override
public void onInterrupt() {}
}
🎯 三、运行效果示例
当不同应用播放时,Logcat 会输出:
I/MediaMonitor: 🎵 [com.spotify.music] Spotify - 正在播放 (位置: 125000)
I/MediaMonitor: 🎵 [com.google.android.youtube] YouTube - 已暂停 (位置: 45000)
I/MediaMonitor: 🎵 [com.netflix.ninja] Netflix - 正在播放 (位置: 89000)
⚠️ 四、重要注意事项
事项 说明
权限限制 MEDIA_CONTENT_CONTROL 是系统级权限,普通应用难以获取
系统应用 此方案最适合系统内置应用或拥有平台签名的应用
备用方案 普通应用可考虑通过通知监听或无障碍服务间接实现
Android 版本 API 21+ (Android 5.0 Lollipop) 及以上支持
性能考量 监控大量会话时注意资源消耗
🔧 五、调试命令
查看当前活跃的 MediaSession
adb shell dumpsys media_session
强制杀死某个播放器(测试状态变化)
adb shell am force-stop com.spotify.music
模拟按键事件
adb shell input keyevent KEYCODE_MEDIA_PLAY
这个方案提供了一个系统级的 MediaSession 监控框架,能够准确捕捉各个应用的播放状态变化并输出包名信息。