IPC之AIDL从认识到实战

news2024/9/29 17:39:16

目录

前言

什么是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.aidl

package com.carrie.demo

//这个文件的作用是声明一个序列化对象,以供其他aidl文件使用
parcelable animal;
//AnimalManager.aidl

package 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 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);
}

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

// IMyAidlInterface.aidl
package com.example.aidl;

// Declare any non-default types here with import statements

interface 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>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public 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();
    }

    //方便打印数据
    @Override
    public 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对象的list
    private List<Book> mBooks = new ArrayList<>();

    //由AIDL文件生成的IMyAidlInterface
    private final IMyAidlInterface.Stub mBookManager = new IMyAidlInterface.Stub() {
        @Override
        public 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<>();
            }
        }


        @Override
        public 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());
            }
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        Book book = new Book();
        book.setName("Android开发艺术探索");
        book.setPrice(28);
        mBooks.add(book);   
    }

    @Nullable
    @Override
    public 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对象的list
    private List<Book> mBooks;

    @Override
    protected 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);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!mBound) {
            attemptToBindService();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(mServiceConnection);
            mBound = false;
        }
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public 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();
                }
            }
        }

        @Override
        public 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() {
        @Override
        public List<Book> getBooks() throws RemoteException {
           ...方法的具体实现
        }
        @Override
        public void addBook(Book book) throws RemoteException {
           ...方法的具体实现
        }
    };
    @Override
    public void onCreate() {
        super.onCreate();
       ...
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
       //返回IMyAidlInterface.Stub对象
        return mBookManager;
    }
}

客户端:

private IMyAidlInterface mBookManager = null;

private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) 
        mBookManager = IMyAidlInterface.Stub.asInterface(service);
        ...
    }
    @Override
    public 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
    {
    }
    @Override
    public 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() {
        @Override
        public 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.coloradmin.cn/o/2136913.html

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

相关文章

怎么浏览URL的PDF文件呢

最近发现PDF文件网页端打开就是丑&#xff0c;不知道怎么办 1. 看着实在不舒服&#xff0c;用chorm的插件 然后原本本地用的也是2345pdf阅读器 2. 之后也下载了adobe pdf的桌面阅读器 2345打开是这个样子 这个是现在啦 如果要一些安装包什么的&#xff0c;评论见~ 最…

相机光学(三十八)——VCM(Voice Coil Motor)音圈马达

VCM&#xff08;Voice Coil Motor&#xff09;音圈马达 0.参考链接1.什么是音圈马达2.对焦&#xff08;变焦&#xff09;原理3.音圈马达结构4.音圈马达工作原理5.VCM 主要性能指标 0.参考链接 &#xff08;1&#xff09;Camera 模组之 VCM篇 &#xff08;2&#xff09;VCM基本…

08 vue3之认识bem架构及less sass 和scoped

bem架构 他是一种css架构 oocss 实现的一种 &#xff08;面向对象css&#xff09; &#xff0c;BEM实际上是block、element、modifier的缩写&#xff0c;分别为块层、元素层、修饰符层&#xff0c;element UI 也使用的是这种架构 1. BEM架构 1. 介绍 1. BEM是Block Element M…

PowerBI 关于FILTERS函数和VALUES函数

本人是powerbi新手&#xff0c;最近在使用Filters()函数和Values()函数时&#xff0c;有点不太明白它们之间的区别&#xff0c;u有时它们得到的结果是一样的&#xff0c;有时却不一样。 官方文档里&#xff0c;Filters()是表示返回直接作为筛选器应用到 columnName 的值 FILT…

MinIO - macOS上配置、Python调用

文章目录 安装配置 MinIO 服务Python 调用SDK 简介调用示例 安装配置 MinIO 服务 1、使用 brew 安装 MinIO 如果您之前使用 brew install minio 安装了MinIO服务器&#xff0c;那么我们建议您改为从 minio/stable/minio 重新安装。 brew install minio/stable/minio2、创建文…

YOLOv5/v8 + 双目相机测距

yolov5/v8双目相机测距的代码&#xff0c;需要相机标定 可以训练自己的模型并检测测距&#xff0c;都是python代码 已多次实验&#xff0c;代码无报错。 非常适合做类似的双目课题&#xff01; 相机用的是汇博视捷的双目相机&#xff0c;具体型号见下图。 用的yolov5是6.1版本的…

Spring Boot集成Akka remoting快速入门Demo

1.什么是Akka remoting&#xff1f; Akka-Remoting一种ActorSystem之间Actor对Actor点对点的沟通协议.通过Akka-Remoting来实现一个ActorSystem中的一个Actor与另一个ActorSystem中的另一个Actor之间的沟通 Akka Remoting限制&#xff1a; 不支持NAT&#xff08;Network Add…

使用Java实现一个简单的B树

1.B树简介 B树是一个搜索树&#xff0c;数据结构可以抽象成如二叉树一样的树&#xff0c;不过它有平衡、有序、多路的特点。 平衡&#xff1a;所有叶子节点都在同一层。有序&#xff1a;任一元素的左子树都小于它&#xff0c;右子树都大于它。多路&#xff1a;B树的每个节点最多…

深入链表的遍历——快慢指针算法(LeetCode——876题)

今天我们一起来学习一下一个快速遍历链表的方法 我们先来看看一道经典的需要遍历链表的题目 &#xff08;题目来自LeetCode&#xff09; 876. 链表的中间结点https://leetcode.cn/problems/middle-of-the-linked-list/ 给你单链表的头结点 head &#xff0c;请你找出并返回链…

C++多态 学习

目录 一、多态的概念 二、多态的实现 三、纯虚函数和多态类 四、多态的原理 一、多态的概念 多态&#xff1a;多态分为编译时多态(静态多态)和运行时多态(动态多态)。编译时多态主要是我们之前学过的函数重载和函数模板&#xff0c;他们在传不同类型的参数就可以调用不同的函…

diff 命令:文本比较

一、diff 命令简介 ​diff ​命令是一个用于比较两个文件并输出它们之间差异的工具。它是文件比较的基本工具&#xff0c;广泛用于源代码管理、脚本编写和日常的文件维护工作中。 ‍ 二、diff 命令参数 diff [选项] 文件1 文件2选项&#xff1a; ​-b​ 或 --ignore-space…

光伏选址和设计离不开气象分析!

都说光伏选址和设计离不开气象分析&#xff0c;气象条件对太阳能发电影响较大&#xff0c;具体有哪些影响呢&#xff1f;今天我就来讲解下。 - 太阳辐射&#xff1a;太阳辐射的强度是光伏发电的首要因素&#xff0c;对光伏发电有着重要的影响。太阳辐射的强度决定了光伏发电系…

信息安全数学基础(14)欧拉函数

前言 在信息安全数学基础中&#xff0c;欧拉函数&#xff08;Eulers Totient Function&#xff09;是一个非常重要的概念&#xff0c;它与模运算、剩余类、简化剩余系以及密码学中的许多应用紧密相关。欧拉函数用符号 φ(n) 表示&#xff0c;其中 n 是一个正整数。 一、定义 欧…

LVGL学习

注&#xff1a;本文使用的lvgl-release-v8.3版本&#xff0c;其它版本可能稍有不同。 01 LVGL模拟器配置 day01-02_课程介绍_哔哩哔哩_bilibili LVGL开发教程 (yuque.com) 如果按照上述视频和文档中配置不成功的话&#xff0c;直接重装VsCode&#xff0c;我的就是重装以后就…

[Visual Stuidio 2022使用技巧]2.配置及常用快捷键

使用vs2022开发WPF桌面程序时常用配置及快捷键。 语言&#xff1a;C# IDE&#xff1a;Microsoft Visual Studio Community 2022 框架&#xff1a;WPF&#xff0c;.net 8.0 一、配置 1.1 内联提示 未开启时&#xff1a; 开启后&#xff1a; 开启方法&#xff1a; 工具-选…

torch.linspace() torch.arange() torch.stack() 函数详解

1 torch.linspace函数详解 torch.linspace(start, end, steps100, outNone, dtypeNone, layouttorch.strided, deviceNone, requires_gradFalse) → Tensor 函数的作用是&#xff0c;返回一个一维的tensor&#xff08;张量&#xff09;&#xff0c;这个张量包含了从start到end…

【专题】2024新能源企业“出海”系列之驶向中东、东南亚报告合集PDF分享(附原数据表)

原文链接&#xff1a; https://tecdat.cn/?p37698 在“双碳”目标引领下&#xff0c;中国新能源产业近年迅猛发展&#xff0c;新能源企业凭借技术革新、政策支持与市场驱动实现快速增长&#xff0c;在产业链完备、技术领先、生产效能及成本控制等方面优势显著。面对国内外环境…

单向循环链表

文章目录 &#x1f34a;自我介绍&#x1f34a;单向循环链表 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏&#xff08;一键四连&#xff09;哦~ &#x1f34a;自我介绍 Hello,大家好&#xff0c;我是小珑也要变强&#xff08;也是小珑&…

Linux(3)--CentOS8下载、安装

文章目录 1. CentOS简介2. 下载3. 使用VmWare安装CentOS4. 第一次使用 1. CentOS简介 这个版本我个人比较推荐大家学习&#xff0c;为何&#xff1f;因为容易学习所以不难入门。 2. 下载 可以从国内的开源镜像站下载&#xff0c;这样比较快&#xff0c;例如阿里巴巴开源镜像…

C语言-整数和浮点数在内存中的存储-详解-上

C语言-整数和浮点数在内存中的存储-详解-上 1.前言2.整数2.1无符号整数2.2原码、反码、补码符号位最大值转换过程补码的意义简化算术运算易于转换方便溢出处理 1.前言 在C语言的使用中&#xff0c;需要时刻关注数据的类型&#xff0c;不同类型交替使用可能会发生错误&#xff…