Android跨进程通信之AIDL

在聊AIDL之前,我们先来了解一下Android的跨进程通信(IPC)。我们都知道,在Android中每个程序都有一个主线成用于更新UI,而线程就是运行在进程中的。每个进程都有其独立的存储空间,正常情况下进程与进程之间是无法访问的,所以为了进行通信,Android提供了几种解决方案。其中AIDL就是其中一种,另外还有Intent、Messenger、ContentProvider、Socket、Binder也可以实现进程间的通信。每种方案都有其不同的应用场景和优缺点,借用一下Android开发艺术探索书中的图表:

名称优点缺点适用场景
Bundle简单易用只能传输Bundle支持的数据类型四大组件的进程间通信
文件共享简单易用不适合高并发场景,并且无法做到进程间的即时通信无并发访问情形,交换简单的数据实时性不高的场景
AIDL功能强大,支持一对多并发通信,支持实时通信使用稍复杂,需要处理好线程同步一对多通信且有RPC需求
Messeger功能一般,支持一对多串行通信,支持实时通信不能很好处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型低并发的一对多即时通信,无RPC需求,或者无须要返回结果的RPC需求
ContentProvider在数据源访问方面功能强大,并支持一对多并发数据共享,可通过Call方法扩展其他操作可以理解为受约束的AIDL,主要提供数据源的CRUD操作一对多的进程间的数据共享
Socket功能强大,可以通过网络传输字节流,支持一对多并发即时通信实现细节稍微复杂优点繁琐,不支持直接的RPC网络数据交换

从上面可以看出我们本章要讲的AIDL功能还是很强大了,很多方案底层也是基于AIDL实现,使用率也是相对较高的,所以这是一个必须要掌握的知识点。

1. 什么是AIDL

第一次认识AIDL的时候,都说他是一个接口定义语言,用于进程间通信的。当时作为一个Android新手,并不能理解这个接口定义语言到底是什么意思,而且实现起来还要分服务端和客户端,感觉很难懂,虽然仿照着网上实现了一个例子,但还是没能理解它的含义,最后被我放弃了-_-。

当我重新温习我的Android开发艺术探索这本书的时候,又重新学习了IPC机制这一章。不过,此时理解起来容易多了。在啃完IPC机制一章时,我发现AIDL仅实现起来并没有那么难,不过其中要注意的细节还是有很多的。学完之后对AIDL的理解是:AIDL只是为了简单的实现Binder的工具,而AIDL接口定义语言意思则是官方规定了一个客户端和服务端相互通信的接口,就如同网络通信中的http协议

2. AIDL的实现

既然我们要进行进程间的通信,那我们需要分为服务端和客户端来实现。

服务端

1. 创建.aidl接口文件

在AIDL中,我们可以声明一个或者多个接口,其中参数和返回值必须是AIDL所支持的数据类型。AIDL所支持的数据类型如下:

  • Java的基本数据类型(int,long,char,boolean,double等)
  • String和CharSequence
  • List:只支持ArrayList,就算使用List接口创建,最终接收端实际是ArrayList
  • Map:只支持HashMap,就算使用Map接口创建,最终接收端实际是HashMap
  • Parcelable:所有实现Parcelable接口的对象
  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

以上就是AIDL所支持的所有类型,其中实现Parcelable接口的对象和AIDL对象都需要显式import进来。

另外还需要注意的是除了基本数据类型,其他类型的参数都需要标明方向:in,out或者out。in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。尽量将方向限定为真正需要的方向,因为编组参数的开销较大。

接下来开始创建AIDL接口:
在这里插入图片描述

// Work.aidl
package com.mlatent.servicedemo;
parcelable Work;


// ILeader.aidl
package com.mlatent.servicedemo;

import com.mlatent.servicedemo.Work;

interface ILeader {

    Work getWork();

    void setWork(in Work work);

}

我们创建了两个AIDL文件,一个是ILeader.aidl用于给客户端提供接口调用,一个是Work.aidl是要传输的数据类型,因为是我们自己创建的对象,所以需要为其创建aidl文件,并声明为parcelable类型。

创建后的目录结构是这样的:
在这里插入图片描述
接着我们创建Work的class文件,注意,AIDL文件要和Class文件路径一致,因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果import的路径不一致会导致反序列化失败。

public class Work implements Parcelable {

    public String content;

    public boolean isComplete;

    public Work(String content, boolean isComplete) {
        this.content = content;
        this.isComplete = isComplete;
    }

    protected Work(Parcel in) {
        content = in.readString();
        isComplete = in.readByte() != 0;
    }

    public static final Creator<Work> CREATOR = new Creator<Work>() {
        @Override
        public Work createFromParcel(Parcel in) {
            return new Work(in);
        }

        @Override
        public Work[] newArray(int size) {
            return new Work[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(content);
        parcel.writeByte((byte) (isComplete ? 1 : 0));
    }
}

可以看到Work类实现了Parcelable接口,这样就可以使用AIDL传输了。

2. 创建Service并实现接口

接下来我们要创建Service,然后实现接口,在Servie中返回,向客户端提供接口调用:

public class LeaderService extends Service {

    private static final String TAG = "LeaderService";

    private Binder mBinder = new ILeader.Stub() {

        @Override
        public Work getWork() throws RemoteException {
            return mWork;
        }

        @Override
        public void setWork(Work work) throws RemoteException {
            if (work != null) {
                mWork = work;
                Log.d(TAG, "工作内容:" + work.content + ", 是否完成:" + work.isComplete);
            }
        }
    };

    private Work mWork;

    @Override
    public void onCreate() {
        super.onCreate();
        // 生成工作内容
        mWork = new Work("完成登陆注册界面", false);
    }


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

然后在AndroidManifest.xml文件中声明Service,并使用android:exported="true"可以让外部应用调用。

 <service
            android:name=".LeaderService"
            android:exported="true" />

客户端实现

首先需要将服务端的aidl文件全部复制到客户端,并且路径要求一致,应为我们用到了自定义对象,所以需要将java下的类也复制到客户端。路径如下:
在这里插入图片描述
然后我们在MainActivity中创建三个按钮,分别用于绑定服务端service,获取工作,汇报工作。代码如下:


public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private ILeader mLeader;

    private Work mWork;

    private TextView textView;


    private ServiceConnection mServiceConnect = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.d(TAG, "onServiceConnected: 绑定服务器成功");
            if(textView != null){
                textView.append("\n绑定服务器成功");
            }
            mLeader = ILeader.Stub.asInterface(iBinder);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView);
        findViewById(R.id.bindService).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent();
                intent.setComponent(new ComponentName("com.mlatent.servicedemo", "com.mlatent.servicedemo.LeaderService"));
                bindService(intent, mServiceConnect, Context.BIND_AUTO_CREATE);
            }
        });
        findViewById(R.id.getWork).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mLeader != null) {
                    try {
                        mWork = mLeader.getWork();
                        textView.append("\n今日工作内容:" + mWork.content);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        findViewById(R.id.complete).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mLeader != null) {
                    if (mWork != null) {
                        mWork.isComplete = true;
                        try {
                            mLeader.setWork(mWork);
                            textView.append("\n工作已完成并汇报给了Leader");
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
    }
}

首先我们创建了ServiceConnect对象,用于绑定服务端的service,然后在onServiceConnected连接成功回调中获取Binder对象。
在这里我们调用了ILeader.Stub.asInterface()方法,该方法用于将服务端的Binder对象转换成客户端需要的AIDL接口类型的对象,所以我们将Binder对象传递获取便可获得ILeader接口。我们拿到ILeader就可以和服务端进行通信了。
例如我们在第二个按钮中调用了getWork(),便可以拿到服务端的数据了,我在TextView中将获取的到Work对象进行了打印,我们可以看一下:
在这里插入图片描述
然后我们可以点击完成工作按钮进行汇报工作,看服务端会不会打印出来:
在这里插入图片描述

2020-07-27 17:01:35.344 12211-12236/com.mlatent.servicedemo D/LeaderService: 工作内容:完成登陆注册界面, 是否完成:true

可以看到服务端收到了客户端汇报的工作。
以上就是我对AIDL的理解,还有需要注意的几点是:

  • 服务端和客户端的AIDL文件路径要一致
  • 客户端在调用远程服务的方法的时候,被调用的方法是运行在服务端的Binder线程池中,此时客户端线程会被挂起。如果客户端知道服务端执行比较耗时,则应该在在单独的线程中调用,否则会造成UI线程卡顿,严重的会发生ANR。
  • 由于服务端的方法都是运行在Binder线程池中,所以本身支持并发请求,所以服务端需要做好并发处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值