Android进程间通信(⼀):AIDL使⽤详解
⼀、概述
AIDL是Android Interface Definition Language的缩写,即Android接⼝定义语⾔。它是Android的进程间通信⽐较常⽤的⼀种⽅式。Android中,每⼀个进程都有⾃⼰的Dalvik VM实例,拥有⾃⼰的独⽴的内存空间,进程与进程之间不共享内存,这就产⽣了进程间通信的需求。
⼆、语法
AIDL是Android接⼝定义语⾔,是⼀门语⾔,所以它拥有⾃⼰的语法和特性。
(⼀)数据类型
AIDL⽀持的数据类型包括以下⼏种:
1. Java的8种基本数据类型:int,short,long,char,double,byte,float,boolean;
2. CharSequence类型,如String、SpannableString等;
3. ArrayList,并且T必须是AIDL所⽀持的数据类型;
4. HashMap<K,V>,并且K和V必须是AIDL所⽀持的数据类型;
5. 所有Parceable接⼝的实现类,因为跨进程传输对象时,本质上是序列化与反序列化的过程;
6. AIDL接⼝,所有的AIDL接⼝本⾝也可以作为可⽀持的数据类型;
有两个需要注意的地⽅:
1、在Java中,如果⼀个对象和引⽤它的类在同⼀个package下,是不需要导包的,即不需要import,⽽在AIDL中,⾃定义的Parceable对象和AIDL接⼝定义的对象必须在所引⽤的AIDL⽂件中显式import进来,不管这些对象和所引⽤它们的AIDL⽂件是否在同⼀个包下。
2、如果AIDL⽂件中使⽤到⾃定义的Parceable对象,则必须再创建⼀个与Parceable对象同名的AIDL⽂件,声明该对象为Parceable类型,并且根据上⼀条语法规定,在AIDL⽂件中进⾏显式import。
(⼆)⽂件类型
1. 所有AIDL⽂件都是以.aidl作为后缀的;
2. 根据⽤途区分,AIDL⽂件的有两种,⼀种是⽤于定义接⼝,另⼀种是⽤于声明parceable对象,以供其他AIDL⽂件使⽤;
(三)定向tag
AIDL中,除了基本数据类型,其他类型的⽅法参数都必须标上数据在跨进程通信中的流向:in、out或inout:
1、in表⽰输⼊型参数:只能由客户端流向服务端,服务端收到该参数对象的完整数据,但服务端对该对象的后续修改不会影响到客户端传⼊的参数对象;
2、out表⽰输出型参数:只能由服务端流向客户端,服务端收到该参数的空对象,服务端对该对象的后续修改将同步改动到客户端的相应参数对象;
3、inout表⽰输⼊输出型参数:可在客户端与服务端双向流动,服务端接收到该参数对象的完整数据,且服务端对该对象的后续修改将同步改动到客户端的相应参数对象;
定向tag需要⼀定的开销,根据实际需要去确定选择什么tag,不能滥⽤。
深⼊理解tag:
(四)其他
1、所有AIDL接⼝都是继承⾃IInterface接⼝的,IInterface接⼝中只声明了⼀个asBinder⽅法:
public interface IInterface
{
/**
* Retrieve the Binder object associated with this interface.
* You must use this instead of a plain cast, so that proxy objects
* can return the correct result.
*/
public IBinder asBinder();
}
2、系统会帮我们为所有⽤于定义接⼝的AIDL⽂件⽣成相应的java代码,⼿写这份java代码与⽤AIDL系统⽣成实际上是⼀样的,AIDL可以⽅便系统为我们⽣成固定格式的java代码。
三、基本⽤法
在AndroidStudio中⼯程⽬录的Android视图下,右键new⼀个AIDL⽂件,默认将创建⼀个与java⽂件夹同级的aidl⽂件夹⽤于存放AIDL⽂件,且aidl⽂件夹下的包名与adle中配置的applicationId⼀致,⽽applicationId默认值是应⽤的包名。
AIDL的底层是基于Binder实现的,⽽Binder机制也是⼀种请求-响应式的通信模型,请求⽅⼀般称为Client,响应⽅称为Server。
Demo介绍:在⼀个应⽤内部新起⼀个进程作为服务端,服务端提供addStudent和getStudentList两个⽅法,分别⽤于客户端向服务端添加Student数据和获取Student列表,Student是⾃定义对象,只有id和name两个属性。。
(⼀)服务端
新建AIDL⽂件,定义⼀个接⼝,在这个接⼝⾥声明两个⽅法,分别⽤于添加Student数据和获取所有Student数据,因为AIDL是接⼝定义语⾔,所以不能在AIDL⽂件⾥对⽅法进⾏实现:
/aidl/com/sqchen/aidltest/IStudentService.aidl
package com.sqchen.aidltest;
//显式import
import com.sqchen.aidltest.Student;
interface IStudentService {
List<Student> getStudentList();
//定向tag
void addStudent(in Student student);
}
因为IStudentService.aidl接⼝中使⽤到的Student是⾃定义对象,不属于Java基本数据类型和CharSequence类型,所以按照语法规定,
在IStudentService.aidl中需要显式import,同时我们要让Student实现Parceable接⼝,并且新建⼀个AIDL⽂件⽤于声明Student类是Parceable 类型:
/aidl/com/sqchen/aidltest/Student.java
public class Student implements Parcelable {
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
}
public void readFromParcel(Parcel parcel) {
this.id = adInt();
this.name = adString();
}
public static Parcelable.Creator<Student> CREATOR = new Parcelable.Creator<Student>() {
@Override
public Student createFromParcel(Parcel source) {
return new Student(source);
}
@Override
public Student[] newArray(int size) {
return new Student[0];
}
};
private Student(Parcel in) {
this.id = in.readInt();
this.name = in.readString();
}
}
/aidl/com/sqchen/aidltest/Student.aidl
package com.sqchen.aidltest;
parcelable Student;
这⾥,我们是在src/main/aidl⽂件夹下创建Student.java的,实际上这将因为不到Student.java⽽报错,因为在AndroidStudio中使⽤Gradle构建项⽬时,默认是在src/main/java⽂件夹中查java⽂件的,如果把Student.java放在src/main/aidl对应包名下,⾃然就会不到这个⽂件了,所以需要修改app的adle⽂件,在sourceSets下添加对应的源⽂件路径,即src/main/aidl:
android {
compileSdkVersion 28
...
sourceSets {
main {
java.srcDirs = ["src/main/java", "src/main/aidl"]
}
}
}
在将src/main/aidl添加到sourceSets中重新构建项⽬后,在AndroidStudio的Android视图下,项⽬的⽬录结构将发⽣变化,此时会发现aidl⽂件夹不见了,⽽在java⽂件夹下,将出现两个⼀样包名的⽬录结构,但这只是在当前视图下的⼀种展⽰⽅式,将src/main/aidl下的⽂件也看作是java⽂件的存放位置,实际上当切换到Project视图时,会发现AIDL⽂件还是存在于aidl⽂件夹下,与java⽂件夹同级。
如果Student.java是放在src/main/java对应的包名路径下,则不需要这个步骤。
接着,创建⼀个Service⽤来响应Client端的请求:
/java/com/sqchen/aidltest/StudentService.java
public class StudentService extends Service {
private static final String TAG = "StudentService";
private CopyOnWriteArrayList<Student> mStuList;
private Binder mBinder = new IStudentService.Stub() {
@Override
public List<Student> getStudentList() throws RemoteException {
return mStuList;
}
@Override
public void addStudent(Student student) throws RemoteException {
mStuList.add(student);
}
};
@Override
public void onCreate() {
init();
}
private void init() {
mStuList = new CopyOnWriteArrayList<>();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
在StudentService中,我们创建了⼀个Binder对象并在onBind⽅法中返回它,这个Binder对象继承⾃IStudentService.Stub,并实现了内部的AIDL ⽅法。
我们⽤CopyOnWriteArrayList来存放mStuList对象,是因为AIDL⽅法是在服务端的Binder线程池中执⾏的,当有多个客户端同时连接时,可能存在多个线程同时访问mStuList对象的情况,⽽CopyOnWriteArrayList⽀持并发读写,可以保证线程安全。
按照AIDL的语法规定,只⽀持传输ArrayList对象,⽽CopyOnWriteArrayList不是继承⾃ArrayList,为什么也可以传输呢?这是因为AIDL中所⽀持的是抽象的List,⽽List只是⼀个接⼝,虽然服务端返回的是CopyOnWriteArrayList,但在Binder中,它会按照List的规范去访问数据并最终形成⼀个新的ArrayList给客户端。类似的还有ConcurrentHashMap。
为StudentService服务端另起⼀个进程,在l配置⽂件中,声明android:process=":remote",即可创建⼀个新的进程实现单应⽤多进程,从⽽模拟进程间通信。这个进程的名字就是remote:
<service
android:name="com.sqchen.aidltest.StudentService"
android:process=":remote"
android:enabled="true"
android:exported="true"></service>
(⼆)客户端
因为客户端和服务端是在不同的进程中,所以客户端要想通过AIDL与远程服务端通信,那么必须也要有服务端的这份AIDL代码。
这⾥分为两种情况:
1、服务端与客户端是两个独⽴应⽤
把服务端的aidl⽂件夹整个复制到客户端的与java⽂件夹同级的⽬录下,保持客户端和服务端的aidl⽂件夹的⽬录结构⼀致。这种情况下需要注意的是,如果前⾯的Student.java⽂件是放置src/main/java对应包名路径下,则在拷贝aidl⽂件夹到客户端的同时,也要将对应的Student.java⼀并拷贝到客户端相同的包名路径下。
2、服务端与客户端是同⼀应⽤的不同进程
这种情况下因为客户端与服务端同属⼀个应⽤,两个进程都可以使⽤这份AIDL代码,则不需要拷贝。
客户端进程即主进程,在MainActivity.java中绑定远程StudentService,就可以向服务端进程remote发起请求了:
/java/com/sqchen/aidltest/MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MainActivity";
private final static String PKG_NAME = "com.sqchen.aidltest";
private Button btnBind;
private Button btnAddData;
private Button btnGetData;
private Button btnUnbind;
private IStudentService mStudentService;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mStudentService = IStudentService.Stub.asInterface(service);
if (mStudentService == null) {
Log.i(TAG, "mStudentService == null");
return;
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
initView();
initData();
}
private void initView() {
btnBind = findViewById(R.id.btn_bind);
btnAddData = findViewById(R.id.btn_add_data);
btnGetData = findViewById(R.id.btn_get_data);
btnUnbind = findViewById(R.id.btn_unbind);
initListener();
}
private void initListener() {
btnBind.setOnClickListener(this);
btnAddData.setOnClickListener(this);
btnGetData.setOnClickListener(this);
btnUnbind.setOnClickListener(this);
}
private void initData() {
mCallback = new ITaskCallback.Stub() {
@Override
public void onSuccess(String result) throws RemoteException {
Log.i(TAG, "result = " + result);
}
@Override
public void onFailed(String errorMsg) throws RemoteException {
Log.e(TAG, "errorMsg = " + errorMsg);
}
};
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_bind:
bindStudentService();
break;
case R.id.btn_add_data:
addData();
break;
case R.id.btn_get_data:
getData();
break;
case R.id.btn_unbind:
unbindStudentService();
break;
default:
break;
}
}
private void bindStudentService() {
Intent intent = new Intent(this, StudentService.class);
intent.setPackage(PKG_NAME);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
private void addData() {
if (mStudentService == null) {
Log.i(TAG, "mStudentService = null");
return;
}
try {
mStudentService.addStudent(new Student(1, "陈贤靖"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void getData() {
if (mStudentService == null) {
Log.i(TAG, "mStudentService = null");
return;
}
try {
List<Student> studentList = StudentList();
Log.i(TAG, "studentList = " + studentList);
} catch (RemoteException e) {
e.printStackTrace();
}安卓intent用法
}
private void unbindStudentService() {
unbindService(mConnection);
mStudentService = null;
}
@Override
protected void onDestroy() {
unbindStudentService();
}
}
在MainActivity.java中,创建4个按钮,分别⽤于绑定服务、添加数据、获取数据、解绑服务:
1、绑定服务
通过bindService⽅式启动StudentService,ServiceConnection是⽤于监视服务端状态的⼀个接⼝,内部⽅法都在主线程被调⽤,所以不能在该接⼝的⽅法中进⾏耗时操作。
/**
* Called when a connection to the Service has been established, with
* the {@link android.os.IBinder} of the communication channel to the
* Service.
*
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论