赞
踩
AIDL 是Android Interface Definition Language (Android 接口定义语言)的缩写。只看这个定义其实也并不明白,其实它就是一个Android IPC通信的一个代码规范。并不是一个新的语言啥的。
分别介绍 在同一APP和不同APP通信的情况。
AS 提供右键提示创建AIDL的功能。
在main目录上右键 ,AIDL文件和Src目录统计
在build目录下就会生成 对应的.java 文件
关于里面的内容,后面会分析
创建的service要在AndroidManifest.xml注册
<service
android:name=".binder.MyAIDLService"
android:enabled="true"
android:exported="true"
android:process=":aidl"/>
如果要让Service运行在不同的进程,添加process属性 android:process=“:hello” 表示当前进程名是包名+aidl 即 com.example.androidlearn:aidl。
先说在前面 : 我们需要保证,在客户端和服务端中都有我们需要用到的所有.aidl 文件和其中涉及到的 .java 文件,因此不管在哪一端写的这些东西,写完之后我们都要把这些文件复制到另一端去。
服务端创建形式和同一app内相同,不同的地方如下:
让客户端和服务端的AIDL保持一致,重构之后,也会生成对应的java文件,这样是为了让客户端可以引用到服务端aidl文件的方法。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_aidlclient_main); findViewById(R.id.aidl_client_btn).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); ComponentName componentName = new ComponentName("com.example.aidl_servier", "com.example.aidl_servier.MyAIDLService"); intent.setComponent(componentName); bindService(intent,serviceConnection,BIND_AUTO_CREATE); } }); }
由于服务端与客户端在不通的应用,要想绑定服务,使用intent的setComponment方法
自己误区的地方:在同一工程不同应用,通过setComponent可以启动工程的其他应用。以为其他Service不需要先运行,setComponent也能这样启动呢,其实对于Service不是这样。如果是这样,我不经过你允许就调起后台全部服务,岂不是崩溃了。
所以必须先启动服务端。
AIDL默认支持的数据类型都是可序列化的。
为什么AIDL的传递的数据都要支持序列化?
由于不同的进程有着不同的内存区域,并且它们只能访问自己的那一块内存区域,目标进程根本不能访问源进程的内存,所以我们必须将要传输的数据转化为能够在内存之间流通的形式。这个转化的过程就叫做序列化与反序列化。
简单来说是这样的:比如现在我们要将一个对象的数据从客户端传到服务端去,我们就可以在客户端对这个对象进行序列化的操作,将其中包含的数据转化为序列化流,然后将这个序列化流传输到服务端的内存中去,再在服务端对这个数据流进行反序列化的操作,从而还原其中包含的数据——通过这种方式,我们就达到了在一个进程中访问另一个进程的数据的目的。
如果想要自定义数据类型,进行AIDL通信,就需要对类型进行序列化,而通常,在我们通过AIDL进行跨进程通信的时候,选择的序列化方式是实现 Parcelable 接口。
默认实现Parcelable 接口代码如下:
public class Student implements Parcelable { protected Student(Parcel in) { } 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) { } }
但是请注意,这里有一个坑:上述默认生成的序列化类的对象只支持为 in 的定向 tag
为什么呢?因为默认生成的类里面只有 writeToParcel() 方法,而如果要支持为 out 或者 inout 的定向 tag 的话,还需要实现 readFromParcel() 方法——而这个方法其实并没有在 Parcelable 接口里面,所以需要我们自己写。后面数据流向有解释。
为Student类添加姓名和年龄信息。
完整的Student类兑现的代码是这样的:
package com.example.aidl_servier; import android.os.Parcel; import android.os.Parcelable; public class Student implements Parcelable { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } 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; } protected Student(Parcel in) { // 从序列化里解析成员变量 name = in.readString(); age = in.readInt(); } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } 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.writeInt(age); } public void readFromParcel(Parcel parcel) { this.name = parcel.readString(); this.age = parcel.readInt(); } }
Parcelable序列化都是标准样式,实际上就做了两件事:
1、将Student数据分别写入到序列化对象Parcel里
2、从序列化对象Parcel里构建出Student对象
至此,关于AIDL中非默认支持数据类型的序列化操作就完成了。
对于同一app下 AIDL使用自定义数据类型通信就不展示了。
主要针对不同app,这个看懂,同一app更不在话下。
上一节形式创建Student序列化类。
同样右键的方式创建AIDL文件,
// Student.aidl
package com.example.aidl_servier;
// Declare any non-default types here with import statements
interface Student {
/**
* 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);
}
将以上内容删除,改造成如下内容:
// Student.aidl
package com.example.aidl_servier;
// Declare any non-default types here with import statements
parcelable Student;
注意: 一定要确保自定义序列化类型数据和对应的aidl文件的包类名一致
解决类重复的问题
先定义了Student.java,当再定义Student.aidl 时,若两者处于同一包下,那么将无法创建Student.aidl文件。
分几种方法解决:
创建IMyserver.aidl文件 使用自定义数据类型
// IMyServer.aidl
package com.example.aidl_servier;
import com.example.aidl_servier.Student; //----------------(1)
interface IMyServer {
void getStudentInfo(int age, in Student student);//------------(2)
}
与之前创建Service逻辑相同
public class MyAIDLService extends Service { private static final String TAG = "MyAIDLService"; IMyServer.Stub myBinder = new IMyServer.Stub(){ @Override public void getStudentInfo(int age, Student student) throws RemoteException { Log.d(TAG, student.getName() + " in server"); } }; public MyAIDLService() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. return myBinder; } }
和之前说的相同,把所有的.aidl文件和涉及到的.java 文件从服务端复制到客户端去。都要保证这些文件的包类名和服务端的包类名保证相同。否则会会报 Symbol not found 错误。AIDL文件是整个复制过去的,所以aidl对应的包名是相同的。对于Student.java 客户端和服务端的包名是不同的,我们可以在客户端新建包名(和服务端相同),然后把服务端的Student.java赋值过去。
为什么这样做呢?
客户端的IMServer.aidl是从服务端复制过去的
构建之后,生成的.IMyServer.java如下
导入的是 com.example.aidl_servier.Student student
如果客户端没有定义Student,肯定会 Symbol not found 错误。所以涉及到.java文件也要复制过去。要和生成的.java文件引入保持一致,也就是包名相同嘛。
客户端业务和之前相同
package com.example.aidl_client; 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 com.example.aidl_servier.IMyServer; import com.example.aidl_servier.Student; public class AIDLClientMainActivity extends AppCompatActivity { private static final String TAG = "AIDLClientMainActivity"; ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d(TAG, "onServiceConnected: "); IMyServer iMyServer = IMyServer.Stub.asInterface(service); try { // iMyServer.say("hello server"); iMyServer.getStudentInfo(2,new Student("xiaoHong",18)); Log.d(TAG, "onServiceConnected: 成功通信"); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_aidlclient_main); findViewById(R.id.aidl_client_btn).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); ComponentName componentName = new ComponentName("com.example.aidl_servier", "com.example.aidl_servier.MyAIDLService"); intent.setComponent(componentName); bindService(intent,serviceConnection,BIND_AUTO_CREATE); } }); } }
运行结果如下
回顾一下常用的方法调用方式:
public void getStudentInfo(int age, Student student) {
student.setName("modify");
}
形参为:int 类型;Student类型;
在同一进程里,当调用该方法时,传入Student引用,方法里对Student成员变量进行了更改,方法调用结束后,调用者持有的Student引用所指向的对象其内容已经更改了。而对于int 类型,方法里却无法更改。
上述涉及到了经典问题:传值与传址。
而对于不同的进程,当客户端调用getStudentInfo(xx)方法时,虽然看起来是直接调用服务端的方法,实际上是底层中转了数据,因此当初传入Student,返回来的已经不是同一个Student引用。
因此,AIDL 规定了数据流方向。
之前又说到 参数类型前面有in关键字。这个是定向tag,表示了在跨进程通信中数据的流向,数据流向是针对在客户端中的那个传入方法的对象而言的,有三种类型
另外,Java 中的基本类型和 String ,CharSequence 的定向 tag 默认是 in,不用标注 。还有,请注意,请不要滥用定向 tag ,而是要根据需要选取合适的——要是不管三七二十一,全都一上来就用 inout ,等工程大了系统的开销就会大很多——因为排列整理参数的开销是很昂贵的。
为测试它们的差异,分别写三个方法:
package com.fish.myapplication;
import package com.example.aidl_servier.Student;
interface IMyServer {
void getStudentInfo(int age, in Student student);
void getStudentInfo2(int age, out Student student);
void getStudentInfo3(int age, inout Student student);
}
基本数据类型如 int、String 默认是数据流类型是: in,不用刻意标注。
服务端实现方法:
IMyServer.Stub myBinder = new IMyServer.Stub(){ @Override public void say(String word) throws RemoteException { Log.d(TAG, "say: "+word); } @Override public void getStudentInfo(int age, Student student) throws RemoteException { Log.d(TAG, "student name:" + student.getName() + " in server in getStudentInfoIn"); student.setName("change name getStudentInfoIn"); } @Override public void getStudentInfo2(int age, Student student) throws RemoteException { Log.d(TAG, "student name:" + student.getName() + " in server in getStudentInfoOut"); student.setName("change name getStudentInfoOut"); } @Override public void getStudentInfo3(int age, Student student) throws RemoteException { Log.d(TAG, "student name:" + student.getName() + " in server in getStudentInfoInout"); student.setName("change name getStudentInfoInout"); } };
将Student name 打印出来,并更改name 内容。
客户端调用服务端方法:
ServiceConnection serviceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.d(TAG, "onServiceConnected: "); IMyServer iMyServer = IMyServer.Stub.asInterface(service); try { // iMyServer.say("hello server"); Student student = new Student("xiaoming", 18); Log.d(TAG, "student name:" + student.getName() + " in client before getStudentInfoIn"); iMyServer.getStudentInfo(2, student); Log.d(TAG, "student name:" + student.getName() + " in client after getStudentInfoIn"); Student student2 = new Student("xiaoming", 18); Log.d(TAG, "student name:" + student2.getName() + " in client before getStudentInfoOut"); iMyServer.getStudentInfo2(2, student2); Log.d(TAG, "student name:" + student2.getName() + " in client after getStudentInfoOut"); Student student3 = new Student("xiaoming", 18); Log.d(TAG, "student name:" + student3.getName() + " in client before getStudentInfoInout"); iMyServer.getStudentInfo3(2, student3); Log.d(TAG, "student name:" + student3.getName() + " in client after getStudentInfoInout"); } catch (RemoteException e) { e.printStackTrace(); } }
构造Student 对象,并分别打印调用服务端方法前后Student name名字。
当编译的时候,发现编译不过,还需要在Student.java 里添加方法:
public Student() {}
public void readFromParcel(Parcel parcel) {
this.name = parcel.readString();
this.age = parcel.readInt();
}
运行结果如下:
总结一下规律:
1、使用 in 修饰Student,服务端收到Student的内容,更改name后,客户端收到Student,其name 并没有改变。表示数据流只能从客户端往服务端传递。
2、使用 out 修饰Student,服务端并没有收到Student的内容,更改name后,客户端收到Student,其name 已经改变。表示数据流只能从服务端往客户端传递。
3、使用 inout 修饰Student,服务端收到Student的内容,更改name后,客户端收到Student,其name 已经改变。表示数据流能在服务端和客户端间传递。
AIDL 文件最终生成.java 文件,因此在该文件里找答案。
当使用 in 修饰时:
对于客户端,将Student数据写入序列化对象。
if ((student!=null)) {
_data.writeInt(1);
student.writeToParcel(_data, 0);
}
对于服务端,并没有将Student写入回复的序列化对象。
当使用 out 修饰时
对于客户端,没有将Student数据写入序列化对象。
对于服务端,将Student写入回复的序列化对象。
_arg1 = new com.fish.myapplication.service.Student();
this.getStudentInfoOut(_arg0, _arg1);
reply.writeNoException();
if ((_arg1!=null)) {
reply.writeInt(1);
//写入reply
_arg1.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
当使用 inout 修饰时
实际上就是 in out 的结合。
参考:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。