什么是AIDL
AIDL的全称为:Android Interface Definition Language
,翻译过来就为安卓接口定义语言
,对理解AIDL没什么实际意义,那么它到底是干什么的呢?简单来说,它是通过一种定义接口的方式,可以让我们快速的进行进程间通讯。关于进程,我想都应该明白它的含义,在Android中,可以直接理解为我们不同的程序,所以如果我们一个APP想要和另外一个APP进行通讯或者数据上的交换,就可以使用AIDL。在之前Eclipse中使用AIDL的文章很多,这篇文章我们来看看在Android Studio中如何使用AIDL。
服务端Demo的功能介绍
在做进程间通讯时,会存在一个服务端
和一个客户端
,所以使用AIDL也一样,我们两个Demo项目中AIDLServer
作为服务端
它的功能为:提供一个输入框,可以输入数字,然后点击下面的按钮,会启动一个Service(如果已经启动,不会再启动),并将输入框的数字通过Intent传到Service中。
先看Activity中的代码:
package com.jucongyuan.aidlserver.activity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import com.jucongyuan.aidlserver.R;
import com.jucongyuan.aidlserver.service.ServerService;
public class ServerActivity extends Activity {
private EditText et;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_server);
et = (EditText) findViewById(R.id.et);
findViewById(R.id.btnStart).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String str = et.getText().toString();
if (TextUtils.isEmpty(str))
Toast.makeText(ServerActivity.this, "请随意输入一些数字", Toast.LENGTH_SHORT).show();
else {
Intent intent = new Intent(ServerActivity.this, ServerService.class);
intent.putExtra("str", str);
startService(intent);
}
}
});
}
}
布局文件的代码就不展示了,现在我们要开始定义aidl文件了,在Android Studio中,我们要创建aidl文件,需要在main文件夹下,也就是和java、res文件夹同一层中创建一个aidl文件夹,如上面第一个图中结构那样。接下来,我们需要在aidl文件中新增一个包,这里为com.jucongyuan
,然后就要开始新建一个aidl文件了,具体步骤为在com.jucongyuan
包上右键,然后new->AIDL->AIDL File
,接着输入文件名,例如这个例子中为IConnect
。
文件生生成后,会默认有一个方法,我们不用管,直接修改或者添加自己想要的方法就可以了。IConnect.aidl
的内容为:
// IConnect.aidl
package com.jucongyuan;
// Declare any non-default types here with import statements
interface IConnect {
String getStr();
}
里面除了被注释的代码,还有包名和一个接口类,接口类中有一个方法,就这么简单。到目前为止,是不是我们就可以开始使用这个aidl文件了呢?如果你现在就使用,你会发现,我们是找不到IConnect这个类的,还需要关键的一步,就是Make Project
:
为什么需要这一步呢,因为AIDL之所以叫定义语言,就是在进程间通讯时,并不会使用这个IConnect.aidl
文件,我们定义好这个aidl文件后,需要利用编译器生成一个真正在进程间通讯会使用到的类,这个类在哪里呢?他在我们的build
文件夹下,具体位置如下:
默认情况下,这个类的代码是没有缩进的,我们需要格式化一下代码,方便我们查看,这个类就是通过IConnect.aidl
生成的,他也是我们进程间通讯的关键类,这里我们就不过多解释里面的内容了,后面会专门介绍进程间通讯的过程和原理。接下来我们才可以开始(也才能)使用这个定义语言生成的类进行进程间通讯。它的使用在ServerService.java
中:
package com.jucongyuan.aidlserver.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import com.jucongyuan.IConnect;
public class ServerService extends Service {
private String str;
IConnect.Stub stub = new IConnect.Stub() {
@Override
public String getStr() throws RemoteException {
return str;
}
};
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
str = intent.getStringExtra("str");
return Service.START_NOT_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return stub;
}
}
在这个Service中,我们是实现了一个刚才生成那个类中的一个内部类IConnect.Stub
,然后在实现方法中返回了一个字符串,这个字符串是哪里来的呢?就是我们之前那个ServerActivity
中输入的,这一点不作过多解释。另外注意,在ServerService
中的onBind是返回了我们实现的IConnect.Stub
这个类的实例,这点非常重要。还有一个非常重要的地方就是在Manifest
文件中,我们在注册这个ServerService
服务时,需要指定一个action:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jucongyuan.aidlserver">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".activity.ServerActivity"
android:label="@string/app_name"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".service.ServerService">
<intent-filter>
<action android:name="com.jucongyuan.IConnect" />"
</intent-filter>
</service>
</application>
</manifest>
这里action的名字为:com.jucongyuan.IConnect
,这个name可以随意命名,但是我们必须记住,因为在待会客户端连接时,会使用到它。
客户端Demo的功能介绍
接下来我们来看看客户端AIDLClient
的项目怎么编写,首选来看看项目结构:
比服务端要简单一下,注意项目结构中的AIDL定义,和服务端是一样的(包名和文件名),甚至里面的内容都是一样的,也必须一样
,所以我们可以直接从服务端的项目中直接拷贝过来,这也是一种比较稳妥的方式,防止再手动创建一次而出错。同样的,我们需要Make Project
。那么,现在不一样的就是ClientActivity
了,我们先看看客户端运行后的界面:
非常简单,上面是一个TextView
(这里没有背景看不出来),下面是一个按钮,通过点击这个按钮,我们就可以从服务端取到在服务端输入框中输入的数字,然后显示到TextView
上,来看看具体的代码(同样布局文件就不在贴了,文末我会提供项目的源码地址):
package com.jucongyuan.aidlclient.activity;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.view.View;
import android.widget.TextView;
import com.jucongyuan.IConnect;
import com.jucongyuan.aidlclient.R;
public class ClientActivity extends Activity {
private TextView tv;
private IConnect connect;
//第一部分
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
connect = IConnect.Stub.asInterface(service);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
//第一部分
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client);
tv = (TextView) findViewById(R.id.tv);
//第二部分
Intent intent = new Intent();
intent.setAction(IConnect.class.getName());
intent.setClassName("com.jucongyuan.aidlserver", "com.jucongyuan.aidlserver.service.ServerService");
bindService(intent, conn, Context.BIND_AUTO_CREATE);
//第二部分
findViewById(R.id.btnGet).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
tv.setText(connect.getStr());
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
其他代码都很容易理解,有两部分值得我们关注,代码中已注明,首先是实例化了一个ServiceConnection
对象,ServiceConnection
是个接口类,我们实现它的两个方法,这两个方法在下面连接成功或者失败是会被回调。第二部分就是连接的代码,intent
中的action
就使我们在服务端的manifest中注册Service时的那个action name。而下面的ClassName就是我们服务端程序的包名和ServerService
所在位置。然后调用bindService方法,成功后,我们就可以在上面的onServiceConnected
方法中获取到一个IConnect
实例,然后我们点击按钮,通过IConnect
的实例connect
调用getStr()
方法,就可以获取到服务端中ServerService中的变量str
的值了,也就是服务端中输入框输入的值。
测试
先运行AIDLServer
项目,然后在输入框中输入666,再点击下面的按钮:
接着运行AIDLClient
项目,只需要点击按钮,然后就会显示出666了:
可以反复多试几次,输入不同数字测试。
AIDL中传输自定义的对象
上面的使用只是AIDL最简单的使用,而在实际的情况中,我们需要在两个程序中传递更为复杂的对象,那么怎么去实现呢?现在我们修改一下需求,在客户端点击按钮时,传递一个学生到服务端,然后服务端将这个学生的学号设为服务端输入框中的值,再返回给客户端,客户端显示这个学生的信息。首先在客户端中定义学生实体:
package com.jucongyuan;
import android.os.Parcel;
import android.os.Parcelable;
public class Student implements Parcelable {
private String name;
private String number;
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
protected Student(Parcel in) {
name = in.readString();
number = in.readString();
}
public static final Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student createFromParcel(Parcel in) {
return new Student(in);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeString(number);
}
}
在客户端中,它所在的包为com.jucongyuan
,注意,这个实体所在的包名和我们aidl文件所在的包名是一致的,接着我们需要新增一个aidl文件,这个aidl文件的作用就是连接这个实体,新增的方式和上面创建IConnect.aidl
方法是一样的,右键包名新建。这个aidl文件的名字为Student.aidl
:
// Student.aidl
package com.jucongyuan;
parcelable Student;
很简单,接下来修改IConnect.aidl
文件中的方法:
// IConnect.aidl
package com.jucongyuan;
import com.jucongyuan.Student;
interface IConnect {
Student getStr(in Student s);
}
这个方法修改为了传入一个Student
参数,然后方法会返回一个Student
,具体实现会在服务端做,接下来重要的一步Make Project
,项目会报错,因为我们已经修改了方法,所以调用时也必须做修改:
package com.jucongyuan.aidlclient.activity;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.view.View;
import android.widget.TextView;
import com.jucongyuan.IConnect;
import com.jucongyuan.Student;
import com.jucongyuan.aidlclient.R;
public class ClientActivity extends Activity {
private TextView tv;
private IConnect connect;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
connect = IConnect.Stub.asInterface(service);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client);
tv = (TextView) findViewById(R.id.tv);
Intent intent = new Intent();
intent.setAction(IConnect.class.getName());
intent.setClassName("com.jucongyuan.aidlserver", "com.jucongyuan.aidlserver.service.ServerService");
bindService(intent, conn, Context.BIND_AUTO_CREATE);
findViewById(R.id.btnGet).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
//修改部分
Student student = new Student();
student.setName("张三");
student = connect.getStr(student);
tv.setText(student.getName() + "的学号是:" + student.getNumber());
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
除了上面注释的修改部分
,其余部分客户端都没有变化,接下来是服务端,服务端需要怎么做呢,和上面的步骤一样,增加实体类、增加aidl文件、修改aidl中的方法,不过要注意,增加的位置要和客户端一模一样,所以我们可以直接把aidl文件的整个内容和实体类从客户端拷贝过去。
新的服务端和客服端项目的结构为:
首先aidl里包名、文件名和内容是一致的,实体类所在的包和内容也是一致的。拷贝完成后Make Project
,同样会报错,因为方法已经变了,接着修改ServerService
package com.jucongyuan.aidlserver.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import com.jucongyuan.IConnect;
import com.jucongyuan.Student;
public class ServerService extends Service {
private String str;
IConnect.Stub stub = new IConnect.Stub() {
// 修改部分
@Override
public Student getStr(Student s) throws RemoteException {
s.setNumber(str);
return s;
}
};
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
str = intent.getStringExtra("str");
return Service.START_NOT_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return stub;
}
}
修改部分
重新实现了getStr()
方法,将输入框中的数字设置为该学生的学号再返回Student
。
测试
运行服务端,输入87564,点击下面的按钮:
运行客户端,点击按钮:
同样的,可以多测试几次。
注意事项
- 客户端、服务端aidl文件的所有定义必须完全一致
- 客户端、服务端实体类的内容和所在包必须完全一致
- IConnect.aidl里方法的参数前面有一个in不能省略,不然
Make Project
会报错
项目源码
小结
AIDL的使用大致就是这些了,没有在更复杂的用法了,如果复杂也是用于项目后架构上的复杂,使用就这些内容了。不过刚也提到过,AIDL是一种定义语言,运行时没有任何意义,Android用它是为了让我更方便的使用进程间通讯,在后面,我们可以绕过AIDL直接使用Android中的类来实现一套自己的进程间通讯,不过再这之前还要了解更多Android中进程间通讯的设计和实现,在后面我们会逐步的介绍。