在上一篇  Android-bindService本地服务-音乐播放-上,博客中是不能在后台中播放到,这次博客增加了一个后台播放

 

通常情况下,Activity切换到后台,Service提升到前台进程,既然Service提升到前台进程就会有一个通知。

      Activity ---> moveTaskToBack(true);

      Service ---> startForeground(1, builder1.getNotification());

 

进程优先级别:前台进程,可视进程,服务进程,后台进程,空进程  (前台进程是最稳定,系统内存不足是先回收 空进程)

为什么要把服务Service提升为前台进程,在内存不足时,前台进程不会那么容易被系统回收

把 服务进程 提升到 前台进程 会自动绑定通知

UI相关,当在播放当过程中点击返回键,就需要告诉用户是否在后台运行

 

点击后台播放,Activity就会被切换到后台,想要再次启动APP就可以点击通知进入:

 

由于这个MainActivity会被多次启动,为了保证单任务,需要设置启动模式:android:launchMode="singleTask"

    <activity android:name=".MainActivity5"
                  android:launchMode="singleTask">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

        </activity>

 

 定义Binder扩展接口:

package liudeli.service1.service.inter;

import android.media.MediaPlayer;

public interface IMusicPlay {

    /**
     * 播放音乐
     * @param musicPath 音乐文件的路径
     */
    void playMusic(String musicPath);

    /**
     * 暂停播放
     */
    void pausedPlay();

    /**
     * 继续播放
     */
    void continuePlay();

    /**
     * 停止播放
     */
    void stopPlay();

    /**
     * 让Activity可以获取到服务使用到MediaPlayer
     * @return
     */
    MediaPlayer getMediaPlayer();
}

 

Service控制播放:

package liudeli.service1.service;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.RequiresApi;
import android.util.Log;

import java.io.IOException;

import liudeli.service1.MainActivity5;
import liudeli.service1.R;
import liudeli.service1.service.inter.IMusicPlay;

public class MyService5 extends Service {

    private final String TAG = MyService5.class.getSimpleName();

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void onCreate() {
        super.onCreate();

        /**
         * 进程优先级别:前台进程,可视进程,服务进程,后台进程,空进程  (前台进程是最稳定,系统内存不足是先回收 空进程)
         *
         * 为什么要把服务Service提升为前台进程,在内存不足时,前台进程不会那么容易被系统回收
         *
         * 把 服务进程 提升到 前台进程 会自动绑定通知
         */

        // 需要用到通知,用户点击通知栏,就计划APP-->Activity

        // 这是以前到写法,已经过时
        /*Notification notification = new
                Notification(R.mipmap.ic_launcher, "我的音乐播放器", System.currentTimeMillis());*/

        // 设置事件信息,点击通知可以跳转到指定Activity
        Intent intent = new Intent(this, MainActivity5.class);


        // 设置事件信息,点击通知可以跳转到指定Activity
        NotificationManager notificationManager = (NotificationManager)
                getSystemService(Context.NOTIFICATION_SERVICE);

        // 设置通知显示相关信息
        Notification.Builder builder1 = new Notification.Builder(this);
        builder1.setSmallIcon(R.mipmap.ic_launcher); //设置图标
        /*builder1.setTicker("显示第二个通知");*/
        builder1.setContentTitle("播放中"); //设置标题
        builder1.setContentText("我的音乐播放器"); //消息内容
        builder1.setWhen(System.currentTimeMillis()); //发送时间
        builder1.setDefaults(Notification.DEFAULT_ALL); //设置默认的提示音,振动方式,灯光
        builder1.setAutoCancel(true);//打开程序后图标消失

        // 延时意图,所谓延时意图就是不是马上执行,需要用户去点击后才执行,其实就是对Intent对封装
        PendingIntent pendingIntent =PendingIntent.getActivity(this, 0, intent, 0);
        builder1.setContentIntent(pendingIntent);
        Notification notification1 = builder1.build();
        notificationManager.notify(124, notification1); // 通过通知管理器发送通知

        // id=通知到唯一标示  notification=通知
        startForeground(1, builder1.getNotification());
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "绑定成功");
        return new PlayMusicBinder();
    }

    private MediaPlayer mediaPlayer;

    /**
     * 增强版Binder,扩展出播放音乐🎵行为
     */
    class PlayMusicBinder extends Binder implements IMusicPlay {

        public PlayMusicBinder() {
            mediaPlayer = new MediaPlayer();
        }

        /**
         * 播放音乐
         *
         * @param musicPath 音乐文件的路径
         */
        @Override
        public void playMusic(String musicPath) {
            try {
                mediaPlayer.reset();
                mediaPlayer.setDataSource(musicPath);
                mediaPlayer.prepare();
                mediaPlayer.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /**
         * 暂停播放
         */
        @Override
        public void pausedPlay() {
            mediaPlayer.pause();
        }

        /**
         * 继续播放
         */
        @Override
        public void continuePlay() {
            mediaPlayer.start();
        }

        /**
         * 停止播放
         */
        @Override
        public void stopPlay() {
            mediaPlayer.stop();
        }

        /**
         * 让Activity可以获取到服务使用到MediaPlayer
         *
         * @return
         */
        @Override
        public MediaPlayer getMediaPlayer() {
            return mediaPlayer;
        }
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "解绑成功");

        // 为什么解绑服务了,音乐还在播放,应该MediaPlay内部是一个服务

        if (mediaPlayer != null) {
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.stop();
            }
            mediaPlayer.release(); // 释放硬件播放资源
        }
        return super.onUnbind(intent);
    }
}

 

MainActivity调用Service代码:

package liudeli.service1;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import liudeli.service1.service.MyService5;
import liudeli.service1.service.inter.IMusicPlay;

public class MainActivity5 extends Activity implements View.OnClickListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main5);

        initView();
        initListener();
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 绑定服务
        bindService(new Intent(this, MyService5.class), connection, BIND_AUTO_CREATE);
    }

    private Button btPlayMusic;
    private Button btPausedContinue;
    private Button btStop;

    private void initView() {
        btPlayMusic = findViewById(R.id.bt_play_music);
        btPausedContinue = findViewById(R.id.bt_paused_continue);
        btStop = findViewById(R.id.bt_stop);
    }

    private void initListener() {
        btPlayMusic.setOnClickListener(this);
        btPausedContinue.setOnClickListener(this);
        btStop.setOnClickListener(this);
    }

    private IMusicPlay iMusicPlay;

    private ServiceConnection connection = new ServiceConnection() {
        /**
         * 连接到服务
         * @param name
         * @param service
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iMusicPlay = (IMusicPlay) service;
        }

        /**
         * 断开连接
         * @param name
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 解绑服务:注意bindService后 必须要解绑服务,否则会报 连接资源异常
        if (null != connection) {
            unbindService(connection);
        }
    }

    // 音乐文件到路径
    private final String MUSIC_PATH = Environment.getExternalStorageDirectory() + "/cjyy.mp3";

    @Override
    public void onClick(View v) {

        if (iMusicPlay == null) {
            Toast.makeText(this, "音乐播放服务连接失败", Toast.LENGTH_LONG).show();
            return;
        }

        switch (v.getId()) {

            case R.id.bt_play_music:
                iMusicPlay.playMusic(MUSIC_PATH);
                break;

            case R.id.bt_paused_continue:
                if ("暂停播放".equals(btPausedContinue.getText().toString())) {
                    btPausedContinue.setText("继续播放");
                    iMusicPlay.pausedPlay();
                } else {
                    btPausedContinue.setText("暂停播放");
                    iMusicPlay.continuePlay();
                }
                break;

            case R.id.bt_stop:
                iMusicPlay.stopPlay();
                break;

            default:
                break;
        }
    }

    /**
     * 用户按返回键,系统会调用到此方法
     * @param keyCode
     * @param event
     * @return
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        super.onKeyDown(keyCode, event);

        // 判断是否在播放,如果在播放中,才告知用户 弹出对话框
        if (iMusicPlay == null) {
            return true;
        }

        MediaPlayer mediaPlayer = iMusicPlay.getMediaPlayer();

        if(mediaPlayer.isPlaying()) {

            switch (keyCode) {
                case KeyEvent.KEYCODE_BACK:
                    showAlertDialog();
                    break;
            }
        }
        return true;
    }

    /**
     * 弹出对话框
     */
    private void showAlertDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity5.this);
        builder.setTitle("提示");
        builder.setMessage("确定要关闭音乐播放?");
        builder.setNegativeButton("确定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                finish();
            }
        });
        builder.setNeutralButton("后台播放", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // 既然是后台播放,就是要把当前Activity切换到后台
                moveTaskToBack(true);
            }
        });
        builder.setPositiveButton("取消", null);
        AlertDialog dialog = builder.create();
        dialog.show();
    }
}