读书人

Service与Android系统设计(五)

发布时间: 2012-11-07 09:56:10 作者: rapoo

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使用上的需求才带来了一些编程上的开销。

读书人网 >Android

热点推荐