项目链接:https://github.com/liaozhoubei/AIDLService
AIDL:Android Interface Definition Language,即Android接口定义语言,这是一种用于Android的跨进程通信(IPC)的工具。
为什么有AIDL呢?这是因为在android中一个进程不允许直接访问另一个进程的内存。
进程是一个应用在手机上执行的过程,这是一个动态的概念,一个app的主进程可以从这个app打开算起,然后直接退出app,这时就算是一个进程的结束。
在android中允许有多进程,因此多进程通信可以在自己的应用中进行通信,也可以在不同应用中通信,而这便是AIDL的使用方向。
但是AIDL有个缺点,那就是比较耗资源,如非必要,就不适用AIDL的为好
AIDL的使用方法
我们知道了AIDL是用来干什么的,那么我们就开始学习如何使用AIDL吧!
我们先建立一个AIDLService项目,这个项目用来模拟远程服务端口的,即我们想通过AIDL调用这个项目中的方法,这就是跨进程通信。
由于只是一个小Demo,所以并没有做太复杂的东西,我们只想通过AIDLService这个进程进行一次基本数据类型的传递,即进行简单的计算,然后将计算结果传回去客户端中。
其次,我们还想通过AIDLService将一个Object对象传给客户端。
那么我们就开始吧。
AIDLService服务端: 创建一个AIDL文件
首先必须要有一个AIDL接口文件,即后缀名为aidl的文件,在这里我们是IService.aidl,代码如下
interface IService {
// 传入两个正整数,返回他们的和
int add(int num1, int num2);
// 传入一个Person对象,返回person集合
List<Person> addPerson(in Person p);
}
看上去是不是很熟悉,没错,这其实就是一个interface接口而已,区别在于它并没有public或者private等修饰符。
其中addPerson(in Person p)中我们看到有个in Person p,这个in是什么呢?简单的说表示这个Person是从外面传进来的(可以自行搜索其中的意思)。
除此之外还需要创建一个Person.aidl,传递序列号之后的Person对象,代码很简单,如下:
parcelable Person;
就这么一行代码。
AIDLService服务端: 创建一个RemoteService远程服务
RemoteService远程服务中将实现IService.aidl中的两个方法,让后通过IBinder传出(IBinder可以让外界调用服务中的方法)。具体代码如下:
public class RemoteService extends Service {
public final String TAG = this.getClass().getSimpleName();
private ArrayList<Person> persons;
public RemoteService() {
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
persons = new ArrayList<>();
return iBinder;
}
private IBinder iBinder = new IService.Stub() {
@Override
public int add(int num1, int num2) throws RemoteException {
return num1 + num2;
}
@Override
public List<Person> addPerson(Person p) throws RemoteException {
persons.add(p);
return persons;
}
};
}
当然,我们不要忘记建立Person.java,而且要对这个类实现序列号,因为Object必须实现序列号才能同传输,代码如下:
public class Person implements Parcelable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
protected Person(Parcel in) {
// 从序列号中取出数据,要按照writeToParcel()写入的顺序一样取出
this.name = in.readString();
this.age = in.readInt();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int describeContents() {
return 0;
}
/**
* 将数据写入序列化中
* @param dest
* @param flags
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
// 写入时的顺序决定取出数据时的顺序
dest.writeString(name);
dest.writeInt(age);
}
/**
* 必须实现的方法,写法基本固定
*/
public static final Creator<Person> CREATOR = new Creator<Person>() {
/**
* 从序列化中取出一个个的字段
* @param in
* @return
*/
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
}
OK,这样我们的AIDLService服务端就已经完成了,剩下的就是使用客户端调用了。
AIDLClient客户端
客户端用来调用AIDLService中的两个方法,那么如何调用呢?
首先必须要创建AIDLClient客户端。
然后我们直接把AIDLService当中的Person.aidl和IService.aidl两个文件拷贝到,需要注意的是这里两个文件所在的包名必须与在AIDLService时的包名保持一致。
还需要把Person.java拷贝过来,包名也需要保持一致。
AIDLClient客户端需要一个简单的界面,activity_main.xml代码如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.aidlclient.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center_horizontal"
>
<EditText
android:id="@+id/num1"
android:layout_width="50dp"
android:layout_height="wrap_content"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+" />
<EditText
android:id="@+id/num2"
android:layout_width="50dp"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="=" />
<EditText
android:id="@+id/result"
android:layout_width="50dp"
android:layout_height="wrap_content" />
</LinearLayout>
<Button
android:id="@+id/btn_addNumber"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="调用"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center_horizontal"
>
<EditText
android:id="@+id/name"
android:layout_width="50dp"
android:layout_height="wrap_content"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+" />
<EditText
android:id="@+id/age"
android:layout_width="50dp"
android:layout_height="wrap_content" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="=" />
<EditText
android:id="@+id/getPerson"
android:layout_width="50dp"
android:layout_height="wrap_content" />
</LinearLayout>
<Button
android:id="@+id/btn_addPerson"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="调用Person"/>
</LinearLayout>
非常简单,这里就不做解释了。
AIDLClient客户端:调用远程服务
MainActivity中的布局写好了,那么我们就开始进行远程吧。方法其实就使用本地服务基本一样。代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private EditText num1;
private EditText num2;
private EditText result;
private EditText name;
private EditText age;
private EditText getPerson;
private Button btn_1;
private Button btn_2;
IService iService;
/**
* 创建ServiceConnection与服务进行链接
*/
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iService = IService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
iService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
bindService();
}
private void initView() {
num1 = (EditText) findViewById(R.id.num1);
num2 = (EditText) findViewById(R.id.num2);
result = (EditText) findViewById(R.id.result);
btn_1 = (Button) findViewById(R.id.btn_addNumber);
btn_2 = (Button) findViewById(R.id.btn_addPerson);
name = (EditText) findViewById(R.id.name);
age = (EditText) findViewById(R.id.age);
getPerson = (EditText) findViewById(R.id.getPerson);
btn_1.setOnClickListener(this);
btn_2.setOnClickListener(this);
}
/**
* 与远程服务进行绑定
*/
private void bindService() {
Intent intent = new Intent();
// 传入AIDL的包名,以及调用远程服务的全类名
intent.setComponent(new ComponentName("com.example.aidlservice", "com.example.aidlservice.RemoteService"));
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_addNumber:
int num1 = Integer.parseInt(this.num1.getText().toString());
int num2 = Integer.parseInt(this.num2.getText().toString());
try {
int num = iService.add(num1, num2);
result.setText(num + "");
Toast.makeText(getApplicationContext(), num + "来到了", Toast.LENGTH_LONG).show();
} catch (RemoteException e) {
e.printStackTrace();
}
break;
case R.id.btn_addPerson:
String p1 = name.getText().toString();
int num3 = Integer.parseInt(age.getText().toString());
Person person = new Person(p1, num3);
try {
ArrayList<Person> persons = (ArrayList<Person>) iService.addPerson(person);
Toast.makeText(getApplicationContext(), persons.toString(), Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
}
在这里,我们看到基本与调用本地的服务的方法没多大区别,只是在使用bindService()方法时,我们要设置包名,即设置我们要访问的服务的信息,
intent.setComponent(new ComponentName("com.example.aidlservice", "com.example.aidlservice.RemoteService"));
总结
AIDL的使用并不难,而且在调用的时候也就象使用普通的服务一样,区别只在双方都必须有aidl文件,其中表明能够被调用的方法而已。
下面总结一下aidl多进程通信的使用步骤
- 首先服务都创建aidl接口文件,这个接口文件表示里面的方法能被外部调用。同时要注意的是如果有Object对象传输,必须实现Parcelable接口。
- 创建一个Service,通过IBinder实现aidl接口中的方法,然后再onBind()中返回这个IBinder.
- 在客户端复制服务端的aidl文件,同时保持包名一致,如果有Object对象,也一样要复制。
- 直接通过bindService()方法绑定服务,调用IBinder中的方法。