首页 > 代码库 > Android 服务类Service 的详细学习

Android 服务类Service 的详细学习

上一篇说到了通知栏Notification,提起通知栏,不得让人想到Service以及BroadcastReceive,作为android的4大组建的2个重要成员,我们没少和它们打交道。它们可以在无形中使我们的软件和网络、数据库、系统等进行交互,之后通过UI(Notification就是一种展示方式)把结果展现在我们面前。可以说,他们是android生命体系里面的神经系统,通过反射条件让身体展现不同的状态。在整个系统中,广播接收器充当着是传输者和监听者的角色,它把系统的一点点变化都反馈上去,之后做出改变。


通过这几篇博文讲讲BroadcastReceiver广播接收器)和Service(服务),以及他们之间的联系。这一篇通过开发文档、源码和书籍中笔记总结分析下service这个类。


开发文档:http://developer.android.com/reference/android/app/Service.html



什么是服务?


service可以说是一个在后台运行的Activity,它不是一个单独的进程,它只需要应用告诉它要在后台做什么就可以了。

它要实现和用户的交互的话需要通过通知栏或则是发送广播,UI去接收显示。它的应用十分广泛,尤其是在框架层,应用更多的是对系统服务的调用


服务有什么用


它用于处理一些不干扰用户使用的后台操作。如下载,网络获取。播放音乐,他可以通过INTENT来开启,同时也可以绑定到宿主对象(调用者例如ACTIVITY上)来使用。


概述


服务是一个应用程序组件代表应用程序执行一个长时间操作的行为,虽然不与用户交互或供应功能供其它应用程序使用。每个服务类必须有一个相应的包的AndroidManifest.xml中 <Service>声明。服务可以通过Context.startService()和Context.bindService()开始工作。它和其他的应用对象一样,在他的宿主进程的主线程中运行。


类组成

extends ContextWrapper
implements ComponentCallbacks2
java.lang.Object
   ?android.content.Context

   ?android.content.ContextWrapper


   ?android.app.Service
Known Direct Subclasses
AbstractInputMethodService, AccessibilityService, DreamService, HostApduService, IntentService,MediaRouteProviderService, NotificationListenerService, OffHostApduService, PrintService, RecognitionService,RemoteViewsService, SettingInjectorService, SpellCheckerService, TextToSpeechService, VpnService,WallpaperService
Known Indirect Subclasses
InputMethodService

它继承至ContextWrapper,再上去就是Context,它的直接子类用很多,间接子类是InputMethodService,下面就随便说几个


1.InputMethodService

这个类提供了一个输入法的标准实现,一般的开发者是不会去考虑这个,输入法公司和ODM厂商则需要去考虑。


2.IntentService

它作为Service的子类,主要用于处理异步请求,防止线程的阻塞,所有的请求将在一个工作线程(HandlerThread)中处理,工作完成了,线程也就结束了。


3.MediaRouteProviderService

它主要用于设备启动和SD卡挂载时候执行多媒体文件的扫描工作。


4. NotificationListenerService

上一篇博文主要就是将通知栏( Android 通知栏Notification的整合 全面学习 ),这个类就是和通知栏有关,它主要用于接收来自系统调用的服务及新通知发布或删除。


5. RecognitionService

它是一个抽象服务类,如果开发者希望实现一个新的语音识别器时候,可以用到它。


服务的类型


按照使用范围分类:


1.本地服务(Local Service):用于应用程序内部


功能:用于实现应用程序自己的一些耗时任务,比如查询升级信息,并不占用应用程序比如Activity所属线程,而是单开线程后台执行,这样用户体验比较好。


使用:在Service可以调用Context.startService()启动,调用Context.stopService()结束。在内部可以调用Service.stopSelf() 或 Service.stopSelfResult()来自己停止。无论调用了多少次startService(),都只需调用一次stopService()来停止。


2.远程服务(Remote Sercie):用于android系统内部的应用程序之间


功能:可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可。


使用可以定义接口并把接口暴露出来,以便其他应用进行操作。客户端建立到服务对象的连接,并通过那个连接来调用服务。调用Context.bindService()方法建立连接,并启动,以调用 Context.unbindService()关闭连接。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService()会先加载它。


按照运行类别分类分:


1.前台服务


前台服务需要调用 startForeground ( android 2.0 及其以后版本 )或 setForeground (android 2.0 以前的版本)使服务成为 前台服务。

使用前台服务可以避免服务在后台运行的时候被系统KILL。


android官方描述如下:


Running a Service in the Foreground


A foreground service is a service that‘s considered to be something the user is actively aware of and thus not a candidate for the system to kill when low on memory. A foreground service must provide a notification for the status bar, which is placed under the "Ongoing" heading, which means that the notification cannot be dismissed unless the service is either stopped or removed from the foreground.

For example, a music player that plays music from a service should be set to run in the foreground, because the user is explicitly aware of its operation. The notification in the status bar might indicate the current song and allow the user to launch an activity to interact with the music player.

To request that your service run in the foreground, call startForeground(). This method takes two parameters: an integer that uniquely identifies the notification and the Notification for the status bar. For example:

Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
        System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
        getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION, notification);

To remove the service from the foreground, call stopForeground(). This method takes a boolean, indicating whether to remove the status bar notification as well. This method does not stop the service. However, if you stop the service while it‘s still running in the foreground, then the notification is also removed.

Note: The methods startForeground() and stopForeground() were introduced in Android 2.0 (API Level 5). In order to run your service in the foreground on older versions of the platform, you must use the previoussetForeground() method—see the startForeground() documentation for information about how to provide backward compatibility.

For more information about notifications, see Creating Status Bar Notifications.

使用:可以看出,我们只需要在onStartCommand里面调用 startForeground方法让服务前台运行,然后再onDestroy里面调用stopForeground解除前台运行既可!

所以,例如手机中的音乐播放器,不管手机是否是在休眠状态,只要开始播放了,系统就不会去KILL这个服务,只有当停止播放音乐时,服务才可能会回收清除。


2.后台服务

后台服务就是处于后台运行的



生命周期:



图:左图:启动方式的生命周期     右图:绑定方式的生命周期


注意:本地服务中,onStart已经被onStartCommand方法取代,Service和Activity都是由Context类派生的,可以通过getApplicationContext()方法获取上下文对象,和Activity一样,它有着自己的生命周期,可是和Activity相比,它所执行的过程略有不同,如上图所示


在服务分类中,提到了3种服务通信类型,一种是通过startService()直接启动服务,一种是通过bindService()的方式启动,2种启动方式对应的生命周期如上图所示。3.使用AIDL方式的Service


下面就说说2种服务的启动流程:


1.context.startService() 启动流程(后台处理工作):


context.startService()  -> onCreate()  -> onStartCommand()  -> Service running  -> context.stopService()  -> onDestroy()  -> Service stop 


所以调用startService的生命周期大致为:

onCreate(只在创建的时候调用一次直到被摧毁) --> onStartCommand (服务开启后,可多次调用) --> onDestroy


服务中的onStartCommand(Intent intent, int flags, int startId)方法会返回一个唯一的整数标识符来识别启动请求,启动请求可以是START_STICKY、START_STICKY_COMPATIBILITY、START_NOT_STICKY、START_REDELIVER_INTENT等,标志位可以是START_FLAG_REDELIVERY、START_FLAG_RETRY。

通过这种方式,服务并不会随着绑定组建的摧毁而摧毁,而是服务自我摧毁。(所以这种方式适用于文件下载,上传等请求自行运行的场景)。


从图中我们可以看出,onCreate方法只在创建时候被调用了一次,这说明:Service被启动时只调用一次onCreate()方法,如果服务已经被启动,在次启动的Service组件将直接调用onStartCommand()方法,通过这样的生命周期,可以根据自身需求将指定操作分配进onCreate()方法或onStartCommand()方法中。


2.context.bindService()启动流程(在本地同一进程内与Activity交互):


context.bindService()  -> onCreate()  -> onBind()  -> Service running  -> onUnbind()  -> onDestroy()  -> Service stop


bindService的生命周期简化为为:onCreate --> onBind --> onUnbind --> onDestory

通过该方法,服务启动时会调用onCreate()来启动服务,可是它不会调用onStartCommand() 方法,并且只有在所有的服务都接触了后,服务才会自动停止运行。通过服务的onBind()方法,可以获的一个客户端与服务器进行通信的IBdiner接口。IBind允许客户端回调服务的方法,比如得到Service的实例、运行状态或其他操作。这个时候把调用者(Context,例如Activity)会和Service绑定在一起,Context退出了,Srevice就会调用onUnbind->onDestroy相应退出。 

:绑定服务的Android组建在摧毁前应解除绑定,否则会造成内存泄漏。


3.使用AIDL方式的Service(进行跨进程通信)(这块不是很懂,这里就不提了)



使用方式


1.创建服务类

public class MyService extends Service {}


2.在AndroidMainfest.xml文件中配置注册该Service

<service android:name="服务类所在的包名.MyService" />


3. 启动服务

 (1)通过直接启动服务的方式:

	Intent intent = new Intent(getApplicationContext(), MyService.class);
	startService(intent);

服务类中:

	@Override
	public void onCreate() {
	}

	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		//接受传递过来的intent的数据等
		return START_STICKY;
	}

	@Override
	public void onDestroy() {
		
	}


(2)通过绑定启动服务的方式:


绑定一个服务,需要设置ServiceConnection和标志位,方法如下:

bindService(Intent service, ServiceConnection conn, int flags)

ServiceConnection可以监听服务的状态,在进行服务绑定的时,其标志位可以为以下几种(这里列出3种):


1).Context.BIND_AUTO_CREATE    

说明:表示收到绑定请求的时候,如果服务尚未创建,则即刻创建,在系统内存不足需要先摧毁优先级组件来释放内存,且只有驻留该服务的进程成为被摧毁对象时,服务才被摧毁


2).Context.BIND_DEBUG_UNBIND    

说明:通常用于调试场景中判断绑定的服务是否正确,但容易引起内存泄漏,因此非调试目的的时候不建议使用


3).Context.BIND_NOT_FOREGROUND    

说明:表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行,该标志位位于Froyo中引入。


注意:绑定服务的以异步方式运行的。绑定服务必须在当前的上下文环境中运行,某些场景中,通过上下文进行添加绑定接触方法如下:

getApplicationContext().bindService(service, conn, flags)


代码如下:

	/** 是否绑定 */
	boolean mIsBound = false;
	
	/** 绑定服务 */
	public void doBindService() {
		bindService(new Intent(MainActivity.this, LocalService.class), mConnection,Context.BIND_AUTO_CREATE);
		mIsBound = true;
	}
	
	/** 解除绑定服务 */
	public void doUnbindService() {
		if (mIsBound) {
			// Detach our existing connection.
			unbindService(mConnection);
			mIsBound = false;
		}
	}
	
	private ServiceConnection mConnection = new ServiceConnection() {

		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			mBoundService = ((LocalService.LocalBinder) service).getService();
			Toast.makeText(MainActivity.this, "服务连接", Toast.LENGTH_SHORT)
					.show();
		}

		@Override
		public void onServiceDisconnected(ComponentName name) {
			mBoundService = null;
			Toast.makeText(MainActivity.this, "服务未连接", Toast.LENGTH_SHORT)
					.show();
		}
	};


服务类中:

	@Override
	public void onCreate() {
		
	}
	/** 绑定的IBinder */
	private final IBinder mBinder = new LocalBinder();
	
	public class LocalBinder extends Binder {
		public LocalService getService() {
			return LocalService.this;
		}
	}

	@Override
	public IBinder onBind(Intent intent) {
		return mBinder;
	}
	
	@Override
	public boolean onUnbind(Intent intent) {
		// TODO Auto-generated method stub
		return super.onUnbind(intent);
	}


4.停止服务

(1)启动服务停止有2种方法:

1)stopSelf()  自我停止服务

2)stopService(Intent name)    被动停止服务

(2)绑定服务的解除绑定方法如下:

unbindService(ServiceConnection conn)



使用细节


1.在注册服务的时候,为了将service纳入编译系统,必须在AndroidMainfest.xml中对Service进行显式声明。


2.计算量较大的又不是UI层的工作的话,可以选择放置在Service中进行工作。


3.通过开发文档你会发现,Android中的Service与宿主(调用者)在同一线程,而不是专门起一条线程,这意味着,如果你的服务要CPU密集型操作(如:MP3播放)或则阻塞操作(如网络)时,必须产生它自己的线程来完成这个工作,否则会造成线程阻塞。在Service的子类里面,IntentService服务可以作为一个标准实施,它的工作自己线程


4.如果在使用Service的时候又使用了广播接收器配合工作,广播如果是动态注册的话,在服务停止的时候记得调用unregisterReceiver(receiver);这个方法来注销掉接收器




拓展


1.如何检查Android后台服务线程(Service类)是否正在运行


Android系统自己提供了一个函数ActivityManager.getRunningServices,可以列出当前正在运行的后台服务线程

private boolean isServiceRunning() {
    ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
    for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
        if ("com.example.MyService".equals(service.service.getClassName())) {
            return true;
        }
    }
    return false;
}

2.Service与UI之间的通信方式


(1)使用直接启动的startService实现信息传递

流程:UI  ——>Service

操作:使用Intent进行数据传递,通过服务中的onStartCommand方法进行接受(和Activity间传递方式一样)


(2)使用绑定启动的bindservice实现信息传递

流程:UI  ——>Service


(3)使用Broadcast(广播)进行信息的双向传递

流程:UI  <——>Service

操作:注册绑定广播接受器,之后通过广播来进行2者间通信

注意:在服务退出的时候记得unregisterReceiver(receiver);注销广播接收器


3.Service和Thread的区别

我们拿服务来进行一个后台长时间的动作,为了不阻塞线程,然而,Thread就可以达到这个效果,为什么我们不直接使用Thread去代替服务呢?(这个问题摘抄至网上,原文地址不是是哪个,所以没写上)

这里提下,


1). Thread:Thread 是程序执行的最小单元,它是分配CPU的基本单位。可以用 Thread 来执行一些异步的操作。 
2). Service:Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 main 线程上的。如:onCreate,onStart 这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。如果是Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上。因此请不要把 Service 理解成线程,它跟线程半毛钱的关系都没有!


既然这样,那么我们为什么要用 Service 呢?其实这跟 android 的系统机制有关,我们先拿 Thread 来说。Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity 被 finish 之后,你不再持有该 Thread 的引用。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制。


举个例子:如果你的 Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity 没有start的时候也在运行。这个时候当你 start 一个 Activity 就没有办法在该 Activity 里面控制之前创建的 Thread。因此你便需要创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread,这样便解决了该问题(因为任何 Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例)。

因此你可以把 Service 想象成一种消息服务,而你可以在任何有 Context 的地方调用 Context.startService、Context.stopService、Context.bindService,Context.unbindService,来控制它,你也可以在 Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,当然这些都是 Thread 做不到的。 




Service服务的总结就到这里,如果有什么错误或者需要补充之处可以提出来。