远程服务的创建和调用需要使用AIDL语言,步骤如下:
- 使用AIDL语言定义远程服务的接口
- 通过继承Service类实现接口中定义的方法和属性
- 绑定和使用远程服务
以下为一个简单Demo ,RemoteMathCallerDemo界面如下:
绑定远程服务后,调用RemoteMathServiceDemo中的MathService服务进行加法运算。
1.使用AIDL语言定义远程服务的接口
以Android Studio为例,首先需要建立对应目录及aidl文件,如下:
(比如直接在java目录下的包上右键新建aidl文件 IDE会自动生成aidl目录及该目录下的包和文件这样的小技巧我可不会随便告诉别人)
IMathService.aidl文件内容如下:
// IMathService.aidl
package com.example.remotemathservicedemo;
// Declare any non-default types here with import statements
interface IMathService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
long Add(long a,long b);
}
然后在build目录下会自动生成与该aidl文件对应的java接口文件,(若没有生成则重新make project) 如下:
在看IMathService.java内容之前呢,不知道你有没有注意到,我前两张截图都截到了上面的一个Module:remotemathcallerdemo,这个就是调用端,目前我们编辑的remotemathservicedemo是服务端。
下面为IMathService.java的完整代码,加上了我自己的理解和注释:
/*
* 这个文件是自动生成的。不要修改
*/
package com.example.remotemathservicedemo;
/* 在这里声明任何非默认类型
所有使用AIDL建立的接口都必须继承 android.os.IInterface 基类接口
这个基类接口中定义了 asBinder()方法 用来获取Binder对象
*/
public interface IMathService extends android.os.IInterface {
/**
* 本地IPC实现stub类
*/
public static abstract class Stub extends android.os.Binder implements com.example.remotemathservicedemo.IMathService {
private static final java.lang.String DESCRIPTOR = "com.example.remotemathservicedemo.IMathService";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
//asInterface(IBinder) 是Stub内部的远程服务接口,调用者可以通过该方法获得远程服务的实例
public static com.example.remotemathservicedemo.IMathService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//判断android.os.IInterface实例是否为本地服务 若是返回android.os.IInterface
//若不是本地服务 构造Proxy对象并返回之
if (((iin != null) && (iin instanceof com.example.remotemathservicedemo.IMathService))) {
return ((com.example.remotemathservicedemo.IMathService) iin);
}
return new com.example.remotemathservicedemo.IMathService.Stub.Proxy(obj);
}
//实现了android.os.IInterface接口定义的asBinder()方法
@Override
public android.os.IBinder asBinder() {
return this;
}
/*
Parcel是Android系统应用程序间传递数据的容器,能够在两个进程中完成打包和拆包的工作
但Parcel不同于通用意义上的序列化
Parcel的设计目的是用于高性能IPC传输 不能将其保存在持久存储设备上
*/
//接收Parcel对象,并从中逐一读取每个参数,然后调用Service内部制定的方法,将结果写进另一个Parcel对象,
// 准备将这个Parcel对象返回给远程的调用者
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_Add: {
data.enforceInterface(DESCRIPTOR);
long _arg0;
_arg0 = data.readLong();
long _arg1;
_arg1 = data.readLong();
long _result = this.Add(_arg0, _arg1);
reply.writeNoException();
reply.writeLong(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
//用来实现远程服务调用
private static class Proxy implements com.example.remotemathservicedemo.IMathService {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
//以一定顺序将所有参数写入Parcel对象,以供Stub内部的onTransact()方法获取参数
@Override
public long Add(long a, long b) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
long _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeLong(a);
_data.writeLong(b);
mRemote.transact(Stub.TRANSACTION_Add, _data, _reply, 0);
_reply.readException();
_result = _reply.readLong();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_Add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public long Add(long a, long b) throws android.os.RemoteException;
}
IMathService.aidl是对远程服务接口的定义,自动生成的IMathService.java内部实现了远程服务数据传递的相关方法,下一步介绍如何实现远程服务,这需要建立一个Service类,并在该类中通过onBind()方法返回IBinder对象,这样调用者使用获取的IBinder对象就可以访问远程服务。
下面是MathService.java的完整代码:
package com.example.remotemathservicedemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.widget.Toast;
/**
* Created by yinghao on 2016/5/7.
*/
public class MathService extends Service {
/*
1. 建立 IMathService.Stub的实例mBinder并实现AIDL文件定义的远程服务接口
2. 在onBind()方法中将mBinder返回给远程调用者
*/
private final IMathService.Stub mBinder = new IMathService.Stub(){
@Override
public long Add(long a, long b) throws RemoteException {
return a + b;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(MathService.this, "远程绑定:MathService", Toast.LENGTH_SHORT).show();
return mBinder;
}
//Return true if you would like to have the service‘s onRebind method later called when new clients bind to it.
@Override
public boolean onUnbind(Intent intent) {
Toast.makeText(MathService.this, "取消远程绑定", Toast.LENGTH_SHORT).show();
return false;
}
}
最后一步,注册service:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.remotemathservicedemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service android:name=".MathService"
android:process=":remote">
<intent-filter>
<action android:name="com.example.remote.MathService" />
</intent-filter>
</service>
</application>
</manifest>
到这里我们的服务端Module : remotemathservicedemo 就完成了。
这完成了文章开头所列举的三个步骤的前两步,最后一步,让我们来看看怎么绑定和使用远程服务吧。
首先,我们需要引入与服务端相同的aidl文件并确保自动生成对应的IMathService.java接口文件。
那么为什么要这样做呢? 这就需要我们了解aidl文件和对应接口文件的用处到底是什么,为了时数据能穿越进程边界,所有数据都必须是“打包”,而自动生成的IMathService.java内部实现了远程服务数据传递的相关方法,那么服务端就有了将数据打包、拆包的能力。而调用端也需要发出数据和接收数据,也需要有将数据打包、拆包的能力,所以它也需要IMathService.java这个类。
然后对远程服务的绑定与调用,其实与本地服务的绑定区别不大,不同之处主要包括两处:
- 使用IMathService生命远程服务实例
- 通过IMathService.Stub的asInterface()方法获取服务实例
下面为remotemathcallerdemo Module中MainActivity.java的完整代码:
package com.example.remotemathcallerdemo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.example.remotemathservicedemo.IMathService;
import org.w3c.dom.Text;
public class MainActivity extends AppCompatActivity {
private TextView textView;
private Button bind;
private Button unbind;
private Button add;
private boolean isBound = false;
private IMathService mathService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mathService = IMathService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
mathService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
bind = (Button) findViewById(R.id.bind);
unbind = (Button) findViewById(R.id.unbind);
add = (Button) findViewById(R.id.add);
bind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isBound) {
final Intent serviceIntent = new Intent();
serviceIntent.setAction("com.example.remote.MathService");
bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
isBound = true;
}
}
});
unbind.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isBound) {
unbindService(mConnection);
isBound = false;
mathService = null;
}
}
});
add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mathService == null) {
textView.setText("未绑定远程服务");
return;
}
long a = Math.round(Math.random() * 100);
long b = Math.round(Math.random() * 100);
long result = 0;
try {
result = mathService.Add(a, b);
} catch (RemoteException e) {
e.printStackTrace();
}
String msg = String.valueOf(a) + "+" + String.valueOf(b) + "=" + String.valueOf(result);
textView.setText(msg);
}
});
}
}
在此例中传递的数据类型为基本数据类型,打包过程是自动完成的,但对于自定义的数据类型,用户则需要实现Parcelable接口,使自定义的数据类型能够转换为系统级原语保存在Parcel对象中,穿越进程边界后可再转换为初始格式,关于自定义数据类型的传递,在下一篇文章中归纳总结。