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

版权声明:欢迎转载,希望声明出处http://blog.csdn.net/dazhaodai https://blog.csdn.net/dazhaoDai/article/details/80286162

Android 深入理解AIDL进程间通信

前言

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

准备

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

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

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中声明实体类

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

parcelable UserBean;

在AIDL 中使用实体类

// 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:

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设置到另一个进程中:

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

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

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

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()吧:

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,那就来看看这里是如何转换的吧:
//这个接口里 有一个静态的抽象类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):

//注意这里的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()(因为你把数据传个远程的服务,远端的服务收到数据也就回调了)

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

 //只有在多进程通信的时候 才会调用这个方法 ,同一个进程是不会调用的。

     //首先 我们要明白 这个方法 一般情况下 都是返回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不过就是帮我们生成了那些数据写入,传送,读取的方法而已。


参考
https://www.cnblogs.com/punkisnotdead/p/5163464.html
https://blog.csdn.net/u011974987/article/details/51243539

猜你喜欢

转载自blog.csdn.net/dazhaoDai/article/details/80286162
今日推荐