Android 根据应用前后台状态播放(或暂停)背景音乐

有时候需要给Android应用添加背景音乐的功能,例如一些小游戏之类的应用。在应用处于前台可见时,需要播放背景音乐,当应用处于后台不可见时(如按了home键或进入其它应用或该应用被销毁时)背景音乐也要随之暂停或停止。

利用Service实现背景音乐播放功能

Service 是一种可在后台执行长时间运行操作而不提供界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。此外,组件可通过绑定到服务与之进行交互,甚至是执行进程间通信 (IPC)。例如,服务可在后台处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序进行交互。

可以通过扩展Service或IntentService来实现服务功能。背景音乐需要在整个应用期间持续播放,停止服务的控制权要交给应用组件,而由于IntentService会在完成任务后自行调用stopSelf()来停止服务,所以不适合此处。这里需要扩展Service来实现功能。



import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;


/**
 * BackgroundMusic
 * 后台背景音乐服务(常应用于小游戏),要求
 * 1.应用不可见时(未销毁),播放暂停,服务保持
 * 2.应用恢复可见时,播放继续(不是重新开始)
 * 3.应用退出或被销毁时,服务停止
 */
public class BgmService extends Service {
    public static final String APP_TAG = "SUDOKU_TAG";
    public static final String ACTION_MUSIC_PLAY= "com.jack..action.ACTION_MUSIC_PLAY";
    public static final String ACTION_MUSIC_PAUSE= "com.jack..action.ACTION_MUSIC_PAUSE";
    private MediaPlayer mediaPlayer;
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    private final class ServiceHandler extends Handler{
        private  ServiceHandler(Looper looper){
            super(looper);
        }
        //播放  暂停
        @Override
        public void handleMessage(@NonNull Message msg) {
            Intent intent = (Intent) msg.obj;
            if (ACTION_MUSIC_PLAY.equals(intent.getAction())) {
                if(mediaPlayer==null)   {
                    mediaPlayer = MediaPlayer.create(BgmService.this,R.raw.bgm72);//create包含prepare()
                    mediaPlayer.setLooping(true);
                }
                mediaPlayer.start();//播放或恢复播放
            }else if (ACTION_MUSIC_PAUSE.equals(intent.getAction())) {
                if(mediaPlayer!=null) {
                    mediaPlayer.pause();
                }
            }
            //mediaPlayer.setOnPreparedListener(BgmService.this);
            //mediaPlayer.prepareAsync(); // prepare async to not block main thread
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("BgmServiceHandlerThread");
        thread.start();
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v(APP_TAG," BgmService "+intent.getAction());
        Message msg =  mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public void onDestroy() {
        Log.v(APP_TAG," BgmService onDestroy");
        super.onDestroy();
        mServiceLooper.quit();
        //mediaPlayer非常消耗资源,务必销毁之
        if (mediaPlayer != null) mediaPlayer.release();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }
}

播放媒体文件是个耗时的过程,必须要开启另外的线程来完成播放任务。此次是播放本地的媒体资源,如果是网络资源,需要实现MediaPlayer.OnPreparedListener接口,当资源准备就绪后才mediaPlayer.start()。
mediaPlayer.prepare();是个阻塞的方法。

在主Activity中开启或停止服务

背景音乐服务需要在应用销毁时停止,如果不做处理,服务会一直存在直到被android系统杀掉。当然也可以通过一个开关配置项来控制背景音乐是否播放。

public class MainActivity extends AppCompatActivity {
		...
		 @Override
    protected void onStart() {
        super.onStart();
       Intent intent = new Intent(this, BgmService.class);
        intent.setAction(BgmService.ACTION_MUSIC_PLAY);
        startService(intent);
    }
		  @Override
    protected void onDestroy() {
        super.onDestroy();
        stopService(new Intent(this, BgmService.class));
    }

		...
}

MainActivity销毁就表示应用销毁了,这样就保证了应用销毁时背景音乐关闭。
那还有个难题,如何控制暂停呢?当应用不可见处于后台时(如按下Home键或进入其它应用),怎样让背景音乐暂停呢,然后应用重新回到前台时背景音乐恢复播放呢?
可能第一想到的是在Activity的onStop()方法中暂停服务。如下:

    @Override
    protected void onStop() {
        super.onStop();
		 Intent intent = new Intent(this, BgmService.class);
        intent.setAction(BgmService.ACTION_MUSIC_PAUSE);
        startService(intent);
    }

显然这是可行的,当它只能保证该Activity不可见(stop状态)时可以暂停播放,但是一个应用包含非常多的Activity,你需要在每个Activity中的onStop()方法都加上上面的代码,甚至onStart()方法也要加上开启服务的代码。显然这样是不可行的方法。因为背景音乐服务是和应用状态相关的,所以这个控制开启或暂停的逻辑应该和应用有关,而不是和应用的某个Activity有关。所以这里用Application.ActivityLifecycleCallbacks来实现才是最佳实现方式。

应用的前后台状态判断



import android.app.Activity;
import android.app.Application;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * 应用前后台状态监听帮助类,仅在Application中使用
 */

public class AppFrontBackHelper {

    private OnAppStatusListener mOnAppStatusListener;

    public AppFrontBackHelper() {

    }

    /**
     * 注册状态监听,仅在Application中使用
     */
    public void register(Application application, OnAppStatusListener listener){
        mOnAppStatusListener = listener;
        application.registerActivityLifecycleCallbacks(activityLifecycleCallbacks);
    }

    public void unRegister(Application application){
        application.unregisterActivityLifecycleCallbacks(activityLifecycleCallbacks);
    }

    private Application.ActivityLifecycleCallbacks activityLifecycleCallbacks = new Application.ActivityLifecycleCallbacks() {
        //打开的Activity数量统计
        private int activityStartCount = 0;

        @Override
        public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {

        }

        @Override
        public void onActivityStarted(@NonNull Activity activity) {
            activityStartCount++;
            //数值从0变到1说明是从后台切到前台
            if (activityStartCount == 1){
                //从后台切到前台
                if(mOnAppStatusListener != null){
                    mOnAppStatusListener.onFront();
                }
            }
        }

        @Override
        public void onActivityResumed(@NonNull Activity activity) {

        }

        @Override
        public void onActivityPaused(@NonNull Activity activity) {

        }

        @Override
        public void onActivityStopped(@NonNull Activity activity) {
            activityStartCount--;
            //数值从1到0说明是从前台切到后台
            if (activityStartCount == 0){
                //从前台切到后台
                if(mOnAppStatusListener != null){
                    mOnAppStatusListener.onBack();
                }
            }
        }

        @Override
        public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {

        }

        @Override
        public void onActivityDestroyed(@NonNull Activity activity) {

        }
    };

    public interface OnAppStatusListener{
        void onFront();
        void onBack();
    }

}

通过给应用注册ActivityLifecycleCallbacks 监听,对每个Acitivity的周期回调的监控,来达到目的。这里只要监听onActivityStarted和onActivityStopped两个方法。

import android.app.Application;

import androidx.preference.PreferenceManager;


public class MyApp extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        AppFrontBackHelper helper = new AppFrontBackHelper();
        helper.register(MyApp.this, new AppFrontBackHelper.OnAppStatusListener() {
            @Override
            public void onFront() {
                //应用切到前台处理
                Intent intent = new Intent(MyApp.this, BgmService.class);
	        intent.setAction(action);
	        MyApp.this.startService(intent);
            }

            @Override
            public void onBack() {
                //应用切到后台处理
                MyApp.this.stopService(new Intent(MyApp.this, BgmService.class));
            }
        });
    }

}

在清单中申明MyApp


        
            
                

                
            
        
      
        
    

最后,在MainActivity中只需要onDestroy的销毁服务,onStart和onStop不需要了。
至此已经实现了背景音乐随应用状态改变而播放和暂停了。通过完成此功能,你会对
Service和Activity的生命周期有更深层的了解。

《道德经》第十一章:
三十辐共一毂,当其无,有车之用。埏埴以为器,当其无,有器之用。凿户牖以为室,当其无,有室之用。故有之以为利,无之以为用。
译文:三十根辐条汇集到一根毂中的孔洞当中,有了车毂中空的地方,才有车的作用。揉和陶土做成器皿,有了器具中空的地方,才有器皿的作用。开凿门窗建造房屋,有了门窗四壁内的空虚部分,才有房屋的作用。所以,“有”给人便利,“无”发挥了它的作用。

你可能感兴趣的:(Android)