Android——service使用详解
| name | 服务类的完全限定名 |
| permission | 设定组件必须具有的权限,得以启动服务或绑定服务。如果startService(),bindService()或stopService()的调用者没有被授予此权限,则该方法将不会工作,并且Intent对象不会传递到服务中 |
| process | 用来运行服务的进程的名称。通常,应用程序的所有组件都运行在应用程序创建的默认进程中,它与应用程序包名具有相同的名称。 < application >元素的process属性可以为所有组件设置不同的默认值,但组件可以使用自己的进程属性覆盖默认值,从而允许跨多个进程扩展应用程序 |
三、启动Service
启动服务由组件通过调用 startService() 启动,服务启动之后,其生命周期即独立于启动它的组件,并且可以在后台无限期地运行,即使启动服务的组件已被销毁也不受影响。因此,服务应通过调用 stopSelf() 来自行停止运行,或者由另一个组件调用 stopService() 来停止
可以通过扩展两个类来创建启动服务:
- Service
这是所有服务的父类。扩展此类时,如果要执行耗时操作,必须创建一个用于执行操作的新线程,因为默认情况下服务将运行于UI线程
- IntentService
这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果应用不需要同时处理多个请求,这是最好的选择。IntentService只需实现构造函数与 onHandleIntent() 方法即可,onHandleIntent()方法会接收每个启动请求的 Intent
3.1、继承Service
这里举一个音乐播放器的例子
继承Service类实现自定义Service,提供在后台播放音乐、暂停音乐、停止音乐的方法
public class MyService extends Service {
private final String TAG = “MyService”;
private MediaPlayer mediaPlayer;
private int startId;
public enum Control {
PLAY, PAUSE, STOP
}
public MyService() {
}
@Override
public void onCreate() {
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer.create(this, R.raw.music);
mediaPlayer.setLooping(false);
}
Log.e(TAG, “onCreate”);
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
this.startId = startId;
Log.e(TAG, "onStartCommand—startId: " + startId);
Bundle bundle = intent.getExtras();
if (bundle != null) {
Control control = (Control) bundle.getSerializable(“Key”);
if (control != null) {
switch (control) {
case PLAY:
play();
break;
case PAUSE:
pause();
break;
case STOP:
stop();
break;
}
}
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.e(TAG, “onDestroy”);
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
}
super.onDestroy();
}
private void play() {
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
}
private void pause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
private void stop() {
if (mediaPlayer != null) {
mediaPlayer.stop();
}
stopSelf(startId);
}
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, “onBind”);
throw new UnsupportedOperationException(“Not yet implemented”);
}
}
在布局中添加三个按钮,用于控制音乐播放、暂停与停止
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void playMusic(View view) {
Intent intent = new Intent(this, MyService.class);
Bundle bundle = new Bundle();
bundle.putSerializable(“Key”, MyService.Control.PLAY);
intent.putExtras(bundle);
startService(intent);
}
public void pauseMusic(View view) {
Intent intent = new Intent(this, MyService.class);
Bundle bundle = new Bundle();
bundle.putSerializable(“Key”, MyService.Control.PAUSE);
intent.putExtras(bundle);
startService(intent);
}
public void stopMusic(View view) {
Intent intent = new Intent(this, MyService.class);
Bundle bundle = new Bundle();
bundle.putSerializable(“Key”, MyService.Control.STOP);
intent.putExtras(bundle);
startService(intent);
//或者是直接如下调用
//Intent intent = new Intent(this, MyService.class);
//stopService(intent);
}
}
在清单文件中声明Service,为其添加label标签,便于在系统中识别Service
<service
android:name=“.MyService”
android:label=“@string/app_name” />
图片描述:
点击“播放音乐”按钮后,在后台将会运行着名为“Service测试”的服务
通过Log日志可以发现,多次点击“播放音乐”按钮,“onCreate()”方法只会在初始时调用一次,“onStartCommand(Intent intent, int flags, int startId)”方法会在每次点击时都被调用,点击“停止音乐”按钮,“onDestroy()”方法会被调用
当中,每次回调onStartCommand()方法时,参数“startId”的值都是递增的,startId用于唯一标识每次对Service发起的处理请求
如果服务同时处理多个 onStartCommand() 请求,则不应在处理完一个启动请求之后立即销毁服务,因为此时可能已经收到了新的启动请求,在第一个请求结束时停止服务会导致第二个请求被终止。为了避免这一问题,可以使用 stopSelf(int) 确保服务停止请求始终基于最新一次的启动请求。也就是说,如果调用 stopSelf(int) 方法的参数值与onStartCommand()接受到的最新的startId值不相符的话,stopSelf()方法就会失效,从而避免终止尚未处理的请求
如果服务没有提供绑定,则使用 startService() 传递的 Intent 是应用组件与服务之间唯一的通信模式。如果希望服务返回结果,则启动服务的客户端可以为广播创建一个 PendingIntent (使用 getBroadcast()),并通过启动服务的 Intent 传递给服务。然后,服务就可以使用广播传递结果
当中,onStartCommand() 方法必须返回一个整数,用于描述系统应该如何应对服务被杀死的情况,返回值必须是以下常量之一:
- START_NOT_STICKY
如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务
- START_STICKY
如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)
- START_REDELIVER_INTENT
如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务
3.2、IntentService
由于大多数启动服务都不必同时处理多个请求,因此使用 IntentService 类实现服务也许是最好的选择
IntentService 执行以下操作:
-
创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent
-
创建工作队列,用于将 Intent 逐一传递给 onHandleIntent() 实现,这样就不必担心多线程问题
-
在处理完所有启动请求后停止服务,因此不必自己调用 stopSelf()方法
-
提供 onBind() 的默认实现(返回 null)
-
提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent()
因此,只需实现构造函数与 onHandleIntent() 方法即可
这里举一个关于输出日志的例子
public class MyIntentService extends IntentService {
private final String TAG = “MyIntentService”;
public MyIntentService() {
super(“MyIntentService”);
}
@Override
protected void onHandleIntent(Intent intent) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, bundle.getString(“key”, “默认值”));
}
}
}
}
public class StartIntentServiceActivity extends AppCompatActivity {
private int i = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start_intent_service);
}
public void startService(View view) {
Intent intent = new Intent(this, MyIntentService.class);
Bundle bundle = new Bundle();
bundle.putString(“key”, “当前值:” + i++);
intent.putExtras(bundle);
startService(intent);
}
}
当中,startService(View view)方法与一个Button绑定,连续快速地多次点击Button,验证IntentService当中的日志是否依次输出,还是交叉着输出
可以看到是依次输出的,即IntentService的工作线程是逐一处理所有启动请求的
此外,查看后台,可以看到当前后台应用程序进程中有两个服务
四、绑定Service
应用组件(客户端)通过调用 bindService() 绑定到服务,绑定是异步的,系统随后调用服务的 onBind() 方法,该方法返回用于与服务交互的 IBinder。要接收 IBinder,客户端必须提供一个 ServiceConnection 实例用于监控与服务的连接,并将其传递给 bindService()。当 Android 系统创建了客户端与服务之间的连接时,会回调ServiceConnection 对象的**onServiceConnected()**方法,向客户端传递用来与服务通信的 IBinder
多个客户端可同时连接到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind() 方法来检索 IBinder。系统随后无需再次调用 onBind(),便可将同一 IBinder 传递至其他绑定的客户端。当所有客户端都取消了与服务的绑定后,系统会将服务销毁(除非 startService() 也启动了该服务)
另外,只有 Activity、服务和内容提供者可以绑定到服务,无法从广播接收器绑定到服务
可以通过以下三种方法定义IBinder接口:
- 扩展 Binder 类
如果服务是供本应用专用,并且运行在与客户端相同的进程中,则应通过扩展 Binder 类并从 onBind() 返回它的一个实例来创建接口。客户端收到 Binder 后,可利用它直接访问 Service 中可用的公共方法
- 使用 Messenger
如需让接口跨不同的进程工作,则可使用 Messenger 为服务创建接口。服务可以这种方式定义对应于不同类型 Message 对象的 Handler。此 Handler 是 Messenger 的基础,后者随后可与客户端分享一个 IBinder,从而让客户端能利用 Message 对象向服务发送命令。此外,客户端还可定义自有 Messenger,以便服务回传消息。这是执行进程间通信 (IPC) 的最简单方法,因为 Messenger 会在单一线程中创建包含所有请求的队列,这样就不必对服务进行线程安全设计
- 使用 AIDL
AIDL(Android 接口定义语言)执行所有将对象分解成原语的工作,操作系统可以识别这些原语并将它们编组到各进程中,以执行 IPC。 之前采用 Messenger 的方法实际上是以 AIDL 作为其底层结构。 如上所述,Messenger 会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。 不过,如果想让服务同时处理多个请求,则可直接使用 AIDL。 在此情况下,服务必须具备多线程处理能力,并采用线程安全式设计。如需直接使用 AIDL,必须创建一个定义编程接口的 .aidl 文件。Android SDK 工具利用该文件生成一个实现接口并处理 IPC 的抽象类,随后可在服务内对其进行扩展
4.1、绑定服务的具体步骤:
4.1.1、扩展 Binder 类
如果服务仅供本地应用使用,不需要跨进程工作,则可以实现自有 Binder 类,让客户端通过该类直接访问服务中的公共方法。此方法只有在客户端和服务位于同一应用和进程内这一最常见的情况下方才有效
以下是具体的设置方法:
-
在服务中创建一个可满足下列任一要求的 Binder 实例:
-
包含客户端可调用的公共方法
-
返回当前 Service 实例,其中包含客户端可调用的公共方法
-
或返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法
-
从 onBind() 回调方法返回此 Binder 实例
-
在客户端中,从 onServiceConnected() 回调方法接收 Binder,并使用提供的方法调用绑定服务
4.1.2、实现 ServiceConnection接口
重写两个回调方法:
- onServiceConnected()
系统会调用该方法以传递服务的onBind() 方法返回的 IBinder
- onServiceDisconnected()
Android 系统会在与服务的连接意外中断时,例如当服务崩溃或被终止时调用该方法。当客户端取消绑定时,系统不会调用该方法
4.1.3、调用 bindService(),传递 ServiceConnection 对象
4.1.4、当系统调用了 onServiceConnected() 的回调方法时,就可以通过IBinder对象操作服务了
4.1.5、要断开与服务的连接需调用 unbindService()方法。如果应用在客户端仍处于绑定状态时销毁客户端,会导致客户端取消绑定,更好的做法是在客户端与服务交互完成后立即取消绑定客户端,这样可以关闭空闲服务
示例代码:
public class MyBindService extends Service {
private IBinder myBinder;
private Random mGenerator;
private final String TAG = “MyBindService”;
public class MyBinder extends Binder {
MyBindService getService() {
return MyBindService.this;
}
}
@Override
public void onCreate() {
Log.e(TAG, “onCreate”);
myBinder = new MyBinder();
mGenerator = new Random();
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, “onBind”);
return myBinder;
}
@Override
public void onDestroy() {
Log.e(TAG, “onDestroy”);
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
Log.e(TAG, “onUnbind”);
return super.onUnbind(intent);
}
@Override
public void onRebind(Intent intent) {
Log.e(TAG, “onRebind”);
super.onRebind(intent);
}
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
public class BindServiceActivity extends AppCompatActivity {
private MyBindService mService;
private boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bind_service);
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
MyBindService.MyBinder binder = (MyBindService.MyBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
}
};