Android进阶 —— 深入理解AIDL进程间通信

Android 深入理解AIDL进程间通信

前言

关于AIDL的资料,层出不穷,但是能让人简单明了理解的文章不多,那么我们就自己撸一遍,清晰明了的理解一下AIDL的原理。

准备

在理解AIDL原理之前,先写一个简单的使用AIDL进行进程间通信的例子,根据这个例子来由浅及深的理解AIDL。

先来定义实体类:UserBean.java
注意:如果要在AIDL中使用实体类,实体类必须要实现序列化接口,这里实现的是Android自带的Parcelable接口

 

typescript

复制代码

package com.t9.news.Model; import android.os.Parcel; import android.os.Parcelable; /** * Created by dai * Created time 2018/5/11 * function:com.t9.news.Model */ public class UserBean implements Parcelable{ private int age; private String name; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } protected UserBean(Parcel in) { age = in.readInt(); name = in.readString(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(age); dest.writeString(name); } @Override public int describeContents() { return 0; } public static final Creator<UserBean> CREATOR = new Creator<UserBean>() { @Override public UserBean createFromParcel(Parcel in) { return new UserBean(in); } @Override public UserBean[] newArray(int size) { return new UserBean[size]; } }; }

声明好了实体类,就需要额外在AIDL中声明实体类

 

go

复制代码

// UserBean.aidl package com.t9.news.Model; parcelable UserBean;

在AIDL 中使用实体类

 

java

复制代码

// IMyAidlInterface.aidl package com.t9.news; // Declare any non-default types here with import statements import com.t9.news.Model.UserBean; interface IUser { List<UserBean> getUser(); void addUser(in UserBean user); }

看一下项目的结构

然后Build 一下 Project,结束之后就生成了Binder代码,来看看Binder代码生成的位置

这时候可以点开看一下这个类,十有八九会被吓到,先不去管它,稍后再来分析。

来声明一个Service:

 

java

复制代码

package com.t9.news; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.support.annotation.Nullable; import android.util.Log; import com.t9.news.Model.UserBean; import java.util.ArrayList; import java.util.List; /** * Created by dai * Created time 2018/5/11 * function:com.t9.news */ public class MyService extends Service { private String TAG = this.getClass().getName(); @Override public void onCreate() { super.onCreate(); } List<UserBean> list = new ArrayList<>(); private IBinder binder = new IUser.Stub() { @Override public List<UserBean> getUser() throws RemoteException { if (list.size() <= 0){ for(int i = 0; i < 5; i++){ UserBean bean = new UserBean(); bean.setAge(5 * i); bean.setName("android-" + i); list.add(bean); } } return list; } @Override public void addUser(UserBean user) throws RemoteException { Log.e(TAG, user.getName()); list.add(user); } }; @Nullable @Override public IBinder onBind(Intent intent) { return binder; } }

定义的Service比较简单。new 了一个 IUser.Stub()并把它向上转型成了IBinder,最后在onBind方法中返回回去。在 IUser.Stub()的内部我们重写getUser()、addUser(UserBean user)方法,这就是AIDL中声明的IUser接口中的两个方法。
既然是跨进程通信,那么将Service设置到另一个进程中:

 

ini

复制代码

<service android:name="com.t9.news.MyService" android:process=":newProcess" />

定义为启动在新进程中,只需要在AndroidMainfest.xml中声明是加上一个process属性即可,不过这里有两个地方值得注意:
1.组件默认的进程名就是包名;
2.定义新的进程名的时候需要以包的形式(eg: com.xu.aidl)。

好,准备工作完成,在Activity中绑定Service

 

kotlin

复制代码

package com.t9.News.HomePage.View.Activity import android.Manifest import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection import android.os.IBinder import android.os.RemoteException import android.util.Log import com.t9.news.Application import com.t9.news.R import com.t9.news.IUser import com.t9.news.Model.UserBean import com.t9.news.MyService /** * Created by dai * Created time 19:06 * function:com.t9.news.Main.View.Activity */ class MainActivity : BaseActivity() { private val TAG = this.javaClass.simpleName; override fun getLayoutId(): Int { return R.layout.activity_main } override fun initView() { initService() } fun initService(){ val service = Intent(this@MainActivity, MyService::class.java) bindService(service, serviceConnection, Context.BIND_AUTO_CREATE) } private var iUser : IUser? = null private val serviceConnection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { iUser = IUser.Stub.asInterface(service) Log.e(TAG,"连接Service成功") try { val list : List<UserBean> = iUser!!.user for (user in list){ Log.e(TAG, "name = " + user.name + " age: = " + user.age) } val user:UserBean = UserBean(); user.name = "张三" user.age = 111 iUser!!.addUser(user) } catch (e: RemoteException) { e.printStackTrace() } } override fun onServiceDisconnected(name: ComponentName) { Log.e(TAG,"连接Service断开") } } }

由于主要是Service和Activity间的通信,所以为了让代码整洁就没有写UI了。

在onCreate(Bundle savedInstanceState)中,我们调用了自己定义的一个方法initService(),这个方法里面我们生成了一个Intent,然后 bindService了这个Intent传入了三个参数分别是Intent、ServiceConnection、Flag。

Intent我们就不用说了,我们看看后面两个参数:
在Activity中,我们new了一个ServiceConnection并实现了他的两个方法onServiceConnected、onServiceDisconnected。在onServiceConnected中我们通过IUser.Stub.asInterface(service)把传来的IBinder转换成了我们定义的iUser。然后我们调用了getUser方法,传递了个字符串和获取从MyService传来的字符串,并且打印了Log。
然后又new 一个UserBean对象,传递给MyService。

然后,我们的编码就完成了,运行并观察Log:

  • MainActivity 中 Log:

  • MyService中Log:

根据运行结果,在这两个不同的进程中都得到了我们想要的结果,所以,一个用aidl实现的跨进程通信就这样完成了。

AIDL的理解

回过头来,我们再来分析之前的 debug 目录下的 IUser.java 类

  • 先来看MyService 中 Binder

还记得我们在MyService中利用new IUser.Stub()向上转型成了IBinder然后在onBind方法中返回的。那我们就看看IUser.Stub()吧:

 

scala

复制代码

public static abstract class Stub extends android.os.Binder implements com.t9.news.IUser{ ...... }

Stub 是 IUser 中静态抽象类,继承了 Binder,并且 实现 IUser接口,这就说明我们定义IUser.Stub的时候为什么需要实现IUser中的方法了,也说明了为什么我们可以把IUser.Stub向上转型成IBinder了。

  • Activity中的IMyInterface
    在Activity中,通过ServiceConnection连接MyService并成功回调onServiceConnected中我们把传回来的IBinder通过IUser.Stub.asInterface(service)转换成为IUser,那就来看看这里是如何转换的吧:
 

java

复制代码

//这个接口里 有一个静态的抽象类Stub(注意这个名字是固定的 永远都是Stub 不会是其他) //并且这个Stub是Binder的子类,并且实现了IUser 这个接口 public static abstract class Stub extends android.os.Binder implements com.t9.news.IUser{ //这个东西就是唯一的binder标示 可以看到就是IUser的全路径名 private static final java.lang.String DESCRIPTOR = "com.t9.news.IUser"; /** Construct the stub at attach it to the interface. */ /** * 这个就是Stub的构造方法,回顾一下 我们如果写好aidl文件以后 写的service里面 是怎么写的? * private final IUser.Stub mBinder = new IUser.Stub() {} * 我们都是这么写的 对吧~~所以想想我们的service里面的代码 就能辅助理解 这里的代码了 */ public Stub() { this.attachInterface(this, DESCRIPTOR); } //这个方法 其实就做了一件事,如果是同一个进程,那么就返回Stub对象本身 //如果不是同一个进程,就返回Stub.Proxy这个代理对象了 public static com.t9.news.IUser asInterface(android.os.IBinder obj){ if ((obj==null)) { return null; } //检查Binder是不是在当前进程 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); //如果是同1个进程,也就是说进程内通信的话 我们就返回括号内里的对象 if (((iin!=null)&&(iin instanceof com.t9.news.IUser))) { return ((com.t9.news.IUser)iin); } //如果不是同一进程,是2个进程之间相互通信,那我们就得返回这个Stub.Proxy 看上去叫Stub 代理的对象了 return new com.t9.news.IUser.Stub.Proxy(obj); } }

首先,我们因该明白的是,传回来的IBinder就是我们在Service的onBind( )方法所return的IBinder,然后我们调用Stub中的静态方法asInterface并把返回来的IBinder当参数传进去。
在asInterface方法中,首先判断了传进来的IBinder是不是null,如果为null就返回一个null;接着就判断传进来的IBinder是不是就在当前进程里面,如果是的话就直接返回IUser,不是的话就返回IUser.Stub.Proxy(obj)。
这里我觉得需要明白的是:直接返回的IUser是实现了定义的接口方法getUser、AddUser的。因为在IUser.Stub中所实现的。当然如果是在同一进程中,那么我们调用IUser的方法时就是在本地调用方法,直接调用就可以了。

如果没在同一进程,就会返回IUser.Stub.Proxy(obj):

 

java

复制代码

//注意这里的Proxy 这个类名也是不变的,从前文我们知道 只有在多进程通信的情况下 才会返回这个代理的对象 private static class Proxy implements com.t9.news.IUser{ private android.os.IBinder mRemote; Proxy(android.os.IBinder remote){ mRemote = remote; } @Override public android.os.IBinder asBinder(){ return mRemote; } public java.lang.String getInterfaceDescriptor(){ return DESCRIPTOR; } //这里我们一共有2个方法 一个getUser 一个addUser 我们就分析一个方法就可以了 //并且要知道 这2个方法运行在客户端!!!!!!!!!!!!!!!! //首先就是创建了3个对象_data 输入对象,_reply输出对象,_result返回值对象 //然后把参数信息 写入到_data里,接着就调用了transact这个方法 来发送rpc请求,然后接着 //当前线程挂起, 服务端的onTransace方法才被调用,调用结束以后 当前线程继续执行,直到 //从_reply中取出rpc的返回结果 然后返回_reply的数据 //所以这里我们就要注意了,客户端发起调用远程请求时,当前客户端的线程就会被挂起了, //所以如果一个远程方法 很耗时,我们客户端就一定不能在ui main线程里在发起这个rpc请求,不然就anr了。 @Override public java.util.List<com.t9.news.Model.UserBean> getUser() throws android.os.RemoteException{ android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<com.t9.news.Model.UserBean> _result; try { _data.writeInterfaceToken(DESCRIPTOR); //传送数据到服务端 mRemote.transact(Stub.TRANSACTION_getUser, _data, _reply, 0); _reply.readException(); //接受从服务端传回的数据 _result = _reply.createTypedArrayList(com.t9.news.Model.UserBean.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addUser(com.t9.news.Model.UserBean user) throws android.os.RemoteException{ android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if ((user!=null)) { _data.writeInt(1); user.writeToParcel(_data, 0); } else { _data.writeInt(0); } mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } }

在Proxy中,我们首先把Service连接成功返回的IBinder它的内部变量mRemote,这里在提一下,这里得IBinder还是是MyService中onBind所返回的。然后,当我们调用IMyInterface的方法的时候,其实就是调用的Proxy的方法了,这也是为什么这个类叫做Porxy的原因了。

当调用IUser.getUser() ,我们就看Proxy中的getInfor,先获取了两个Parcel对象 _data、_data,从变量名就可以看出,一个是传送数据的,另一个则是接受返回数据的。接着,向_data中写入了DESCRIPTOR(也就是这个类的全名),再写入了方法参数。然后就到了最重要的一步了,

mRemote.transact(Stub.TRANSACTION_getInfor, _data, _reply, 0);
这里我们调用了IBinder的transact方法,来把数据传给远端的服务器。然后在我们远程的MyService中,里面的Stub中就会回调onTransact()(因为你把数据传个远程的服务,远端的服务收到数据也就回调了)

注意:这里是在远程的服务里调用的。

 

kotlin

复制代码

//只有在多进程通信的时候 才会调用这个方法 ,同一个进程是不会调用的。 //首先 我们要明白 这个方法 一般情况下 都是返回true的,也只有返回true的时候才有意义,如果返回false了 就代表这个方法执行失败, //所以我们通常是用这个方法来做权限认证的,其实也很好理解,既然是多进程通信,那么我们服务端的进程当然不希望谁都能过来调用 //所以权限认证是必须的,关于权限认证的代码 以后我再讲 先略过。 //除此之外 ,onTransact 这个方法 就是运行在Binder线程池中的,一般就是客户端发起请求,然后android底层代码把这个客户端发起的 //请求 封装成3个参数 来调用这个onTransact方法,第一个参数code 就代表客户端想要调用服务端 方法的 标志位。 //其实也很好理解 服务端可能有n个方法 每个方法 都有一个对应的int值来代表,这个code就是这个int值,用来标示客户端想调用的服务端的方法 //data就是方法参数,reply就是方法返回值。都很好理解 //其实隐藏了很重要的一点,这个方法既然是运行在binder线程池中的,所以在这个方法里面调用的服务器方法也是运行在Binder线程池中的, //所以我们要记得 如果你的服务端程序 有可能和多个客户端相联的话,你方法里使用的那些参数 必须要是支持异步的,否则的话 //值就会错乱了!这点一定要记住!结论就是Binder方法 一定要是同步方法!!!!!! @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException{ switch (code){ case INTERFACE_TRANSACTION:{ reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getUser:{ data.enforceInterface(DESCRIPTOR); // 远程服务调用自己本地实现的方法获取返回值 java.util.List<com.t9.news.Model.UserBean> _result = this.getUser(); reply.writeNoException(); //写入返回值 reply.writeTypedList(_result); return true; } case TRANSACTION_addUser:{ data.enforceInterface(DESCRIPTOR); com.t9.news.Model.UserBean _arg0; if ((0!=data.readInt())) { _arg0 = com.t9.news.Model.UserBean.CREATOR.createFromParcel(data); } else { _arg0 = null; } this.addUser(_arg0); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); }

nTransact方法是在Stub的内部实现的。

先看一下它的四个参数:

  • code:每个方法都有一个int类型的数字用来区分(后面中的swicth),在我们例子中也就是我们Proxy中的Stub.TRANSACTION_geIUser。
  • data:传过来的数据,其中包含我们的参数,以及类的描述。
  • reply:传回的数据,我们要写入是否发生了Exception,以及返回值
  • flags:该方法是否有返回值 ,0表示有返回值。

调用onTransact就表示有数据传来,首先就会通过swicth判断是哪个方法,然后取出方法参数,调用本地实现的方法获取返回值,写入返回值到reply。最后,返回true,才会把数据发送出去,发挥false就不会把结果返回给Activity了。这里也就是说,只有返回true,我们Proxy中才能接受从远端传回的数据。

注意:Service也是把数据发送出来,让客户端接受的。

Service发出了数据,客户端接收到了,就会一层一层返回去。所以,当我们简单的调用IUser的getUser时候,先是Proxy的transact发送出数据,然后服务端的onTransact接受并处理传来的数据,再把处理得到的数据写入返回值并发送给客户端,客户端读取值后就成为调用方法的返回值返回了。

到这里 相信大家 至少在应用层上面,就对Binder就一个很直观的理解了,对于进程间通信来说,具体的流程就分为如下几步:

  • 1.Client 发起远程调用请求 也就是RPC 到Binder。同时将自己挂起,挂起的原因是要等待RPC调用结束以后返回的结果
  • 2.Binder 收到RPC请求以后 把参数收集一下,调用transact方法,把RPC请求转发给service端。
  • 3.service端 收到rpc请求以后 就去线程池里 找一个空闲的线程去走service端的 onTransact方法 ,实际上也就是真正在运行service端的 方法了,等方法运行结束 就把结果 写回到binder中。
  • 4.Binder 收到返回数据以后 就唤醒原来的Client 线程,返回结果。至此,一次进程间通信 的过程就结束了

关于AIDL的基本原理就是这样了,看明白了AIDL,才发现原来AIDL不过就是帮我们生成了那些数据写入,传送,读取的方法而已。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值