http://blog.csdn.net/yihongyuelan/article/details/7216218
什么是Service?
Service翻译过来就是服务的意思,当我们的应用程序需要做一个长耗时的操作,还有可能需要和别的程序交互的时候,我们就需要使用Service。
1. Service不是一个单独的进程,除非单独声明,否则它不会运行在单独的进程中,而是和启动它的程序运行在同一个进程中。
2. Service不是线程,这意味著它将在主线程里运行。
也就是说Service既不是进程也不是线程,它们之间的关系如下:
可能有的朋友会问了,既然是长耗时的操作,那么Thread也可以完成啊。没错,在程序里面很多耗时工作我们也可以通过Thread来完成,那么还需要Service干嘛呢。接下来就为大家解释以下Service和Thread的区别。
首先要说明的是,进程是系统最小资源分配单位,而线程是则是最小的执行单位,线程需要的资源通过它所在的进程获取。
Service与Thread的区别:
Thread:Thread 是程序执行的最小单元,可以用 Thread 来执行一些异步的操作。
Service:Service 是android的一种机制,当它运行的时候如果是Local Service,那么对应的 Service 是运行在主进程的 main 线程上的。如果是Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上。
Thread 的运行是独立的,也就是说当一个 Activity 被 finish 之后,如果没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity 被 finish 之后,不再持有该 Thread 的引用,也就是不能再控制该Thread。另一方面,没有办法在不同的 Activity 中对同一 Thread 进行控制。
例如:如果 一个Thread 需要每隔一段时间连接服务器校验数据,该Thread需要在后台一直运行。这时候如果创建该Thread的Activity被结束了而该Thread没有 停止,那么将没有办法再控制该Thread,除非kill掉该程序的进程。这时候如果创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread,这样便解决了该问题(因为任何 Activity 都可以控制同一个Service,而系统也只会创建一个对应 Service 的实例)。
因此可以把 Service 想象成一种消息服务,可以在任何有 Context 的地方调用 Context.startService、Context.stopService、Context.bindService、 Context.unbindService来控制它,也可以在 Service 里注册 BroadcastReceiver,通过发送 broadcast 来达到控制的目的,这些都是 Thread 做不到的。
启动service有两种方法:
1. Context.startService()
调用者(Client)与服务端(Server)之间没有关联,即使调用者退出,服务仍可运行。
2. Context.bindService()
调用者(Client)与服务端(Server)绑定在一起,可以多个调用者(Client)绑定一个服务端(Server),当所有的调用者(Client)退出,服务端(Server)也就终止。
Service的生命周期:
1. 被启动的服务(startService())的生命周期。
如果一个Service被某个Activity 调用Context.startService() 方法启动,那么不管是否有Activity使用bindService()绑定或unbindService()解除绑定到该Service,该 Service都在后台运行。如果一个Service被多次执行startService(),它的onCreate()方法只会调用一次,也就是说该 Service只会创建一个实例,而它的onStartCommand()将会被调用多次(对应调用startService()的次数)。该 Service将会一直在后台运行,直到被调用stopService(),或自身的stopSelf方法。当然如果系统资源不足,系统也可能结束服务。
2. 被绑定的服务(bindService())的生命周期。
如果一个Service被调用 Context.bindService ()方法绑定启动,不管调用bindService()调用几次,onCreate()方法都只会调用一次,而onStartCommand()方法始终 不会被调用,这时会调用onBind()方法。当连接建立之后,Service将会一直运行,除非调用Context.unbindService() 断开连接或者之前调用bindService() 的 Context 不存在了(如该Activity被finish),系统将会自动停止Service,对应onDestroy()将被调用。
3. 被启动又被绑定的服务的生命周期。
如果一个Service又被启动又被绑定,则该Service将会一直在后台运行。调用unbindService()将不会停止Service,而必须调用stopService()或Service的stopSelf()方法来停止服务。
4. 当服务被停止时清除服务。
当一个Service被终止时,Service的onDestroy()方法将会被调用,在这里应当做一些清除工作,如停止在Service中创建并运行的线程等。
Process的生命周期:
当Service运行在低内存的环境时,系统会kill掉一些进程。因此进程的优先级将会狠重要:
1. 如果Service当前正在执行onCreate()、onStartCommand()、onDestroy()方法,那麼此时主进程将会成为前台进程来保证代码可以执行完成而避免被kill。
2. 如果Service已经启动,那么主进程将会比其他可见的进程的重要性低,但比其他看不见的进程高。这裡说的可见指的是对用户来讲,可见的进程优先级永 远是最高的,用户至上嘛。但只有少部分进程始终是用户可见的,因此除非系统处於极度低内存的时候,不然 service是不会被kill的。
3. 如果有Client端连到Service,那么Service永远比Client端重要。
4. Service可以使用startForeground()将Service放到前台状态。这样在低内存时被kill的几率更低,但如果在极低内存的情况下,该Service理论上还是会被kill掉。但这个情况基本不用考虑。
1. 广播交互
提到Activity与Service的交互,可能狠多人首先想到的就是BroadCast——广播。在Android中,广播是系统提供的一种很好的交 互方式。比如:在电池电量过低,开机完成等情况下,系统都会发出相应的系统广播,我们的应用程序只需要注册相应的广播接收器,就可以接收到这些系统的广 播。同时,我们也可以定义自己的广播,这样在不同的Activity、Service以及应用程序之间,就可以通过广播来实现交互。我们通过模拟应用程序 后台下载的情况来分析Service与Activity的交互方式。
当我们点击StartService按钮之后,界面上的进度条将会每隔一秒加1。因为是模拟下载,因此下载动作我们在Service中通过一个Timer定时器来实现,在Timer中对一个整型数据i进行自加(i++),然后Client端获取Server端的i值并显示在界面上,从而达到模拟的目的。
1.1. 实现原理
Server端将目前的下载进度,通过广播的方式发送出来,Client端注册此广播的监听器,当获取到该广播后,将广播中当前的下载进度解析出来并更新到界面上。
1.2. 实现步骤
1.2.1 在Client端中通过startService()啟动Service。
[java] view plaincopy
1. if(v == startBtn){
2. Log.i(TAG, "start button clicked...pid: "+Process.myPid());
3. mIntent.setClass(BroadCastService.this, DownLoadService.class);
4. startService(mIntent);
5. }
这里的mIntent = new Intent();Process.myPid()方法可以获取当前进程的ID号。
1.2.2 DownLoadService接到启动的命令之后,执行onCreate()方法,并在其中开启timer计数模拟下载。
[java] view plaincopy
1. @Override
2. public void onCreate() {
3. super.onCreate();
4. Log.i(TAG, "DownLoadService.onCreate()...pid: "+Process.myPid());
5. intent = new Intent("com.seven.broadcast");
6. mTimer = new Timer();
7. mTimer.schedule(new MyTimerTask(), 0 , TIME * 1000);
8. }
这 里的intent是Server端向Client端传送数据用的,使用的action是”com.seven.broadcast”,Client端只有 註册了相应action才能够接收到Server端的广播,并解析其中的内容。Process.myPid()是获取当前进程的ID。
1.2.3 在Server端的timer计数其中发送广播,告知Client端目前下载进度。
[java] view plaincopy
1. class MyTimerTask extends TimerTask{
2. @Override
3. public void run() {
4. if(i==100){
5. i=0;
6. }
7. intent.putExtra("CurrentLoading", i);
8. sendBroadcast(intent);
9. i++;
10. Log.e(TAG, "i= "+i);
11. }
12. }
通过intent.putExtra(key,value);设置intent的值,然后通过sendBroadcast(intent)2方法,将广播发送出去。
1.2.4 在Client端通过匿名内部类的方式实例化BroadcastReceiver并覆写其中的onReceive()方法。
[java] view plaincopy
1. BroadcastReceiver receiver = new BroadcastReceiver() {
2. @Override
3. public void onReceive(Context context, Intent intent) {
4. if(MYACTION.equals(intent.getAction())){
5. Log.i(TAG, "get the broadcast from DownLoadService...");
6. curLoad = intent.getIntExtra("CurrentLoading", ERROR);
7. mHandler.sendMessage(mHandler.obtainMessage());
8. }
9. }
10. };
在onReceive()方法中,判断是否为Server端发送的广播,如果是则对广播中携带的intent数据进行解包处理。这裡也可以单独写一个类继 承自BroadcastReceiver,在其中覆写onReceive()方法,在Client端中实例化其对象,同样可以达到相应的效果,这样做可以 为后面实现静态注册广播。
1.2.5 更新主介面下载进度。
[java] view plaincopy
1. Handler mHandler = new Handler(){
2. @Override
3. public void handleMessage(Message msg) {
4. super.handleMessage(msg);
5. Log.i(TAG, "current loading: "+curLoad);
6. if(curLoad<0||curLoad>100){
7. Log.e(TAG, "ERROR: "+curLoad);
8. return;
9. }
10. mProgressBar.setProgress(curLoad);
11. mTextView.setText(curLoad+"%");
12. }
13. };
这里对获取到的进度进行了一次判断,如果获取到的值没有异常,那么将会显示到界面,并更新进度条的进度,如果异常则返回。
1.2.6 一定要对Broadcast进行注册和取消注册。只有注册之后相应的broadcast之后才能接收到广播注册方法有两种。
动态注册/取消注册:
[java] view plaincopy
1. @Override
2. protected void onResume() {
3. super.onResume();
4. Log.i(TAG, "register the broadcast receiver...");
5. IntentFilter filter = new IntentFilter();
6. filter.addAction(MYACTION);
7. registerReceiver(receiver, filter);
8. }
9. @Override
10. protected void onDestroy() {
11. super.onDestroy();
12. Log.i(TAG, "unregister the broadcast receiver...");
13. unregisterReceiver(receiver);
14. }
动态註册可以随时註册随时取消。
静态註册:
[html] view plaincopy
1. <receiver android:name="MyBroadcastReceiver">
2. <intent-filter>
3. <action android:name="com.seven.broadcast" />
4. </intent-filter>
5. </receiver>
注:这里的MyBroadcastReceiver是一个继承自BroadcastReceiver的类。静态注册只要注册了一次那么只要该程序没有被卸载那么该广播将一直有效。
最后贴出整个AndroidManifest.xml文件
[html] view plaincopy
1. <application android:icon="@drawable/icon" android:label="@string/app_name">
2. <activity android:name=".BroadCastService"
3. android:label="@string/app_name">
4. <intent-filter>
5. <action android:name="android.intent.action.MAIN" />
6. <category android:name="android.intent.category.LAUNCHER" />
7. </intent-filter>
8. </activity>
9. <service android:name="DownLoadService" android:process=":remote"/>
10. </application>
这里的android:process =”:remote”可以使该Service运行在单独进程中,从而可以模拟跨进程通信。
1.3 小结
通过广播的方式实现Activity与Service的交互操作简单且容易实现,可以胜任简单级的应用。但缺点也十分明显,发送广播受到系统制约。系统会 优先发送系统级广播,在某些特定的情况下,我们自定义的广播可能会延迟。同时在广播接收器中不能处理长耗时操作,否则系统会出现ANR即应用程序无响应。
2. 共享文件交互
这里提到的共享文件指的是Activity和Service使用同一个文件来达到传递数据的目的。我们使用SharedPreferences来实现共 享,当然也可以使用其它IO方法实现,通过这种方式实现交互时需要注意,对于文件的读写的时候,同一时间只能一方读一方写,不能两方同时写。
2.1 实现原理
Server端将当前下载进度写入共享文件中,Client端通过读取共享文件中的下载进度,并更新到主界面上。
2.2 实现步骤
2.2.1 在Client端通过startService()啟动Service。
[java] view plaincopy
1. if(startSerBtn==v){
2. Log.i(TAG, "Start Button Clicked.");
3. if(intent!=null){
4. startService(intent);
5. timer.schedule(new MyTimerTask(), 0, TIME * 1000);
6. }
7. }
这里的intent = new Intent()2只是为了启动Server端。
2.2.2 Server端收到启动intent之后执行onCreate()方法,并开启timer,模拟下载,以及初始化SharedPreferences对象preferences。
[java] view plaincopy
1. @Override
2. public void onCreate() {
3. super.onCreate();
4. Log.i(TAG, "DownLoadService.onCreate()...");
5. preferences = getSharedPreferences("CurrentLoading_SharedPs", 0);
6. timer = new Timer();
7. timer.schedule(new MyTimerTask(), 0, TIME*1000);
8. }
通过preferences=getSharedPreferences(String,MODE)2可以在/data/data/com.seven.servicetestdemo/shared_prefs文件夹下建立相应的xml文件。
2.2.3 开始计数并将下载进度写入shared_prefs文件夹下的xml文件中,内容以键值对的方式保存。
[java] view plaincopy
1. class MyTimerTask extends TimerTask{
2. @Override
3. public void run() {
4. setCurrentLoading();
5. if(100==i){
6. i=0;
7. }
8. i++;
9. }
10. }
11. private void setCurrentLoading() {
12. preferences.edit().putInt("CurrentLoading", i).commit();
13. }
对於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文件,并取得里面的键值对,从而获取到当前的下载进度,并更新到主界面上。
[java] view plaincopy
1. Handler mHandler = new Handler(){
2. @Override
3. public void handleMessage(Message msg) {
4. super.handleMessage(msg);
5. int couLoad = preferences.getInt("CurrentLoading", 0);
6. mProgressBar.setProgress(couLoad);
7. currentTv.setText(couLoad+"%");
8. }
9. };
2.3 小结
因為方法简单,因此就不贴出AndroidManifest.xml文件了。对於这种方式实现Activity与Service的交互,可以说很方便,就 像使用管道,一个往裡写,一个往外读。但这种方式也有缺陷,写入数据较为复杂以及数据量较大时,就有可能导致写入与读数据出不一致的错误。同时因为经过了 一个中转站,这种操作将更耗时。
3. Messenger交互(信使交互)
Messenger翻译过来指的是信使,它引用了一个Handler对象,别人能够向它发送消息(使用mMessenger.send(Message msg)方法)。该类允许跨进程间基于Message通信,在服务端使用Handler创建一个 Messenger,客户端只要获得这个服务端的Messenger对象就可以与服务端通信了。也就是说我们可以把Messenger当做Client端 与Server端的传话筒,这样就可以沟通交流了。
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端信使。
[java] view plaincopy
1. public void onServiceConnected(ComponentName name, IBinder service) {
2. rMessenger = new Messenger(service);
3. ......
4. }
这里虽然是new了一个Messenger,但我们查看它的实现
[java] view plaincopy
1. public Messenger(IBinder target) { mTarget = IMessenger.Stub.asInterface(target); }
发现它的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端的信使对象。
[java] view plaincopy
1. private Handler mHandler = new Handler(){
2. @Override
3. public void handleMessage(Message msg) {
4. super.handleMessage(msg);
5. switch (msg.what) {
6. case TEST:
7. Log.e(TAG, "Get Message from MainActivity.");
8. cMessenger = msg.replyTo;
9. mTimer.schedule(new MyTimerTask(), 1000,TIME * 1000);
10. break;
11. default:
12. break;
13. }
14. }
15. };
16. //It's the messenger of server
17. private Messenger mMessenger = new Messenger(mHandler);
3.3.2 在Client端使用bindService()方法绑定Server端。
[java] view plaincopy
1. private void doBindService(){
2. Log.i(TAG, "doBindService()...");
3. mIsBind = bindService(intent, serConn, BIND_AUTO_CREATE);//if bind success return true
4. Log.e(TAG, "Is bind: "+mIsBind);
5. }
3.3.3 在Server端的onBind()方法中返回一个binder对象。
[java] view plaincopy
1. @Override
2. public IBinder onBind(Intent intent) {
3. Log.i(TAG, "MessengerService.onBind()...");
4. return mMessenger.getBinder();
5. }
这裡的mMessenger就是Server端的信使对象。
3.3.4 Client端使用ServiceConnected()方法来获取Server端的信使对象。
[java] view plaincopy
1. private ServiceConnection serConn = new ServiceConnection() {
2. @Override
3. public void onServiceDisconnected(ComponentName name) {
4. Log.i(TAG, "onServiceDisconnected()...");
5. rMessenger = null;
6. }
7. @Override
8. public void onServiceConnected(ComponentName name, IBinder service) {
9. Log.i(TAG, "onServiceConnected()...");
10. rMessenger = new Messenger(service);//get the object of remote service
11. mMessenger = new Messenger(mHandler);//initial the object of local service
12. sendMessage();
13. }
14. };
获取Server端的信使对象的同时,也初始化Client端的自己的信使对象,并且通过sendMessage()方法发送消息给Server端,表示可以开始下载了。
3.3.5 Client端使用获取到的rMessenger来发送消息给Server端,同时将Client端的信使封装到消息中,一并发送给Server端。
[java] view plaincopy
1. private void sendMessage() {
2. Message msg = Message.obtain(null, MessengerService.TEST);//MessengerService.TEST=0
3. msg.replyTo = mMessenger;
4. try {
5. rMessenger.send(msg);
6. } catch (RemoteException e) {
7. e.printStackTrace();
8. }
9. }
这里的MessengerService.TEST為Server端里的一个静态常量。Msg.replyTo=mMessenger;表示发送给Server端的信息里携带Client端的信使。
3.3.6 Server端获取Client端发送的消息并得到Client端的信使对象。
[java] view plaincopy
1. private Handler mHandler = new Handler(){
2. @Override
3. public void handleMessage(Message msg) {
4. super.handleMessage(msg);
5. switch (msg.what) {
6. case TEST:
7. Log.e(TAG, "Get Message from MainActivity.");
8. cMessenger = msg.replyTo;//get the messenger of client
9. mTimer.schedule(new MyTimerTask(), 1000,TIME * 1000);
10. break;
11. default:
12. break;
13. }
14. }
15. };
在接收到Client端的信息之后,Server端开啟timer模拟下载,并接收Client端的信使对象。
3.3.7 Server端向Client端发送数据。
[java] view plaincopy
1. class MyTimerTask extends TimerTask {
2. @Override
3. public void run() {
4. if (i == 100) {
5. i = 0;
6. }
7. try {
8. //send the message to the client
9. Message message = Message.obtain(null, MessengerService.TEST,i, 0);
10. cMessenger.send(message);
11. } catch (RemoteException e) {
12. e.printStackTrace();
13. }
14. i++;
15. }
16. }
直接使用接收到的Client端的信使对象来发送当前下载进度给Client端。
3.3.8 Client端接收来自Server端的数据。
[java] view plaincopy
1. private Handler mHandler = new Handler(){
2. @Override
3. public void handleMessage(Message msg) {
4. super.handleMessage(msg);
5. switch (msg.what) {
6. case MessengerService.TEST:
7. Log.e(TAG, "Get Message From MessengerService. i= "+msg.arg1);
8. int curLoad = msg.arg1;
9. mTextView.setText(curLoad+"%");
10. mProgressBar.setProgress(curLoad);
11. break;
12. default:
13. break;
14. }
15. }
16. };
Client端的接收和Server端的接收狠类似。接收到Server端传过来的数据之后进行介面更新,以及下载进度更新。
以下是AndroidManifest.xml文件:
[html] view plaincopy
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3. package="com.seven.messengerservicedemo"
4. android:versionCode="1"
5. android:versionName="1.0">
6. <uses-sdk android:minSdkVersion="10" />
7. <application android:icon="@drawable/icon" android:label="@string/app_name">
8. <activity android:name=".MainActivity"
9. android:label="@string/app_name">
10. <intent-filter>
11. <action android:name="android.intent.action.MAIN" />
12. <category android:name="android.intent.category.LAUNCHER" />
13. </intent-filter>
14. </activity>
15. <service android:name="MessengerService">
16. <intent-filter>
17. <action ndroid:name="com.seven.messagerservice.MessengerService" />
18. </intent-filter>
19. </service>
20. </application>
21. </manifest>
这里在Service的註册中加入了过滤动作,只有相匹配的action才能启动相应的Service。
3.4 小结
通过Messenger来实现Activity和Service的交互,稍微深入一点我们就可以知道,其实Messenger也是通过AIDL来实现的。 对於前两种实现方式,Messenger方式总体上来讲也是比较容易理解的,这就和平时使用Handler和Thread通信一个道理。
4. 自定义接口交互
何谓自定义接口呢,其实就是我们自己通过接口的实现来达到Activity与Service交互的目的,我们通过在Activity和Service之间架设一座桥樑,从而达到数据交互的目的,而这种实现方式和AIDL非常类似(后文会说到)。
4.1 实现原理
自定义一个接口,该接口中有一个获取当前下载进度的空方法。Server端用一个类继承自Binder并实现该接口,覆写了其中获取当前下载进度的方法。Client端通过ServiceConnection获取到该类的对象,从而能够使用该获取当前下载进度的方法,最终实现实时交互。
4.2 实现步骤
4.2.1 新建一个Interface,并在其中创建一个用于获取当前下载进度的的空方法getCurrentLoad()。
[java] view plaincopy
1. package com.seven.servicetestdemo;
2.
3. public interface ICountService {
4. public int getCurrentLoad();
5. }
4.2.2 新建Server端DownService实现ICountService并在其中通过一个内部类ServiceBinder继承自Binder并实现ICoutService接口。
[java] view plaincopy
1. public class DownLoadService extends Service implements ICountService{
2. private ServiceBinder serviceBinder = new ServiceBinder();
3. public class ServiceBinder extends Binder implements ICountService{
4. @Override
5. public int getCurrentLoad() {
6. Log.i(TAG, "ServiceBinder getCurrentLoad()... i=:"+i);
7. return i;
8. }
9. }
10. @Override
11. public int getCurrentLoad() {
12. return 0;
13. }
14. }
在Server端中,实现获取下载进度的空方法getCurrentLoad();这是Eclipse自动生成的,重点不在这裡。我们需要在ServiceBinder类中覆写getCurrentLoad()方法,这裡我们返回当前的下载进度i。
4.2.3 Client端使用bindService()绑定Server端。
[java] view plaincopy
1. if (startSerBtn == v) {
2. Log.i(TAG, "Start Button Clicked.");
3. bindService(intent, serConn, BIND_AUTO_CREATE);
4. timer.schedule(new MyTimerTask(), 1000, TIME * 1000);//这里一定要延迟一下再开始获取数据,不然会报空指针异常
5. }
在Client端绑定Server端的同时,延迟1s开始获取下载进度。其中的intent = new Intent(“com.seven.test”)2com.seven.test该字符串要与在AndroidManifest.xml中申明的一致。
4.2.4 Server端返回binder对象。
[java] view plaincopy
1. @Override
2. public IBinder onBind(Intent intent) {
3. Log.i(TAG, "DownLoadService.onBind()...");
4. return serviceBinder;
5. }
这里的serviceBinder因为继承了Binder因此也是Binder对象。
4.2.5 Client端通过ServiceConnection来获取Server端的binder对象。
[java] view plaincopy
1. private ServiceConnection serConn = new ServiceConnection() {
2. @Override
3. public void onServiceDisconnected(ComponentName name) {
4. iCountService = null;
5. }
6. @Override
7. public void onServiceConnected(ComponentName name, IBinder service) {
8. Log.i(TAG, "onServiceConnected()...");
9. iCountService = (ICountService)service;
10. }
11. };
获取的过程是在bindService()过程中完成的,这里的iCountService是接口ICountService的对象,在这里得到实例化。
4.2.6 在绑定完成之后,Server端会开启下载,在实际情况中Server端会开启独立线程用于下载,这里用i++来代替。
[java] view plaincopy
1. @Override
2. public void onCreate() {
3. super.onCreate();
4. Log.i(TAG, "DownLoadService.onCreate()...");
5. timer = new Timer();
6. timer.schedule(new MyTimerTask(), 0, TIME*1000);
7. }
8. class MyTimerTask extends TimerTask{
9. @Override
10. public void run() {
11. if(100==i){
12. i=0;
13. }
14. i++;
15. }
16. }
bindService()方法执行之后会调用DownLoadService中的onCreate()方法,在其onCreate()方法中开启timer使得i++。
4.2.7 Server端已经开启了下载,那么Client端需要及时获取下载进度并在主界面上更新。
[java] view plaincopy
1. Handler mHandler = new Handler(){
2. @Override
3. public void handleMessage(Message msg) {
4. super.handleMessage(msg);
5. Log.i(TAG, "handleMessage...");
6. int curLoad = iCountService.getCurrentLoad();
7. mProgressBar.setProgress(curLoad);
8. currentTv.setText(curLoad+"%");
9. }
10. };
11. class MyTimerTask extends TimerTask{
12. @Override
13. public void run() {
14. mHandler.sendMessage(mHandler.obtainMessage());
15. }
16. }
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.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 来安装)。
AIDLServer端中新建了一个ICountService.aidl的文件,该文件内容如下:
[plain] view plaincopy
1. package com.seven.aidlserver;
2.
3. interface ICountService{
4. int getCount();
5. }
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自动生成,请勿随便修改,后文我们需引用到的内容如下:
[java] view plaincopy
1. public static com.seven.aidlserver.ICountService asInterface(android.os.IBinder obj) {
2. if ((obj == null)) {
3. return null;
4. }
5. android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR);
6. if (((iin != null) && (iin instanceof com.seven.aidlserver.ICountService))) {
7. return ((com.seven.aidlserver.ICountService) iin);
8. }
9. return new com.seven.aidlserver.ICountService.Stub.Proxy(obj);
10. }
5.4.3 在Server端新建一个内部类继承自ICountService.Stub并覆写其中的getCount()方法,以及实例化该类的一个对象serviceBinder。
[java] view plaincopy
1. private AIDLServerBinder serviceBinder = new AIDLServerBinder();
2. class AIDLServerBinder extends ICountService.Stub{
3. @Override
4. public int getCount() throws RemoteException {
5. return i;
6. }
7. }
这里与前面提到的“通过接口实现交互”非常类似。
5.4.4 在Server端的onBind()方法中,返回前面的serviceBinder对象。
[java] view plaincopy
1. @Override
2. public IBinder onBind(Intent intent) {
3. Log.i(TAG, "AIDLServer.onBind()...");
4. return serviceBinder;
5. }
5.4.5 在Server端的onCreate()方法中,开启timer,模拟下载。在Client端通过bindService()绑定Server端的时候,会首先执行Server端的onCreate()方法。
[java] view plaincopy
1. @Override
2. public void onCreate() {
3. super.onCreate();
4. Log.i(TAG, "AIDLServer.onCreate()...");
5. mTimer = new Timer();
6. mTimer.schedule(new MyTimerTask(), 0,TIME * 1000);
7. }
8. class MyTimerTask extends TimerTask{
9. @Override
10. public void run() {
11. if(i==100){
12. i=0;
13. }
14. i++;
15. }
16. }
5.4.6 Client端通过bindService()绑定Server端。
[java] view plaincopy
1. if(startBtn==v){
2. Log.i(TAG, "start button click.");
3. mIsBind = bindService(intent, serConn, BIND_AUTO_CREATE);
4. mTimer.schedule(new MyTimerTask(), 1000 ,TIME * 1000);
5. }
这里的intent = new Intent(“com.seven.aidlserver”);这里跟Server端注册Service时过滤的要一致,也就是说只有发出相同的action才会启动该Service。同时开启了一个timer用于获取下载进度。
5.4.7 Client端通过ServiceConnection来获取Server端的binder对象。
[java] view plaincopy
1. private ServiceConnection serConn = new ServiceConnection() {
2. @Override
3. public void onServiceDisconnected(ComponentName name) {
4. iCountService = null;
5. }
6. @Override
7. public void onServiceConnected(ComponentName name, IBinder service) {
8. Log.i(TAG, "AIDLClient.onServiceConnected()...");
9. iCountService = ICountService.Stub.asInterface(service);
10. }
11. };
这里的iCountService对象实际上就是ICountService的对象在此实例化。
5.4.8 获取当前下载进度并更新到界面上。
[java] view plaincopy
1. Handler mHandler = new Handler(){
2. @Override
3. public void handleMessage(Message msg) {
4. super.handleMessage(msg);
5. try {
6. int count = iCountService.getCount();
7. mTextView.setText(count+"%");
8. mProgressBar.setProgress(count);
9. } catch (RemoteException e) {
10. e.printStackTrace();
11. }
12. }
13. };
AIDL在Android中是进程间通信常用的方式,可能使用较為复杂,但效率高,扩展性好。同时很多系统服务就是以这种方式完成与应用程序通信的。
1. 服务类型比较
表1.1
2. 五种交互方式比较
表2.1
通过表1.1和表2.1我们可以知道:
(1) 跨进程通信
只有Broadcast、Messenger、AIDL这三种方式支持跨进程通信,也就是Server可以运行在独立的进程中并与之通信。
(2) 易用性
通过对比之后发现Broadcast、SharedPreferences、Interface这三种方法使用起来比较方便,也较容易掌握,而Messenger和AIDL使用起来稍微繁琐一些,特别是AIDL。
(3) 执行效率
因為是模拟下载以及数据交换量较小,因此对於执行效率来说影响不大。但如果数据量较大,那么Messenger、Interface以及AIDL的执行效 率较高,而Broadcast和SharedPreferences的效率相对较低。系统会优先处理系统的Broadcast,如果此时正好发出我们的 Broadcast,那麼接收会有延迟。对於SharedPreferences来说,文件操作如果涉及道德数据吞吐量较大,那麼延迟会较高,稳定性也狠 难得到保证。
(4) 可扩展性
对于Broadcast和SharedPreferences因为执行效率的原因,如果再加上扩展(比如传递大量数据),这两种方法就会捉襟见肘。同时在 BroadcastReceiver中的onReceive方法中不能处理长耗时事件,否则会出现ANR错误。同样的,对于 SharedPreferences方法,如果有大量的数据需要写入文件的话,那么也会耗时,同样存在操作风险。因为Messenger、 Interface以及AIDL可以直接添加相应的处理方法来扩展,因此扩展性较高。
3. 注意事项
3.1 如果采用bindService()方法启动Server端的话,需要通过ServiceConnection()方法来获取Server端返回的binder对象。
3.2 在调用 bindService 绑定到Service的时候,应当保证在处理完成之后调用unbindService解除绑定(尽管 Activity 被 finish 的时候绑定会自动解除,并且Service会自动停止)。
3.3 使用startService启动服务之后,一定要使用stopService停止服务,不管是否使用bindService。
3.4 同时使用 startService 与 bindService 时要注意,Service 的终止,需要unbindService与stopService同时调用才能终止 Service。如果先调用 unbindService 此时服务不会自动终止,再调用stopService之后服务才会停止;如果先调用 stopService 此时服务也不会终止,而在调用 unbindService或者之前调用 bindService的Context不存在了(如Activity 被 finish 的时候)之后服务才会停止。
3.5 当设备屏幕在“横”“竖”切换时,此时因为Activity会重新创建,因此旋转之前的使用bindService 建立的连接便会断开(Context 丢失了),就相当於已经解除绑定了。