栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

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 getBooks();

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

注意:Book.kt 的包名要与 Book.aidl 一致,不然会会报 Symbol not found 的错误

如果包名路径不一致的话,修改 build.gradle 文件:在 android{} 中间加上下面的内容:

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

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

编写服务端代码

class AIDLService : Service() {
    val TAG = this.javaClass.simpleName

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

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

        override fun getBooks(): MutableList {
            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

        
            
                
                
            
        
2.2 客户端实现

移植相关文件
把服务端的整个 aidl 文件夹复制到客户端,还要单独将数据类文件放到 java 文件夹里去。

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

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? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById

当我们调用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 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 {
            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 则表示数据可在服务端与客户端之间双向流通,服务端能接收到客户端传来的完整对象,并且服务端修改对象后客户端会同步变动。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/582597.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号