Service与Android系统实现(1)-- 应用程序里的Service (三)

双向Remote Service

在AIDL编程环境里实际上是支持反向调用的,原理跟我们实现一个Remote Service一样,就是通过把Proxy与Stub反过来,就得到了这样的回调式的aidl编程。唯一的区别是,当我们的Stub在Activity时实现时,我们实际上跟后台线程执行也没有区别,Callback并非是在主线程里执行的,于是不能进行重绘界面的工作。于是,我们必须像后台线程编程一样,使用Handler来处理界面显示处理。

定义AIDL

前面我们说过aidl是可以互相引用的,于是我们可以借用这样的机制,通过引用另一个新增的aidl文件来加强我们前面的单向的TaskService版本。我们先增加一个新的ITaskServiceCallback.aidl文件,与ITaskService保持同一目录:

[java] view plain copy
  1. package org.lianlab.services;  
  2. onewayinterface ITaskServiceCallback {  
  3.    void valueCounted(int value);  
  4. }  

在这一定义里,我们新增加了一个ITaskServiceCallback的接口类,基本上与我们前面的ITaskService.aidl一样,在这个接口类里,我们新加了一个valueCounted()方法,这一方法将会被Service所使用。

在这个文件里,唯一与ITaskService.aidl不同之处在于,我们使用了一个oneway的标识符,oneway可以使aidl调用具有异步调用的效果。在默认情况下,基于aidl的调用都会等待远端调用完成之后再继续往下执行,但有时我们可能希望在跨进程调用会有异步执行的能力,我们在发出调用请求后会立即返回继续执行,调用请求的结果会通过其他的callback返回,或是我们干脆并不在乎成功与否,此时就可以使用oneway。当然,从我们前面分析aidl底层进行的工作,我们可以知道,所谓的远程调用,只不过是通过Binder发送出去一个命令而已,所以在aidl里面如果使用了oneway限定符,也就是发送了命令就收工。

然后,我们修改一下我们的ITaskService.aidl,使我们可以使用上这个新加入的回调接口:

[java] view plain copy
  1. package org.lianlab.services;  
  2. import org.lianlab.services.ITaskServiceCallback;  
  3. interface ITaskService {  
  4.     intgetPid (ITaskServiceCallback callback);  
  5. }  

在Service端调用回调方法

我们会引用前面定义好的ITaskServiceCallback.aidl文件,通过包名+接口的方式进行引用。为了省事,我们直接在原来的getPid()方法里进行修改,将新定义的ITaskServiceCallback接口类作为参数传递给getPid()接口。于是,在Service端Stub对象里实现的getPid()方法,将可以使用这一回调对象:

[java] view plain copy
  1. package org.lianlab.services;  
  2. import android.app.Service;  
  3. import android.content.Intent;  
  4. import android.os.IBinder;  
  5. import android.os.Process;  
  6. import android.os.RemoteException;  
  7.    
  8. public class TaskService extends Service {  
  9.    
  10.    static private int mCount = 0;  
  11.      
  12.    @Override  
  13.    public IBinder onBind(Intent intent) {  
  14.        if (ITaskService.class.getName().equals(intent.getAction())) {  
  15.            return mTaskServiceBinder;  
  16.        }  
  17.        return null;  
  18.     }  
  19.    
  20.    private final ITaskService.Stub mTaskServiceBinder = newITaskService.Stub() {  
  21.        public int getPid(ITaskServiceCallback callback) { 1  
  22.            mCount ++ ;     2  
  23.            try {   3  
  24.                 callback.valueCounted(mCount);    4  
  25.            } catch (RemoteException e) {  
  26.                 e.printStackTrace();  
  27.            }  
  28.            return Process.myPid();  
  29.        }  
  30.     };  
  31. }  

加入了回调之后的代码结构并没有大变,只增加了3部分的内容,通过这三部分的内容,我们此时便可以记录我们的getPid()总共被调用了多少次。

  1. getPid()方法,是通过aidl定义来实现的,否则会报错。所以我们这里新的getPid()会按照aidl里的定义加入ITaskServiceCallback对象作为参数,与ITaskService对象相反,这一对象实际上是由客户端提供给Service端调用的。
  2. 为了记录下getPid()被调用了多少次,我们使用了一个mCount来进行计数,这一int为static类型,于是在Service生存周期里会始终有效。但这部分的改动与我们的回调改进并无直接关系。
  3. 在使用回调接口ITaskServiceCall之前,因为这是一个远程引用,我们会需要捕捉Remote Exception,由客户端抛出的异常将在这里被捕获处理。
  4. 调用ITaskServiceCall里定义的回调方法,将处理发送给客户端。此时,因为是oneway,这时很多就会从回调方法里返回,继续执行原来的getPid(),再将处理结果以返回值的形式发送回客户端。

加入了回调之后,对Service端的实现并没有增加多大的工作量,因为作为回调,实现是放在客户端上来完成的。

在Client端加入回调实现

因为我们的aidl接口已经发生了变动,于是需要将新加的ITaskServiceCall.aidl与改变过的ITaskService.aidl文件拷贝到应用程序工程里。我们再来看一下客户端实现代码需要作怎样的调整:

[java] view plain copy
  1. package org.lianlab.services;  
  2. import android.os.Bundle;  
  3. import android.os.Handler;  
  4. import android.os.IBinder;  
  5. import android.os.RemoteException;  
  6. import android.app.Activity;  
  7. import android.content.ComponentName;  
  8. import android.content.Context;  
  9. import android.content.Intent;  
  10. import android.content.ServiceConnection;  
  11. import android.view.View;  
  12. import android.view.View.OnClickListener;  
  13. import android.widget.TextView;  
  14.    
  15. import org.lianlab.services.R;  
  16.    
  17. public class MainActivity extends Activity {  
  18.    
  19.    ITaskService mTaskService = null;  
  20.    private TextView mCallbackText;  
  21.    private Handler mHandler = new Handler();  
  22.    
  23.    @Override  
  24.    public void onCreate(Bundle savedInstanceState) {  
  25.        super.onCreate(savedInstanceState);  
  26.    
  27.        try {  
  28.            bindService(newIntent(ITaskService.class.getName()), mTaskConnection,  
  29.                     Context.BIND_AUTO_CREATE);  
  30.        } catch (SecurityException e) {  
  31.            e.printStackTrace();  
  32.        }  
  33.    
  34.        setContentView(R.layout.activity_main);  
  35.    
  36.         ((TextView)findViewById(R.id.textView1)).setOnClickListener(new OnClickListener() {  
  37.            @Override  
  38.            public void onClick(View v) {  
  39.                 if (mTaskService != null) {  
  40.                     try {  
  41.                         int mPid = -1;  
  42.                         mPid =mTaskService.getPid(mCounter); 1  
  43.                         ((TextView)findViewById(R.id.textView1))  
  44.                                .setText("Service pid is " + mPid);  
  45.                     } catch (RemoteException e){  
  46.                         e.printStackTrace();  
  47.                     }  
  48.                 } else {  
  49.                     ((TextView)findViewById(R.id.textView1)).setText("No service connected");  
  50.                 }  
  51.            }  
  52.        });  
  53.    
  54.        mCallbackText = (TextView) findViewById(R.id.callbackView); 2  
  55.        mCallbackText.setText("Clicked 0 times");  
  56.     }  
  57.    
  58.    @Override  
  59.    public void onDestroy() {  
  60.        super.onDestroy();  
  61.        if (mTaskService != null) {  
  62.            unbindService(mTaskConnection);  
  63.        }  
  64.     }  
  65.    
  66.    private ServiceConnection mTaskConnection = new ServiceConnection() {  
  67.        public void onServiceConnected(ComponentName className, IBinder service){  
  68.            mTaskService = ITaskService.Stub.asInterface(service);  
  69.        }  
  70.    
  71.        public void onServiceDisconnected(ComponentName className) {  
  72.            mTaskService = null;  
  73.        }  
  74.     };  
  75.    
  76.    private ITaskServiceCallback.Stub mCounter = newITaskServiceCallback.Stub() {       3  
  77.        public void valueCounted(final int n) {     
  78.            mHandler.post(new Runnable() {     4  
  79.                 public void run() {  
  80.                    mCallbackText.setText("Clicked " + String.valueOf(n) + "  times");  
  81.                 }  
  82.            });  
  83.        }  
  84.     };  
  85.    
  86. }  

新加入的回调接口对象,也没给我们带来多大的麻烦,我们最重要是提供一个实例化的ITaskServiceCallback.Stub对象,然后通过getPid()将这一远程对象的引用发送给Service端。之后,Service处理后的回调请求,则会通过Binder会回到客户端,调用在Stub对象里实现的回调方法。所以我们实际增加的工作量,也仅是写一个ITaskServiceCallback.Stub接口类的实现而已:

  1. 我们需要使用通过getPid(),将ITaskServiceCallback.Stub传递给Service。因为这一对象是用于Service使用的,于是我们必须在使用前先创建,然后再以引用的方式进行传递,像我们代码例子里的mCounter对象。
  2. 这一部分的改动,只是为了让我们检查效果时更方便,我们通过一个新加的id为callbackView的textView,来显示getPid()被调多次的效果。
  3. 这是我们真正所需的改动,通过新建一个ITaskServiceCallback.Stub对象,于是当前进程便有了一个Stub实体,用于实现aidl里定义的valueCounted()接口方法,对Binder过来这一接口方法的调用请求作响应。
  4. valueCounted()是一个回调方法,从编程模型上我们可以类似的看成是由Service进程所执行的代码,于是我们需要通过Handler()来处理显示。当然,在实现上不可能如此神奇,我们可以把一个方法搬运到另一个进程空间里运行,但valueCounted()既然也不是在主线程环境里执行,而是通过线程池来响应Binder请求的,于是跟后台线程的编程方式一样,我们使用Handler来处理回显。Handler本身是一种很神奇的实现机制,它可以弱化编程环境里的有限状态机的硬性限制,也可以使代码在拓展上变得更灵活,我们会在后续内容里加以说明。

从上面的代码可以看出,我们通过aidl创建回调方法好像比我们直接通过aidl写一个Remote Service还要简单。事实上,并非回调创建方便,在原则上,我们本只需要一个Stub对象便可以得到我们想要的RPC能力了,只不过出于管理存活周期的需要,才融入到了Service管理框架里,因为这种Service使用上的需求才带来了一些编程上的开销


你可能感兴趣的:(Service与Android系统实现(1)-- 应用程序里的Service (三))