You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android通知触发铃声播放时应用卡顿锁定问题求助

解决Android通知播放音乐时应用锁定的问题

你遇到的核心问题是主线程被阻塞了!

你用Handler.post()把铃声播放逻辑放到了UI主线程里,而Ringtone.play()是个阻塞式调用——它会一直占用主线程直到铃声播放完毕,这就导致整个UI完全卡住,没法响应任何点击、滚动或页面跳转操作。至于普通线程没声音,大概率是因为音频播放需要在合适的线程上下文(比如持有Looper的线程,或者正确绑定音频服务),单纯的普通线程执行完后就被销毁,没法维持播放状态。

正确的解决方案:用后台Service托管音频播放

Android的Service是专门处理后台任务的组件,我们可以把音频播放逻辑放到Service中,用MediaPlayer替代Ringtone(MediaPlayer更灵活,支持异步播放,不会阻塞线程),这样既能在后台播放音频,又不会影响UI操作。

步骤1:创建音频播放Service

public class AudioPlayService extends Service {
    private MediaPlayer mediaPlayer;
    private AudioManager audioManager;
    private AudioFocusRequest audioFocusRequest;

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 处理Android 8.0+的前台服务要求
        setupForegroundNotification();

        // 获取音频URI
        Uri audioUri = intent.getParcelableExtra("AUDIO_URI");
        if (audioUri != null) {
            requestAudioFocus();
            playAudio(audioUri);
        }

        return START_NOT_STICKY; // 服务被杀死后不需要重启
    }

    // 设置前台通知(Android 8.0+必须)
    private void setupForegroundNotification() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(
                    "AUDIO_PLAY_CHANNEL",
                    "通知音频播放",
                    NotificationManager.IMPORTANCE_LOW
            );
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, "AUDIO_PLAY_CHANNEL")
                    .setContentTitle("通知音频播放中")
                    .setSmallIcon(R.drawable.ic_notification) // 替换成你的应用图标
                    .setPriority(NotificationCompat.PRIORITY_LOW)
                    .build();
            startForeground(1, notification);
        }
    }

    // 请求音频焦点,避免和其他应用音频冲突
    private void requestAudioFocus() {
        audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            audioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
                    .setAudioAttributes(new AudioAttributes.Builder()
                            .setUsage(AudioAttributes.USAGE_NOTIFICATION)
                            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                            .build())
                    .setOnAudioFocusChangeListener(focusChange -> {
                        if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
                            // 失去焦点时停止播放
                            stopAudio();
                        }
                    })
                    .build();
            audioManager.requestAudioFocus(audioFocusRequest);
        } else {
            audioManager.requestAudioFocus(
                    focusChange -> {
                        if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
                            stopAudio();
                        }
                    },
                    AudioManager.STREAM_NOTIFICATION,
                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT
            );
        }
    }

    // 播放音频
    private void playAudio(Uri uri) {
        try {
            mediaPlayer = new MediaPlayer();
            mediaPlayer.setDataSource(this, uri);
            // 设置音频属性为通知类型
            mediaPlayer.setAudioAttributes(new AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_NOTIFICATION)
                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                    .build());
            // 异步准备,避免阻塞线程
            mediaPlayer.prepareAsync();
            mediaPlayer.setOnPreparedListener(mp -> {
                mp.start();
                // 播放完成后自动清理资源并停止服务
                mp.setOnCompletionListener(completedMp -> stopAudio());
            });
            mediaPlayer.setOnErrorListener((mp, what, extra) -> {
                stopAudio();
                return true;
            });
        } catch (IOException e) {
            e.printStackTrace();
            stopAudio();
        }
    }

    // 停止播放并释放资源
    private void stopAudio() {
        if (mediaPlayer != null) {
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.stop();
            }
            mediaPlayer.release();
            mediaPlayer = null;
        }
        // 释放音频焦点
        if (audioManager != null) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && audioFocusRequest != null) {
                audioManager.abandonAudioFocusRequest(audioFocusRequest);
            } else {
                audioManager.abandonAudioFocus(null);
            }
        }
        // 停止服务
        stopSelf();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopAudio();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

步骤2:在Manifest中注册Service

<service android:name=".AudioPlayService" />

步骤3:启动Service播放音频

在你需要触发通知音乐的地方,调用这段代码:

Uri audioUri = // 你的音频URI,比如从RingtoneManager获取的目标URI
Intent playIntent = new Intent(context, AudioPlayService.class);
playIntent.putExtra("AUDIO_URI", audioUri);

// Android 8.0及以上必须用startForegroundService
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    context.startForegroundService(playIntent);
} else {
    context.startService(playIntent);
}

额外注意事项

  1. 随时终止播放:如果用户操作(比如启动Activity、返回)需要停止音频,直接调用context.stopService(new Intent(context, AudioPlayService.class))即可。
  2. 音频焦点处理:代码中添加了音频焦点请求,确保你的播放不会打断其他应用的音频,同时在失去焦点时自动停止。
  3. 避免Ringtone长时间播放Ringtone设计用于短铃声(比如通知提示音),长时间播放用MediaPlayer更稳定可控。

内容的提问来源于stack exchange,提问作者Silkking

火山引擎 最新活动