IPC之AIDL从认识到实战

目录

前言

什么是AIDL?

为什么要设计出这样一种语言?它能帮助我们干什么?

还有其他方法能实现跨进程通信吗?相较于别的方法AIDL有什么优势呢?

AIDL的相关语法

Java与AIDL的不同之处

AIDL默认支持的数据类型:

定向tag

AIDL的分类

AIDL我们该如何使用呢?

1.如何编写AIDL文件?

创建AIDL文件

AIDL文件的内容

AIDL的编译产物

2.如何使用AIDL

移植相关文件

编写服务端代码

编写客户端代码

AIDL文件是怎么工作的?

结合客户端和服务端的代码分析AIDL文件

客户端

服务端


前言

为什么要写这一篇笔记?

距离上一次写笔记,好像过去了一年多的时间,这一年多的时间是繁忙的,忙着加班加班加班,牛马大概就是这样,但也接触了很多新的东西,也成长了很多,这是学生时代体验不到的另一种生活,忙碌但也充实。回归正题,为什么要写这一篇笔记呢?因为我太多纸质的笔记了,但每一个笔记本最后都沦为和草稿纸共用的下场,导致当时对一些知识点理解的笔记早已不翼而飞,所以想了想还是整理成博客吧,CSDN才应该是我笔记的最终归属地555...

好了,接下来言归正传。

什么是AIDL?

AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言。

所以简而言之,AIDL就是一种语言。

为什么要设计出这样一种语言?它能帮助我们干什么?

设计这门语言的初衷是为了实现跨进程通信(IPC)

(进程是一个独立的资源分配单元,不同进程之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源,但不同的进程需要进行信息的交互和状态的传递等,所以需要有一个方法来实现进程之间的通信)

设计的目的google官方有明确说明: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service,这就说明是为多线程,多客户端并发访问设计的,

AIDL可以帮助我们在一个进程上访问另一个进程中的数据,甚至调用一些特定的方法。

还有其他方法能实现跨进程通信吗?相较于别的方法AIDL有什么优势呢?

Messenger: Messenger的核心就是Message和Handler来进行线程间通信,因为Messenger进行跨进程通信时请求队列是同步进行的(Messenger,service收到的请求是放在Handler的MessageQueue里面,Handler大家都用过,它需要绑定一个Thread,然后不断poll message执行相关操作,这个过程是同步执行的),无法并发执行,但是使用AIDL的时候,service端每收到一个client端的请求时,就会启动一个线程(非主线程)去执行相应的操作。所以在要求多进程的情况下Messenger是不适用的。

BroadcastReceiver:BroadcastReceiver它占用的系统资源比较多,如果进程间频繁调动,使用AIDL显然更可取。

ContentProvider : ContentProvider只是将自己的数据库暴露出去,别的应用app可以获取到数据,但是获取的这个数据并不是实时的,所以用来跨进程通信并不可取。

Socket : 它分为两种TCP协议和UDP协议,实际编码过程中一般不会用到

TCP(Transmission Control Protocol)是传输控制协议,它的连接需要三次握手,断开连接需要四次挥手。它具有建立连接,数据无限制,速度慢,但是可靠的特点。他提供超时重传机制,所以很稳定。

UDP(User Datagram Protocol)是用户数据报协议,它具有不建立连接通道,数据有限制,不可靠,速度快等特点。虽然UDP的效率更高,但是数据的传输是不可靠的,不能保证数据能够正确的传输。

文件共享: 文件共享是将对象序列化后保存到文件中,再通过反序列化读取文件中的对象,此方法对文件的格式没有具体要求,可以是xml、json、txt等。常见的有使用ObjectInputStream和ObjectOutputStream存取对象。但是因为文件共享存在并发读写的问题,所以有很大的局限性,比如读取的数据不完整或者读取的数据并不是最新的,文件共享适合在对数据同步要求不高的进程间通信,并且要妥善处理并发读/写的问题。

现在让我们正式开始了解AIDL吧~~~

AIDL的相关语法

从上面我们知道,AIDL是一门语法,并且和Java的语法相似,其下主要是与Java语法的不同之处

Java与AIDL的不同之处

1. 文件类型:AIDL的文件后缀是.aidl , java的文件后缀是.java

2. 导包:AIDL除了默认支持的数据类型不需要导包以外,其他数据类型都是需要导包的,哪怕在同一个包下,但是Java在同一个包下时就不需要导包

eg: 在com.carrie.demo包下有三个文件Cat.java,  AnimalManager.aidl,  AnimalManager.java ,如果在需要在AnimalManager.aidl文件中使用Cat对象,那么,必须通过import com.carrie.demo.Cat导入Cat对象的包,但是因为AnimalManager.java和Cat.java在同一目录下,所以不需要导包

AIDL默认支持的数据类型:

1. Java的八种数据类型:boolean、byte、short、int、long、float、double、char

2. String类型

3. CharSequence类型

4. List类型:List类型可以使用泛型,但是List中的所有元素的数据类型必须是aidl默认支持的数据类型,或者是其他AIDL生成的接口,或者是定义的parcelable

5. Map类型:Map类型不可以使用泛型,但是Map中的所有元素的数据类型必须是aidl默认支持的数据类型,或者是其他aidl生成的接口,或者是定义的parcelable

定向tag

Android官方文档对定向tag是这么介绍的

All non-primitive parameters require a directional tag indicating which way the data goes . Either in , out , or inout . Primitives are in by default , and connot be otherwise .

所有的非基本参数都需要一个定向tag来定义数据的流通方式,例如in、out、intout.并且基本参数的定向tag默认并且只能是in

in表示数据从客户端流向服务端,

out表示数据从服务端流向客户端

inout表示数据在客户端和服务端之间相互流动

定向tag表示了在跨进程通信中的数据流向。其中数据流向是针对客户端传入方法中的对象而言的,tag为in表示服务端会收到那个对象的完整数据,但是客户端的那个对象不会因为服务端对传入参数的修改而发生改变;tag为out表示服务端会收到那个对象的空对象,服务端对空对象的任何修改客户端都会进行同步修改;tag为inout表示服务端会收到那个对象的完整数据,并且客户端会同步服务端对该对象的修改。

另外,Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in但注意不要全部都使用inout,而是需要根据需要来进行选择。如果全部使用inout系统的开销就会大很多——因为排列整理参数的开销是很昂贵的。

AIDL的分类

AIDL主要分为两大类:一种是为了定义parcelable对象,以供其他AIDL文件使用AIDL文件中的非默认支持的数据类型;一种是定义接口来方便系统实现跨进程通信。

//Animal.aidlpackage com.carrie.demo//这个文件的作用是声明一个序列化对象,以供其他aidl文件使用
parcelable animal;
//AnimalManager.aidlpackage com.carrie.demo
//因为声明的Animal对象不是aidl默认支持的数据类型,所以需要导入
import com.carrie.demo.Animal;interface AnimalManager{//方法中的参数除了Java的八种基本类型,string类型,CharSequence类型,其他类型都需要在前面加上定向tag,具体加什么tag依情况而定void setAnimalName(string name,in Animal animal);void setAnimalCount(in Animal animal,int count);//在返回值前不需要加任何tag,无论什么数据类型Animal getAnimal();String getAnimalName();List<Animal> getAnimalList();

如果在AIDL定义的方法中不包含不默认支持的数据类型,那么只需要编写一个AIDL文件,如果AIDL定义的方法中包含N个不默认支持的数据类型,那么需要编写N+1个AIDL文件。

AIDL我们该如何使用呢?

1.如何编写AIDL文件?

创建AIDL文件

Android Studio本身支持创建AIDL文件,先创建名为IMyAidlInterface的AIDL文件。
在Module上单击右键新建AIDL file:

b63b8900b33b48e080cb8279033d87b2.png

c8aa5b6547f240f59a7f3f66a3204f1e.png

 

72eb31a717eb4cd9a7b9fdb4addd5096.png

由于是第一次创建AIDL文件,AS还帮我们创建了aidl包作为目录结构

AIDL文件的内容

我们打开刚创建的IMyAidlInterface.aidl文件,内容如下:

// IMyAidlInterface.aidl
package com.example.aidl;// Declare any non-default types here with import statementsinterface 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);
}

其中basicTypes(xx)方法是自动生成的,用来指导我们如何编写方法,可以去掉。
IMyAidlInterface 接口里声明的方法为Server端暴露给外部调用的方法,先为Server添加方法:

// IMyAidlInterface.aidl
package com.example.aidl;// Declare any non-default types here with import statementsinterface IMyAidlInterface {//所有的返回值前都不需要加任何东西,不管是什么数据类型List<Book> getBooks();//传参时除了Java基本类型以及String,CharSequence之外的类型//都需要在前面加上定向tag,具体加什么量需而定void addBook(in Book book);
}

切换到Project模式,点击编译:发现Book类没有定义

e6a4c89a523b4371aedda707c20a6d81.png

所以再定义一个Book类

4ce2c95dd2964ec79caadd57786089f0.png

// Book.aidl
package com.example.aidl;//这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用parcelable Book;
package com.example.aidl;import android.os.Parcel;
import android.os.Parcelable;public class Book implements Parcelable{public String getName() {return name;}public void setName(String name) {this.name = name;}public int getPrice() {return price;}public void setPrice(int price) {this.price = price;}private String name;private int price;public Book(){}public Book(Parcel in) {name = in.readString();price = in.readInt();}public static final Creator<Book> CREATOR = new Creator<Book>() {@Overridepublic Book createFromParcel(Parcel in) {return new Book(in);}@Overridepublic Book[] newArray(int size) {return new Book[size];}};@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeString(name);dest.writeInt(price);}/*** 参数是一个Parcel,用它来存储与传输数据* @param dest*/public void readFromParcel(Parcel dest) {//注意,此处的读值顺序应当是和writeToParcel()方法中一致的name = dest.readString();price = dest.readInt();}//方便打印数据@Overridepublic String toString() {return "name : " + name + " , price : " + price;}
}

AIDL的编译产物

在我们实际编写客户端和服务端代码的过程中,真正协助我们工作的其实是这个生成的.java文件,而 .aidl 文件从头到尾都没有出现过。

0451789a0335440d9a70af60138c320e.png

这里我们暂时不分析这个文件,具体分析内容见后面段落-----“AIDL文件是怎样工作的?”

2.如何使用AIDL

基本的操作流程就是:在服务端实现AIDL中定义的方法接口的具体逻辑,然后在客户端调用这些方法接口,从而达到跨进程通信的目的。

移植相关文件

服务端和客户端是两个不同的进程,在客户端和服务端中都有我们需要用到的 .aidl 文件和其中涉及到的 .java 文件,因此不管在哪一端写的这些东西,写完之后我们都要把这些文件复制到另一端去。

编写服务端代码

package com.example.aidl;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;import androidx.annotation.Nullable;import java.util.ArrayList;
import java.util.List;/*** 服务端的AIDLService.java*/
public class AIDLService extends Service {public final String TAG = this.getClass().getSimpleName();//包含Book对象的listprivate List<Book> mBooks = new ArrayList<>();//由AIDL文件生成的IMyAidlInterfaceprivate final IMyAidlInterface.Stub mBookManager = new IMyAidlInterface.Stub() {@Overridepublic List<Book> getBooks() throws RemoteException {synchronized (this) {Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());if (mBooks != null) {return mBooks;}return new ArrayList<>();}}@Overridepublic void addBook(Book book) throws RemoteException {synchronized (this) {if (mBooks == null) {mBooks = new ArrayList<>();}if (book == null) {Log.e(TAG, "Book is null in In");book = new Book();}//尝试修改book的参数,主要是为了观察其到客户端的反馈book.setPrice(2333);if (!mBooks.contains(book)) {mBooks.add(book);}//打印mBooks列表,观察客户端传过来的值Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());}}};@Overridepublic void onCreate() {super.onCreate();Book book = new Book();book.setName("Android开发艺术探索");book.setPrice(28);mBooks.add(book);   }@Nullable@Overridepublic IBinder onBind(Intent intent) {Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));return mBookManager;}
}

分析一下以上代码,主要分为三块:

第一块是初始化。在 onCreate() 方法里面我进行了一些数据的初始化操作。

第二块是重写 IMyAidlInterface.Stub()中的方法。在这里面提供AIDL里面定义的方法接口的具体实现逻辑。

第三块是重写 onBind() 方法。在里面返回写好的 IMyAidlInterface.Stub()

编写客户端代码

package com.example.aidl;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.util.Log;
import android.view.View;
import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;import java.util.List;/*** 客户端的AIDLActivity.java*/
public class AIDLActivity extends AppCompatActivity {//由AIDL文件生成的Java类private IMyAidlInterface mBookManager = null;//标志当前与服务端连接状况的布尔值,false为未连接,true为连接中private boolean mBound = false;//包含Book对象的listprivate List<Book> mBooks;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}/*** 按钮的点击事件,点击之后调用服务端的addBookIn方法** @param view*/public void addBook(View view) {//如果与服务端的连接处于未连接状态,则尝试连接if (!mBound) {attemptToBindService();Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();return;}if (mBookManager == null) return;Book book = new Book();book.setName("APP研发录In");book.setPrice(30);try {mBookManager.addBook(book);Log.e(getLocalClassName(), book.toString());} catch (RemoteException e) {e.printStackTrace();}}/*** 尝试与服务端建立连接*/private void attemptToBindService() {Intent intent = new Intent();intent.setAction("com.carrie.aidl");intent.setPackage("com.carrie.ipcserver");bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);}@Overrideprotected void onStart() {super.onStart();if (!mBound) {attemptToBindService();}}@Overrideprotected void onStop() {super.onStop();if (mBound) {unbindService(mServiceConnection);mBound = false;}}private ServiceConnection mServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.e(getLocalClassName(), "service connected");mBookManager = IMyAidlInterface.Stub.asInterface(service);mBound = true;if (mBookManager != null) {try {mBooks = mBookManager.getBooks();Log.e(getLocalClassName(), mBooks.toString());} catch (RemoteException e) {e.printStackTrace();}}}@Overridepublic void onServiceDisconnected(ComponentName name) {Log.e(getLocalClassName(), "service disconnected");mBound = false;}};
}

AIDL文件是怎么工作的?

根据以上内容,我们可以发现,在整个服务器和客户端通信的过程中,其实并没有使用到我们编写的IMyAidlInterface.aidl文件,而是使用的它自动生成的IMyAidlInterface.java文件。

那我们编写的.aidl文件到底有何用处呢?它的作用不会就是只用来生成对应的.java文件吧?

我们来做一个实验:

我们将/aidl/com.example.aidl中的IMyAidlInterface.aidl文件删除,然后将build目录下自动生成的IMyAidlInterface.java文件拷贝到/java/com.example.aidl目录下运行,发现通信仍然正常

925eb4ec1ba94bbd98c6251e5e897a1e.png

所以我们编写的.aidl文件它的作用就是用来生成对应的.java文件,AIDL语言只是在简化我们写这个 .java 文件的工作而已,而要研究AIDL是如何帮助我们进行跨进程通信的,其实就是研究这个生成的 .java 文件是如何工作的。

结合客户端和服务端的代码分析AIDL文件

让我们再看下服务端、客户端、AIDL的代码:

AIDL:

interface IMyAidlInterface {List<Book> getBooks();void addBook(in Book book);
}

服务端:

public class AIDLService extends Service {//获取IMyAidlInterface.Stub对象private final IMyAidlInterface.Stub mBookManager = new IMyAidlInterface.Stub() {@Overridepublic List<Book> getBooks() throws RemoteException {...方法的具体实现}@Overridepublic void addBook(Book book) throws RemoteException {...方法的具体实现}};@Overridepublic void onCreate() {super.onCreate();...}@Nullable@Overridepublic IBinder onBind(Intent intent) {//返回IMyAidlInterface.Stub对象return mBookManager;}
}

客户端:

private IMyAidlInterface mBookManager = null;private ServiceConnection mServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) mBookManager = IMyAidlInterface.Stub.asInterface(service);...}@Overridepublic void onServiceDisconnected(ComponentName name) {...}
};public void addBook(View view) {...mBookManager.addBook(book);
}

结合看这三部分的代码,会有以下疑问:

 IMyAidlInterface是一个接口,接口意味着什么?方法都没有具体实现。在客户端代码中我们调用的addBook()方法必然来自于 IMyAidlInterface的实现类。看以上代码,方法的实现在服务端,方法的调用在客户端,方法的实现与调用在两个进程中,但好像契合的像在一个类中完成实现与调用一样。这是怎么办到的呢?奥秘应该就藏在生成的这个IMyAidlInterface.java文件中吧,带着此问题我们通过连携客户端和服务端的代码一起分析一下

生成的这个.java文件的具体内容如下:

0451789a0335440d9a70af60138c320e.png

/** This file is auto-generated.  DO NOT MODIFY.*/
package com.example.aidl;
public interface IMyAidlInterface extends android.os.IInterface
{/** Default implementation for IMyAidlInterface. */public static class Default implements com.example.aidl.IMyAidlInterface{//所有的返回值前都不需要加任何东西,不管是什么数据类型@Override public java.util.List<com.example.aidl.Book> getBooks() throws android.os.RemoteException{return null;}//传参时除了Java基本类型以及String,CharSequence之外的类型//都需要在前面加上定向tag,具体加什么量需而定@Override public void addBook(com.example.aidl.Book book) throws android.os.RemoteException{}@Overridepublic android.os.IBinder asBinder() {return null;}}/** Local-side IPC implementation stub class. */public static abstract class Stub extends android.os.Binder implements com.example.aidl.IMyAidlInterface{/** Construct the stub at attach it to the interface. */public Stub(){this.attachInterface(this, DESCRIPTOR);}/*** Cast an IBinder object into an com.example.aidl.IMyAidlInterface interface,* generating a proxy if needed.*/public static com.example.aidl.IMyAidlInterface asInterface(android.os.IBinder obj){if ((obj==null)) {return null;}android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.example.aidl.IMyAidlInterface))) {return ((com.example.aidl.IMyAidlInterface)iin);}return new com.example.aidl.IMyAidlInterface.Stub.Proxy(obj);}@Override public android.os.IBinder asBinder(){return this;}@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{java.lang.String descriptor = DESCRIPTOR;switch (code){case INTERFACE_TRANSACTION:{reply.writeString(descriptor);return true;}}switch (code){case TRANSACTION_getBooks:{data.enforceInterface(descriptor);java.util.List<com.example.aidl.Book> _result = this.getBooks();reply.writeNoException();reply.writeTypedList(_result);return true;}case TRANSACTION_addBook:{data.enforceInterface(descriptor);com.example.aidl.Book _arg0;if ((0!=data.readInt())) {_arg0 = com.example.aidl.Book.CREATOR.createFromParcel(data);}else {_arg0 = null;}this.addBook(_arg0);reply.writeNoException();return true;}default:{return super.onTransact(code, data, reply, flags);}}}private static class Proxy implements com.example.aidl.IMyAidlInterface{private android.os.IBinder mRemote;Proxy(android.os.IBinder remote){mRemote = remote;}@Override public android.os.IBinder asBinder(){return mRemote;}public java.lang.String getInterfaceDescriptor(){return DESCRIPTOR;}//所有的返回值前都不需要加任何东西,不管是什么数据类型@Override public java.util.List<com.example.aidl.Book> getBooks() throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();java.util.List<com.example.aidl.Book> _result;try {_data.writeInterfaceToken(DESCRIPTOR);boolean _status = mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);if (!_status) {if (getDefaultImpl() != null) {return getDefaultImpl().getBooks();}}_reply.readException();_result = _reply.createTypedArrayList(com.example.aidl.Book.CREATOR);}finally {_reply.recycle();_data.recycle();}return _result;}//传参时除了Java基本类型以及String,CharSequence之外的类型//都需要在前面加上定向tag,具体加什么量需而定@Override public void addBook(com.example.aidl.Book book) throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();try {_data.writeInterfaceToken(DESCRIPTOR);if ((book!=null)) {_data.writeInt(1);book.writeToParcel(_data, 0);}else {_data.writeInt(0);}boolean _status = mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);if (!_status) {if (getDefaultImpl() != null) {getDefaultImpl().addBook(book);return;}}_reply.readException();}finally {_reply.recycle();_data.recycle();}}public static com.example.aidl.IMyAidlInterface sDefaultImpl;}static final int TRANSACTION_getBooks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);public static boolean setDefaultImpl(com.example.aidl.IMyAidlInterface impl) {// Only one user of this interface can use this function// at a time. This is a heuristic to detect if two different// users in the same process use this function.if (Stub.Proxy.sDefaultImpl != null) {throw new IllegalStateException("setDefaultImpl() called twice");}if (impl != null) {Stub.Proxy.sDefaultImpl = impl;return true;}return false;}public static com.example.aidl.IMyAidlInterface getDefaultImpl() {return Stub.Proxy.sDefaultImpl;}}public static final java.lang.String DESCRIPTOR = "com.example.aidl.IMyAidlInterface";//所有的返回值前都不需要加任何东西,不管是什么数据类型public java.util.List<com.example.aidl.Book> getBooks() throws android.os.RemoteException;//传参时除了Java基本类型以及String,CharSequence之外的类型//都需要在前面加上定向tag,具体加什么量需而定public void addBook(com.example.aidl.Book book) throws android.os.RemoteException;
}

客户端

然后先看客户端是如何获取到这个IMyAidlInterface对象的

private ServiceConnection mServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {...mBookManager = IMyAidlInterface.Stub.asInterface(service);...}

我们先看一下IMyAidlInterface.Stub.asInterface(service)这个方法做了什么

 /*** Cast an IBinder object into an com.example.aidl.IMyAidlInterface interface,* generating a proxy if needed.*/public static com.example.aidl.IMyAidlInterface asInterface(android.os.IBinder obj){if ((obj==null)) {return null;}//DESCRIPTOR = "com.example.aidl.IMyAidlInterface",搜索本地是否已經有可用的对象了,如果有就将其返回android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.example.aidl.IMyAidlInterface))) {return ((com.example.aidl.IMyAidlInterface)iin);}//如果本地没有的话就新建一个返回return new com.example.aidl.IMyAidlInterface.Stub.Proxy(obj);}

接下来我们去看com.example.aidl.IMyAidlInterface.Stub.Proxy(obj)这个里面做了什么

private static class Proxy implements com.example.aidl.IMyAidlInterface{private android.os.IBinder mRemote;Proxy(android.os.IBinder remote){mRemote = remote;}@Override public android.os.IBinder asBinder(){return mRemote;}public java.lang.String getInterfaceDescriptor(){return DESCRIPTOR;}@Override public java.util.List<com.example.aidl.Book> getBooks() throws android.os.RemoteException{...}@Override public void addBook(com.example.aidl.Book book) throws android.os.RemoteException{...}public static com.example.aidl.IMyAidlInterface sDefaultImpl;}

基本上可以看出客户端最终通过这个Proxy类与服务端进行通信。

我们来看一下getBooks()这个方法里干了什么

 @Override public java.util.List<com.example.aidl.Book> getBooks() 
throws android.os.RemoteException {//_data用来存储流向服务端的数据流,//_reply用来存储服务端流回客户端的数据流android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();java.util.List<com.example.aidl.Book> _result;try {_data.writeInterfaceToken(DESCRIPTOR);//调用 transact() 方法将方法id和两个 Parcel 容器传过去boolean _status = mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);if (!_status) {if (getDefaultImpl() != null) {return getDefaultImpl().getBooks();}}_reply.readException();//从_reply中取出服务端执行方法的结果_result = _reply.createTypedArrayList(com.example.aidl.Book.CREATOR);}finally {_reply.recycle();_data.recycle();}//返回结果return _result;}
  • 关于 _data 与 _reply 对象:一般来说,我们会将方法的传参的数据存入_data 中,而将方法的返回值的数据存入 _reply 中——在没涉及定向 tag 的情况下。如果涉及了定向 tag ,情况将会变得稍微复杂些,具体是怎么回事请参见这篇博文:你真的理解AIDL中的in,out,inout么?
  • 关于 Parcel :简单的来说,Parcel 是一个用来存放和读取数据的容器。我们可以用它来进行客户端和服务端之间的数据传输,当然,它能传输的只能是可序列化的数据。具体 Parcel 的使用方法和相关原理可以参见这篇文章:Android中Parcel的分析以及使用
  • 关于 transact() 方法:这是客户端和服务端通信的核心方法。调用这个方法之后,客户端将会挂起当前线程,等候服务端执行完相关任务后通知并接收返回的 _reply 数据流。关于这个方法的传参,这里有两点需要说明的地方:
  • 方法 ID :transact() 方法的第一个参数是一个方法 ID ,这个是客户端与服务端约定好的给方法的编码,彼此一一对应。在AIDL文件转化为 .java 文件的时候,系统将会自动给AIDL文件里面的每一个方法自动分配一个方法 ID。
  • 第四个参数:transact() 方法的第四个参数是一个 int 值,它的作用是设置进行 IPC 的模式,为 0 表示数据可以双向流通,即 _reply 流可以正常的携带数据回来,如果为 1 的话那么数据将只能单向流通,从服务端回来的 _reply 流将不携带任何数据。
    注:AIDL生成的 .java 文件的这个参数均为 0。

具体的就不在这里继续阐述了,因为它涉及到Binder里比较底层的东西,先埋个坑,如果以后有时间的话再补充进来~ 坑坑坑 下面直接上总结吧

Proxy 类的方法里面一般的工作流程:

  • 1,生成 _data 和 _reply 数据流,并向 _data 中存入客户端的数据。
  • 2,通过 IBinder service调用 transact() 方法将它们传递给服务端,并请求服务端调用指定方法。
  • 3,接收 _reply 数据流,并从中取出服务端传回来的数据。

接下来让我们再从服务端来进行分析

服务端

首先从上面我们知道客户端是通过transact() 方法将它们传递给服务端,那服务端是通过什么来接收的呢?让我们接着往下看

首先我们在IMyAidlInterface.java这个文件中,可以看到有这么一个方法,如下:

@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{java.lang.String descriptor = DESCRIPTOR;switch (code){case INTERFACE_TRANSACTION:{...}}switch (code){case TRANSACTION_getBooks:{...}case TRANSACTION_addBook:{...}default:{return super.onTransact(code, data, reply, flags);}}}

通过它的方法名和传入的参数,路过的大爷应该都会猜一下,onTransact()这个方法是不是和客户端将数据传递给服务端调用的 transact() 方法有什么关系。

所以它会是服务端接收数据的那个方法吗?

当我们仔细去看这个方法里面的内容,它其实在传入参数后,进行了一个Switch判断,让我们看一下case TRANSACTION_getBooks里面都具体做了什么

case TRANSACTION_getBooks: {data.enforceInterface(DESCRIPTOR);//调用 this.getBooks() 方法,在这里开始执行具体的事务逻辑//result 列表为调用 getBooks() 方法的返回值java.util.List<com.lypeer.ipcclient.Book> _result = this.getBooks();reply.writeNoException();//将方法执行的结果写入 reply ,reply.writeTypedList(_result);return true;
}

直接调用服务端这边的具体方法实现,然后获取返回值并将其写入 reply 流,在执行完 return true 之后系统将会把 reply 流传回客户端

然后还是直接上总结~

服务端的一般工作流程:

  • 1,获取客户端传过来的数据,根据方法 ID 执行相应操作。
  • 2,将传过来的数据取出来,调用本地写好的对应方法。
  • 3,将需要回传的数据写入 reply 流,传回客户端。

总而言之,言而总之,学习AIDL不要把它看成一个新的东西,你要把它当成简化你代码工作量的工具,学习起来会有意思的多。

差不多到这里就结束了,实在是困了,如果以后还有要补充更详细的东西,会再进行更新~

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1535237.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

博弈美业系统实操:美业门店管理系统如何查看客户档案?美业SaaS系统源码

1.打开博弈美业App&#xff1b; 2.点击App下方【客户】&#xff0c;进入客户管理页&#xff1b; 3.找到想要查看的客户&#xff0c;点击进入客户详情页&#xff1b; 4.客户详情页可查看客户个人信息、个性标签、消费记录、回访记录等等详细信息。

RedisTemplate操作ZSet的API

文章目录 ⛄概述⛄常见命令有⛄RedisTemplate API❄️❄️ 向集合中插入元素&#xff0c;并设置分数❄️❄️向集合中插入多个元素,并设置分数❄️❄️按照排名先后(从小到大)打印指定区间内的元素, -1为打印全部❄️❄️获得指定元素的分数❄️❄️返回集合内的成员个数❄️❄…

026.(娱乐)魔改浏览器-任务栏图标右上角加提示徽章

一、目标&#xff1a; windows中&#xff0c;打开chromium&#xff0c;任务栏中会出现一个chromium的图标。我们的目标是给这个图标的右上角&#xff0c;加上"有1条新消息"的小提示图标&#xff0c;也叫徽章(badge)注意&#xff1a;本章节纯属娱乐&#xff0c;有需要…

钻机、塔吊等大型工程设备,如何远程维护、实时采集运行数据?

在建筑和工程领域&#xff0c;重型设备的应用不可或缺&#xff0c;无论是在道路与桥梁建设、高层建筑施工&#xff0c;还是在风电、石油等能源项目的开发中&#xff0c;都会用到塔吊、钻机等大型机械工程设备。 随着数字化升级、工业4.0成为行业发展趋势&#xff0c;为了进一步…

基于python+django+mysql+Nanodet检测模型的水稻虫害检测系统

博主介绍&#xff1a; 大家好&#xff0c;本人精通Java、Python、C#、C、C编程语言&#xff0c;同时也熟练掌握微信小程序、Php和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我有丰富的成品Java、Python、C#毕设项目经验&#xff0c;能够为学生提供各类…

Qt (16)【Qt 事件 —— Qt 事件简介 | 如何重写相关的 Event 函数】

阅读导航 引言一、事件介绍二、如何重写相关的 Event 函数1. 事件的处理简介2. 示例重写鼠标相关的 Event 函数&#xff08;1&#xff09;新建Qt项目&#xff0c;设计UI文件&#xff08;2&#xff09;新添加MyLabel类&#xff08;3&#xff09;重写enterEvent()方法和leaveEven…

果蔬识别系统架构+流程图

相关文章和代码 果蔬识别系统 果蔬识别系统优化&#xff08;1~5&#xff09; 架构图 流程图 初始化 识别流程 学习流程 同步流程 与初始化类似&#xff0c;只是同步只同步一个storeCode数据 删除数据流程 导入数据

三、k8s中的控制器的使用

一 什么是控制器 官方文档&#xff1a; 工作负载管理 | Kubernetes 控制器也是管理pod的一种手段 自主式pod&#xff1a;pod退出或意外关闭后不会被重新创建 控制器管理的 Pod&#xff1a;在控制器的生命周期里&#xff0c;始终要维持 Pod 的副本数目 Pod控制器是管理pod…

软件安装攻略:EmEditor编辑器下载安装与使用

EmEditor是一款在Windows平台上运行的文字编辑程序。EmEditor以运作轻巧、敏捷而又功能强大、丰富著称&#xff0c;得到许多用户的好评。Windows内建的记事本程式由于功能太过单薄&#xff0c;所以有不少用户直接以EmEditor取代&#xff0c;emeditor是一个跨平台的文本编辑器&a…

用SpringBoot进行阿里云大模型接口调用同步方法和异步方法

同步效果就不展示了,这里展示更常用的异步,多轮异步流式效果展示如下: 结果内容组合 1、同步版本环境准备以及代码 需要开通阿里大模型服务,如果没有开通服务,单独的去生成 key 是无效的。 阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台 生成你需要的 key 1、…

2.C++中程序的语法基础--关键字与分隔符

现在回过头来看上一篇中所写的程序&#xff1a; #include <bits/stdc.h> using namespace std; int main() {// 程序主体cout << "HelloWorld" << endl; return 0; } 我们会看到许多英文单词&#xff0c;像"include"、“using”&…

Vue路由二(嵌套多级路由、路由query传参、路由命名、路由params传参、props配置、<router-link>的replace属性)

目录 1. 嵌套(多级)路由2. 路由query传参3. 路由命名4. 路由params传参5. props配置6. <router-link>的replace属性 1. 嵌套(多级)路由 pages/Car.vue <template><ul><li>car1</li><li>car2</li><li>car3</li></ul…

【Java面试】第七天

&#x1f31f;个人主页&#xff1a;时间会证明一切. 目录 有三个线程T1,T2,T3如何保证顺序执行&#xff1f;依次执行start方法使用join使用CountDownLatch使用线程池使用CompletableFuture Spring Bean的生命周期是怎么样的&#xff1f;Autowired和Resource的关系&#xff1f;相…

读取CSV中文件报ArrayIndexOutOfBounds异常

序言 有个需求要将csv文件入库&#xff0c;之前测试的文件都是可以正常解析入库的&#xff0c;但新提供的一个csv文件读取的时候捕获的异常信息就总是提示&#xff1a;Index 1 out of bounds for length 1。 读取csv文件的方法 public static List<Map<String, Object…

8.6小波变换(Wavelet Transform)边缘检测

实验原理 由于OpenCV本身并不直接支持小波变换&#xff08;Wavelet Transform&#xff09;&#xff0c;我们需要借助一些技巧来实现它。一种常见的方法是利用离散余弦变换&#xff08;DCT&#xff09;或离散傅立叶变换&#xff08;DFT&#xff09;来近似实现小波变换的功能。但…

【机器学习(六)】分类和回归任务-LightGBM算法-Sentosa_DSML社区版

文章目录 一、算法概念二、算法原理&#xff08;一&#xff09;Histogram&#xff08;二&#xff09;GOSS1、信息增益2、近似误差 &#xff08;三&#xff09;EFB 三、算法优缺点&#xff08;一&#xff09;优点&#xff08;二&#xff09;缺点 四、LightGBM分类任务实现对比&a…

AI基础 L21 Quantifying Uncertainty and Reasoning with Probabilities III

Bayesian Networks 1 Bayesian Networks • A Bayesian Network (BN) represents the dependencies among variables and encodes the full joint probability distribution concisely. • A BN is a directed graph, where each node is annotated with probability informati…

[项目][WebServer][CGI机制 设计]详细讲解

目录 1.何为CGI机制&#xff1f;2.理解CGI机制3.CGI接口设计1.ProcessNonCgi2.ProcessCgi 1.何为CGI机制&#xff1f; CGI(Common Gateway Interface)是外部应用程序(CGI程序)与WEB服务器之间的接口标准&#xff0c;是在CGI程序和WEB服务器之间传递信息的过程 2.理解CGI机制 …

[XILINX] 正点原子ZYNQ7015开发板!ZYNQ 7000系列、双核ARM、PCIe2.0、SFPX2,性能强悍,资料丰富!

正点原子ZYNQ7015开发板&#xff01;ZYNQ 7000系列、双核ARM、PCIe2.0、SFPX2&#xff0c;性能强悍&#xff0c;资料丰富&#xff01; 正点原子Z15 ZYNQ开发板&#xff0c;搭载Xilinx Zynq7000系列芯片&#xff0c;核心板主控芯片的型号是XC7Z015CLG485-2。开发板由核心板&…

JAVA开源项目 在线视频教育平台 计算机毕业设计

本文项目编号 T 027 &#xff0c;文末自助获取源码 \color{red}{T027&#xff0c;文末自助获取源码} T027&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 新…