Android 进程间通信——AIDL学习与使用

一、概述

AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言。目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。
AIDL的语法和Java是一样的,只是在一些细微处有些许差别:

  • 文件类型:用AIDL书写的文件的后缀是 .aidl,而不是 .java。

  • 数据类型:AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包。默认支持的数据类型包括:Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。String 类型。CharSequence类型。
    List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。List可以使用泛型。
    Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的。

  • 定向tag:AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。另外,Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。注意:不要滥用定向 tag ,而是要根据需要选取合适的,全都用 inout ,等工程大了系统的开销就会大很多。

  • 两种AIDL文件:AIDL文件大致可以分为两类。一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。

二、简单使用

2.1 服务端实现

数据类
首先要定义两个进程间通信的数据类,由于不同的进程有着不同的内存区域,并且它们只能访问自己的那一块内存区域。所以我们必须将要传输的数据转化为能够在内存之间流通的形式。这个转化的过程就叫做序列化与反序列化。选择的序列化方式是实现 Parcelable 接口。

@Parcelize
data class Book(var name: String? = null, var price: Int = 0) : Parcelable {
    fun readFromParcel(dest: Parcel) {
        //注意,此处的读值顺序应当是和writeToParcel()方法中一致的
        name = dest.readString()
        price = dest.readInt()
    }
}

在kotlin中,中实现 Parcelable 非常简单首先,在所属模块的 build.gradle 文件中应用 kotlin-parcelize 插件,然后在定义的实体类添加 @Parcelize 注解,并实现 Parcelable 接口即可。注意:添加 @Parcelize 注解生成器就会自动创建writeToParcel()/ createFromParcel()方法,但不会生成readFromParcel()方法,而如果要支持为 out 或者 inout 的定向 tag 的话,还需要实现 readFromParcel() 方法

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    /*序列化插件*/
    id 'kotlin-parcelize'
}

书写AIDL文件
鼠标移到app上面去,点击右键,然后 new->AIDL->AIDL File
Android 进程间通信——AIDL学习与使用_第1张图片

Book.aidl

// Book.aidl
package com.matt.myaidltest.ipc;

//注意parcelable是小写
parcelable Book;

BookManager.aidl

// BookManager.aidl
package com.matt.myaidltest.ipc;
import com.matt.myaidltest.ipc.Book;

interface BookManager {
    //所有的返回值前都不需要加任何东西,不管是什么数据类型
    List<Book> getBooks();

    //传参时除了Java基本类型以及String,CharSequence之外的类型
    //都需要在前面加上定向tag,具体加什么量需而定
    void addBook(in Book book);
}

注意:Book.kt 的包名要与 Book.aidl 一致,不然会会报 Symbol not found 的错误
Android 进程间通信——AIDL学习与使用_第2张图片
如果包名路径不一致的话,修改 build.gradle 文件:在 android{} 中间加上下面的内容:

sourceSets {
    main {
        java.srcDirs = ['src/main/java', 'src/main/aidl']
    }
}

在写完AIDL文件后,需要Rebuild Project一下项目,生成相应文件,然后再编写Server

编写服务端代码

/**
 * 服务端的AIDLService
 *
 */
class AIDLService : Service() {
    val TAG = this.javaClass.simpleName

    //包含Book对象的list
    private var list: MutableList<Book> = mutableListOf()

    //由AIDL文件生成的BookManager
    private val mBookManager: BookManager.Stub = object : BookManager.Stub() {

        override fun getBooks(): MutableList<Book> {
            synchronized(this) {
                Log.i(TAG, "客户端获取书本列表: $list")
                return list
            }
        }

        override fun addBook(book: Book) {
            synchronized(this) {
                list.add(book)
                Log.i(TAG, "客户端添加新书: $book")
            }
        }
    }

    override fun onCreate() {
        super.onCreate()
        for (i in 0..4) {
            val book = Book("第" + i + "本书", i)
            list.add(book)
        }
    }


    override fun onBind(intent: Intent): IBinder {
        Log.d(TAG, String.format("on bind,intent = %s", intent.toString()))
        return mBookManager
    }
}

上述代码主要在创建时分为三块:第一块是初始化,在 onCreate() 方法里面我进行了一些数据的初始化操作。第二块是重写 BookManager.Stub 中的方法。在这里面提供AIDL里面定义的方法接口的具体实现逻辑。第三块是重写 onBind() 方法,在里面返回写好的 BookManager.Stub 。

接下来在 Manefest 文件里面注册这个我们写好的 Service

        <service
            android:name=".ipc.AIDLService"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BookService"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>

2.2 客户端实现

移植相关文件
把服务端的整个 aidl 文件夹复制到客户端,还要单独将数据类文件放到 java 文件夹里去。
Android 进程间通信——AIDL学习与使用_第3张图片

在客户端我们要完成的工作主要是调用服务端的方法,但是在那之前,我们首先要连接上服务端,完整的客户端代码是这样的:

class MainActivity : AppCompatActivity() {
    val TAG = javaClass.simpleName

    //由AIDL文件生成的Java类
    private var mBookManager: BookManager? = null

    //标志当前与服务端连接状况的布尔值,false为未连接,true为连接中
    private var mBound = false

    //包含Book对象的list
    private var list: List<Book>? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<Button>(R.id.button).setOnClickListener {
            addBook()
        }
        findViewById<Button>(R.id.button2).setOnClickListener {
            getBook()
        }
    }

    /**
     * 按钮的点击事件,点击之后调用服务端的addBookIn方法
     *
     * @param view
     */
    fun addBook() {
        //如果与服务端的连接处于未连接状态,则尝试连接
        if (!mBound) {
            attemptToBindService()
            Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show()
            return
        }
        if (mBookManager == null) return
        val book = Book("APP研发录In", 30)
        try {
            mBookManager!!.addBook(book)
            Log.e(TAG, "添加新书$book")
        } catch (e: RemoteException) {
            e.printStackTrace()
        }
    }

    fun getBook() {
        //如果与服务端的连接处于未连接状态,则尝试连接
        if (!mBound) {
            attemptToBindService()
            Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show()
            return
        }
        if (mBookManager == null) return
        Log.e(TAG, "服务器返回:${mBookManager!!.books}")
    }

    /**
     * 尝试与服务端建立连接
     */
    private fun attemptToBindService() {
        val intent = Intent()
        intent.action = "android.intent.action.BookService"
        intent.setPackage("com.example.mynativeapplication")
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
    }

    override fun onStart() {
        super.onStart()
        if (!mBound) {
            attemptToBindService()
        }
    }

    override fun onStop() {
        super.onStop()
        if (mBound) {
            unbindService(mServiceConnection)
            mBound = false
        }
    }

    private val mServiceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            Log.d(TAG, "service connected")
            mBookManager = BookManager.Stub.asInterface(service)
            mBound = true
        }

        override fun onServiceDisconnected(name: ComponentName) {
            Log.e(TAG, "service disconnected")
            mBound = false
        }
    }
}

当我们调用addBook()方法时,客户端打印信息
在这里插入图片描述
服务端打印信息
在这里插入图片描述

当我们调用getBook()方法时,客户端打印信息
在这里插入图片描述
服务端打印信息
在这里插入图片描述

三、in,out,inout的区别

所有的非基本参数都需要一个定向tag来指出数据流通的方式,不管是 in , out , 还是 inout 。基本参数的定向tag默认是并且只能是 in 。其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。

修改BookManager.aidl

// BookManager.aidl
package com.matt.myaidltest.ipc;
import com.matt.myaidltest.ipc.Book;

interface BookManager {
    //所有的返回值前都不需要加任何东西,不管是什么数据类型
    List<Book> getBooks();

    //传参时除了Java基本类型以及String,CharSequence之外的类型
    //都需要在前面加上定向tag,具体加什么量需而定
    void addBook(in Book book);

    //通过三种定位tag做对比试验,观察输出的结果
    void addBookIn(in Book book);
    void addBookOut(out Book book);
    void addBookInout(inout Book book);
}

修改服务器端
分别将接收到的 in、out、 inout 定向tag的形参后,修改其价格

    private val mBookManager: BookManager.Stub = object : BookManager.Stub() {

        override fun getBooks(): MutableList<Book> {
            synchronized(this) {
                Log.i(TAG, "客户端获取书本列表: $list")
                return list
            }
        }

        override fun addBook(book: Book) {
            synchronized(this) {
                list.add(book)
                Log.i(TAG, "客户端添加新书: $book")
            }
        }

        override fun addBookIn(book: Book) {
            synchronized(this) {
                list.add(book)
                Log.i(TAG, "addBookIn: $book")
                book.price = 61
            }
        }

        override fun addBookOut(book: Book) {
            synchronized(this) {
                list.add(book)
                Log.i(TAG, "addBookOut: $book")
                book.price = 62
            }
        }

        override fun addBookInout(book: Book) {
            synchronized(this) {
                list.add(book)
                Log.i(TAG, "addBookInout: $book")
                book.price = 63
            }
        }
    }

修改客户端

    fun addBook() {
        //如果与服务端的连接处于未连接状态,则尝试连接
        if (!mBound) {
            attemptToBindService()
            Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show()
            return
        }
        if (mBookManager == null) return
        try {
            val book1 = Book("APP开发1", 31)
            mBookManager!!.addBookIn(book1)
            Log.e(TAG, "addBookIn 添加新书$book1")

            val book2 = Book("APP开发2", 32)
            mBookManager!!.addBookOut(book2)
            Log.e(TAG, "addBookOut 添加新书$book2")

            val book3 = Book("APP开发3", 33)
            mBookManager!!.addBookInout(book3)
            Log.e(TAG, "addBookInout 添加新书$book3")

        } catch (e: RemoteException) {
            e.printStackTrace()
        }
    }

看一下客户端的打印结果
在这里插入图片描述

服务端的打印结果
在这里插入图片描述
可以很容易理解,

  • in 表示数据只能由客户端流向服务端,服务端能够正常的接收到客户端传过来的数据,但是服务端修改此参数后,不会影响客户端的对象。
  • out 表示数据只能由服务端流向客户端,服务端收到的参数是空对象,并且服务端修改对象后客户端会同步变动。
  • inout 则表示数据可在服务端与客户端之间双向流通,服务端能接收到客户端传来的完整对象,并且服务端修改对象后客户端会同步变动。

你可能感兴趣的:(Android,android,java)