Android四大组件之Service详解

时间:2022-12-21 11:42:12

转载注明出处:http://blog.csdn.net/loveyaozu/article/details/51693188


一、Service简介

Service和Activity一样都是Android的四大组件之一,不同的是Activity显示图形用户界面,而Service的运行是不可见的。如执行Intent查找、处理数据,更新ContentProvider、激活Intent和触发Notification。Activity在它的生命周期内定期启动、停止和重新创建,而Service则被设计为长生命周期的。用来执行一些持续性、可能耗时的操作。

运行中的Service具有比处于非激活状态或者不可见状态(已停止)的Activity要高的优先级,所以它们被运行时的资源管理终止的可能性会小一些。Android终止一个Service的情况就是为前台组件提供资源。如果发生这种情况,当有资源可用的时候,可以通过配置来重新启动Service。

如果Service需要和用户直接交互,例如音乐播放,那么有必要把这个Service标识为前台组件,从而提高其优先级。这样除了极端情况外,该Service是不会被终止的。但是,这会降低运行时管理资源的能力,从而可能会降低用户体验。


二、Service的启动方式有两种,一种是通过startService(...),一种是bindService(...)


2.1 启动式Service

2.1.1创建和控制Service

package com.example.androidtest1;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

/**
* @author syz
* @date 2016-6-16
*/
public class MyService extends Service {

private static final String TAG = "MyService";

@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate() : MyService is created");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
backgroundTask(intent, startId);
Log.i(TAG, "intent = " + intent + "\t flags = " + flags + "\t startId = " + startId);
return Service.START_STICKY;
// return Service.START_NOT_STICKY;
// return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy() : MyService is destroyed");
}

private void backgroundTask(Intent intent, int startId) {
if (intent != null) {
Log.i(TAG, "intent params value= " + intent.getExtras().getString("key") + "\t startId = " + startId);
}
}

}


Service与Activity一样,都需要在清单文件manifest中注册。

<service android:enable="true" android:name:".MyService"/>

 

为了确保Service只能由本应用启动和停止,需要为其设置权限

<service android:enable="true" android:name:".MyService"
android:permission:"com.syz.MY_SERVICE_PERMISSION"/>
当第三方应用想要访问这个Service的时候就需要在其清单文件manifest中包含uses-permission属性。


2.1.2 执行一个Service并控制它的重启行为

重写onStartCommand事件处理程序执行一个由Service封装的任务(或者启动持续进行的操作)。在这个程序中,也可以指定Service的重启行为。当一个Service通过startService启动的时候就会调用onStartCommand,所以这个方法可能会在Service整个生命周期内被执行多次。

onStartCommand是在API level 5(Android 2.0)以后引入的。代替现在不再建议使用的onStart方法。

需要注意的是Service实在主线程中运行的,所以也就意味着onStartCommond运行在UI线程中。实现Service的标准模式是从onStartCommond中创建一个新的线程,用来执行后台任务,并在该线程执行完成后终止这个Service。还有一种方式就是继承IntentService,这样就不需要单独为后台任务创建一个新的线程。


Service的重启模式有三种:Service.START_STICKYService.START_NOT_STICKYService.START_REDELIVER_INTENT

1. START_STICKY  如果返回了这个值,那么在运行时终止Service后,当重新启动Service时,将会调用onStartCommond方法。注意:当重新启动Service的时候,传入onStartCommond的Intent值为null。这种模式通常处理自身状态的Service,以及根据需要通过startService和stopService显式的启动和终止Service。这些Service包括播放音乐的Service或者处理其他后台任务的Service。

2. START_NOT_STICKY 这种模式用于启动以处理特殊的操作和命令的Service。通常当命令完成后,这些Service会调用stopSelf终止自己。当被运行终止后,只有当存在未处理的启动调用时候,设为这个模式的Service才会重新启动。如果在终止Service后没有进行startService调用,那么Service将停止运行,而不会调用onStartCommond。

对于特殊的处理请求,尤其像更新或者网络轮询这样定期处理,这是一种理想的模式。这种模式是比较谨慎的当停止Service后,它会在下一个调度间隔中尝试重新启动,而不会在资源竞争激烈的时候重启Service。

3. START_REDELIVER_INTENT 这种模式是前两种模式的组合。如果Service被运行终止,那么只有当存在未处理的启动调用或者进程在调用stopSelf之前被终止时候,,才会重新启动Service。在后一种情况中,将调用onStartCommond方法,传入没有正常完成的Intent。


在onStartCommond的返回值中指定的重新启动模式将会影响在后面的调用中传入的参数值。起初,会传入一个Intent参数给startService来启动Service。当系统重新启动Service后,Intent在START_STICKY模式中将为null,而在START_REDEKVIER_INTENT中将为原来的Intent。可以使用flag参数来找出启动Service的方式。特别的是,可以确定一下哪种情况为true。

1. START_FLAG_REDELIVER  表示Intent参数是由于系统运行时在通过调用stopSelf显式的停止Service之前终止它而重新传递的。

2. FLAG_RETRY 表示Service已经在异常终止后重新启动。如果Service之前被设为START_STICKY,则会传入这个标志。


启动一个startService(...)有两种方式,一种是显式启动,另一种方式是隐式启动。但是Android 5.0之后Google就禁止使用隐式方式启动Service了,如果使用隐式方式启动就会抛出安全异常。


2.1.3 停止一个Service

可以用

Intent intent = new Intent(this,MyService.class);
stopService(intent);

执行上述代码即可销毁MyService(前提是该Service没有被其他组件绑定


2.2 绑定式Service

2.2.1 创建一个Service类继承自Service

绑定式Service是可以和Activity之间进行通信的。看下面的例子

package com.example.androidtest1;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

/**
* @author syz
* @date 2016-6-21
*/
public class Service2 extends Service {

private static final String TAG = Service2.class.getSimpleName();

private MyBinder mBinder = new MyBinder();

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}

@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Service2 is created");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Service2 ---> onStartCommand");
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "Service2 ---> onDestroy()");
}

class MyBinder extends Binder{

public void doBackgroundTask(){
Log.i(TAG,"Service2.MyBinder ---> doBackgroundTask()" );
}
}
}

该Service内部有一个内部类,MyBinder,该类集成自Binder。MyBinder可以作为Service和Activity之间的通信的媒介。

再看Activity类

package com.example.androidtest1;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;

import com.example.androidtest1.Service2.MyBinder;

public class MainActivity extends Activity implements OnClickListener {

protected static final String TAG = MainActivity.class.getSimpleName();

private MyBinder myBinder;

/** 服务是否已绑定标记*/
private boolean binded = false;

private ServiceConnection connection = new ServiceConnection() {

@Override
public void onServiceDisconnected(ComponentName name) {
/**
* 在服务崩溃或被杀死导致的连接中断时被调用,而如果我们自己解除绑定时则不会被调用
*/
Log.i(TAG, "MyService onServiceDisconnected");
}

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "MyService 绑定成功!");
if(service == null){
return;
}
myBinder = (MyBinder) service;
myBinder.doBackgroundTask();
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.startService).setOnClickListener(this);
findViewById(R.id.stopService).setOnClickListener(this);
findViewById(R.id.bindService).setOnClickListener(this);
findViewById(R.id.unBindService).setOnClickListener(this);
}

@Override
protected void onDestroy() {
super.onDestroy();
}

@Override
public void onClick(View v) {
Intent intent = new Intent(this,MyService.class);
intent.putExtra("key", "Test start service");
switch (v.getId()) {
case R.id.startService:
startService(intent);
break;
case R.id.stopService:
stopService(intent);
break;
case R.id.bindService:
Intent service = new Intent(this,Service2.class);
boolean result = false;
result = bindService(service, connection, BIND_AUTO_CREATE);
if(result){
binded = true;
}
break;
case R.id.unBindService:
if(binded){
unbindService(connection);
binded = false;
}
break;
default:
break;
}

}
}


在使用bindService绑定服务时,我们需要一个ServiceConnection代表与服务的连接,它只有两个方法,onServiceConnected和onServiceDisconnected,前者是在操作者在连接一个服务成功时被调用,而后者是在服务崩溃或被杀死导致的连接中断时被调用,而如果我们自己解除绑定时则不会被调用,所以我们这里只研究onServiceConnected这个方法。与启动式Service不同,绑定式的Service在绑定的时候需要三个参数,第一个是Intent,这个就不多说了,第二个是前面提到的ServiceConnection,该参数用于获取在绑定Service成功后,Service中onBind()方法返回的Binder;第三个参数是一个标志位,这里传入BIND_AUTO_CREATE表示在Activity和Service建立关联后自动创建Service,这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。

在该方法中我们还发现有一个布尔型的binded字段,为什么设置这个字段呢?这个字段的作用就是记录该Activity是否绑定过Service2,如果绑定过才可以解绑,否则不能解绑,为什么要这样呢?因为unbindService(connection);不能重复调用,如果重复调用就会出现java.lang.IllegalArgumentException: Service not registered

绑定Service后我们就可以通过MyBinder和Service2进行通讯了,需要注意的是MyBinder中的方法不要做耗时的操作,如果有耗时的操作,需要新开启一个线程,进行处理。

上面提到的一个参数BIND_AUTO_CREATE。在API 14即Android 4.0的时候又引入了许多新的标志。用来在Service和应用程序绑定时单独或者结合起来使用。

BIND_ADJUST_WITH_ACTIVITY 当绑定他的Activity,处于前台的时候,Service的优先级会被提高。


BIND_IMPORTANTBIND_ABOVE_CLIENT 对于正在绑定一个Service的客户端来说,这个Service非常重要,以至于当客户端处于前台状态的时候,Service也应该编程前台进程。BIND_ABOVE_CLIENT指定在内存很低的情况下,运行时会在终止绑定的Service之前先终止Activity。


BIND_NOT_FOREGROUND 该标志位标识,绑定的Service用于不会处于前台进程中。默认情况下,绑定的时候会提高他的相对优先级。


BIND_WAIVE_PRIORITY  表示绑定一个Service不应该改变该Service优先级。


注意销毁一个Service,必须解除和这个Service关联的所有组件。例如,当一个Service被一个组件通过startService启动,有通过bindService绑定,那么单独stopService,或者单独unBindService的时候,这个Service都不会被销毁掉。只有当同时调用了stopService()和unBindService()的时候才会被销毁掉。

不论是startService(...),还是bindService(...),如果该Service的实例已经存在,都不会重新创建,即不会调用onCreate方法。

1、多次bindService时,服务本身的onBind不会被多次执行。

2、bind上一个Service后,执行一次unBindService就够了。不然会出错。

3、一个App里,同一个Activity多次bind一个服务,除了第一次,后面的bind不会有任何onBind、onServiceConnected打印。

    一个App里,不同的Activity去bind一个服务,第一次bind有onBind、onServiceConnected打印,后面的bind只会有onServiceConnected打印。

4、一个Activity bind上一个Service后,如果Activity finish前没有调用unBind,App会崩溃,Log打印如下:



android.app.ServiceConnectionLeaked: Activity  com.example.androidtest1.MainActivity has leaked ServiceConnection

com.example.androidtest1.MainActivity$1@272b8601 that was originally bound here.

Service 的生命周期

Android四大组件之Service详解Android四大组件之Service详解



三、创建前台Service

Service存在优先级,一般的,普通的Service优先级相对比较低,相比之下,在系统内存吃紧的情况下,更容易被回收。前台Service就不同了,优先级很高,一般情况下不会轻易被回收。前台进程需要在通知栏显示一个持续运行的Notification。最常见的像墨迹天气,360手机卫士等等,会在标题栏一直显示一个持久运行的Notification。前台Service不应该轻易使用,如果有多个前台进程在运行的时候,因为是前台进程所以不会轻易被回收掉,系统性能会大大降低。

在原来Service2的基础上增加一个方法frontServiceNotification(),该方法主要是指定一个持续工作的Notification。在Service2的onCreate()中调用该方法。

package com.example.androidtest1;

import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

/**
* @author syz
* @date 2016-6-21
*/
public class Service2 extends Service {

private static final String TAG = Service2.class.getSimpleName();

private MyBinder mBinder = new MyBinder();

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}

@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Service2 is created");
frontServiceNotification();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Service2 ---> onStartCommand");
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "Service2 ---> onDestroy()");
}

class MyBinder extends Binder{

public void doBackgroundTask(){
Log.i(TAG,"Service2.MyBinder ---> doBackgroundTask()" );
}
}

@SuppressLint("NewApi")
private void frontServiceNotification(){
Notification.Builder builder = new Notification.Builder(this);
builder.setTicker("FrontService is running");
builder.setContentTitle("FrontService");
builder.setContentText("FrontService is running");
// builder.setOngoing(true);
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher));
Intent intent = new Intent(this,MainActivity.class);
// intent.setPackage(this.getPackageName());
PendingIntent pendingIntent = PendingIntent.getActivity(this, 1, intent, 0);
builder.setContentIntent(pendingIntent);
Notification notification = builder.build();
notification.flags = notification.flags | Notification.FLAG_ONGOING_EVENT;
startForeground(1, notification);
}


}


通过startService(...)方式或者bindService(...)启动Service2,Service2即可成为前台Service。如果用 bindService(...)启动Service2,在MainActivity销毁的时候必须要解绑,这样体会不到效果。所以用startService(...)启动Service2,当程序关闭的时候依然可以在通知栏看到Notification,同时在应用程序管理里面可以看到Service2这个服务处于正在运行状态。

Android四大组件之Service详解

我们可以为用户提供一个将前台Service变成普通的Service,并移除Notification。我们可以调用stopForeground(true);

使前台Service编程普通的后台Service,同时移除Notification。如果不移除Notification,参数置为false即可,

stopForeground(false);