Android-Mediasession-播放状态监控

Android 监控 MediaSession 播放状态并打印包名的 Java 实现

下面是一个完整的 Java 示例,展示如何系统级监控所有应用的 MediaSession 播放状态,并打印当前正在播放的应用包名。

📦 一、核心原理

通过 MediaSessionManager 获取所有活跃的 MediaSession,监听其状态变化,并提取对应的包名。

🛠️ 二、完整实现代码

  1. 权限配置 (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>
  1. 全局媒体监控服务 (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();
}

}

  1. 启动监控的 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));
}

}

  1. 简化的无障碍服务方案 (普通应用备用方案)

如果无法获取 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 监控框架,能够准确捕捉各个应用的播放状态变化并输出包名信息。

相关推荐
:mnong2 小时前
跟着学伴AI项目设计分析学习安卓APP研发
android·人工智能·学习
Chase_______2 小时前
【JAVA基础指南(四)】快速掌握类和对象 基础篇
android·java·开发语言
黄林晴2 小时前
Android 侧载新规:名义开放,实则锁死——你等得起一天吗?
android
学而要时习3 小时前
从“推理”回归“控制”:通过经典强化学习透视AI大语言模型的逻辑底层
android·数据挖掘·回归
Kapaseker3 小时前
让你的 App 成为 AI 的一环
android·kotlin
空中海3 小时前
7.3 优化实践
android·flutter
Lsk_Smion3 小时前
Sability安卓(三)_基础开发知识扫盲,开学XML......
android·java·android studio·安卓
三少爷的鞋3 小时前
Android 慢性病之拒绝"带病"上线:为什么 ANR 是必须根除的代码 HP?
android
草莓熊Lotso3 小时前
Linux 线程深度剖析:线程 ID 本质、地址空间布局与 pthread 源码全解
android·linux·运维·服务器·数据库·c++