第十章 Service与BroadcastReceiver

综合技术 2017-11-20 阅读原文

Service是Android四大组件中与Activity最相似的组件,他们都代表可执行的程序,Service与Activity的区别在于:Service一直在后台运行,他没有用户界面,所以绝不会到前台来,一旦Service被启动起来,他就与Activity一样,他完全具有自己的生命周期。关于程序中Activity和Service的选择标准是:如果某个程序需要在运行时向用户呈现某种界面,或者该程序需要与用户交互,就需要使用Activity;否则就应该考虑使用Service了。

开发者开发Service的步骤与开发Activity的步骤很像,开发Service组件需要先开发一个Service子类,然后在AndroidManifest.xml文件中配置该Service,配置时通过。 元素指定它可被那些Intent启动。

Android系统本身提供了大量的Service组件,开发者可通过这些系统Service来操作Android系统本身。

本章还将向读者介绍BroadcastReceiver组件。BroadcastReceiver组件就像一个全局的事件监听器,只不过用于监听系统Broadcast。通过是BroadcastReceiver,即可在不同应用程序之间通讯。

10.1 Service简介

Service组件也是可执行得让程序,他有自己的生命周期。创建、配置Service与创建、配置Activity的过程基本相似,下面详细介绍Android Service的开发。

10.1 创建、配置Service

就像开发Activity需要两个步骤:

(1)开发Activity子类;

2、在AndroidManifest.xml文件中配置Activity。开发Service也需要两个步骤。

(1)定义一个继承Service的子类。

(2)在AndroidManifest.xml文件中配置该Service

Service与Activity还有一些相似之处,它们都是从Content派生出来的,因此它们都可以调用Content里定义的如getResources、getContentResolver()等方法。

与Activity相似的是,Service中也定义了一系列生命周期方法,如下所示:

1、 IBinder onBind(Intent intent):该方法是Service之类必须实现的方法。该方法返回一个IBinder对象,应用程序可通过该对象与Service组件通讯。

2、 void onCreat():在该Service第一次被创建后立即回调该方法。

3、 void onDestroy():在该Service第一次被创建后将立即回调该方法。

4、 void onStartCommand(Intent intent,int flags,int starId):该方法的在其版本是Void onStart(Intent intent,int startId),每次客服端调用startService(Intent)方法启动该Service时都会回调该方法。

5、 boolean onUnbind(Intent intent):当该Service上绑定的所有客户端都断开链接时将会回调该方法。

下面的类定义了一个Service

提示:

上面这个Service什么也没干,但实际上它是Service组建的框架,如果希望Service组件做某些事情,那么只要在onCreat()或onStartCommand()方法中定义相关业务代码即可。

定义了上面的Service之后,接下来需要在AndroidManifest.xml文件中配置该Service,配置Service使用

与配置Activity相似的是,配置Service时也可以为元素配置子文件,用于说明该Service可被那些Intent启动。

从上面的配置片段不难看出,配置Service与配置Activity的差别并不大,只是配置Service使用,而且无需指定android:label属性-------因为Service没有界面,总是位于后台运行,为该Service指定标签没有太大的意义。

1、通过Content的startService()方法:通过该方法启动Service,访问者与Service没有关联,即使访问者退出了,Service也仍然运行。

2、通过Content的bindService()方法:使用该方法启动Service,访问者与Service绑定在一起,访问者退出了,Service也就终止了。

下面先示范第一种方式运行Service

从上面程序中的粗字体代码不难看出,启动、关闭Service十分简单,调用Content里定义的startService、stopService()方法即可启动、关闭Service。

提示:

可能读者会注意到本程序使用了显示Intent(直接指定要启动的目标组件实现类)来启动Service,单本书第二版使用了隐士Intent启动Service。这是因为从Android5.0开始,Google要求必须使用显示Intent启动Service组件。

运行该程序,通过程序界面先启动Service,在关闭Service,将可以在AS的Logcat面板看到如上图所示的输出。

如果不关闭Service的情况下,连续三次单击“启动Service”按钮,

从上图可以看出,每当Service被创建时会回调onCreat()方法,每次Service被启动时都会回调onStartCommand()方法-----多次启动一个已有的Service组件将不会在回调onCreat()方法,但每次启动都会回调onStartCommand()。

10.1.3 绑定本地Service并与之通讯

当程序通过starService()和stopService()启动、关闭Service时,Service与访问者之间基本上不存在太多的关联,因此Service和访问者之间也无法进行通讯、交换数据。

如果Service和调用者之间需要数据交换,则应该使用bindService和unbindService()方法启动和关闭Service。

Content的bindService()方法完整的方法签名是:bindService(Intent service,ServiceConnection conn,int flags),该方法的三个参数解释如下。

1、Service:该参数通过Intent指定要启动的Service。

2、conn:该参数是一个Serviceconnection对象,该对象用于监听访问者与Service之间的连接情况。当访问者与Service之间连接成功时将回调该ServiceConnection对象的onServiceConnected(CompontName name,Ibinder service)方法;当Service所在的宿主进程由于异常终止或其他原因终止,导致该Service与访问者之间断开连接时回调该ServiceConnection对象的onServiceDisconnected(ComponentName name)方法。

注意:

当调用者主动通过unBindService()方法断开与Service的连接时,ServiceConnection对象的onSrviceDisconnected(ComponentName name)方法并不会被调用。

flags:指定绑定时是否自动创建Service(如果Service还未创建)。该参数可指定为0(不自动创建)或BIND_AUTO_CREATE(自动创建)。

注意到Serviceconnection对象的onServiceConnected()方法中有一个IBinder对象,该对象即可实现与被绑定Service之间的通讯。

当开发Service类时,该service必须提供一个IBinder onBind(Intent intent)方法,在绑定本地Service情况下,onBind(Intent intent)方法返回的IBinder对象将会传给ServiceConnection对象里onServiceConnected(CompontName name,Ibinder service)方法的Service参数,这样访问者就可通过该IBinder对象与Service进行通讯了。

提示:

IBinder对象相当于Service组件的内部钩子,该钩子关联到绑定的Service组件,当其他程序组件绑定该Service时,Service将会把IBinder对象返回给其他程序组件,其他程序组件通过该IBinder对象即可与Service组件进行实时通讯。

实际上开发时通常会采用继承Binder(IBinder的实现类)的方式实现自己的IBinder对象。下面的程序示范了如何在Activity中绑定本地Service,并获取Service的运行状态。该程序的Service类需要“真正”实现onBind()方法,并让该方法返回一个有效的IBinder对象。该Service类的代码如下。

上面Service类的粗体字代码实现了onBind()方法,该方法返回了一个可访问该Service状态数据(count值)的Binder对象,该对象被传给该Service的访问者。

上面程序中的通过继承Binder雷山县了一个IBinder对象,这个MyBinder类是Service的内部类,这对于本地绑定Service并与之通讯的场景的一种常见情形。

接下来定义一个Activity来绑定该Service,并在该Activity中通过MyBinder对象访问Service内部状态。该Activity的界面上包含三个按钮,第一个按钮用于绑定Service;第二个按钮用于接触绑定;第三个按钮则用于获取Service的运行状态。该Activity的代码如下。

上面程序中

这段代码用于在该Activity与Service链接成功时获取Service的onBind()方法所返回的MyBinder对象;

这段代码即可通过myBinder对象来访问Service的运行状态了。

运行该程序,单击程序界面中的绑定"Service"按钮,即可看到Android Studio的logcat有如图10.3所示的输出。

在该Activity中绑定Service之后,该Activity还可通过MyBinder对象来获取Service的运行状态,如果用户单击程序界面上的"获取Service状态"按钮,即可看到如图10.4所示的输出

图 10.4

从图10.4所示的输出可以看到,该Activity可以非常方便地访问到Service的运行状态。虽然本程序只是一个简单的示例,该Activity只是访问了Service的一个简单count值,但实际上完全可以让Mybinder去操作Service中更多的数据-----到底需要访问Service的多少数据,完全取决于实际业务需要。

提示

对于Servicer的onBind()方法所返回的IBinder对象来说,它可以被当成该Service组件所返回的代理对象,Service允许客户端通过该IBinder对象来访问内部的数据,这样即可实现客户端与Service之间的通讯。

如果我们单击程序界面上的"解除绑定"按钮,即可在AS的logCat中看到如图10.5所示输出

图10.5(解除绑定)

正如10.5中所示,当程序调用unBindService()方法解除对某个Service的绑定时,系统会先回调该Service的onUnbinde()方法,然后调用onDestry()方法。

与多次调用startService()方法启动Service()方法启动Service不同的是,多次调用bindService()方法并不会执行重复绑定。对于前一个示例程序,用户每单击"启动"按钮一次,系统就会回调Service的onStartCommand()方法一次;对于这个示例程序,不管用户单击"绑定Service”按钮多少次,系统只会回调Service的onBiond()方法一次。

10.1.4 Service的生命周期

通过前面两个示例,读者应该大致明白Service的生命周期了。随着应用程序启动Service方式不同,Service的生命周期也略有不同。

如果应用程序通过startService()方法来启动Service,Service的生命周期如图10.6左边所示。如果应用程序通过bindService方法来启动Service的生命周期如右图所示:

Service的生命周期还有一个特殊的情形--------如果Service已由某个客户端通过startService()方法启动了。接下来其他客户端调用bindService()方法绑定该Service之后,再调用unbinService()方法解除绑定,最后有调用binService()方法再次绑定到Service,这个过程所触发的生命周期方法如下所示 onCreat()---- onStartCommand() ---- onBind() ---- onUnbind()(重写该方法时返回了true) ---- onRebind() ,在上面这个触发过程中,onCreat()是创建该Service后立即调用的,只有当该service被创建时才会被调用;onStartCommand()方法则是有客户端调用startService()方法时触发的。图10.7所示的logcat显示了上面生命周期的输出。

图10.7

在图10.7所示的输出中,可以看到Service的onRebind()方法被回调了。如果希望该方法被回调,除了需要该Service是由Activity的startService()方法启动之外,还需要Service子类重写onUnbind()方法时返回true。

注意:

在图10.7所示输出中,并没有发现Service回调onDestroy()方法,这是因为该Service并不是由Activity通过bindService()方法启动的(该Service事先已由Activity通过startActivity()方法启动了),因此当Activity调用unBindService方法取消与该Service的绑定时,该Service也不会终止。

10.1.5 使用IntentService

IntentService是Service的子类,因此它不是普通的Service,它比普通的Service增加了额外的功能。

先看Service本身存在的两个问题。

1、Service不会专门启动一个单独的进程,Service与它塑造应用位于一个进程中。

2、Service不是一条新的线程,因此不应该在Service中直接处理耗时的任务。

提示:

如果开发者需要在Service中处理耗时任务,建议在Service中另外启动一条新的线程来处理该耗时任务。就像在前面BindService中所看到的,程序在BindService的onCreate()方法中启动了一条新线程来处理耗时任务。可能有读者感到疑惑:直接在其他程序组件中启动子线程来处理耗时任务不行吗?这种方式也不可靠,由于Activity可能会被用户退出,而BraodcastReceiver的生命周期本身就很短。可能出现的情况是:在子线程还没结束的情况下,Activity已经被用户退出了,或者BroadcastReceiver已经结束了。在Activity已经退出、BroadcastReceiver已经结束的情况下,此时他们所在的进程就变成了空进程(没有任何活动组件的进程),系统需要内存时可能会优先终止该进程,那么进程内的所有子线程也会被终止,这样就可能导致子线程无法执行完成。

而IntentService正好可以弥补Service的上述两个不足:IntentService将会使用队列来管理请求Intent,每当客户端代码通过Intent请求启动IntentService时,IntentServiec会将该Intent加入队列中,然后开启一条新的worker线程来处理该IntentService。对于异步的startService()请求,IntentService会次序依次处理队列中的Intent,该线程保证同一时刻只处理一个Intent。由于IntentService使用新的orker线程处理Intent请求,因此IntentService不会阻塞主线程,所以IntentService自己就可以处理耗时任务。

归纳起来,IntentService具有如下特请求征:

1、IntentService会创建单独的worker线程来处理所有的Intent请求

2、IntentService汇创建单独的worker线程来处理onHandleIntent方法的实现代码,因此开发者无需处多线程的问题。

3、当所有请求处理完成后。IntentService会自动停止,因此开发者无须调用stopSelf()方法来停止该Service。

4、为Service的onBind()方法提供了默认实现,默认实现的onBind()方法返回null。

5、为Service的onStartCommand()方法提供了默认实现,该实现将请求Intent添加到队列中,

从上面的介绍可以看出,扩展IntentService实现Service无须重写onBind()、onStartCommand方法,只要重写onHandleIntent()方法即可。

下面的示例程序界面中包含了两个按钮,分别用于启动普通Service和IntentService,两个Service都需要处理耗时任务。该程序的界面布局代码很简单,这里不再给出。主程序Activity代码:

上面Activity的两个事件处理方法中分别启动了MyService和MyIntentService,其中MyService是继承service的子类,而MyIntentService则是继承IntentService的子类。

下面是MyService类的代码。

上面MySerevice在onStartCommand()方法中使用线程暂停的方式模拟了耗时任务,该线程暂停了20秒,相当于该耗时任务需要执行20秒,由于普通Service的执行会阻塞主线程,因此启动该线程将会导致程序出现ANR(Application Not Responding)异常。

下面是MyIntentService类的代码

从上面的代码可以看出,MyIntentService继承了IntentService,并不需要实现onBind()、onStartCommand()方法,只要实现 onHandleIntent() 方法即可,在该方法中定义该Service需要完成的认为诶。本示例的onHandleIntent()方法也用线程暂停的方式模拟了耗时任务,线程同样暂停了20秒。但由于IntentService会使用单独的线程来完成该耗时任务,因此启动MyIntentService不会阻塞前台线程

运行该示例,如果单击界面上的“启动普通Service”按钮,竟会激发startService()方法,该方法竟会启动MyService去执行耗时任务,此时将会导致程序UI线程被阻塞(程序界面失去响应),而且由于阻塞时间太长,因此将会看到如图10.8所示的ANR异常。

相反,如果调用“启动IntentService”来启动MyIntentService,虽然MyIntentService也需要执行耗时任务,但由于MyService会使用单独的worker线程,因此MyIntentService不会阻塞前台的UI线程,所以程序界面不会失去响应。

10.2 电话管理器(TelephonyManager)

TelephonyManager是一个管理手机通话状态、电话网络的服务类,该类提供了大量的getXxx()方法来获取电话网络的相关信息。

在程序中获取TelephonyManager十分简单,只要调用如下代码即可:

接下来就可以通过TelephonyManager获取相关信息或者进行相关操作了。

实例:获取网络和SIM卡信息

通过TelephonyManager提供的一系列方法即可获取手机网络、SIM卡的相关信息,该程序使用了一个ListView来显示网络和SIM卡的相关信息。

该程序代码如下:

由于该应用需要获取手机位置和手机状态,因此程序还需要在AndroidManifest.xml文件中增加如下配置:

10.3 短信管理器(SmsManager)

SmsManager是Android提供的的另一个非常常见的服务,SmsManager提供了一系列sendXxxMessage()方法用于发送短信,不过就现在实际应用来看,短信通常是普通的文本内容,也就是调用sendTextMessage()方法进行发送即可。

实例:发送短信

本实例程序十分简单,程序提供了文本框让用户输入收件人号码,一个文本框让用户输入短信内容,接下来单击“发送”按钮即可短信发送出去。

从上面程序可以看出,使用SmsManager发送短信十分简单,简单地调用sendTextMessage()方法即可发送短信。

上面程序中用到了一个PendingIntent对象,PendingIntent是对Intent的包装,一般通过调用PendingIntent的getActivity()、getService()、getBroadcastReceiver()静态方法来获取PpendingIntent对象。与Intent对象不同的是,PendingIntent通常回传给其他应用组件,从而有其他应用程序来执行PendingIntent所包装的Intent。

该程序需要调用SMsManager来发送短信,因此还需要授予程序发送短信的权限,也就是在AndroidManifest.xml文件中增加如下代码:

简书

责编内容by:简书阅读原文】。感谢您的支持!

您可能感兴趣的

GodBlessYou: 让你的应用不再崩溃 项目地址: github.com/ximsfei/God… GBU尝试在运行时修复Android应用的崩溃问题,在使部分功能失效的前提下,让应...
用广播 BroadcastReceiver 更新 UI 界面真的好吗?全方位解析广播... 大家好,由于公众号有一个勘误,所以在掘金重新更正后发布本文。 这是面试系列 的第三期。本期我们将来探讨一下 Android 四大组件的重要组成部分:广播 ...
自定义Androidk全量更新组件   自动更新功能对于一个APP来说是必备的功能,特别是对于未投放市场下载的APP,每次都让用户删掉原来的,再下载新的版本,肯定是不合适的。 实现思路: ...
安卓广播的底层实现原理 相信广播大家都有用过,也知道安卓广播的一些基础知识,如静态广播、动态广播、粘性广播等等,但相信很多人都不知道系统层面是怎样实现这些广播特性的,这篇文章就让我们来...
Can not catch java.lang.Veri... I'm getting this error: "Uncaught handler: thread main exiting due to uncaught e...