AIDL学习之路(一)
1. 简介
AIDL
全称为(Android Interface Definition Language),由此可以看出这应当是一种定义接口 的语言
2. 具体场景
客户端与服务端进行通信时,需要定义双方都认可的接口 ,AIDL
这是用来完成这件工作的工具
AIDL
主要应用于IPC
,即进程间通信
2.1. 你是否真的需要AIDL
首先,如果并不需要实现IPC,可以直接通过继承Binder
类实现client-sever
间的通信
其次,如果的确需要实现IPC,但是无需处理多线程,那么应该通过Messager
实现,因为Messager
能够保证信息是串行 的(其实内部也是通过AIDL
实现的)
综上,真正需要使用到AIDL
的场景是IPC 和并发处理多个请求 并存
3. 使用步骤
创建.aidl
后缀名的文件,定义需要用到的接口
实现定义的接口
将接口暴露给客户端使用
3.1. 创建.aidl文件,定义接口
使用AS新建一个空项目,在当前模块的src
目录下直接创建一个AIDL
文件
然后重命名为IRemoteService
,因为这是一个接口
然后点击Finish
完成创建
现在项目结构中多了一个aidl
目录,下面有一级和java
包下包名一样的目录,里面正好就是刚刚创建的aidl
文件
以下为默认的aidl模板,我们来定义自己的
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.minos.aidldemo;interface IRemoteService { void basicTypes (int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) ;}
在此之前,需要先了解一下AIDL
中支持的基础类型
3.1.1. 支持类型
所有的基本类型(如int、float等)
String
CharSequence
List
Map
其中,对于List
,Map
这种复合类型 的要求是其中的元素类型必须为①前面的支持类型 ,或者是②其他的AIDL接口类型 ,或者是③已经声明的Parcelable类型
List -> ArrayList(实例) [支持指定泛型]
Map -> HashMap(实例) [不支持指定泛型]
知道了AIDL可以支持的类型,接下来我们开始定义自己的接口
首先,我们需要定义一个要用到的数据类型,这是需要在进程进行传递的数据类型
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 public class Msg { private String msg; private int pid; public Msg (String msg, int pid) { this .msg = msg; this .pid = pid; } public String getMsg () { return msg; } public void setMsg (String msg) { this .msg = msg; } public int getPid () { return pid; } public void setPid (int pid) { this .pid = pid; } }
这里直接定义了一个常见的Bean类
但是,如果需要这个数据对象能够在进程间进行传递,那么还需要这个类实现Parcelable
接口
这是Android提供的一种序列化方式
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 import android.os.Parcel;import android.os.Parcelable;public class Msg implements Parcelable { private String msg; private int pid; public Msg (String msg, int pid) { this .msg = msg; this .pid = pid; } public String getMsg () { return msg; } public void setMsg (String msg) { this .msg = msg; } public int getPid () { return pid; } public void setPid (int pid) { this .pid = pid; } @Override public int describeContents () { return 0 ; } @Override public void writeToParcel (Parcel parcel, int i) { parcel.writeString(this .msg); parcel.writeInt(this .pid); } protected Msg (Parcel in) { this .msg = in.readString(); this .pid = in.readInt(); } public static final Parcelable.Creator<Msg> CREATOR = new Parcelable .Creator<Msg>() { @Override public Msg createFromParcel (Parcel source) { return new Msg (source); } @Override public Msg[] newArray(int size) { return new Msg [size]; } }; }
把数据对象的结构定义完成后,还需要为这个新定义的数据类型设置一个同名的AIDL文件
1 2 3 package com.minos.aidldemo;parcelable Msg;
这个文件只声明了刚刚的类型,但是最终并不会参与编译
由于IRemoteService
会用到这个类型,因此还需要import
导入一下这个类型
尽管在同一个包下,也依然需要写import
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.minos.aidldemo;import com.minos.aidldemo.Msg; interface IRemoteService { Msg getResponse () ; }
至此,完成AIDL文件的编写工作,开始make
对应module
接下来,SDK会为我们生成对应的Java文件
对应module下build的生成文件中可以找到
大概看一下结构
这里系统生成了IRemoteService.java
这个文件
可以看到,里面有一个Stub
类
这是一个静态抽象类,并且继承了Binder
类,并且实现了IRemoteService
接口
asInterface()
方法接收一个Binder
类型参数,并且将其转化为当前的接口类型返回,即IRemoteService
3.2. 实现定义的接口
1 2 3 4 5 6 7 8 val mBinder = object : IRemoteService.Stub() { override fun getResponse () : Msg { return Msg( "msg from service at ${Thread.currentThread().name} \ntid is ${Thread.currentThread().id} \nmain thread id is ${mainLooper.thread.id} " , myPid() ) } }
实现需要用到的接口,即之前定义的,消息中会有线程的信息和进程id
3.3. 将接口暴露给客户端使用
接下来创建一个服务,叫RemoteService
随后在onBind()
中返回之前实现的接口
1 2 3 4 5 6 7 8 9 10 11 12 val mBinder = object : IRemoteService.Stub() { override fun getResponse () : Msg { return Msg( "msg from service at ${Thread.currentThread().name} \ntid is ${Thread.currentThread().id} \nmain thread id is ${mainLooper.thread.id} " , Process.myPid() ) } } override fun onBind (intent: Intent ) : IBinder { return mBinder }
然后编写Activity
模仿一下客户端
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 <?xml version="1.0" encoding="utf-8" ?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:app ="http://schemas.android.com/apk/res-auto" xmlns:tools ="http://schemas.android.com/tools" android:layout_width ="match_parent" android:layout_height ="match_parent" tools:context =".MainActivity" > <TextView android:id ="@+id/textView" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:layout_marginStart ="20dp" android:layout_marginTop ="100dp" android:text ="等待..." android:textSize ="24sp" app:layout_constraintStart_toStartOf ="parent" app:layout_constraintTop_toTopOf ="parent" /> <Button android:id ="@+id/button" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:text ="显示进程ID" app:layout_constraintStart_toStartOf ="@+id/textView" app:layout_constraintTop_toBottomOf ="@+id/textView" /> <Button android:id ="@+id/button2" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:capitalize ="sentences" android:text ="GetResponse" android:textAllCaps ="false" app:layout_constraintStart_toStartOf ="@+id/button" app:layout_constraintTop_toBottomOf ="@+id/button" /> </androidx.constraintlayout.widget.ConstraintLayout >
然后,编写一下业务
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 60 61 62 63 64 65 66 67 68 69 70 package com.minos.aidldemoimport android.annotation .SuppressLintimport android.content.ComponentNameimport android.content.Intentimport android.content.ServiceConnectionimport android.os.Bundleimport android.os.IBinderimport android.os.Process.myPidimport android.util.Logimport android.widget.Buttonimport android.widget.TextViewimport androidx.appcompat.app.AppCompatActivityclass MainActivity : AppCompatActivity () { private var mRemoteService: IRemoteService? = null private var mBind = false private lateinit var mPidText: TextView private lateinit var mBtnShowPid: Button private lateinit var mBtnGetResponse: Button private val mConnection: ServiceConnection = object : ServiceConnection { override fun onServiceConnected (name: ComponentName , service: IBinder ) { mRemoteService = IRemoteService.Stub.asInterface(service) mBind = true } override fun onServiceDisconnected (name: ComponentName ) { mRemoteService = null mBind = false } } @SuppressLint("SetTextI18n" ) override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) mPidText = findViewById(R.id.textView) mPidText.text = "the client pid is ${myPid()} " mBtnShowPid = findViewById(R.id.button) mBtnShowPid.setOnClickListener { if (mBind) { Log.i("RemoteServiceMsg" , "the service pid is ${mRemoteService?.response?.pid} " ) } } mBtnGetResponse = findViewById(R.id.button2) mBtnGetResponse.setOnClickListener { if (mBind) { Log.i("RemoteServiceMsg" , "${mRemoteService?.response?.msg} " ) } } } override fun onStart () { super .onStart() val intent = Intent(this , RemoteService::class .java) bindService(intent, mConnection, BIND_AUTO_CREATE) } override fun onStop () { super .onStop() unbindService(mConnection) mBind = false } }
mRemoteService = IRemoteService.Stub.asInterface(service)
这一句是关键所在,客户端的操作主要是利用该接口的引用执行方法的调用
如果是非跨进程 的情况下,这里可以直接使用强制类型转换
接下来,再到清单文件中修改注册条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <service android:name =".RemoteService" android:process =":remote" <!--指定运行到另一进程-- > android:enabled="true" android:exported="true" /> <activity android:name =".MainActivity" android:exported ="true" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity >
现在Activity扮演客户端,而Service扮演另一进程的服务端
接下来运行项目
这边是我们的两个进程,3010是Activity所在的进程,而3052是Service所在进程
点击一下“显示进程ID”按钮,这是另一进程中Service返回的消息
接着,再点击”GetResponse“
如果非跨进程通信,一定为主线程 ,跨进程的时候处理的线程便无法预测了