Android使用AIDL进行跨进程通信

本博客以此文Android:学习AIDL,这一篇文章就够了(上)作为参考,这篇文章给予我很大的帮助,非常感谢。

首先介绍一下跨进程通信(IPC,Inter-Process Communication),指至少两个进程或线程间传送数据或信号的一些技术或方法。下面是维基百科的介绍:

进程是计算机系统分配资源的最小单位(严格说来是线程)。每个进程都有自己的一部分独立的系统资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。通常,使用进程间通信的两个应用可以被分为客户端和服务器(见主从式架构),客户端进程请求数据,服务端响应客户端的数据请求。有一些应用本身既是服务器又是客户端,这在分布式计算中,时常可以见到。这些进程可以运行在同一计算机上或网络连接的不同计算机上。

在安卓中,我们可以通过AIDL来完成跨进程(不同的应用间)的通信。AIDL全称是Android Interface Definition Language,也就是接口定义语言。正如上面提到的,我们之所以需要IPC是因为一个应用进程通常无法访问另一个应用进程的内存,为了能够在进程间进行通信,进程需要将其对象分解成可供操作系统理解的原语,并将其编组为可供操作的对象,这也正是AIDL存在的原因。下面将介绍AIDL的相关概念和具体用法:

 

1. AIDL的语法:

它的语法与java类似,不过有如下区别:

(1)文件类型是aidl而不是java;

(2)支持的数据类型:基本类型(byte、short、int、long、double、float、boolean、char)以及String和CharSequence;

(3)List集合,其中的元素必须是AIDL支持的类型之一,或者是其他AIDL生成的接口,或者是parcelable可序列化对象;同时支持泛型;

(4)Map对象,与List一样,但是不支持泛型;

(5)定向tag:取值有三种in、out、inout,各自含义如下:

  • in:表示修饰的参数只能由客户端流向服务端(服务端可以接收客户端传递过来的完整对象,但是服务端对对象的修改不会反馈到客户端);
  • out:表示修饰的参数只能由服务端流向客户端(服务端接收到的是空对象,但是任何对该空对象的改动都将反馈到客户端);
  • inout:表示修饰的参数支持客户端和服务端的双向传递(服务端可以接收客户端传递过来的完整对象,而且服务端对对象的修改将会同步反馈到客户端)

(注:String和CharSequence类型的参数只能使用in修饰,而且不要滥用inout,这可能会造成过多的系统开销)

 

2. AIDL文件

AIDL的文件大概可以分为两种类型:

(1)定义可序列化的实现Parcelable接口的数据类(可扩展AIDL传递参数所支持的数据类型);

(2)定义客户端可调用的,由服务端来实现的方法接口(类似于接口,并不涉及具体的实现);

介绍完相关概念后,下面将使用一个小例子来实现跨进程(应用)调用方法进行通信的完整过程。

 

3. 具体用法

大概可以分为如下步骤:

(1)创建用于跨进程传递的数据模型类;

(2)生成对应的aidl文件,包括两个文件(一个用于引入序列化对象供其他AIDL文件使用,另一个用于声明待实现的方法接口列表);

(3)服务端实现待调用的方法;

(4)向客户端公开接口;

(5)通过IPC传递对象;

3.1 定义数据模型类

首先在作为客户端的应用AidlClientDemo项目中定义如下文件:

package com.zjhtest.aidlclient;

import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;

public class Book implements Parcelable {
    private String name;
    private int price;

    public Book(String name, int price) {
        this.name = name;
        this.price = price;
    }

    protected Book(Parcel in) {
        name = in.readString();
        price = in.readInt();
    }

    public static final Creator CREATOR = new Creator() {
        @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;
    }

    /**
     * 如果仅重写writeToParcel方法,那么Book类的实例化对象在AIDL中仅会支持为in的定向tag
     */
    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(name);
        parcel.writeInt(price);
    }

    /**
     *  实现readFromParcel方法,那么Book类的实例化对象在AIDL中还可以支持为out及inout的定向tag
     * @param dest Parcel对象,用于存储和传输数据
     */
    public void readFromParcel(Parcel dest) {
        name = dest.readString();
        price = dest.readInt();
    }

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

    @NonNull
    @Override
    public String toString() {
        return "Book{ " +
                "name: " +
                name +
                ", price: " +
                price +
                " }";
    }
}

上面的代码中,我们定义了一个Book类,并实现了Parcelable接口,使其为可序列化的。这里需要注意的一点是,我们如果想要实例化的book对象能够声明为out或者是inout,需要实现readFromParcel方法。

3.2 生成对应的aidl文件

在Android Studio中,我们可以通过在app文件夹上右键点击New,再选中AIDL File进行创建。

Book.aidl:

// Book.aidl
// 这个文件的作用是引入了一个序列化对象Book供其他的AIDL文件使用
package com.zjhtest.aidlclient;

parcelable Book;

BookManager.aidl文件:

// BookManager.aidl
package com.zjhtest.aidlclient;

import com.zjhtest.aidlclient.Book;

interface BookManager {
    List getBooks();
    void addBook(in Book book);
}

上面声明的getBooks和addBook方法就是服务端需要实现的方法;

此时项目的结构如下:

Android使用AIDL进行跨进程通信_第1张图片

注意aidl目录和java目录处于同级目录下,而且具有同样的包路径。

3.3 服务端实现待调用的方法

在实现待调用的方法之前,先要将3.1和3.2在客户端中定义的Book类和对应的Book.aidl以及BookManager.aidl文件拷贝到AidlServerDemo中。同时必须保证包路径与客户端完全一致(com.zjhtest.aidlclient),否则会出现如下异常:

java.lang.IllegalStateException: Could not execute method of the activity

接着需要创建一个服务类AIDLService,在这个服务中需要实现待调用方法并返回一个IBindler实例供客户端使用(客户端正式通过这个IBinder实例调用服务端的方法)。具体实现如下:

package com.zjhtest.aidlclient;
......

public class AIDLService extends Service {
    private static final String TAG = "AIDLService";
    private List mBooks = new ArrayList<>();

    private final BookManager.Stub mBookManager = new BookManager.Stub() {
        @Override
        public List getBooks() throws RemoteException {
            synchronized (this) {
                Log.d(TAG, "invoking server getBook method, now the list is " + mBooks);
                if (mBooks != null) {
                    return mBooks;
                }
                return new ArrayList<>();
            }
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            synchronized (this) {
                if (book == null) {
                    Log.d(TAG, "Book is null from client (In)");
                    book = new Book("defaultBook", 88);
                }

                if (!mBooks.contains(book)) {
                    mBooks.add(book);
                }

                Log.d(TAG, "invoking server addBook method, now the list is " + mBooks);
            }
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        Book initBook = new Book("initBook", 22);
        mBooks.add(initBook);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, String.format("onBind execute, intent is %s", intent.toString()));
        return mBookManager;
    }
}

上面的代码中做了如下事情:

(1)onCreate在服务创建时初始化一个Book实例,并保存到mBooks集合中;

(2)实现根据自定义接口生成的BookManager.Stub,并实例化得到mBookManager(这个对象就是一个IBinder实例);

(3)重写的onBind方法中返回mBookManager,供客户端使用;

接下来需要到AndroidManifest.xml文件中注册这个服务:


    
        
        
    

上面需要注意两点:

(1)如果我们希望这个服务能够被外部进程所调用,android:exported属性必须设置为true;

(2)必须在intent-filter中指定自定义的action字符串常量,这个action字符串常量在客户端应用AidlClientDemo中构建启动服务的Intent中会用到;

之后我们就需要启动该服务,在MainActivity中:

package com.zjhtest.aidlclient;
......

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity Server";

    private boolean mBound = false;
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.d(TAG, "AIDLService is connected");
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.d(TAG, "AIDLService is disconnected");
            mBound = false;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

//        Intent intent = new Intent(this, AIDLService.class);
//        startService(intent);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!mBound) {
            Intent intent = new Intent(this, AIDLService.class);
            bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
        }
    }

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
//        Intent intent = new Intent(this, AIDLService.class);
//        stopService(intent);
    }
}

需要注意在服务端应用启动服务AIDLService时,必须通过bindService的方式,不能通过startService,熟悉服务的同学应该都知道,startService启动的服务,只会执行onCreate方法,并不会执行onBind方法,这样也就无法生成IBinder实例返回给客户端了。

这样AIDLServiceDemo服务端应用的代码基本就完成了,下面是项目结构:

Android使用AIDL进行跨进程通信_第2张图片

3.3 通过IPC传递对象

接下来,客户端应用功能AIDLClientDemo可以指定启动服务端应用AIDLServerDemo的AIDLService服务,并跨进程调用其实现的getBooks和addBook方法。回到我们的AIDLClientDemo应用中,在MainActivity中启动该服务:

package com.zjhtest.aidlclient;
......

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "MainActivity Client";
    private static final String AIDL_SERVER_PACKAGE = "com.zjhtest.aidlserver";  // 跨进程调用目标进程的唯一包名
    private static final String AIDL_SERVER_SERVICE_ACTION = "com.zjhtest.aidlserver.service";  // 跨进程调用目标进程的服务action

    // 由AIDL文件生成的BookManager接口
    private BookManager mBookManager;
    private boolean mBound = false;
    private List mBooks;
    private Button mAddBookBtn;

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.d(TAG, "client: service connected");
            mBound = true;
            mBookManager = BookManager.Stub.asInterface(iBinder);

            if (mBookManager != null) {
                try {
                    mBooks = mBookManager.getBooks();  //  跨进程调用com.zjhtest.aidlserver应用的getBooks方法
                    Log.d(TAG, "client: get books is " + mBooks);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.d(TAG, "client: service disconnected");
            mBound = false;
        }
    };

    private void addBook() {
        if (!mBound) {
            attemptToBindService();
            Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
            return ;
        }

        if (mBookManager == null) {
            return ;
        }

        Book book = new Book("Android权威编程指南", 98);
        try {
            mBookManager.addBook(book);  // 跨进程调用com.zjhtest.aidlserver应用的addBook方法
            Log.d(TAG, "client: newBook is " + book);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    private void attemptToBindService() {
        Intent intent = new Intent();
        intent.setPackage(AIDL_SERVER_PACKAGE);
        intent.setAction(AIDL_SERVER_SERVICE_ACTION);
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAddBookBtn = findViewById(R.id.addBookBtn);
        mAddBookBtn.setOnClickListener(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!mBound) {
            Log.d(TAG, "attemptToBindService execute");
            attemptToBindService();
        }
    }

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

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.addBookBtn:
                addBook();
        }
    }
}

然后在真机上分别安装应用AIDLServerDemo和AIDLClientDemo,先启动AIDLServer,再启动AIDLClient,此时logcat日志中输出:

2020-06-20 00:39:50.117 5389-5389/? D/MainActivity Client: attemptToBindService execute
2020-06-20 00:39:50.320 5389-5389/? D/MainActivity Client: client: service connected
2020-06-20 00:39:50.321 5389-5389/? D/MainActivity Client: client: get books is [Book{ name: initBook, price: 22 }]

getBooks方法跨进程调用成功,AIDLClient获取到了AIDLServer应用启动AIDLService服务时在onCreate中初始化的Book对象。

然后点击AIDLClient的addBook按钮,客户端logcat日志输出如下:

2020-06-20 00:45:00.843 5389-5389/com.zjhtest.aidlclient D/MainActivity Client: client: newBook is Book{ name: Android权威编程指南, price: 98 }

服务端logcat日志输出如下:

2020-06-20 00:45:00.842 5869-5901/com.zjhtest.aidlserver D/AIDLService: invoking server addBook method, now the list is [Book{ name: initBook, price: 22 }, Book{ name: Android权威编程指南, price: 98 }]

同样成功调用addBook方法,客户端传递新创建的Book实例,服务端接收后将其add到mBooks集合中。

通过上面这个简单的例子,大致了解了AIDL的使用过程,但是对于原理性的东西还需要继续深究,可以查看官方文档。

AIDLClient完整项目地址:https://gitlab.com/zhoujhpaul/aidlclientdemo

AIDLServer完整项目地址:https://gitlab.com/zhoujhpaul/aidlserverdemo

你可能感兴趣的:(Android使用AIDL进行跨进程通信)