Kotlin实现AIDL

本篇文章主要讲解使用kotlin语言实现aidl进行app跨进程通信
相比java,kotlin实现总体说代码少很多,简洁多了,非常推荐使用kotlin。


AIDL简介

  • 什么是AIDL

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

  • AIDL能做什么

    每一个进程都有自己的Dalvik VM实例,有自己独立的内存,在其存储数据执行操作。但是它们怎么才能相互传输数据呢?
    AIDL其目的就是:实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。
    它就类似于两座岛之间沟通的桥梁,我们可以通过它,在一个进程中访问另一个进程的数据,甚至执行它的一些特定方法。

  • AIDL进程通信有那些优势

    话说,进程之间数据通信还是有其他的方案能够去实现。比如:BroadcastReceiver、Messenger等。
    但:

    * BroadcastReceiver 占用的系统资源比较多,频繁的跨进程通信则不可取。
    * Messenger 进行跨进程通信时请求队列是同步进行的,无法并发执行。

AIDL语法讲解

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

  • 数据类型:AIDL默认支持一些数据类型,非支持数据类型请注意:必须要导入包名。以下是默认支持的数据类型

    • Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char
    • CharSequence类型
    • String 类型
    • List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。List可以使用泛型。
    • Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的。
  • 定向tag:AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
    • 注意事项
      • Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in 。
      • 不要滥用定向 tag ,如果全都一上来就用 inout,等工程大了系统的开销就会大很多
  • 两种AIDL文件:

    • 定义parcelable对象,供其他AIDL文件使用AIDL中非默认支持的数据类型的

    • 定义方法接口,以供系统使用来完成跨进程通信的

      注:所有的非默认支持数据类型必须通过第一类AIDL文件定义才能被使用。示例:

    • Book.aidl

      1
      2
      3
      4
      5
      6
      //第一类AIDL文件的例子
      //这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
      //注意:Book.aidl与Book.java的包名应当是一样的
      package top.goluck.aidl_2017_8_27;
      //注意parcelable是小写
      parcelable Book;
    • BookManager.aidl

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      //第二类AIDL文件的例子
      package top.goluck.aidl_2017_8_27.aidl;
      //导入所需要使用的非默认支持数据类型的包
      import top.goluck.aidl_2017_8_27.aidl.Book;

      interface IBookManager {

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

      //所有的返回值前都不需要加任何东西,不管是什么数据类型
      String basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
      double aDouble, String aString);

      }

AIDL实战

这里实现一个需要传递自定义对象的示例吧

创建一个需要传递的自定义对象

  • 创建Book.kt对象,并实现序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package top.goluck.aidl_2017_8_27.model

import android.os.Parcel
import android.os.Parcelable
import android.R.attr.name



/**
* Created by luck on 2017/8/27.
*/
data class Book(var name:String,var price:Float): Parcelable {

constructor(parcel: Parcel) : this(parcel.readString(), parcel.readFloat())

override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(this.name)
parcel.writeFloat(this.price)
}

/**
* 参数是一个Parcel,用它来存储与传输数据
* @param dest
*/
fun readFromParcel(dest: Parcel) {
name = dest.readString()
price = dest.readFloat()
}

override fun describeContents(): Int {
return 0
}

companion object CREATOR : Parcelable.Creator<Book> {
override fun createFromParcel(parcel: Parcel): Book {
return Book(parcel)
}

override fun newArray(size: Int): Array<Book?> {
return arrayOfNulls(size)
}
}
}

注意:默认生成的模板类的对象只支持为 in 的定向 tag 。因为实现序列化的类里面只有 writeToParcel() 方法,而如果要支持为 out 或者 inout 的定向 tag 的话,还需要实现 readFromParcel() 方法——而这个方法其实并没有在 Parcelable 接口里面,所以需要我们自己实现。

创建AIDL文件

使用 AndroidStudio 鼠标移到app上面去,点击右键 -> New -> AIDL -> AIDL File

1
2
3
4
5
// Book.aidl
package top.goluck.aidl_2017_8_27.aidl;

//注意parcelable是小写
parcelable Book;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// IBookManager.aidl
package top.goluck.aidl_2017_8_27.aidl;
import top.goluck.aidl_2017_8_27.aidl.Book;

interface IBookManager {

void sendBook(in Book book);

void udpateBook(out Book book);

String basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);

}

编写服务端代码

  • 实现 IBookManager.adil 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package top.goluck.aidl_2017_8_27.service

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
import top.goluck.aidl_2017_8_27.aidl.Book
import top.goluck.aidl_2017_8_27.aidl.IBookManager

/**
* Created by luck on 2017/8/27.
*/
class AidlService : Service() {
override fun onBind(p0: Intent?): IBinder {
return mIBinder
}

val mIBinder: IBinder = object : IBookManager.Stub() {
override fun sendBook(book: Book?) {
var mBook = Book("Android开发的艺术", 66.6f)
book!!.name = mBook.name
book!!.price = mBook.price
Log.i("service:", "sendBook:" + mBook.toString())
}

override fun udpateBook(book: Book?) {
var mBook = Book("Android开发的艺术", 45.9f)
book?.name = mBook.name
book?.price = mBook.price
Log.i("service:", "udpateBook:" + mBook.toString())
}

override fun basicTypes(aInt: Int, aLong: Long, aBoolean: Boolean, aFloat: Float,
aDouble: Double, aString: String): String? {
var result: String? = "服务端收到,这是给你的回复 aInt+aLong+aBoolean+aFloat+aDouble+aString= ${aString + aLong + aInt + aBoolean + aFloat + aDouble}"
Log.i("service:", "basicTypes:" + result)
return result
}
}
}
  • manifest.xml中注册Service
    1
    2
    3
    4
    5
    6
    7
    8
     <service
    android:name=".service.AidlService"
    android:exported="true">
    <intent-filter>
    <action android:name="top.goluck.aidl_2017_8_27.service"/>
    <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
    </service>

编写客户端代码

  • 复制需要传递的自定义类Book.kt到客户端(注意:包名需要一致)

  • 复制build后生成的IBookManager.class到客户端(注意:包名需要一致,build后生成的路径 build -> intermediates -> classes -> debug -> 包名 -> IBookManager.class)

  • 正常使用Service即可,以下是我demo中的一种实现方案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    class MainActivity : AppCompatActivity() {

    lateinit var mBookManager: IBookManager
    var mBound: Boolean = false
    var mBook1: Book = Book()
    var mBook2: Book = Book("Android第一行代码", 49.9f)

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    }

    override fun onStart() {
    super.onStart()
    val intent = Intent()
    intent.action = "top.goluck.aidl_2017_8_27.service"
    intent.`package` = "top.goluck.aidl_2017_8_27"
    bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
    Log.i("client", "-------------bindService")
    }

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

    private val mServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName, service: IBinder) {
    Log.i("client", "-------------service connected")
    mBookManager = IBookManager.Stub.asInterface(service)
    mBound = true

    if (mBookManager != null) {
    try {
    mBookManager.udpateBook(mBook1)
    mBookManager.sendBook(mBook2)

    var result = mBook1.toString() +"\n" + mBook2.toString() +"\n" + mBookManager.basicTypes(1,1231,true,34.5f, 123123.0,"我是可是客户端传入的数据")
    txt.text = result
    Log.i("-------------client", result)
    } catch (e: RemoteException) {
    e.printStackTrace()

    Log.i("-------------client", "错误")
    txt.text = "没有获取到相应进程的通信数据"
    }

    }
    }

    override fun onServiceDisconnected(name: ComponentName) {
    Log.i("client", "-------------service disconnected")
    mBound = false
    }
    }
    }
  • 还是来看下日志输出呗

    • service

      1
      2
      3
      -------------udpateBook:name : Android开发的艺术 , price : 45.9
      -------------sendBook:name : Android开发的艺术 , price : 66.6
      -------------basicTypes:服务端收到,这是给你的回复 aInt+aLong+aBoolean+aFloat+aDouble+aString= 我是可是客户端传入的数据12311true34.5123123.0
    • client

      1
      2
      3
      4
      -------------service connected
      -------------client: name : Android开发的艺术 , price : 45.9
      name : Android第一行代码 , price : 49.9
      服务端收到,这是给你的回复 aInt+aLong+aBoolean+aFloat+aDouble+aString= 我是可是客户端传入的数据12311true34.5123123.0

      (特别提醒:明显可以看到 updateBook 方法使用的out 服务端可以将客户端传递的对象给修改了。 out 和 in 大家慎用啊!)

本篇完

(注意:转载文章请注明来源[Kotlin实现AIDL](http://goluck.top/2017/08/27/Kotlin%E5%AE%9E%E7%8E%B0AIDL/))