本文是对AIDL跨进程通信的学习和总结,主要介绍了AIDL的基本概念和使用方法,通过一个简单的Demo来展示AIDL跨进程通信,是对自己前几天学习的简要总结。
AIDL(Android Interface Definition Language)是一种接口定义语言,用于生成可以在Android设备上两个进程之间进行进程通信,AIDL内部主要通过Binder机制来实现, 适用于进程之间交互频繁、通信数据量小的场景。
在使用AIDL进行跨进程通信的时候,通常将请求通信的一方称之为客户端(Client),客户端的主要工作就是发送数据;而接收通信数据的一方称之为服务端(Server),服务端主要工作是处理客户端发送过来的数据,并通过回调(Callback)的方式返回客户端数据,实现双向通信。
AIDL支持以下数据类型:
Java的8种基本类型,即int、long、char等。
String和CharSequence。
List:其中的每个元素都必须是AIDL支持的。客户端实际接收的具体类始终是ArrayList,但生成的方法使用的是List接口。
Map:其中的每个元素都必须是AIDL支持的。客户端实际接收的具体类始终是HashMap,但生成的方法使用的是Map接口。
Parcelable:必须要显式import,即使它跟.aidl是同一个包下。
AIDL接口:必须要显式import,即使它跟.aidl是同一个包下。
AIDL中的方法:可有零、一或多个参数,可有返回值或void。
AIDL中除了基本数据类型(默认为in,不能是其他方向),其他类型的参数必须标上方向:
in
,表示输入型参数,由客户端赋值;
out
,表示输出型参数,由服务端赋值;
inout
,表示输入输出型参数,可由客户端或服务端赋值;
AIDL只expose方法,不会expose静态变量。
第一个Demo是Client输入用户名和密码,并通过AIDL接口发送给Server,简单展示了AIDL的基本用法。
新建工程AIDLServer,作为服务端App,在main文件夹下建立一个aidl文件夹,用于存放aidl文件,新建aidl文件IMyAidlInterface.aidl:
文件内容如下:
// IMyAidlInterface.aidl
package com.jason.aidlserver;
import com.jason.aidlserver.ICallback;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
void login(String userName,String passWord);
}
其中,方法basicTypes
是自动生成的,如果不需要的话,完全可以删掉。方法login
是我们自定义的,用于发送用户名和密码。生成之后,点击Make Project按钮,生成AIDL对应的.java
文件:
如果点开这个文件的话,会看到AS对.aidl
文件中的interface的具体实现,我们在这里不再对源码进行解读,你只需要知道,里面的Stub
抽象类是实现AIDL接口核心部分,它继承自Binder(这也直接印证了AIDL接口依赖于Binder实现) 。
然后在我们的Java源文件包中新建一个Service,这里命名为AIDLService,用于实现接收Client的数据:
package com.jason.aidlserver;
public class AIDLService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public interface OnLoginListener {
void login(String userName, String passWord);
}
private OnLoginListener onLoginListener;
public void setOnLoginListener(OnLoginListener onLoginListener) {
this.onLoginListener = onLoginListener;
}
class MyBinder extends IMyAidlInterface.Stub {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public void login(String userName, String passWord) throws RemoteException {
if (onLoginListener != null) {
onLoginListener.login(userName, passWord);
}
}
public AIDLService getService() {
return AIDLService.this;
}
}
}
这里为了方便阅读,隐去了一些不必要的代码,完全的工程已经上传到GitHub,可以参看文末的链接。在这个Service中,主要是通过实现Stub
抽象类来扩展其中的login
方法,同时与Service的IBinder
进行绑定,用于下一步的服务链接。此外,我们还设置了一个OnLoginListener
接口,把实现部分抽离出来,提供了一个接口。在MainActivity
代码中,存在如下实现:
public class MainActivity extends AppCompatActivity implements OnLoginListener{
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.text_view);
Intent intent = new Intent(this,AIDLService.class);
bindService(intent,mAIDLConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mAIDLConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
MyBinder binder = (MyBinder) iBinder;
AIDLService aidlService = binder.getService();
aidlService.setOnLoginListener(MainActivity.this);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
private Handler mHandler = new Handler();
@Override
public void login(final String userName, final String passWord) {
mHandler.post(()->{
textView.setText("Message from client:\nuserName:"+userName+"\npassWord:"+passWord);
Toast.makeText(this,"login successful!",Toast.LENGTH_LONG).show();
});
}
}
MainActivity代码主要做了两件事:连接服务和实现AIDL接口。其中,连接服务需要在AndroidManifest文件中进行注册,如下:
<service android:name=".AIDLService"
android:exported="true">
<intent-filter>
<action android:name="com.jason.aidlserver"/>
<category android:name="android.intent.category.DEFAULT"/>
intent-filter>
service>
而AIDL接口的实现需要注意,虽然这里实现在了MainActivity中,但是真正调用这段代码的场景未必在主线程中,因此,如果代码中存在UI操作的话,需要用一个主线程的handler来实现相关UI操作。至此,Server的工作告一段落。
首先,与Server相同,第一步是创建AIDL文件,需要特别注意的是:AIDL的文件名与包名和Server必须相同,因此,我们直接把Server端的AIDL文件夹整体复制粘贴到Client的对应位置即可。弄完了之后,记得Make Project一下。
Client端的实现要简单的多,主要做了两件事:连接Server端的Service和发送数据,因此,Client的代码实现为:
public class MainActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
intent = new Intent();
intent.setAction("com.jason.aidlserver");
intent.setPackage("com.jason.aidlserver");
bindService(intent, new ConnectCallBack(), Context.BIND_AUTO_CREATE);
loginButton = findViewById(R.id.login);
loginButton.setOnClickListener(v -> {
login();
});
display = findViewById(R.id.display);
}
public void login() {
if (iMyAidlInterface != null) {
try {
userName = userNameEditText.getText().toString();
passWord = passWordEditText.getText().toString();
iMyAidlInterface.login(userName, passWord);// 【AIDL接口调用】
} catch (Exception e) {
}
}
}
class ConnectCallBack implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
iMyAidlInterface = null;
}
}
}
同样的,为了显示方便,隐去了不必要的代码(不然太长了……)。
注意到,这里的服务连接主要是通过Intent+bindService 的方式,其中,setPackage
设置的是Server APK的包名,而不是Server服务的包名。设置不正确会导致bindService为false,setAction
就是我们刚才在AndroidManifest中注册的Action。至此,Client的工作就完成了。我们看一下最终实现效果:
从Client中输入用户名和密码后,顺利的传入到了Server端,并显示了出来。至此,我们的第一个AIDL跨进程通信Demo完成了。
但是显然,这个Demo存在一个缺陷:这个AIDL通信是单工通信,我们无法从Server向Client发送数据。
如何实现全双工通信呢?常用的做法是再写一个AIDL接口,然后作为参数放在调用的另一个AIDL方法中,这个过程称之为回调。听起来有点抽象,但是实际实现起来很简单。
Client工程中,在AIDL文件夹中生成一个ICallback.aidl
文件:
内容如下:
// ICallback.aidl
package com.jason.aidlserver;
// Declare any non-default types here with import statements
interface ICallback {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void server2client(in String data);
}
然后,修改另一个AIDL文件:
// IMyAidlInterface.aidl
package com.jason.aidlserver;
import com.jason.aidlserver.ICallback;
// Declare any non-default types here with import statements
interface IMyAidlInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
void login(String userName,String passWord,ICallback icb);
}
主要变化是login
多了一个ICallback
参数,同样的,Make Project一下,生成对应的接口文件。然后在MainActivity做对应的修改:
public class MainActivity extends AppCompatActivity {
...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intent = new Intent();
intent.setAction("com.jason.aidlserver");
intent.setPackage("com.jason.aidlserver");
bindService(intent, new ConnectCallBack(), Context.BIND_AUTO_CREATE);
loginButton = findViewById(R.id.login);
loginButton.setOnClickListener(v -> {
login();
});
display = findViewById(R.id.display);
}
public void login() {
if (iMyAidlInterface != null) {
try {
userName = userNameEditText.getText().toString();
passWord = passWordEditText.getText().toString();
iMyAidlInterface.login(userName, passWord, iCallback);
} catch (Exception e) {
}
}
}
class ConnectCallBack implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
iMyAidlInterface = null;
}
}
private ICallback iCallback = new ICallback.Stub() {
@Override
public void server2client(String code) throws RemoteException {
display.setText(code);
}
};
}
不难发现,改动仅在于ICallback的实现上,这段代码实现在Client端,但是却是由Server调用的(和刚才反过来了)。
与刚才相同,还是先创建一个一模一样的AIDL文件,Make Project一下即可。然后,先修改AIDLService中的内容:
@Override
public void login(String userName, String passWord, ICallback iCallback) throws RemoteException {
if (onLoginListener != null) {
onLoginListener.login(userName, passWord);
iCallback.server2client("Message from Server:\nuserName:"+userName+"\npassWord:"+passWord);
}
}
Server端改动比较小,因为Server只需要调用就行了,所以这里仅列出了改动的地方,因为login方法定义改变了,因此这里也要对应的改动,然后,我们在这里调用iCallback的server2client
方法(当然也可以在其他地方调用,只要iCallback非null即可),然后Server的工作就完成了。看一下最终的效果:
为了显示效果,这里加了一个TextView,可以看出,Client向Server发送完用户名和密码后,Server也向Client返回了一串字符串,并在Client中的TextView显示了出来。至此,一个简单的全双工AIDL通信完成。