Service与Android系统设计(五)

Service与Android系统设计(5)

特别声明:本系列文章LiAnLab.org著作权所有,转载请注明出处。作者系LiAnLab.org资深Android技术顾问吴赫老师。本系列文章交流与讨论:@宋宝华Barry

1.1   双向Remote Service

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

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

package org.lianlab.services;

 

onewayinterface ITaskServiceCallback {

   void valueCounted(int value);

}

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

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

package org.lianlab.services;

importorg.lianlab.services.ITaskServiceCallback;

 

interface ITaskService {

    intgetPid (ITaskServiceCallback callback);

}

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

package org.lianlab.services;

import android.app.Service;

import android.content.Intent;

import android.os.IBinder;

import android.os.Process;

import android.os.RemoteException;

 

public class TaskService extends Service {

 

   static private int mCount = 0;

   

   @Override

   public IBinder onBind(Intent intent) {

       if (ITaskService.class.getName().equals(intent.getAction())) {

           return mTaskServiceBinder;

       }

       return null;

    }

 

   private final ITaskService.Stub mTaskServiceBinder = newITaskService.Stub() {

       public int getPid(ITaskServiceCallback callback) { 1

           mCount ++ ;     2

           try {   3

                callback.valueCounted(mCount);    4

           } catch (RemoteException e) {

                e.printStackTrace();

           }

           return Process.myPid();

       }

    };

}

加入了回调之后的代码结构并没有大变,只增加了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端的实现并没有增加多大的工作量,因为作为回调,实现是放在客户端上来完成的。因为我们的aidl接口已经发生了变动,于是需要将新加的ITaskServiceCall.aidl与改变过的ITaskService.aidl文件拷贝到应用程序工程里。我们再来看一下客户端实现代码需要作怎样的调整:

package org.lianlab.services;

 

import android.os.Bundle;

import android.os.Handler;

import android.os.IBinder;

import android.os.RemoteException;

import android.app.Activity;

import android.content.ComponentName;

import android.content.Context;

import android.content.Intent;

import android.content.ServiceConnection;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.TextView;

 

import org.lianlab.services.R;

 

public class MainActivity extends Activity {

 

   ITaskService mTaskService = null;

   private TextView mCallbackText;

   private Handler mHandler = new Handler();

 

   @Override

   public void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

 

       try {

           bindService(newIntent(ITaskService.class.getName()), mTaskConnection,

                    Context.BIND_AUTO_CREATE);

       } catch (SecurityException e) {

           e.printStackTrace();

       }

 

       setContentView(R.layout.activity_main);

 

        ((TextView)findViewById(R.id.textView1)).setOnClickListener(new OnClickListener() {

           @Override

           public void onClick(View v) {

                if (mTaskService != null) {

                    try {

                        int mPid = -1;

                        mPid =mTaskService.getPid(mCounter); 1

                        ((TextView)findViewById(R.id.textView1))

                               .setText("Service pid is " + mPid);

                    } catch (RemoteException e){

                        e.printStackTrace();

                    }

                } else {

                    ((TextView)findViewById(R.id.textView1)).setText("No service connected");

                }

           }

       });

 

       mCallbackText = (TextView) findViewById(R.id.callbackView); 2

       mCallbackText.setText("Clicked 0 times");

    }

 

   @Override

   public void onDestroy() {

       super.onDestroy();

       if (mTaskService != null) {

           unbindService(mTaskConnection);

       }

    }

 

   private ServiceConnection mTaskConnection = new ServiceConnection() {

       public void onServiceConnected(ComponentName className, IBinder service){

           mTaskService = ITaskService.Stub.asInterface(service);

       }

 

       public void onServiceDisconnected(ComponentName className) {

           mTaskService = null;

       }

    };

 

   private ITaskServiceCallback.Stub mCounter = newITaskServiceCallback.Stub() {       3

       public void valueCounted(final int n) {   

           mHandler.post(new Runnable() {     4

                public void run() {

                   mCallbackText.setText("Clicked " + String.valueOf(n) + "  times");

                }

           });

       }

    };

 

}

新加入的回调接口对象,也没给我们带来多大的麻烦,我们最重要是提供一个实例化的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使用上的需求才带来了一些编程上的开销。