1、概念:
a、可以长时间运行在后台,而没有用户界面的组件,不可以与用户直接交互。
b、一个服务不是一个单独的线程。默认情况下,Service中的所有代码都运行在主线程中。注意:因为Service默认工作在主线程中,所以不能直接用它做耗时的工作,最好在Service中开启新的线程运行耗时的任务。
c、一个服务不是一个单独的进程。默认情况下,它与运行程序工作在同一线程中。
d、Service很大程度上充当了应用程序后台线程管理器的角色。(如果在Activity中开启一个线程,当Activity关闭后,线程依然在工作,但是与开启它的Activity失去联系,也就是说这个线程处于失去管理的状态。使用Service可以对后台运行的线程进行有效的管理)
e、服务的分类:
1、本地服务Local Service 用于应用程序内部;用于实现应用程序自己的耗时任务
2、远程服务Remote Service 用于android系统内部的应用程序之间。可以定义接口并把接口暴露出来,以便其它应用进行操作。
2、Service需要注册:
总结四大组件均如何注册、继承关系图…………….
3、IntentService
相对Service的好处:
1、自带线程,onHandleIntent()方法工作在子线程中,不必手动开启新的线程
2、线程工作结束后自动终止线程,无需手动调用stopService()或者stopSelf()方法终止线程(终止服务的方法)
3、提供了一个onStartCommand()方法的默认实现,它将Intent先传送至工作队列,然后从工作队列中每次取出一个Intent传送至onHandleIntent()方法,在该方法中对Intent做相应的处理
4、提供了一个onBind()方法的默认实现,它返回null
**5、IntentService使用队列的方式将请求的Intent加入队列,然后开启一个worker thread(线程)来处理队列中的Intent,对于异步的startService请求,IntentService会处理完成一个之后再处理第二个,每一个请求都会在一个单独的worker thread中处理,不会阻塞应用程序的主线程。但你若是想在Service中让多个线程并发的话,就得使用startService()方法,在Service内部起多个线程,但是这样的话,你可要处理好线程的同步。
4、备注:
a、Service是不能自己启动的,只有通过 Context 对象调用startService() 和bindService() 方法来启动。
b、在Service每一次的开启关闭过程中,只有onStartCommand()可被多次调用(通过多次startService调用),其他onCreate()、onBind()、onUnbind()、onDestory()在一个生命周期中只能被调用一次。
c、当Activity被销毁,Service是否被销毁?
bindService会,Activity被销毁会调用Service的onUnBind(),onDestroy()方法
StartService不会
注意:多个Activity可以使用同一个Service,当Service被创建过之后,若再有Activity开启服务则只调用onStartCommand()方法,并且,在任一Activity中关闭Service,就会调用onDestroy方法销毁服务。
5、启动Service有两种方法
(一)Context.startService(Intent)
调用者与服务之间没有关联,即使调用者退出,服务仍然可以运行
1、startService的生命周期:
onCreate() 创建服务
onStartCommand() 服务开始运行
onDestory() 服务被停止
在程序中调用Context.startService()会先构造一个Service,然后触发执行生命周期中的onCreate() onStartCommand()方法,此时服务就开始正式运行;
若此时退出程序,没有调用stopService(),Service会一直在后天运行,再打开程序,则由于Service没有被停止,所以只调用onStartCommand()方法(所以一个Service的onStartCommand()方法可能会被调用很多次);
如果在程序中调用Context.stopService()会触发执行生命周期中的onDestory()方法,会让服务停止,此时,再想运行服务,就会再构造一个Service再执行onCreate()、onStartCommand()方法
2、案例:
a、在主页面中点击download按钮,开启一个Service下载视频,将视频保存在SD卡的指定位置;
b、下载完成后发送一个广播,广播的意图action为“ido”;
c、新建一个接收广播类继承BroadcastReceiver,在MainActivity中动态注册,在该类的onReceive方法中判断接受的Intent的action是否是“ido”频段,若是,发送一个通知,通知中包含一个等待意图(PendingIntent);
d、用户点击后跳转到播放界面,播放界面有一个VideoView,设置VideoView的视频地址,start()方法开始播放视频,并取消通知栏的指定通知
主界面:
public class MainActivity extends AppCompatActivity {
private NotificationReceiver receiver = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 注意:***第一次:忘记写了
receiver = new NotificationReceiver();
}
@Override
protected void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("ido");
registerReceiver(receiver,intentFilter);
}
@Override
protected void onPause() {
super.onPause();
if(receiver != null){
unregisterReceiver(receiver);
}
}
public void download(View view) {
Intent intent = new Intent(this, VedioDownLoadService.class);
startService(intent);
}
}
下载视频服务:
public class VedioDownLoadService extends IntentService {
//重写该方法时,原方法有一个参数是name,可以删除后给父类带参构造传入一个“”
public VedioDownLoadService() {
super("");
}
@Override
protected void onHandleIntent(Intent intent) {
byte[] data = OkHttpUtils.getBytesByUrl(MyUrl.VIDEO_URL);
boolean flag = SDCardUtils.saveData2SDCard(data,"sign","hehe.mp4");
if(flag){
Intent intent1 = new Intent();
intent1.setAction("ido");
sendBroadcast(intent1);
}
}
}
接收广播并发送通知:
public class NotificationReceiver extends BroadcastReceiver {
public NotificationReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if("ido".equalsIgnoreCase(action)){
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setTicker("biubiu");
builder.setContentTitle("下载完成");
builder.setContentText("请观看!");
Intent intent2 = new Intent(context,VedioPlayActivity.class);
PendingIntent pi = PendingIntent.getActivity(context,1,intent2,PendingIntent.FLAG_ONE_SHOT);
builder.setContentIntent(pi);
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(2,builder.build());
}
}
}
播放视频界面
public class VedioPlayActivity extends AppCompatActivity {
private VideoView vvPlay = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_vedio_play);
initView();
String path = SDCardUtils.getSDCardPath()+ File.separator+"sign"+File.separator+"hehe.mp4";
vvPlay.setVideoPath(path);
vvPlay.start();
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(2);
}
private void initView() {
vvPlay = (VideoView) findViewById(R.id.vvPlay);
}
}
3、onStartCommand回调方法的返回值
START_STICKY(常量值:1):sticky的意思是“粘性的”。使用这个返回值时,我们启动的服务跟应用程序”粘”在一起,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务。当再次启动服务时,传入的第一个参数将为null;
START_NOT_STICKY(常量值:2):“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。
START_REDELIVER_INTENT(常量值:3):重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值再次传入。
START_STICKY_COMPATIBILITY(常量值:0):如果执行完onStartCommand()方法后服务被异常终止,系统并不保证该服务会被再次启动
可以理解为发生车祸后的人:
START_STICKY:(常量值:1)车祸后自己苏醒,但是失忆;
START_NOT_STICKY:(常量值:2)车祸后再也没有苏醒;
START_REDELIVER_INTENT:(常量值:3)车祸后自己苏醒,依然保持记忆。
4、自定义线程,使程序终止
// 自定义一个线程,目的是让程序发生OOM异常,以终止当前app,来观察服务是否会自动重新启动,是否会携带数据
class MyThread extends Thread{
@Override
public void run() {
super.run();
List<Bitmap> list = new ArrayList<Bitmap>();
while(true){
list.add(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher));
}
}
}
(二)Context. bindService(intent, conn, BIND_AUTO_CREATE);
第一个参数指定了要绑定的Service的意图;方法的第二个参数为ServiceConnection接口的实现类的对象;方法的第三个参数指的是在service不存在时创建一个。
内部流程:在程序中调用 bindService(intent, conn, BIND_AUTO_CREATE)方法会先创建Service实例然后触发执行Service生命周期的onCreate()、onBind()方法,此时服务开始运行,onBind()方法会返回一个IBinder接口实现类的实例;此实例作为参数传到onServiceConneted方法中,可以通过实现类的实例进行操作。
生命周期:
onCreate()创建服务
onBind()绑定服务,服务开始运行
onUnbind()取消绑定
onDestory()服务被停止
ServiceConnection接口实现类:
作用:它监视服务类与Activity之间的连接,在重写onServiceConnected(ComponentName componentName, IBinder iBinder)方法时,其中第二个参数IBinder即onBind()方法返回的接口实例
必须重写两个方法:
onServiceConnected(ComponentName componentName, IBinder iBinder)
onServiceDisconnected(ComponentName componentName)Android系统与Service连接意外丢失时调用这个,比如服务崩溃了或被强杀了。
注意:当客户端解除绑定时,这个方法不会被调用。
案例:
启动MediaPlayer播放音乐:
实现功能:
1、点击start后播放
2、可以拖动滑块调节进度,调节音频播放进度
3、播放时可以暂停,暂停了可以播放,停止后归零
MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener,SeekBar.OnSeekBarChangeListener{
private SeekBar sbShow = null;
private TextView txtAll , txtAlso;
private Button btnStart,btnStop;
private PlayService playService = null;
private PlayConn conn = null;
private PlayReceiver receiver = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
setListioner();
setTitle("早睡早起身体好");
Intent intent = new Intent(this,PlayService.class);
conn = new PlayConn();
bindService(intent,conn,BIND_AUTO_CREATE);
}
@Override
protected void onStart() {
super.onStart();
receiver = new PlayReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("sign");
registerReceiver(receiver,intentFilter);
}
@Override
protected void onStop() {
super.onStop();
if(receiver != null){
unregisterReceiver(receiver);
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btnStart:
playService.play();
if(playService.isPlaying()){
btnStart.setText("pause");
}else{
btnStart.setText("start");
}
break;
case R.id.btnStop:
playService.stop();
btnStart.setText("start");
sbShow.setProgress(0);
txtAll.setText(R.string._00_00);
txtAlso.setText(R.string._00_00);
break;
}
}
// 用户拖动时因为没有广播发送,所以要根据滑块的进度来更新时间
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
currentTime(sbShow.getMax(),progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
playService.isSend(false);
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
playService.isSend(true);
playService.setPregress(seekBar.getProgress());
}
class PlayReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if("sign".equalsIgnoreCase(intent.getAction())){
int duration = intent.getIntExtra("duration",0);
int position = intent.getIntExtra("position",0);
sbShow.setMax(duration);
sbShow.setProgress(position);
currentTime(duration,position);
}
}
}
public void currentTime(int duration,int position){
int max = duration/1000;
int mm = max/60;
int ms = max%60;
String strMM="";
String strMS="";
if(mm<10){
strMM = "0"+mm;
}else{
strMM = ""+ms;
}
if(ms<10){
strMS = "0"+ms;
}else {
strMS = ""+ms;
}
txtAll.setText(strMM+":"+strMS);
int also = (duration-position)/1000;
int am = also/60;
int as = also%60;
String strAM = "";
String strAS = "";
if(am<10){
strAM = "0"+am;
}else{
strAM = ""+as;
}
if(as<10){
strAS = "0"+as;
}else{
strAS = ""+as;
}
txtAlso.setText(strAM+":"+strAS);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(conn);
}
class PlayConn implements ServiceConnection{
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
PlayService.MyBinder ibinder = (PlayService.MyBinder) service;
playService = ibinder.getService();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
private void setListioner() {
btnStart.setOnClickListener(this);
btnStop.setOnClickListener(this);
sbShow.setOnSeekBarChangeListener(this);
}
private void initView() {
sbShow = (SeekBar) findViewById(R.id.sbShow);
txtAll = (TextView) findViewById(R.id.txtAll);
txtAlso = (TextView) findViewById(R.id.txtAlso);
btnStart = (Button) findViewById(R.id.btnStart);
btnStop = (Button) findViewById(R.id.btnStop);
}
}
PlayService
public class PlayService extends Service {
private MediaPlayer mediaPlayer = null;
private boolean isSend = true;
public PlayService() {
}
@Override
public void onCreate() {
super.onCreate();
// 创建MediaPlayer对象,包含两个参数,context和歌曲的资源id
mediaPlayer = MediaPlayer.create(this, R.raw.hua);
// 设置媒体一直循环
mediaPlayer.setLooping(true);
// 开启播放线程
new PlayThread().start();
}
// 播放线程,每隔一秒发送广播,发送当前进度和最大值
class PlayThread extends Thread {
@Override
public void run() {
super.run();
while (true) {
// 当mediaPlayer正在播放且不为null发送广播
if (mediaPlayer.isPlaying() & mediaPlayer != null) {
int duration = mediaPlayer.getDuration();
int position = mediaPlayer.getCurrentPosition();
Intent intent = new Intent("sign");
intent.putExtra("duration", duration);
intent.putExtra("position", position);
// 如果isSend为true即用户没有拖动滑块,发送广播
if (isSend) {
sendBroadcast(intent);
}
// 每隔一秒
SystemClock.sleep(1000);
}
}
}
}
// 控制mediaPlayer的播放与暂停(若正在播放就暂停,若暂停就设置为播放)
public void play() {
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
} else {
mediaPlayer.start();
}
}
// 获得当前mediaPlayer的播放状态
public boolean isPlaying() {
return mediaPlayer.isPlaying();
}
// 停止播放
// parse是暂停,之后可以直接调用start从原位置恢复播放
// stop是停止,停止播放并暂时释放资源,需要prepare后再调用start才可以从原位置播放
public void stop() {
mediaPlayer.seekTo(0);
mediaPlayer.pause();
// mediaPlayer.stop();
}
// 标志滑块是否处在被触摸或被拖动的状态
public void isSend(boolean isSend) {
this.isSend = isSend;
}
// 根据用户拖动后滑块后的位置设置音频的播放进度
public void setPregress(int progress) {
mediaPlayer.seekTo(progress);
}
// 让媒体播放器停止
@Override
public void onDestroy() {
super.onDestroy();
mediaPlayer.stop();
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
// 内部类PlayBinder,目的是将该对象返回给onBind()方法的IBinder,并且可以获得的IBinder实例获得PlayService实例
class MyBinder extends Binder {
public PlayService getService() {
return PlayService.this;
}
}
}
案例:
class CounterBinder extends Binder implements ICounter
在IBinder实现类中实现接口ICounter的方法,在MainActivity中即可将IBinder对象转为ICounter对象,进而调用ICounter中的方法。
备注:
getSystemService(String name)是Android中很重要的一个方法,根据NAME来取得对应的Object,然后转化为相应的服务对象。