第 5 章 Android服务(Service) 本章要点 . Service组件的作用和意义 . Service与Activity的区别 . 运行Service的两种方式 . 绑定Service执行的过程 . Service的生命周期 . 跨进程调用Service . 调用系统发短信服务 本章知识结构图 本章示例 根据Activity 的生命周期,程序中每次只有一个Activity 处于激活状态,并且 Activity的执行时间有限,不能做一些比较耗时的操作。如果需要多种工作同时进行,比 如一边听音乐,一边浏览网页,使用Activity则比较困难。针对这种情况,Android为我 们提供了另一种组件———服务(Service)。 Android编程(第2版) 5.1 Service概述 Service是一种Android应用程序组件,可在后台运行一些耗时但不显示界面的 操作。 5.1 Srie介绍 1.evc Service与Activity类似,都是Android中四大组件之一,并且二者都是从Context派 生而来,最大的区别在于Service没有实际的界面,而是一直在Android系统的后台运行, 相当于一个没有图形界面的Activity程序,它不能自己直接运行,需要借助Activity或其 他Context对象启动。 Service主要有两种用途:后台运行和跨进程访问。通过启动一个Service,可以在不 显示界面的前提下在后台运行指定的任务,这样可以不影响用户做其他事情,如后台播放 音乐,前台显示网页信息。AIDL 服务可以实现不同进程之间的通信,这也是Service的 重要用途之一。 其他的应用程序组件一旦启动Service,该Service将会一直运行,即使启动它的组件 跳转到其他页面或销毁。此外,组件还可以与Service绑定,从而与之进行交互,甚至执行 一些进程内通信。例如,服务可以在后台执行网络连接、播放音乐、执行文件操作或者是 与内容提供者交互等。 5.1.2 启动Service的两种方式 在Android系统中,常采用以下两种方式启动Service。 120 第5章Android服务(Service) (1)通过Context的startService()启动Service。启动后,访问者与Service之间没 有关联,该Service将一直在后台执行,即使调用startService的进程结束了,Service仍然 还存在,直到有进程调用stopService(),或者Service自杀(stopSelf())。这种情况下, Service与访问者之间无法进行通信和数据交换,往往用于执行单一操作,并且没有返回 结果。例如通过网络上传、下载文件,操作一旦完成,服务应该自动销毁。 (2)通过Context的bindService()绑定Service。绑定后,Service就和调用 bindService()的组件“同生共死”了,也就是说当调用bindService()的组件销毁了,它 绑定的Service也要跟着结束,当然期间也可以调用unbindService()让Service提前 结束。 注意:一个Service可以与多个组件绑定,只有当所有的组件都与之解绑后,该 Service才会被销毁。 以上两种方式也可以混合使用,即一个Service既可以启动也可以绑定,只需要同时 实现onStartCommand()(用于启动)和onBind()(用于绑定)方法,那么只有当调用 stopService(),并且调用unbindService()方法后,该Service才会被销毁。 注意:Service运行在它所在进程的主线程。Service并没有创建它自己的线程,也没 有运行在一个独立的进程上(单独指定的除外),这意味着,如果Service做一些消耗CPU 或者阻塞的操作,应该在服务中创建一个新的线程去处理。通过使用独立的线程,就会降 低程序出现ANR(ApplicationNoResponse,程序没有响应)风险的可能,程序的主线程 仍然可以保持与用户的交互。 5.3 Srie中常用方法 1.evc 与开发其他Android组件类似,开发Service组件需要先开发一个Service子类,该类 需继承系统提供的Service类,系统中Service类包含的方法主要有以下几种。 (1)abstractIBinderonBind(Intentintent):该方法是一个抽象方法,因此所有 Service的子类必须实现该方法。该方法将返回一个IBinder对象,应用程序可通过该对 象与Service组件通信。 (2)voidonCreate():当Service第一次被创建时,将立即回调该方法。 (3)voidonDestroy():当Service被关闭之前,将回调该方法。 (4)vintrCnItnnet,itfas,ntrId) odoSatommad(netitnnlgitsat:该方法的早期版本 是vodoSat(netitnntrId), trSrie(netitn intrItnnet,itsat每次客户端调用satevcItnnet) 方法启动该Service时都会回调该方法。 (5)boenoid(netitnevc olanUnbnItnnet):当该Srie上绑定的所有客户端都断开连 接时将会回调该方法。 定义的Service子类必须实现onBind()方法,然后还需在AndroidManifestxml文件 中对该Service子类进行配置,配置时可通过<intent-filter. />元素指定它可(.) 被哪些 Intent启动。下面具体来创建一个Service子类并对它进行配置,代码如下。 121 Android编程(第2版) 1 22 程序清单:codes\ch05\ServiceTest\app\src\main\java\ iet\jxufe\cn\servicetest\MyService.java 1 public class MyService extends Service { →自定义服务类 2 private static final String TAG ="MyService"; →定义字符串常量 3 public IBinder onBind(Intent arg0) { →重写onBind()方法 4 Log.i(TAG, "MyService onBind invoked!"); →打印日志信息 5 return null; 6 } 7 public void onCreate() { →重写onCreate()方法 8 super.onCreate(); →调用父类方法 9 Log.i(TAG, "MyService onCreate invoked!"); →打印日志信息 10 } 11 public void onDestroy() { →重写onDestroy()方法 12 super.onDestroy(); →调用父类方法 13 Log.i(TAG, "MyService onDestroy invoked!"); →打印日志信息 14 } 15 public int onStartCommand(Intent intent, int flags, int startId) { →重写方法 16 Log.i(TAG, "MyService onStartCommand invoked! ") ; →打印日志信息 17 return super.onStartCommand(intent, flags, star tI d) ; →返回结果 18 } 19 } 上述代码中创建了自定义的MyService类,该类继承了Android.app.Service类,并 重写了onBind()、onCreate()、onStartCommand()、onDestroy()等方法,在每个方法中, 通过LOG语句测试和查看该方法是否被调用。 定义Service之后,还需在项目的AndroidManifest.xml文件中配置该Service,增加 配置片段如下。 1 <service android:name=“.MyService”> →Service 标签 2 <intent-filter> →过滤条件 3 <action android:name=“iet.jxufe.cn.MY_SERVICE”/> 4 </intent-filter> 5 </service> 虽然目前MyService已经创建并注册了,但系统仍然不会启动MyService,要想启动 这个服务,必须显式地调用startService()方法。如果想停止服务,需要显式地调用 stopService()方法。下面的代码使用Activity作为Service的启动者,分别定义了“启动 服务”和“关闭服务”两个按钮,并为它们添加了事件处理。 程序清单:codes\ch05\FirstService\src\iet\jxufe\cn\android\MainActivity.java 1 public class MainActivity extends AppCompatActivity { 2 private Intent intent; →声明Intent 第5章 Android服务(Service) 1 23 3 @Override 4 protected void onCreate(Bundle savedInstanceState) { 5 super.onCreate(savedInstanceState); 6 setContentView(R.layout.activity_main); →加载布局文件 7 intent=new Intent(this,MyService.class); →创建Intent 对象 8 } 9 public void start(View view){ →按钮事件处理 10 startService(intent); →启动服务 11 } 12 public void stop(View view){ →按钮事件处理 13 stopService(intent); →停止服务 14 } 15 } 运行本节的例子后,第一次单击“启动服务”按钮后,LogCat视图下的输出如图5-1 所示。 图5-1 启动ServiceLogCat控制台打印信息 然后单击“停止服务”按钮,LogCat视图有如图5-2所示的输出。 图5-2 关闭ServiceLogCat控制台打印信息 下面按如下的单击按钮顺序的重新测试一下本例。 “启动服务”→“启动服务”→“启动服务”→“停止服务” 测试程序完成,查看LogCat控制台输出信息如图5-3所示。系统只在第一次单击 “启动服务”按钮时调用onCreate()和onStartCommand()方法,再单击该按钮时,系统只 会调用onStartCommand()方法,而不会重复调用onCreate()方法。 图5-3 连续启动ServiceLogCat控制台打印信息 启动服务后退出该程序,查看LogCat控制台输出信息,发现并没有打印onDestroy()方 法被调用的信息,即服务并没有销毁,可以通过查看系统服务,查看该服务是否正在运行, 具体操作为:打开模拟器功能清单中的“设置”,选择“关于模拟设备”,然后连续单击版本 号7次进入开发者模式,接着退出“关于模拟设备”,进入“系统选项”,选择“高级”下面的 Android编程(第2版 ) “开发者选项”,即可看到正在运行的服务,如图5-4所示 。 图5- 4 查看系统正在运行的服务 5.1.4 绑定Service过程 如果使用5.1.2节介绍的方法启动服务,并且未调用stopService() 来停止服务, Service将会一直驻留在手机的服务之中。Service会在Android系统启动后一直在后台 运行,直到Android系统关闭后Service才停止。但这往往不是我们所需要的结果,我们 希望在启动服务的Activity关闭后Service会自动关闭,这就需要将Activity和Service 绑定。在Context类中专门提供了一个用于绑定Service的bindService() 方法,Context 的bindService() 方法的完整方法签名为:bindService(Intentservice,ServiceConnection conn,intflags), 该方法的三个参数解释如下。 (1)service:该参数表示与服务类相关联的Intent对象,用于指定所绑定的Service 应该符合哪些条件。 (2)conn:该参数是一个ServiceConnection对象,该对象用于监听访问者与Service 间的连接。当访问者与Service间连接成功时,将回调该ServiceConnection对象的 onevcCnetd(ompnname,neevc方法; evc SrieonceCoetNamenIBidrsrie) 当访问者与Srie 之间断开连接时将回调该ServiceConnection 对象的onServiceDisconnected (ComponentNamename)方法。 (3)flags:指定绑定时是否自动创建Service(如果Service还未创建), 该参数可指定 BIND_AUTO_CREATE(自动创建)。 当定义Service类时,该Service类必须提供一个onBind() 方法,在绑定本地Service 的情况下,onBind() 方法所返回的IBinder对象将会传给ServiceConnection对象里 124 第5章 Android服务(Service) 1 25 onServiceConnected(ComponentNamename,IBinderservice)方法的service参数,这样 访问者就可以通过IBinder对象与Service进行通信。 在上述示例中,添加两个按钮,一个用于绑定Service,一个用于解绑,然后分别为其 添加事件处理。在绑定Service时,需要传递一个ServiceConnection对象,所以先创建该 对象。在MainActivity.java中添加如下代码。 1 private ServiceConnection conn=new ServiceConnection() { 2 public void onServiceDisconnected(ComponentName name) { 3 Log.i(TAG,"MainActivity onServiceDisconnected invoked!"); 4 } 5 public void onServiceConnected(ComponentName name, IBinder service) { 6 Log.i(TAG,"MainActivity onServiceConnected invoked!"); 7 } 8 }; 然后在MyService中添加两个与绑定Service相关的方法onUnbind()和onRebind(),与 其他方法类似,只在方法体中打印出该方法被调用了的信息,代码如下。 1 public boolean onUnbind(Intent intent) { 2 Log.i(TAG, "MyService onUnbind invoked!"); →打印日志信息 3 return super.onUnbind(intent); →调用父类方法 4 } 5 public void onRebind(Intent intent) { 6 super.onRebind(intent); →调用父类方法 7 Log.i(TAG, "MyService onRebind invoked!"); →打印日志信息 8 } 最后在MainActivity中为“绑定服务”和“解绑服务”按钮添加事件处理,代码如下。 1 public void bind(View view){ →绑定服务事件处理 2 bindService(intent,conn, Service.BIND_AUTO_CREATE); →绑定服务 3 } 4 public void unbind(View view){ →解绑服务事件处理 5 unbindService(conn); →解绑服务 6 } 程序执行后,单击“绑定服务”按钮,LogCat控制台打印信息如图5-5所示,首先调用 onCreate()方法,然后调用onBind()方法。 图5-5 绑定服务LogCat控制台打印信息 单击“解绑服务”按钮,LogCat控制台打印信息如图5-6所示,首先调用onUnbind() 方法,然后调用onDestroy()方法。 Android编程(第2版) 1 26 图5-6 解绑服务LogCat控制台打印信息 程序运行后,单击“绑定服务”按钮,然后退出程序,LogCat控制台打印信息如图5-7 所示。可以看出,程序退出后,Service会自动销毁。 图5-7 绑定服务直接退出程序LogCat控制台打印信息 当单击“绑定服务”按钮后,再重复多次单击“绑定服务”按钮,查看控制台打印信息, 发现程序并不会多次调用onBind()方法。 采用绑定服务的另一个优势是组件可以与Service之间进行通信,传递数据。这主要 是通过IBinder对象进行的,因此需在Service中创建一个IBinder对象,然后让其作为 onBind()方法的返回值返回,对数据的操作是放在IBinder对象中的。修改MyService 类,添加一个内部类MyBinder,同时在onCreate()方法中启动一个线程,模拟后台服务, 该线程主要做数据递增操作,在MyBinder类中,提供一个方法,可以获取当前递增的值 (count的值),具体代码如下。 1 public class MyService extends Service { 2 private static final String TAG ="MyService"; 3 private int count=0; 4 private boolean quit=false; →是否退出的标志 5 private MyBinder myBinder=new MyBinder(); →创建对象 6 public class MyBinder extends Binder { 7 public MyBinder() { →构造方法 8 Log.i(TAG, "MyBinder Constructure invoked!"→);打 印 日志信息 9 } 10 public int getCount() { →获取数据方法 11 return count; →返回数据 12 } 13 } 14 public IBinder onBind(Intent arg0) { →重写onBind 方法 15 Log.i(TAG, "MyService onBind invoked!"); →打印日志信息 16 return myBinder; →返回对象 17 } 18 public void onCreate() { 19 Log.i(TAG, "MyService onCreate invoked!"); 20 super.onCreate(); 第5章 Android服务(Service) 1 27 21 new Thread(){ →创建线程 22 public void run(){ 23 while(!quit){ →判断是否退出 24 try{ 25 Thread.sleep(500); →休眠0.5s 26 count++; →数据递增 27 }catch (Exception e) { →捕获异常 28 e.printStackTrace(); →打印异常信息 29 } 30 } 31 } 32 }.start(); →启动线程 33 } 34 public void onDestroy() { →重写方法 35 Log.i(TAG, "MyService onDestroy invoked!"); →打印日志信息 36 super.onDestroy(); →调用父类方法 37 quit=true; →修改退出标志 38 } 39 } 接着在MainActivity中添加一个“获取数据”的按钮,获取数据的前提是要绑定服 务,所以先绑定服务,在ServiceConnection()对象的onServiceConnected()方法中,获取 绑定服务时返回的IBinder对象,然后将该对象强制类型转换成MyBinder对象,最后利 用MyBinder对象获取服务中的数据信息, 首先改写创建ServiceConnection对象的方法,将绑定服务中的IBinder对象强制类 型转换成MyService.MyBinder对象,关键代码如下。 1 private ServiceConnection conn=new ServiceConnection() { 2 public void onServiceDisconnected(ComponentName name) { 3 Log.i(TAG,"MainActivity onServiceDisconnected invoked!"); 4 } 5 public void onServiceConnected(ComponentName name, IBinder service) { 6 Log.i(TAG,"MainActivity onServiceConnected invoked!"); 7 myBinder=(MyService.MyBinder)service; 8 } 9 }; 然后,为“获取数据”按钮添加事件处理方法,关键代码如下。 1 public void getData(View view){ →事件处理方法 2 Toast.makeText(MainActivity.this, "Count="+myBinder.getCount(), Toast.LENGTH_SHORT).show(); →弹出显示数据 3 } 此时,单击“绑定服务”后,LogCat 控制台打印信息如图5-8 所示,首先创建 Android编程(第2版) MyBinder对象,因为该对象是作为MyService的成员变量进行创建的,完成MyService 的初始化工作,然后调用onCreate() 方法,再调用onBind() 方法,该方法返回一个IBinder 对象,因为IBinder对象不为空,表示有服务连接,所以会调用ServiceConnection接口的 onServiceConnected() 方法,并将IBinder对象作为它的第二个参数。 图5- 8 绑定ServiceLogCat控制台打印信息 单击“绑定服务”按钮后,后台服务就开始执行,此时单击“获取数据”按钮,得到如 图5-9所示结果。多次单击时,得到的数据不一致,从而可以动态获取后台服务状态。 图5- 9 单击“获取数据”按钮的运行效果 当混合使用这两种运行Service方式时,它的执行效果又将是怎么样的呢? 下面我们 以不同的顺序来运行服务,观察LogCat控制台打印的信息。 (1)先启动Service,然后绑定Service。测试步骤为单击“启动服务”→“绑定服务”→ “启动服务”→“停止服务”→“绑定服务”→“解绑服务”。LogCat控制台打印信息如图5-10 所示。 总结:调用顺序为onCreate()→[onStartCommand() 1~ N 次]→onBind()→ onServiceConnected()→onUnbind()[→onServiceConnected()→onRebind()0~ N 次] →onDestroy()。 (2)先绑定Service,后启动Service。测试步骤为单击“绑定服务”→“启动服务”→“绑定 128