1.什么是AIDL
AIDL全称是Android Interface Definition Language,中文译为Android接口定义语言,AIDL的提出是为了解决进程间通讯,我们知道,在Android系统中,每个进程在内存中是相互独立的,类似一个个独立王国,一个进程通常情况下无法访问其他进程的内存,但进程之间不是老死不相往来的状态,他们之间有许多数据交互的需求,为此提出了AIDL,通过AIDL,定义双方认可的数据交互接口,再由AIDL翻译成操作系统理解的底层语言,进而实现进程间通讯。
2.AIDL的使用
进程间通讯转换成具体业务逻辑则是一个服务端和客户端的会话。服务端和客户端约定好AIDL接口,服务端实现接口的具体逻辑并暴露给客户端,客户端再调用接口。既然涉及到两方,则在工程中也创建服务端和客户端两个项目,见图2,我的是服务端和客户端放在一个工程下,分开放在两个工程目录下也是可行的。
具体的开发步骤如下:
1.服务端创建.aidl文件
AIDL接口和日常生活的合同比较相似,服务端和客户端约定AIDL接口相当于双方草拟业务合作合同,开发过程中一般都在服务端这边准备合同,也即在服务端创建.aidl文件,方法为:
a.在service模块下的main目录下,新建aidl文件,在其之下新建一个包(package),包名自定义,然后右键新建.aidl文件,如图所示,aidl文件名字自定义,确定之后,AS帮我们新建一个.aidl文件。
b.点开新建的aidl文件会发现不是空白,而是有了内容,这是AS自动生成帮忙填写的内容,其中basicTypes
接口是演示AIDL可直接使用的基本数据类型,如果想用自己定义的数据类型,还需要做一些操作,这将在另一篇博客说明。删掉basicType
后,填上要实现的接口,这里填写ServiceGreet,表示服务端跟客户端打个招呼,代码内容如下,添加之后记得同步一下工程,否则在调用还是默认的basicType
函数,这样aidl文件创建好了。
package com.pm.service;
interface ServiceAidlInterface {
String ServiceGreet();
}
2.服务端实现接口
aidl文件创建成功后,也就是草拟好了合同,接下来就是履行合同的过程,即服务端这边根据aidl文件实现接口。
我们注意到,aidl文件有别于java格式文件,aidl文件那怎么和java联系起来呢,毕竟开发语言是java。实际上在创建aidl文件时,Android SDK工具自动生成一个同名的java接口,例如ServiceAidlInterface.aidl生成的文件名是ServiceAidlInterface.java,服务端调用接口实际是和该java文件打交道,这也是为什么创建或者更改aidl文件要及时同步的原因,如果同步不及时,SDK工具可能不及时自动生成的更新java接口。
AS根据aidl文件自动生成的java接口里有一个名为 Stub 的内部抽象类,Stub意思是存根,根据百度百科,存根意思是票据、证件等开出后所留的底子,这个抽象类的确有这个意思,具体需要另外展开文章来说明了,这里可先简单理解一下,我们把aidl进程间通讯理解成银行汇款过程,服务端通过该银行汇款(数据交互)给客户端,在aidl里声明的各种接口相当于汇款单,实现接口相当于打入钱的过程,从这个角度可勉强理解存根的意义,现实中拿到存根已经汇完钱了,而这里是拿到存根再汇钱(即实现接口),实际上,实现 .aidl 生成的接口是扩展生成的YourInterface.Stub接口的过程,实现接口的代码如下:
package com.pm.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
public class MyService extends Service {
private ServiceAidlInterface.Stub serviceAidlInterface= new ServiceAidlInterface.Stub() {
@Override
public String ServiceGreet() throws RemoteException {
return "Hello Client!";
}
};
@Override
public IBinder onBind(Intent intent) {
return serviceAidlInterface.asBinder();
}
}
最后别忘了在AndroidManifest.xml注册服务,内容如下
<service
android:name=".MyService"
android:exported="true">
<intent-filter>
<action android:name="com.pm.service.MyService" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
这样服务端工作就完成了。
3.客户端调用接口
现实中合同都是一式两份的,这里也不例外,客户端也要拿到aidl文件,而且要求aidl所在的包名必须和服务端aidl所在的包名一致,最稳妥的办法是直接整个拷贝服务端的aidl目录,同样的,拷贝完aidl文件之后,也要及时同步一下项目,AS才能及时自动生成对应的java接口。
3.1 首先,建立一个服务连接,在连接上时,初始化接口,代码如下,我们注意到,这里还是用的Stub,这样和服务端的存根对得上
private ServiceConnection connection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
serviceAidlInterface = ServiceAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
3.2 传入第一步创建的连接,绑定服务,这样客户端和服务端同呼吸共命运了
private void bindService() {
Intent intent = new Intent();
intent.setAction("com.pm.service.MyService");
intent.setPackage("com.pm.service");
this.bindService(intent, connection, BIND_AUTO_CREATE);
}
3.3 最后一步是调用接口了,这一步比较简单,调用服务器打招呼接口
String greet= serviceAidlInterface.ServiceGreet();
tvInfo.setText(greet);
3.总结
把aidl当成一个合同文件来理解,有助于理解aidl的作用,其中因为存根概念,又从银行汇款来理解。aidl创建后,Android SDK工具,这里是Android Studio自动帮忙生成对应的java接口,前提是要及时同步工程,工程不及时同步的话,aidl文件更新了,对应的java接口还是旧数据。aidl创建之后,服务器实现Stub存根里的方法,之后aidl拷贝给客户端,客户端创建连接,在连接里实例化存根,这样便取出了存根里的方法,最后绑定服务后调用即可。
最后附上完整客户端代码和布局
package com.pm.myaidldemo;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.pm.service.ServiceAidlInterface;
public class MainActivity extends AppCompatActivity {
private Button bServiceGreet;
private TextView tvInfo;
private ServiceAidlInterface serviceAidlInterface;
private ServiceConnection connection=new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
serviceAidlInterface = ServiceAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService();
bServiceGreet=findViewById(R.id.btnServiceGreet);
tvInfo=findViewById(R.id.tvInfo);
bServiceGreet.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
String greet= serviceAidlInterface.ServiceGreet();
tvInfo.setText(greet);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
private void bindService() {
Intent intent = new Intent();
intent.setAction("com.pm.service.MyService");
intent.setPackage("com.pm.service");
this.bindService(intent, connection, BIND_AUTO_CREATE);
}
}
客户端布局文件
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tvInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnServiceGreet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>