Android监听第三方播放获取音乐信息及包名

文章目录

NotificationListenerService

NotificationListenerService 是 Android 系统提供的一个特殊服务,允许应用监听和获取其他应用发出的通知。通过这个服务,开发者可以读取通知的内容、发送时间、来源应用等信息,还可以对通知进行操作(如删除通知)。

核心功能。

  • 监听通知:获取系统中所有应用发出的通知。
  • 读取通知内容:包括标题、文本、图标等。
  • 通知操作:删除通知或执行通知中的操作。

在 Android 中使用 NotificationListenerService 监听媒体播放器状态是可行的,因为大多数媒体应用(如音乐播放器、视频播放器)会在发出的通知中包含当前播放状态(播放 / 暂停)、曲目信息(标题、艺术家、专辑)等内容。下面介绍如何实现这一功能。

注意:有一些应用NotificationListenerService是监听不到播放状态的,比如其不发通知,或者是在后台播放情况下没有通知,这种可以考虑不使用NotificationListenerService实现,而是用一个定时器去轮询MediaSessionManager的getActiveSessions()函数获取音乐信息,这里不贴代码了。

代码获取播放音乐

步骤 1:声明权限和服务

在AndroidManifest.xml中添加以下权限和服务:

xml 复制代码
<uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" />

<service
    android:name=".MediaSessionListenerService"
    android:label="Notification Listener"
    android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
    <intent-filter>
        <action android:name="android.service.notification.NotificationListenerService" />
    </intent-filter>
</service>

步骤 2:实现NotificationListenerService

创建MediaSessionListenerService类继承NotificationListenerService:

java 复制代码
import android.app.Notification;
import android.media.session.MediaController;
import android.media.MediaMetadata;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;


public class MediaSessionListenerService extends NotificationListenerService {

    private static final String TAG = "MusicProgressListener";
    private MediaController mMediaController;
    private String currentPlayingPackage;

    public void onMusicStateChanged(PlaybackState state) {
        if (state != null) {
            long currentPosition = state.getPosition();
            Log.d(TAG, "[" + currentPlayingPackage + "] 当前进度: " + currentPosition + " ms");
        }
    }

    public void onMusicMetadataChanged(MediaMetadata metadata) {
        if (metadata != null) {
            long duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
            String title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
            String artist = metadata.getString(MediaMetadata.METADATA_KEY_ARTIST);

            Log.d(TAG, "[" + currentPlayingPackage + "] 歌曲: " + title + " - " + artist);
            Log.d(TAG, "[" + currentPlayingPackage + "] 总时长: " + duration + " ms");
        }
    }

    private final MediaController.Callback mControllerCallback = new MediaController.Callback() {

        @Override
        public void onPlaybackStateChanged(PlaybackState state) {
            onMusicStateChanged(state);
        }

        @Override
        public void onMetadataChanged(MediaMetadata metadata) {
            onMusicMetadataChanged(metadata);
        }
    };

    @Override
    public void onListenerConnected() {
        super.onListenerConnected();
        Log.d(TAG, "通知监听服务已连接");
        // 服务连接后,立即检查当前是否有活跃的媒体会话
        checkActiveMediaSessions();
    }

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
        super.onNotificationPosted(sbn);
        // 当有新通知发出时,检查是否是媒体通知
        handleMediaNotification(sbn);
    }

    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
        super.onNotificationRemoved(sbn);
        // 当媒体通知被移除时(如音乐停止),清理资源
        if (isMediaNotification(sbn)) {
            // 确保只清理当前正在播放的会话
            if (mMediaController != null && sbn.getPackageName().equals(currentPlayingPackage)) {
                mMediaController.unregisterCallback(mControllerCallback);
                mMediaController = null;
                currentPlayingPackage = null; // 重置包名
                Log.d(TAG, "媒体会话已断开: " + sbn.getPackageName());
            }
        }
    }

    private void checkActiveMediaSessions() {
        StatusBarNotification[] activeMediaSessions = getActiveNotifications();
        if (activeMediaSessions != null && activeMediaSessions.length > 0) {
            // 通常我们只关心第一个活跃的媒体会话
            handleMediaNotification(activeMediaSessions[0]);
        }
    }

    private void handleMediaNotification(StatusBarNotification sbn) {
        if (!isMediaNotification(sbn)) {
            return;
        }

        // 如果已经是当前正在处理的包,无需重复操作
        if (sbn.getPackageName().equals(currentPlayingPackage)) {
            return;
        }

        try {
            Bundle extras = sbn.getNotification().extras;
            MediaSession.Token token = extras.getParcelable(Notification.EXTRA_MEDIA_SESSION);

            if (token != null) {
                // 如果已经有一个控制器,先注销它
                if (mMediaController != null) {
                    mMediaController.unregisterCallback(mControllerCallback);
                }

                // 创建新的 MediaController
                mMediaController = new MediaController(this, token);
                mMediaController.registerCallback(mControllerCallback);

                // 从 StatusBarNotification 获取包名并存储
                currentPlayingPackage = sbn.getPackageName();
                Log.d(TAG, "已连接到媒体会话: " + currentPlayingPackage);

                // 立即获取一次当前状态,防止错过回调
                PlaybackState state = mMediaController.getPlaybackState();
                MediaMetadata metadata = mMediaController.getMetadata();
                if (state != null) {
                    onMusicStateChanged(state);
                }
                if (metadata != null) {
                    onMusicMetadataChanged(metadata);
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "处理媒体通知时出错", e);
        }
    }

    private boolean isMediaNotification(StatusBarNotification sbn) {
        // 判断一个通知是否为媒体通知
        Notification notification = sbn.getNotification();
        return notification != null && notification.extras != null
                && notification.extras.containsKey(Notification.EXTRA_MEDIA_SESSION);
    }
}

步骤 3:请求用户启用通知监听权限

在Activity中检查并请求权限,系统不会自动授予此权限。你需要引导用户去设置页面手动开启:

MainActivity.java

java 复制代码
private void checkNotificationListenerPermission() {
    if (!isNotificationServiceEnabled()) {
        startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
    }
}

private boolean isNotificationServiceEnabled() {
    String packageName = getPackageName();
    String flat = Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners");
    if (flat != null) {
        String[] names = flat.split(":");
        for (String name : names) {
            ComponentName cn = ComponentName.unflattenFromString(name);
            if (cn != null && cn.getPackageName().equals(packageName)) {
                return true;
            }
        }
    }
    return false;
}

然后MainActivity.java中调用

java 复制代码
checkNotificationListenerPermission();

用户必须点击进入设置,并手动开启你的 App 的"通知监听"开关。

启动服务

通常不需要显式启动 NotificationListenerService,只要用户开启了权限,系统就会自动绑定并运行服务。

但你可以调用以下代码来"尝试"触发绑定(实际是否运行仍由系统控制),

java 复制代码
// 可以尝试启动服务(非必需)
Intent intent = new Intent(this, MediaSessionListenerService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}

运行正常的话onListenerConnected()会被自动调用

java 复制代码
@Override
public void onListenerConnected() {
    super.onListenerConnected();
    Log.d(TAG, "通知监听服务已连接"); // 这行会被执行
    checkActiveMediaSessions();
}

你可以在 Logcat 中搜索 "通知监听服务已连接" 来确认是否成功触发。

注意事项

  1. 用户授权:必须引导用户到设置中启用应用的通知监听权限。
  2. 媒体会话限制:并非所有应用都使用MediaSession,因此可能无法检测到所有播放情况。
  3. 多应用播放:可能存在多个应用同时播放的情况,需根据业务逻辑处理。

如果报以下错误,

java.lang.SecurityException: Missing permission to control media.

需要加上这个权限

获取播放信息如下图:

注意上面有总时长为-1的,这个是不对的需要特殊处理。

作者:帅得不敢出门

相关推荐
砖厂小工1 小时前
用 GLM + OpenClaw 打造你的 AI PR Review Agent — 让龙虾帮你审代码
android·github
张拭心2 小时前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
张拭心2 小时前
Android 17 来了!新特性介绍与适配建议
android·前端
SimonKing3 小时前
OpenCode AI辅助编程,不一样的编程思路,不写一行代码
java·后端·程序员
FastBean3 小时前
Jackson View Extension Spring Boot Starter
java·后端
Kapaseker4 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴4 小时前
Android17 为什么重写 MessageQueue
android
Seven975 小时前
剑指offer-79、最⻓不含重复字符的⼦字符串
java
皮皮林55114 小时前
Java性能调优黑科技!1行代码实现毫秒级耗时追踪,效率飙升300%!
java
冰_河14 小时前
QPS从300到3100:我靠一行代码让接口性能暴涨10倍,系统性能原地起飞!!
java·后端·性能优化