Android服务之混合方式开启服务

引言
前面介绍过了Android服务的两种开启方式:Start方式可以让服务在后台运行;bind方式能够调用到服务中的方法。
在实际的开发工作中,有很多需求是:既要在后台能够长期运行,又要在服务中操作业务。那么就需要两种方式结合在一起,才能做到我们想要的结果。


需求:模仿音乐后台播放案例,实现应用退出后,服务中依然可以在后台运行。

代码如下
AndroidManifest.xml 清单文件中配置service

 <service android:name=".service.music.MusicPlayService" />

MusicPlayService.class 自定义一个服务类

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
/**
 * 创建一个服务:模拟后台播放音乐(以控制台打印Log代替)
 */
public class MusicPlayService extends Service {
    //音乐播放状态(播放:true; 暂停:false)
    private boolean running = false;

    @Override
    public IBinder onBind(Intent intent) {
        return new MusicBindImpl();
    }
    @Override
    public void onCreate() {
        super.onCreate();
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
    //服务中的方法:播放音乐
    public void player() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                running = true;
                while (true) {
                    if (running) {
                        SystemClock.sleep(500);
                        Log.e("music", "音乐播放中...");
                    }
                }
            }
        }).start();
    }
    //服务中的方法:继续播放
    public void rePlayer() {
        player();
    }
    //服务中的方法:暂停音乐
    public void pause() {
        running = false;
    }

    /**
     * 创建中间帮助类对象
     */
    class MusicBindImpl extends Binder implements IMusicService {
        @Override
        public void callPlayer() {
            player();
        }
        @Override
        public void callRePlayer() {
            rePlayer();
        }
        @Override
        public void callPause() {
            pause();
        }
    }
}

IMusicService.interface 抽取一个接口封装方法

/**
 * 定义一个接口,封装中间帮助类要实现的函数
 */
public interface IMusicService {
    public void callPlayer();
    public void callRePlayer();
    public void callPause();
}

MusicServiceConnection.class 创建一个服务连接对象

import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.IBinder;
/**
 * 定义服务连接对象,接收中间帮助类对象
 */
public class MusicServiceConnection implements ServiceConnection {

    private MusicPlayService.MusicBindImpl musicBind;

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        //获取:服务绑定成功后,返回的中间帮助类对象
        this.musicBind = (MusicPlayService.MusicBindImpl) service;
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
    }
    //暴露方法:获取中间帮助类对象
    public MusicPlayService.MusicBindImpl getMusicBindImpl(){
        return musicBind;
    }
}

MusicPlayActivity.class
该类使用kotlin语言,模拟音乐 开、关、暂停、继续、退出 操作

/**
 * 混合方式开启服务
 */
class MusicPlayActivity : BaseActivity() {
    //意图,开启服务
    private var musicIntent: Intent? = null
    //服务连接对象
    private var msconn: MusicServiceConnection? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_music_play)
        startMusicService()
        tvPlayer.setOnClickListener {
            playerMethod()
        }
        tvPause.setOnClickListener {
            pauseMethod()
        }
        tvRePlayer.setOnClickListener {
            rePlayerMethod()
        }
        tvSignOut.setOnClickListener {
            signOutMethod()
        }
    }
    //开启服务
    private fun startMusicService() {
        //1. 使用start方式开启服务
        musicIntent = Intent(this, MusicPlayService::class.java)
        this.startService(musicIntent)
        //2. 通过bind方式绑定该服务(同一个Intent对象)
        if (msconn == null) {
            //保证ServiceConnection连接对象的唯一性
            msconn = MusicServiceConnection()
        }
        bindService(musicIntent, msconn, Context.BIND_AUTO_CREATE)
    }
    //播放
    private fun playerMethod() {
        msconn?.musicBindImpl?.callPlayer()
    }
    //暂停
    private fun pauseMethod() {
        msconn?.musicBindImpl?.callPause()
    }
    //继续
    private fun rePlayerMethod() {
        msconn?.musicBindImpl?.callRePlayer()
    }
    //退出(先暂停播放,在解绑,最后应用退出)
    private fun signOutMethod() {
        msconn?.musicBindImpl?.callPause()
        //3. 解绑服务并停止服务
        if (msconn != null) {
            this.unbindService(msconn)
            msconn = null
        }
        if (musicIntent != null) {
            stopService(musicIntent)
        }
        this.finish()
    }
    override fun onDestroy() {
        super.onDestroy()
        if (msconn != null) {
            this.unbindService(msconn)
            msconn = null
        }
        musicIntent = null
    }
}

代码流程补充说明:

  • 创建一个Service,定义具体的操作。并创建一个中间人对象(中间帮助类),由中间对象调用Service内的方法(内部类的使用)。
  • 创建Service连接对象,当Service绑定成功后,接收中间人对象。
  • 在Activity中开启服务,获取中间人对象,通过中间人对象执行Service内的方法
      1. 先以 start方式开启服务:。目的:让服务可以在后台运行。
      1. 再以 bind方式开启服务。目的:能够调用到服务中的方法。
      1. unbind 解绑服务。(解绑后切记把ServiceConnection对象置null,避免运行异常)
      1. stopService终止服务。(用户主动关闭服务执行该操作)

PS:

  • 混合方式开启的服务虽然可以做到后台运行,但并不是不能被Kill掉,假如整个应用程序的进程被Kill掉了,那么服务就会结束。
  • 平常我们操作退出应用,应用的进程只是从前台进程变成了后台进程,并没有直接被kill掉,所以我们的服务还能够运行。
  • 如果想要服务即便是在应用进程被kill掉的情况下依然可以运行,那么就需要做"服务后台保活"的操作。

这里先提供一个方案:在代码中定义两个Service,如:ServiceA,ServiceB,当ServiceA执行onDestroy时,让ServiceA开启ServiceB,当ServiceB执行onDestroy时,再让ServiceB开启ServiceA。这样两个服务相互开启,就会一直有一个服务在运行中。(具体的代码操作,后面我抽时间会写一个案例贴出了,大家一起探讨)

posted @ 2019-09-22 18:04  ming3  阅读(363)  评论(0编辑  收藏  举报