服务(Service)是Android系统中的四大组件之一,与Activity不同,它是不能与用户交互的。它是一种长生命周期、没有可视化界面、运行于后台的服务程序。比如我们播放音乐的时候,有可能想干其他事情,当退出播放音乐的应用,如果不用Service,我们就听不到歌;又比如一个应用的数据是通过网络获取的,不同时间(一段时间)的数据是不同的,这时候可以用Service在后台定时更新,而不用在打开应用的时候去获取。
1.本地服务
本地服务(Local Service)用于应用程序内部,可以实现应用程序的一些耗时任务,比如查询升级信息、网络传输,或者需要在后台执行,比如播放音乐并不占用应用程序的线程,而是在后台单开线程执行,从而使用户体验比较好。
1.2两种启动方式
Service有两种启动方式:Context.bindService()和Context.startService()。这两种方式对Service生命周期的影响是不同的。
1.通过Context.bindService(Intent intent,ServiceConnection conn,int flags)启动
(1)绑定时,bindService→onCreate()→onBind();绑定Service需要3个参数。
①intent:Intent对象,需要定义执行服务类。
②conn:ServiceConnection接口对象,创建该对象要实现它的onServiceConnected()和onServiceDisconnected()来判断是连接成功还是断开连接。
onServiceConnected(ComponentName name,IBinder service):系统调用该函数来传递由service的onBind()方法返回的IBinder。
onServiceDisconnected(ComponentName name):如果对service的连接意外丢失,比如当service奔溃或被杀死时,系统就会调用该函数。
bindService方法执行之后会自动调用ServiceConnection接口里的onServiceConnected方法;如果执行unbindService方法,则不会自动调用ServiceConnection接口里的onServiceDisconnected方法。因为ServiceConnection接口的onServiceDisconnected方法只会在Service被停止或者被系统杀死后调用,也就是说,执行unbindService只是告诉系统已经和这个服务没有关系了。在内存不足的时候,系统可以优先杀死这个服务。
这里需要注意一点的是,Service和需要绑定的Activity需要放在同一个包内,否则将无法调用ServiceConnection接口中的上述方法。
③flags:创建Service模式,一共有以下三种模式。
- Service.BIND_AUTO_CREATE:指定绑定的时候自动创建Service,这是最常使用的模式。
- Service.BIND_DEBUG_UNBIND:测试绑定的时候创建创建Service,这里进行调试所用的模式。
- Service.BIND_NOT_FOREGROUND:不在前台进行绑定时创建Service。(例如后台播放音乐)
(2)解绑定时,unbindService→onUnbind()→onDestroy()。
此时如果调用者(如Activity)直接退出,Service由于与调用者绑定在一起,就会随着调用者一同停止。
用Context.bindService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onBind()方法。此时如果调用者和服务绑定在一起,调用者退出了,系统就会先调用服务的onUnbind()方法,接着调用onDestroy()方法。如果调用bindService()方法前服务已经被绑定,多次调用Context.bindService()方法并不会导致多次创建服务及绑定(也就是说,onCreate()和onBind()方法并不会被多次调用)。如果调用者希望与正在绑定的服务解除绑定,可以调用Context.unbindService()方法,调用该方法也会导致系统调用服务的onUnbind()和onDestroy()方法。
下面以播放一首歌为例,新建一个工程项目,
音乐类(Music.java)的代码如下。
package com.example.demo_test_for_service_music;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
import androidx.annotation.Nullable;
public class Music extends Service {
private MediaPlayer mediaPlayer;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
this.mediaPlayer = MediaPlayer.create(this,R.raw.liekkas);
this.mediaPlayer.start();
}
@Override
public void onDestroy() {
super.onDestroy();
this.mediaPlayer.stop();
}
}
Music类继承自Service,它是一个服务,在创建的时候开始播放一首歌,在销毁的时候停止播放。在AndroidManifest.xml中进行配置并声明Action,配置文件如下。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.demo_test_for_service_music">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".Music">
<intent-filter>
<action android:name="com.example.demo_test_for_service_music.Music"/>
</intent-filter>
</service>
</application>
</manifest>
播放类代码如下。
package com.example.demo_test_for_service_music;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
private void initView(){
Button playButton = findViewById(R.id.start);
Button stopButton = findViewById(R.id.stop);
}
private View.OnClickListener clickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start:
bindService(new Intent("com.example.demo_test_for_service_music.Music"),null, Service.BIND_AUTO_CREATE);
break;
case R.id.stop:
unbindService(null);
default:
break;
}
}
};
}
当点击播放时进行播放,当点击停止时音乐停止。当播放的时候退出程序,音乐也随之停止了。这就是用绑定方法启动服务产生的效果。有时候我们的需求并非这么简单,比如我们希望在播放音乐的时候,可以干其他事情,退出音乐播放界面后还要求音乐还能够继续在后台播放。第二种启动方式将有助于解决这一问题。
2.通过Context.startService(Intent intent)启动
(1)启动时,startService→onCreate()→onStart()
(2)停止时,stopService→onDestroy()
此时如果调用者(Activity)直接退出而没有停止Service,则Service会一直在后台运行。Context.startService() 方法启动服务,在Service未被创建时,系统会先调用Service的onCreate()方法,接着调用onStart()方法。如果调用Context.startService()方法并不会导致多次创建服务,但会导致多次调用onStart()方法。采用Context.startService()启动的服务,只能调用Context.stopService()方法结束,服务结束时会调用onDestroy()方法。
在上一个例子中,如果退出程序后还能够播放音乐,我们只需改变它的启动方式即可,代码如下。
package com.example.demo_test_for_service_music;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
private void initView(){
Button playButton = findViewById(R.id.start);
Button stopButton = findViewById(R.id.stop);
}
private View.OnClickListener clickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start:
startService(new Intent("com.example.demo_test_for_service_music.Music"));
break;
case R.id.stop:
stopService(new Intent("com.example.demo_test_for_service_music.Music"));
default:
break;
}
}
};
}
1.2生命周期
Service生命周期一般有两种运行模式。
(1)在程序没有停止Service或者Service自己停止的情况下,Service将一直在后台运行。
在该模式下,Service通过Context.startService()方法开始,并通过Context.stopService()方法停止。当然,它也可以通过Service.stopSelf()方法或者Service.stopSelfResult()方法来停止自身。stopService()方法只须调用一次便可停止服务。
(2)Service可以通过接口被外部程序调用。外部程序建立一个到Service的连接,并通过这个连接来操作Service。建立连接开始于Context.bindService(),结束于Context.unbindService()。多个客户端可以绑定到同一个Service。如果Service没有启动,可以通过Context.bindService()启动它。
这两种模式并不是完全分离,可以被绑定到一个通过Context.startService()启动的服务上。比如一个Intent想要播放音乐,就通过Context.startService()方法启动在后台播放音乐的Service。如果用户想要操作播放器或者获取当前正在播放的音乐的信息,一个新的Activity就会通过Context.bindService()建立一个到此Service的连接。举例如下。
//每一次进入该界面,都要绑定这个服务,这样才能查看在该服务器中所播放的音乐的信息
//而多次调用startService方法并不会每次都创建一个新的服务实例
bindService(new Intent(this,MusicService.class),conn,Context.BIND_AUTO_CREATE);
startService(new Intent(this,MusicService.class));
//像Activity一样,Service也有可以监视生命周期状态的方法,如下所示。
void onCreate()
void onStart(Intent intent)//由Context.startService()启动的服务所具有的方法
void onBind()//由Context.bindService()启动的服务所具有的方法
void onUnBind()//由Context.bindService()启动的服务所具有的方法
void onReBind()//由Context.bindService()启动的服务所具有的方法
void onDestroy
通过实现这几个方法,我们看一下Service的生命周期。
1.整个生命周期
Service的生命周期从onCreate()开始,到onDestroy()结束,跟Activity很类似。Service生命周期在onCreate()中执行初始化操作,在onDestroy()中释放所有用到的资源。如后台播放音乐可以在onCreate()创建一个播放音乐的线程,在onDestroy中销毁这个线程。
2.活动生命周期
Service的活动生命周期开始于onStart(),或者开始于onBind()方法。在音乐播放器中,使用Context.startService()方法启动,音乐服务会通过Intent来查看要播放哪首歌曲并开始播放。注意:onCreate()和onDestroy()用于所有通过Context.startService()或者Context.bindService()启动的Service,而onStart()只用于通过Context.startService() 开始的Service,onBind()则用于通过Context.bindService()启动的Service。
绑定的Service能触发以下的方法。
IBinder onBind(Intent intent)
boolean onUnbind(Intent intent)
void onRebind(Intent intent)
onBind()被传递给调用Context.bindService()的Intent,onUnbind被Context.unbindService()中的Intent使用。如果服务允许被绑定,那么onBind()方法返回客户端和Service的连接通道。如果一个新的客户端连接到服务,onUnbind()会触发onRebind()的调用。
2.远程服务
远程服务(Remote Service)用于Android系统内部的应用程序之间。
远程服务可以通过自己定义并暴露出来的接口进行程序操作。连接以调用Context.bindService()方法建立,以调用Context.unbindService()关闭。多个应用程序可以绑定至同一个服务。此时如果服务还没有加载,Context.bindService会先加载它。远程服务可被其他应用程序复用,比如天气预报服务,其他应用程序不再写这样的服务,调用已有的即可。
在Android系统中,一个进程通常不能直接访问其他进程的内存空间。如果要在不同的进程间传递对象,需要把对象解析成操作系统能够理解的数据格式,Android采用AIDL(Android Interface Definition Language,接口定义语言)的方式实现这个操作。
3.服务小程序
在综合案例中,客户端需要定时与服务器进行交互,以获得最新的话题、私信以及好友的信息,并在界面中进行提示、刷新。MsgService类可以完成这些功能。
MsgService类继承自Service类,实现了其中的onCreate() 、onStar()、onBind()与onDestroy()方法,同时也实现了接口Runnable和其中的run()方法。
onCreate()方法初始化Service,并获得当前登录用户的用户名与密码信息,为请求服务器的参数设置做好准备。
onStart()方法利用Runnable开启一个线程,并运行run()方法,进行服务器的数据请求。在线程中请求是为了防止程序过多地占用内存。在run()方法中对所需要的数据进行不同的请求,通过调用request()方法来连网实现,并在request()方法中对结果进行判别,最后对返回的数据进行解析以完成数据库的更新和修改操作,run()方法中根据request()方法的返回结果进行判断是否有新数据插入数据库,是否需要刷新界面,若有,则要进行界面刷新,刷新UI界面的工作通过Handler来实现。在onStart()方法中,setNextRequestTime是设置定时启动服务的方法,因为涉及定时器类AlarmManager类,在此不多做叙述。
onBind()方法用来绑定服务。
onDestroy方法用来销毁此后台服务。