http://www.cnblogs.com/freeliver54/archive/2012/06/13/2547765.htmlhtml
简述了Service的一些基础知识以及Service和Thread的简单区别,本文将着重讲解与Service交互的五种基本方式:广播交互、共享文件交互、Mssenger(信使)交互、自定义接口交互、AIDL交互。java
1. 广播交互android
提到Activity与Service的交互,可能狠多人首先想到的就是BroadCast——广播。在Android中,广播是系统提供的一种很好的交互方式。好比:在电池电量太低,开机完成等状况下,系统都会发出相应的系统广播,咱们的应用程序只须要注册相应的广播接收器,就能够接收到这些系统的广播。同时,咱们也能够定义本身的广播,这样在不一样的Activity、Service以及应用程序之间,就能够经过广播来实现交互。咱们经过模拟应用程序后台下载的状况来分析Service与Activity的交互方式。实现效果如图1.1:服务器
图1.1并发
当咱们点击StartService按钮以后,界面上的进度条将会每隔一秒加1。由于是模拟下载,所以下载动做咱们在Service中经过一个Timer定时器来实现,在Timer中对一个整型数据i进行自加(i++),而后Client端获取Server端的i值并显示在界面上,从而达到模拟的目的。app
1.1. 实现原理框架
Server端将目前的下载进度,经过广播的方式发送出来,Client端注册此广播的监听器,当获取到该广播后,将广播中当前的下载进度解析出来并更新到界面上。ide
1.2. 实现步骤this
1.2.1 在Client端中经过startService()啟动Service。spa
这里的mIntent = new Intent();Process.myPid()方法能够获取当前进程的ID号。
1.2.2 DownLoadService接到启动的命令以后,执行onCreate()方法,并在其中开启timer计数模拟下载。
这里的intent是Server端向Client端传送数据用的,使用的action是”com.seven.broadcast”,Client端只有註册了相应action才可以接收到Server端的广播,并解析其中的内容。Process.myPid()是获取当前进程的ID。
1.2.3 在Server端的timer计数其中发送广播,告知Client端目前下载进度。
经过intent.putExtra(key,value);设置intent的值,而后经过sendBroadcast(intent)2方法,将广播发送出去。
1.2.4 在Client端经过匿名内部类的方式实例化BroadcastReceiver并覆写其中的onReceive()方法。
在onReceive()方法中,判断是否为Server端发送的广播,若是是则对广播中携带的intent数据进行解包处理。这裡也能够单独写一个类继承自BroadcastReceiver,在其中覆写onReceive()方法,在Client端中实例化其对象,一样能够达到相应的效果,这样作能够为后面实现静态注册广播。
1.2.5 更新主介面下载进度。
这里对获取到的进度进行了一次判断,若是获取到的值没有异常,那么将会显示到界面,并更新进度条的进度,若是异常则返回。
1.2.6 必定要对Broadcast进行注册和取消注册。只有注册以后相应的broadcast以后才能接收到广播注册方法有两种。
动态注册/取消注册:
动态註册能够随时註册随时取消。
静态註册:
注:这里的MyBroadcastReceiver是一个继承自BroadcastReceiver的类。静态注册只要注册了一次那么只要该程序没有被卸载那么该广播将一直有效。
最后贴出整个AndroidManifest.xml文件
这里的android:process =”:remote”可使该Service运行在单独进程中,从而能够模拟跨进程通讯。
1.3 小结
经过广播的方式实现Activity与Service的交互操做简单且容易实现,能够胜任简单级的应用。但缺点也十分明显,发送广播受到系统制约。系统会优先发送系统级广播,在某些特定的状况下,咱们自定义的广播可能会延迟。同时在广播接收器中不能处理长耗时操做,不然系统会出现ANR即应用程序无响应。
2. 共享文件交互
这里提到的共享文件指的是Activity和Service使用同一个文件来达到传递数据的目的。咱们使用SharedPreferences来实现共享,固然也可使用其它IO方法实现,经过这种方式实现交互时须要注意,对于文件的读写的时候,同一时间只能一方读一方写,不能两方同时写。实现效果如图2.1:
图2.1
2.1 实现原理
Server端将当前下载进度写入共享文件中,Client端经过读取共享文件中的下载进度,并更新到主界面上。
2.2 实现步骤
2.2.1 在Client端经过startService()啟动Service。
这里的intent = new Intent()2只是为了启动Server端。
2.2.2 Server端收到启动intent以后执行onCreate()方法,并开启timer,模拟下载,以及初始化SharedPreferences对象preferences。
经过preferences=getSharedPreferences(String,MODE)2能够在/data/data/com.seven.servicetestdemo/shared_prefs文件夹下创建相应的xml文件。
2.2.3 开始计数并将下载进度写入shared_prefs文件夹下的xml文件中,内容以键值对的方式保存。
对於SharedPreferences的使用须要注意一下几点:
首先,使用sharedPreferences前须要获取文件引用。
preferences = getSharedPreferences("CurrentLoading_SharedPs", 0);
其次,使用sharedpreferences写数据方式。
preferences.edit().putInt("CurrentLoading", i).commit();
最后,读取数据的方式。
int couLoad = preferences.getInt("CurrentLoading", 0);
2.2.4 Client端经过读取/data/data/com.seven.servicetestdemo/shared_prefs文件夹下的xml文件,并取得里面的键值对,从而获取到当前的下载进度,并更新到主界面上。
2.3 小结
因為方法简单,所以就不贴出AndroidManifest.xml文件了。对於这种方式实现Activity与Service的交互,能够说很方便,就像使用管道,一个往裡写,一个往外读。但这种方式也有缺陷,写入数据较为复杂以及数据量较大时,就有可能致使写入与读数据出不一致的错误。同时由于通过了一个中转站,这种操做将更耗时。
3. Messenger交互(信使交互)
Messenger翻译过来指的是信使,它引用了一个Handler对象,别人可以向它发送消息(使用mMessenger.send(Message msg)方法)。该类容许跨进程间基于Message通讯,在服务端使用Handler建立一个 Messenger,客户端只要得到这个服务端的Messenger对象就能够与服务端通讯了。也就是说咱们能够把Messenger当作Client端与Server端的传话筒,这样就能够沟通交流了。实现效果如图3.1:
图3.1
3.1 实现原理
在Server端与Client端之间经过一个Messenger对象来传递消息,该对象相似于信息中转站,全部信息经过该对象携带。
3.2 Messenger的通常用法
(1). 在Server端建立信使对象。
mMessenger = new Messenger(mHandler)
(2). Client端使用bindService()绑定Server端。
(3). Server端的onBind()方法返回一个binder对象。
return mMessenger.getBinder();
(4). Client端使用返回的binder对象获得Server端信使。
这里虽然是new了一个Messenger,但咱们查看它的实现
发现它的mTarget是经过AIDL获得的,实际上就是远程建立的那个。
(5). Client端可使用这个Server端的信使对象向Server端发送消息。
rMessenger.send(msg);
这样Server端的Handler对象就能收到消息了,而后能够在其handlerMessage(Message msg)方法中进行处理。通过这5个步骤以后只有Client端向Server端发送消息,这样的消息传递是单向的,那么如何实现消息的双向传递呢?
首先须要在第5步作修改,在send(msg)前经过msm.replyTo = mMessenger将Client端本身的信使设置到消息中,这样Server端接收到消息时同时也获得了Client端的信使对象,而后Server端也能够经过使用获得的Client端的信使对象来项Client端发送消息 cMessenger = msg.replyTo2 cMessenger.send(message);
这样即完成了从Server端向Client端发送消息的功能,这样Client端能够在本身的Handler对象的handlerMessage()方法中接收服务端发送来的message进行处理。
3.3 实现步骤
3.3.1 建立并初始化Server端的信使对象。
3.3.2 在Client端使用bindService()方法绑定Server端。
3.3.3 在Server端的onBind()方法中返回一个binder对象。
这裡的mMessenger就是Server端的信使对象。
3.3.4 Client端使用ServiceConnected()方法来获取Server端的信使对象。
获取Server端的信使对象的同时,也初始化Client端的本身的信使对象,而且经过sendMessage()方法发送消息给Server端,表示能够开始下载了。
3.3.5 Client端使用获取到的rMessenger来发送消息给Server端,同时将Client端的信使封装到消息中,一并发送给Server端。
这里的MessengerService.TEST為Server端里的一个静态常量。Msg.replyTo=mMessenger;表示发送给Server端的信息里携带Client端的信使。
3.3.6 Server端获取Client端发送的消息并获得Client端的信使对象。
在接收到Client端的信息以后,Server端开啟timer模拟下载,并接收Client端的信使对象。
3.3.7 Server端向Client端发送数据。
直接使用接收到的Client端的信使对象来发送当前下载进度给Client端。
3.3.8 Client端接收来自Server端的数据。
Client端的接收和Server端的接收狠相似。接收到Server端传过来的数据以后进行介面更新,以及下载进度更新。
如下是AndroidManifest.xml文件:
这里在Service的註册中加入了过滤动做,只有相匹配的action才能启动相应的Service。
3.4 小结
经过Messenger来实现Activity和Service的交互,稍微深刻一点咱们就能够知道,其实Messenger也是经过AIDL来实现的。对於前两种实现方式,Messenger方式整体上来说也是比较容易理解的,这就和平时使用Handler和Thread通讯一个道理。
4. 自定义接口交互
何谓自定义接口呢,其实就是咱们本身经过接口的实现来达到Activity与Service交互的目的,咱们经过在Activity和Service之间架设一座桥樑,从而达到数据交互的目的,而这种实现方式和AIDL很是相似(后文会说到)。实现效果如图4.1:
图4.1
4.1 实现原理
自定义一个接口,该接口中有一个获取当前下载进度的空方法。Server端用一个类继承自Binder并实现该接口,覆写了其中获取当前下载进度的方法。Client端经过ServiceConnection获取到该类的对象,从而可以使用该获取当前下载进度的方法,最终实现实时交互。
4.2 实现步骤
4.2.1 新建一个Interface,并在其中建立一个用于获取当前下载进度的的空方法getCurrentLoad()。
4.2.2 新建Server端DownService实现ICountService并在其中经过一个内部类ServiceBinder继承自Binder并实现ICoutService接口。
在Server端中,实现获取下载进度的空方法getCurrentLoad();这是Eclipse自动生成的,重点不在这裡。咱们须要在ServiceBinder类中覆写getCurrentLoad()方法,这裡咱们返回当前的下载进度i。
4.2.3 Client端使用bindService()绑定Server端。
在Client端绑定Server端的同时,延迟1s开始获取下载进度。其中的intent = new Intent(“com.seven.test”)2com.seven.test该字符串要与在AndroidManifest.xml中申明的一致。
4.2.4 Server端返回binder对象。
这里的serviceBinder由于继承了Binder所以也是Binder对象。
4.2.5 Client端经过ServiceConnection来获取Server端的binder对象。
获取的过程是在bindService()过程当中完成的,这里的iCountService是接口ICountService的对象,在这里获得实例化。
4.2.6 在绑定完成以后,Server端会开启下载,在实际状况中Server端会开启独立线程用于下载,这里用i++来代替。
bindService()方法执行以后会调用DownLoadService中的onCreate()方法,在其onCreate()方法中开启timer使得i++。
4.2.7 Server端已经开启了下载,那么Client端须要及时获取下载进度并在主界面上更新。
Client端的Timer在bindService()完成以后1秒再开始获取下载进度,获取方法是直接经过int curLoad = iCountService.getCurrentLoad();这里的getCurrentLoad()方法是DownLoadService内部类ServiceBinder中的方法。Client端将获取到的下载进度更新到介面上并更新进度条。
4.3 小结
经过上面的例子能够知道,这种方法简单实用,扩展性强,但其也有一些缺点,好比须要延迟一些再开始获取Server端的数据,从而没法彻底实现从零开始同步更新。综其所述,经过自定义接口实现Activity与Service交互的方法仍是比较实用的。适用於同进程中通讯,不能进行跨进程通讯。
5. AIDL交互
什么是AIDL?
AIDL是Android Interface Definition Language的首字母缩写, 也就是Android接口定义语言。说起AIDL就不得不说下Android的服务,Android 支持两种服务类型的服务即本地服务和远程服务。
本地服务没法供在设备上运行的其余应用程序访问,也就是说只能该应用程序内部调用,好比某些应用程序中的下载类服务,这些服务只能由内部调用。而对于远程服务,除了能够由本应用程序调用,还能够容许其余应用程序访问。远程服务通常经过AIDL来实现,能够进行进程间通讯,这种服务也就是远程服务。
本地服务与远程服务仍是有一些重要的区别。具体来说,若是服务彻底只供同一进程中的组件使用(运行后台任务),客户端一边经过调用 Context.startService()来启动该服务。这种类型的服务为本地服务,它的通常用途是后台执行长耗时操做。而远程服务通常经过bindService()方法启动,主要为不一样进程间通讯。咱们也将远程服务称为AIDL支持服务,由于客户端使用 AIDL 与服务通讯。Android中对于远程服务有多种叫法:远程服务、AIDL服务、外部服务和RPC服务。
5.1 AIDL实现流程图
图5.1
这属于代理/存根结构,经过这张AIDL的流程图,很容易发现Android实现IPC实际上是在原来的C/S框架上加入了代理/存根结构。
好比,你到自动取款机上去取款。那么你就是客户(Client),取款机就是你的代理(Proxy);你不会在意钱具体放在那里,你只想将你的钱从取款机中取出来。你同银行之间的操做彻底是取款机代理实现。你的取款请求经过取款机传到另外一边,即银行的服务器(Server)。它也没有必要知道你在哪儿取钱,它所关心的是你的身份和你取款多少。当它确认你的权限,就进行相应的操做,返回操做结果给取款机,取款机根据服务器返回结果,从保险柜里取出相应数量的钱给你。你取出卡后,操做完成。取款机不是直接同服务器链接的,他们之间还有一个“存根(Stub)”,取款机与存根通讯,服务器与存根通讯,从某种意义上说存根就是服务器的代理。实现效果如图5.2:
图5.2
5.3 实现原理
AIDL属于Android的IPC机制,经常使用于跨进程通讯,主要实现原理基于底层Binder机制。
5.4 实现步骤
5.4.1 创建工程。按照图5.3和图5.4创建AIDLServer端以及AIDLClient端。在AIDLServer端中只有一个服务程序,没有主界面,其主要功能就是负责下载。AIDLClient端从AIDLServer端获取当前下载进度(注:AIDLServer端和AIDLClient端是不一样的两个APK,在模拟本例的时候,须要先在模拟器上安装AIDLServer编译出来的APK,安装方法能够直接在模拟器上运行一次,能够经过adb install your.apk 来安装)。
图5.3
AIDLServer端中新建了一个ICountService.aidl的文件,该文件内容以下:
aidl文件的书写规范以下:
(1). Android支持String和CharSequence(以及Java的基本数据类型);
(2). 若是须要在aidl中使用其它aidl接口类型,须要import,即便是在相同包结构下;
(3). Android容许传递实现Parcelable接口的类,须要import;
(4). Android支持集合接口类型List和Map,可是有一些限制,元素必须是基本型或者前面三种状况,不须要import集合接口类,可是须要对元素涉及到的类型import;
(5). 非基本数据类型,也不是String和CharSequence类型的,须要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是二者都可设置。
图5.4
AIDLClient端须要将AIDLServer端的ICountService.aidl文件复製过去,这裡为了方便,新建了一个和Server端同名的包,并将ICountService.aidl放与其中。
5.4.2 咱们在Server端创建好ICoutService.aidl文件以后,Eclipse会在/gen/com.seven.aidlserver/目录下自动生成ICountService.java文件。该文件由Eclipse自动生成,请勿随便修改,后文咱们需引用到的内容以下:
5.4.3 在Server端新建一个内部类继承自ICountService.Stub并覆写其中的getCount()方法,以及实例化该类的一个对象serviceBinder。
这里与前面提到的“经过接口实现交互”很是相似。
5.4.4 在Server端的onBind()方法中,返回前面的serviceBinder对象。
5.4.5 在Server端的onCreate()方法中,开启timer,模拟下载。在Client端经过bindService()绑定Server端的时候,会首先执行Server端的onCreate()方法。
5.4.6 Client端经过bindService()绑定Server端。
这里的intent = new Intent(“com.seven.aidlserver”);这里跟Server端注册Service时过滤的要一致,也就是说只有发出相同的action才会启动该Service。同时开启了一个timer用于获取下载进度。
5.4.7 Client端经过ServiceConnection来获取Server端的binder对象。
这里的iCountService对象实际上就是ICountService的对象在此实例化。
5.4.8 获取当前下载进度并更新到界面上。
经过更新介面上的进度条,能够狠容易的后去当前下载进度。因為AIDLServer端只是一个继承自Service的服务,所以就不贴出其AndroidManifest.xml文件了。
5.5 小结
AIDL在Android中是进程间通讯经常使用的方式,可能使用较為复杂,但效率高,扩展性好。同时不少系统服务就是以这种方式完成与应用程序通讯的。
本文经过五个例子,分别介绍了五种与Service交互的方法,这些方法有的简单,有的可能要复杂一些。在这里只是作为对Servie的一些总结。后文附上源码下载连接,不须要积分的哦。:D
全部源码均在Ubuntu 10.04 Eclipse-Indigo下实验经过 模拟器采用的是2.3的镜像