前言
本篇博客聊一下Android下的Service组件,对于Service组件,有点类似于Windows下的服务。Service是Android四大组件中与Activity最相似的组件,它们的区别在于:Service一直在后台运行,它没有用户界面。一旦Service被启动起来之后,它就与Activity一样,也具有自己的生命周期。
在开发过程中,对于Activity与Service的选择标准是:如果某个程序组件需要在运行时向用户呈现某种界面,或者该程序需要与用户交互,就需要使用Activity,否则就应该考虑使用Service。
本篇博客主要内容:
Service概述
与开发一个Activity类似,它需要继承Service这个抽象类,并在实现类中,需要重写一些回调方法,用于处理Service的生命周期各部分的操作。而Service也是继承自Context,因此它也可以调用Context里定义的如getResource()、getContentResolver()等方法。
Service中定义的生命周期方法,对Service服务的开发大部分工作就围绕以下几个方法进行操作:
通过服务的启动方式与适用范围,可将服务分为两类服务:
虽然上面提到了服务有两种类别,但是一个服务类所要继承的类是一样的,都是Service类。也就是说,一个服务,可以包含上面两种运行方式的服务,只是与它重载的方法有关,如果重写了onStartCommand()即支持启动服务,如果重写onBiind()即支持绑定服务,所以如果同时重载实现这两个方法即可实现两种服务。
而对于两种启动方式的服务,生命周期中被回调的方法也不一样,下图明确说明了Service两种情况下的生命周期:
清单文件的配置
Service是Android四大组件之一,所以它也必须在 AndroidManifest清单文件中进行配置,否则系统将找不到这个服务。与Activity一样,Service的配置也是在<application/>节点下,使用<service/>配置,其中android:name属性为Service类。
如果开发的服务需要被外部应用操作,还需要配置<intent-filter/>节点,但是如果仅本程序使用,则无需配置它也可以。
如果这个服务强制仅本应用操作,可以配置<service/>节点的android:exported属性为false,这样即使配置了<intent-filter/>,外部应用也无法操作这个服务,android:exported属性默认为true。
清单文件配置示例:
1 <application> 2 <!-- 普通的服务 --> 3 <service android:name=".Service1"></service> 4 <!-- 可被外部应用访问的服务 --> 5 <service android:name=".Service2"> 6 <intent-filter > 7 <action android:name="com.bgxt.Service2"/> 8 </intent-filter> 9 </service> 10 <!-- 无法被外部应用访问的服务 --> 11 <service android:name=".Service3" android:exported="false"> 12 <intent-filter > 13 <action android:name="com.bgxt.Service3"/> 14 </intent-filter> 15 </service> 16 </application>
Service的开发步骤
既然Service是一个与Activity类似的Android组件,所以它的开发步骤大致也为一下几步:
启动服务
启动服务必须实现Service.onStartCommond()方法。启动服务使用startService(Intent intent)方法开启一个服务,仅需要传递一个Intent对象即可,在Intent对象中指定需要启动的服务。而使用startService()方法启动的服务,在服务的外部,必须使用stopService()方法停止,在服务的内部可以调用stopSelf()方法停止当前服务。一旦使用startService()或者stopSelf()方法请求停止服务,系统会就会尽快销毁这个服务。
对于启动服务,一旦启动将与访问它的组件无任何关联,即使访问它的组件被销毁了,这个服务也一直运行下去,直到被销毁!
启动服务-示例
下面开发一个简单的使用startService()启动的服务,首先开发一个服务类:
1 package com.example.intentdemo2; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.IBinder; 6 import android.util.Log; 7 8 public class StartService extends Service { 9 private final static String TAG = "main"; 10 11 @Override 12 public IBinder onBind(Intent arg0) { 13 // 仅通过startService()启动服务,所以这个方法返回null即可。 14 return null; 15 } 16 17 @Override 18 public void onCreate() { 19 super.onCreate(); 20 Log.i(TAG, "Service is Created"); 21 } 22 23 @Override 24 public int onStartCommand(Intent intent, int flags, int startId) { 25 Log.i(TAG, "Service is started"); 26 return super.onStartCommand(intent, flags, startId); 27 } 28 29 @Override 30 public void onDestroy() { 31 Log.i(TAG, "Service is Destroyed"); 32 super.onDestroy(); 33 } 34 35 }
虽然这个Service类没有什么处理逻辑,但是它包含了Service框架,在实际开发过程中,只需要在其中回调的方法中加入实际的业务实现代码即可。下面再使用一个Activity来操作这个服务,在这个Activity中有两个按钮,分别用于启动和停止这个服务。
1 package com.example.intentdemo2; 2 3 import android.app.Activity; 4 import android.content.ComponentName; 5 import android.content.Intent; 6 import android.os.Bundle; 7 import android.util.Log; 8 import android.view.View; 9 import android.widget.Button; 10 11 public class ServiceActivity1 extends Activity { 12 private Button btnSer1, btnSer2; 13 private Intent service=null; 14 @Override 15 protected void onCreate(Bundle savedInstanceState) { 16 super.onCreate(savedInstanceState); 17 setContentView(R.layout.activity_service1); 18 btnSer1 = (Button) findViewById(R.id.btnSer1); 19 btnSer2 = (Button) findViewById(R.id.btnSer2); 20 // 设置服务启动的Intent 21 service=new Intent(ServiceActivity1.this,StartService.class); 22 btnSer1.setOnClickListener(new View.OnClickListener() { 23 24 @Override 25 public void onClick(View v) { 26 // 启动服务 27 startService(service); 28 } 29 }); 30 31 btnSer2.setOnClickListener(new View.OnClickListener() { 32 33 @Override 34 public void onClick(View v) { 35 // 停止服务 36 stopService(service); 37 } 38 }); 39 } 40 }
执行结果均在日志中,可以在LogCat中查看,先启动服务,再停止服务:
绑定服务
如果Service和宿主之间需要进行方法调用或者数据交换,则应该使用Context对象的bindService()和unbindService()方法来绑定和解除绑定服务。
Context的bindService()方法的完整方法签名为:
bindService(Intent service,ServiceConnection conn,int flags)
下面简单介绍一下这个方法的三个参数的意义:
从上面的bindService方法可以看出,绑定一个服务于宿主交互,依托于一个ServiceConnection接口,这个接口对象必须声明在主线程中,通过实现其中的两个方法,来实现与Service的交互,下面分别介绍一下这两个方法:
在使用绑定的服务的时候,该Service类必须提供一个IBinder onBind(Intent intent)方法,在绑定本地Service的情况下,onBind()方法说返回的IBinder对象会传给宿主的ServiceConnection.onServiceConnected()方法的service参数,这样宿主就可以通过IBinder对象与Service进行通信。实际开发中一般会继承Binder类(IBinder的实现类)的方式实现自己的IBinder对象。
需要注意的是,如果绑定服务提供的onBind()方法返回为Null,则也可以使用bindService()启动服务,但不会绑定上Service,因此宿主的ServiceConnection.onServiceConnected()方法不会被执行,也就不存在于宿主与服务的交互。
绑定服务-示例
说了这么多绑定服务相关的内容,下面通过一个例子来实现Service的绑定与数据交互。
开发一个Service类,用于进行绑定,在Service类中,做一个简单的数值累加,每秒加一。
1 package com.example.intentdemo2; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.Binder; 6 import android.os.IBinder; 7 import android.util.Log; 8 9 public class BindService extends Service { 10 private final static String TAG = "main"; 11 private int count; 12 private boolean quit; 13 14 private Thread thread; 15 private MyBinder binder=new MyBinder(); 16 public class MyBinder extends Binder 17 { 18 // 声明一个方法,把count暴露给外部程序。 19 public int getCount(){ 20 return count; 21 } 22 } 23 24 @Override 25 public void onCreate() { 26 super.onCreate(); 27 Log.i(TAG, "Service is Created"); 28 thread=new Thread(new Runnable() { 29 @Override 30 public void run() { 31 // 每间隔一秒count加1 ,直到quit为true。 32 while(!quit){ 33 try{ 34 Thread.sleep(1000); 35 }catch(InterruptedException e){ 36 e.printStackTrace(); 37 } 38 count++; 39 } 40 } 41 }); 42 thread.start(); 43 } 44 45 @Override 46 public boolean onUnbind(Intent intent) { 47 Log.i(TAG, "Service is Unbinded"); 48 return true; 49 } 50 51 @Override 52 public int onStartCommand(Intent intent, int flags, int startId) { 53 Log.i(TAG, "Service is started"); 54 return super.onStartCommand(intent, flags, startId); 55 } 56 57 @Override 58 public void onDestroy() { 59 super.onDestroy(); 60 Log.i(TAG, "Service is Destroyed"); 61 this.quit=true; 62 63 } 64 65 @Override 66 public IBinder onBind(Intent intent) { 67 Log.i(TAG, "Service is Binded"); 68 return binder; 69 } 70 }
然后使用一个Activity来绑定上面这个Service类,并且声明一个ServiceConnection对象,用于进行数据交互。
1 package com.example.intentdemo2; 2 3 import android.app.Activity; 4 import android.app.Service; 5 import android.content.ComponentName; 6 import android.content.Intent; 7 8 import android.content.ServiceConnection; 9 import android.os.Bundle; 10 import android.os.IBinder; 11 import android.util.Log; 12 import android.view.View; 13 import android.widget.Button; 14 import android.widget.Toast; 15 16 public class ServiceActivity2 extends Activity { 17 private final String TAG = "main"; 18 Button bind, unbind, getServiceStatus; 19 BindService.MyBinder binder; 20 private ServiceConnection conn = new ServiceConnection() { 21 @Override 22 public void onServiceDisconnected(ComponentName name) { 23 Log.i(TAG, "--Service Disconnected--"); 24 } 25 @Override 26 public void onServiceConnected(ComponentName name, IBinder service) { 27 Log.i(TAG, "--Service Connected--"); 28 // 取得Service对象中的Binder对象 29 binder = (BindService.MyBinder) service; 30 } 31 }; 32 33 @Override 34 protected void onCreate(Bundle savedInstanceState) { 35 super.onCreate(savedInstanceState); 36 setContentView(R.layout.activity_bindservice1); 37 38 bind = (Button) findViewById(R.id.bind); 39 unbind = (Button) findViewById(R.id.unbind); 40 getServiceStatus = (Button) findViewById(R.id.getServiceStatus); 41 42 final Intent intent = new Intent(); 43 // 指定开启服务的action 44 intent.setAction("com.bgxt.BindServiceDemo"); 45 46 bind.setOnClickListener(new View.OnClickListener() { 47 48 @Override 49 public void onClick(View v) { 50 // 绑定服务到当前activity中 51 bindService(intent, conn, Service.BIND_AUTO_CREATE); 52 } 53 }); 54 unbind.setOnClickListener(new View.OnClickListener() { 55 56 @Override 57 public void onClick(View v) { 58 // 解除绑定 59 binder=null; 60 unbindService(conn); 61 } 62 }); 63 getServiceStatus.setOnClickListener(new View.OnClickListener() { 64 65 @Override 66 public void onClick(View v) { 67 if(binder!=null) 68 { 69 // 通过绑定服务传递的Binder对象,获取Service暴露出来的数据 70 Toast.makeText(ServiceActivity2.this, 71 "Service的Count值为" + binder.getCount(), 72 Toast.LENGTH_SHORT).show(); 73 Log.i(TAG, "Service的Count值为" + binder.getCount()); 74 } 75 else 76 { 77 Toast.makeText(ServiceActivity2.this, 78 "还没绑定呢,先绑定。", 79 Toast.LENGTH_SHORT).show(); 80 } 81 } 82 }); 83 } 84 }
执行结果,先绑定服务,然后获取当前服务运行时count的值,最后解除绑定,把执行过程输出到LogCat中。
Service的选用
相信看完以上的内容,对Android下Service组件有了一定的了解,但是对于选用startService()还是使用bindService(),有一些原则需要讲明。如果仅仅是想要启动一个后台服务长期驻留在内存中执行某项任务,那么仅使用startService()启动一个服务即可。但是如果想要与正在运行的服务进行交互,一种方式是使用broadcast,这个以后再介绍,另外一种方式就是使用bindService()绑定一个服务到组件上,使用broadcast的缺点是如果数据交互频繁,容易造成性能上的问题,并且BroadcastReceiver本身执行代码的时间不确定,也许代码执行到一半,后面的代码将不被执行,但是使用bindService()绑定服务即没有这些问题。另外,如果一个服务是依托于某项应用的,那么也最好使用绑定服务,在依托的应用启动的时候绑定服务,这样可以在不使用的时候避免浪费系统资源。
源码下载
总结
值得注意的是,Android下的Service组件也是运行在主线程中的,所以一些Android4.0+无法在主线程上进行的操作,在Service中也必须另外开启线程来完成,如访问网络,还可以使用继承Service的子类IntentService来实现,这个内容会在之后的博客中介绍。Android系统本身还提供了大量的Service组件,开发人员可以通过这些系统Service来操作Android系统本身,这不属于本篇博客的范畴,以后再慢慢详解。