Android前台倒计时APP开发:自定义音效与前台保持问题求助
Android前台倒计时APP问题解决方案
问题背景
我是Android开发新手,正在开发一款前台持续运行的倒计时APP,要求显示倒计时,结束时播放自定义音效。目前卡在两个问题上:
- 音效的暂停(不是停止)和单独播放功能实现
- 如何让APP始终保持在前台
我已经基于一个倒计时模板开发,集成了loadMusic方法,还写了Sound类,相关代码如下:
现有代码
loadMusic方法
private void loadMusic() { if (timerStatus == TimerStatus.PAUSED) new Runnable() { @Override public void run() { playSound(R.raw.sound1); } public void playSound(int sound1) { MediaPlayer mp = MediaPlayer.create(getBaseContext(), (R.raw.sound2)); mp.start(); } }.run(); }
Sound类代码
package de.codeyourapp.countdowntimer; import android.annotation.SuppressLint; import android.media.MediaPlayer; import android.os.Bundle; import android.widget.ImageView; import androidx.appcompat.app.AppCompatActivity; public class Sound extends AppCompatActivity { ImageView imageButton1, imageButton2, imageButton3, imageButton4; @SuppressLint("WrongViewCast") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.sound); imageButton1 = findViewById(R.id.iv_bowl1); imageButton2 = findViewById(R.id.iv_bowl2); imageButton3 = findViewById(R.id.iv_bowl3); imageButton4 = findViewById(R.id.iv_bowl5); MediaPlayer mediaPlayer1 = MediaPlayer.create(this, R.raw.sound1); MediaPlayer mediaPlayer2 = MediaPlayer.create(this, R.raw.sound2); MediaPlayer mediaPlayer3 = MediaPlayer.create(this, R.raw.sound3); MediaPlayer mediaPlayer4 = MediaPlayer.create(this, R.raw.sound5); imageButton1.setOnClickListener(v -> mediaPlayer1.start()); imageButton2.setOnClickListener(v -> mediaPlayer2.start()); imageButton3.setOnClickListener(v -> mediaPlayer3.start()); imageButton4.setOnClickListener(v -> mediaPlayer4.start()); } }
解决方案
一、音效的暂停/单独播放实现
现有代码中MediaPlayer是局部变量,每次调用start都会重新创建实例,无法实现暂停。需要将MediaPlayer改为全局变量,并管理好生命周期:
1. 重构Sound类,支持暂停/切换播放
package de.codeyourapp.countdowntimer; import android.annotation.SuppressLint; import android.media.MediaPlayer; import android.os.Bundle; import android.widget.ImageView; import androidx.appcompat.app.AppCompatActivity; public class Sound extends AppCompatActivity { ImageView imageButton1, imageButton2, imageButton3, imageButton4; // 全局变量记录当前播放的MediaPlayer private MediaPlayer currentMediaPlayer; @SuppressLint("WrongViewCast") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.sound); imageButton1 = findViewById(R.id.iv_bowl1); imageButton2 = findViewById(R.id.iv_bowl2); imageButton3 = findViewById(R.id.iv_bowl3); imageButton4 = findViewById(R.id.iv_bowl5); MediaPlayer mediaPlayer1 = MediaPlayer.create(this, R.raw.sound1); MediaPlayer mediaPlayer2 = MediaPlayer.create(this, R.raw.sound2); MediaPlayer mediaPlayer3 = MediaPlayer.create(this, R.raw.sound3); MediaPlayer mediaPlayer4 = MediaPlayer.create(this, R.raw.sound5); // 点击按钮时切换音效,支持暂停/继续当前音效 imageButton1.setOnClickListener(v -> switchSound(mediaPlayer1)); imageButton2.setOnClickListener(v -> switchSound(mediaPlayer2)); imageButton3.setOnClickListener(v -> switchSound(mediaPlayer3)); imageButton4.setOnClickListener(v -> switchSound(mediaPlayer4)); } private void switchSound(MediaPlayer targetPlayer) { // 暂停当前正在播放的音效 if (currentMediaPlayer != null && currentMediaPlayer.isPlaying()) { currentMediaPlayer.pause(); } // 如果点击的是当前暂停的音效,继续播放 if (currentMediaPlayer == targetPlayer) { targetPlayer.start(); return; } // 切换到新音效播放 currentMediaPlayer = targetPlayer; currentMediaPlayer.start(); } // 单独暂停当前音效的方法(可绑定到暂停按钮) public void pauseCurrentSound() { if (currentMediaPlayer != null && currentMediaPlayer.isPlaying()) { currentMediaPlayer.pause(); } } // 单独继续当前音效的方法(可绑定到继续按钮) public void resumeCurrentSound() { if (currentMediaPlayer != null && !currentMediaPlayer.isPlaying()) { currentMediaPlayer.start(); } } // 页面销毁时释放资源,避免内存泄漏 @Override protected void onDestroy() { super.onDestroy(); if (currentMediaPlayer != null) { currentMediaPlayer.release(); currentMediaPlayer = null; } } }
2. 优化loadMusic方法
原方法中参数未使用且硬编码音效资源,调整如下:
// 在主Activity中声明全局MediaPlayer private MediaPlayer countdownEndPlayer; // 初始化倒计时结束音效 private void initCountdownSound() { countdownEndPlayer = MediaPlayer.create(this, R.raw.sound1); } private void loadMusic() { if (timerStatus == TimerStatus.PAUSED) { if (countdownEndPlayer != null) { // 切换播放/暂停状态 if (countdownEndPlayer.isPlaying()) { countdownEndPlayer.pause(); } else { countdownEndPlayer.start(); } } } } // Activity销毁时释放资源 @Override protected void onDestroy() { super.onDestroy(); if (countdownEndPlayer != null) { countdownEndPlayer.release(); countdownEndPlayer = null; } }
二、让APP始终保持在前台
Android API 26及以上,前台服务必须显示通知才能避免被系统回收,实现步骤如下:
1. 添加权限到AndroidManifest.xml
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <!-- Android 13及以上需添加通知权限 --> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
2. 创建前台服务类
package de.codeyourapp.countdowntimer; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.Build; import android.os.IBinder; import androidx.core.app.NotificationCompat; public class CountdownForegroundService extends Service { private static final String CHANNEL_ID = "Countdown_Channel"; private static final int NOTIFICATION_ID = 1001; @Override public void onCreate() { super.onCreate(); createNotificationChannel(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 点击通知跳转回主Activity Intent notificationIntent = new Intent(this, MainActivity.class); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE); Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("倒计时运行中") .setContentText("点击回到APP") .setSmallIcon(R.drawable.ic_notification) .setContentIntent(pendingIntent) .build(); // 启动前台服务 startForeground(NOTIFICATION_ID, notification); // 此处可添加倒计时逻辑,或与Activity绑定控制 return START_STICKY; } private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "倒计时前台服务", NotificationManager.IMPORTANCE_LOW ); NotificationManager manager = getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } } @Override public IBinder onBind(Intent intent) { // 无需绑定则返回null,需绑定可返回Binder实例 return null; } @Override public void onDestroy() { super.onDestroy(); stopForeground(true); } }
3. 在AndroidManifest.xml注册服务
<service android:name=".CountdownForegroundService" android:foregroundServiceType="mediaPlayback" /> <!-- 根据场景选择类型,如mediaPlayback、dataSync等 -->
4. 在主Activity中启动/停止前台服务
// 倒计时开始时启动前台服务 private void startCountdownForeground() { Intent serviceIntent = new Intent(this, CountdownForegroundService.class); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startForegroundService(serviceIntent); } else { startService(serviceIntent); } } // 倒计时结束或退出APP时停止服务 private void stopCountdownForeground() { Intent serviceIntent = new Intent(this, CountdownForegroundService.class); stopService(serviceIntent); }
注意:Android 13及以上需动态申请POST_NOTIFICATIONS权限,否则通知无法显示,前台服务可能被系统终止。
内容的提问来源于stack exchange,提问作者rico
相关产品推荐
相关产品推荐

