AIDL实现Android IPC

1.AIDL文本解释

在软件工程中,接口定义语言(IDL)已经成为通用术语,是用来描述软件组件接口的特定语言。在Android中,该IDL被称为Android接口定义语言(AIDL),它是纯文本文件,使用Java类似语法编写。但是,编写Java接口的编写AIDL文件还有有些不同的。

首先,对所有的非原始类型参数,需要指定如下三种类型方向指示符之一:in,out,inout。in类型方向指示符只用于输入,客户端不会看到Service对对象的修改。out类型表明输入对象不包含相关的数据,但会由Service生成相关的数据。inout类型是上面两种类型的结合。切记只使用需要的类型,因为每种类型都有相应的消耗。

另一个需要记住的是,所有用于通信的自定义类都需要创建一个AIDL文件,是用来声明该类实现了Parcelable接口。

2.Parcel

Binder事务通常会传递事务数据。这种数据被称为parcel(包裹)。Android提供了相应的API。允许开发者为大多数Java对象创建parcel。

Android中的parcel和JAVA SE中的序列化对象类似。不同之处在于,开发者需要使用parcelable实现对象的编解码工作。该接口定义了两个编写Parcel对象的方法,以及一个静态的不可被复写的Creator对象,该对象用来从Parcel中读取相应的对象。如下所示:

public class CustomData implements Parcelable {
    public static final Creator<CustomData> CREATOR=new Creator<CustomData>(){
        @Override
        public CustomData createFromParcel(Parcel source) {
            CustomData customData=new CustomData();
            customData.mName=source.readString();
            customData.mReferences=new ArrayList<>();
            source.readStringList(customData.mReferences);
            customData.mCreated=new Date(source.readLong());
            return customData;
        }

        @Override
        public CustomData[] newArray(int size) {
            return new CustomData[size];
        }
    };
    private String mName;
    private List<String> mReferences;
    private Date mCreated;

    public CustomData(){
        this.mName="";//默认为空字符串
        this.mReferences=new ArrayList<String>();
        this.mCreated=new Date();//默认为当前时间
    }

    public Date getmCreated() {
        return mCreated;
    }

    public List<String> getmReferences() {
        return mReferences;
    }

    public String getmName() {
        return mName;
    }

    public void setmName(String mName) {
        this.mName = mName;
    }

    public void setmReferences(List<String> mReferences) {
        this.mReferences = mReferences;
    }

    public void setmCreated(Date mCreated) {
        this.mCreated = mCreated;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.mName);
        dest.writeStringList(this.mReferences);
        dest.writeLong(this.mCreated.getTime());
    }

    @Override
    public boolean equals(Object o) {
        if(this==o)return true;
        if(o==null || getClass()!=o.getClass())return false;
        CustomData that=(CustomData)o;
        return this.mCreated.equals(that.mCreated) && this.mName.equals(that.mName);
    }

    @Override
    public int hashCode() {
        int result=this.mName.hashCode();
        result=31*result+this.mCreated.hashCode();
        return result;
    }
}

前面的代码显示了实现Parcelable接口的CustomData类。注意CREATOR成员对象的实现,以及createFromParcel()如何使用Parcel.readStringList()方法来读取整个List对象,而不需要指定列表的长度,Parcel对象内部会处理这种情况。

实现该接口后就可以通过Binder IPC在应用间发送该类的对象了。

3.Binder简介

Binder IPC通信示意图:

Binder通讯遵循客户端-服务器模式。客户端使用客户端代理来处理与内核驱动程序的通信。在服务器端,Binder框架维护了一系列Binder线程。内核驱动会使用服务器端的Binder线程把消息从客户端代理分发给接受对象。这一点需要特别注意,因为当通过Binder接受Service调用时,它们并不会运行在应用程序的主线程上。这样一来,客户端到远程Service的连接就不会阻塞应用的主线程。

开发者使用Binder基类以及IBinder实现Binder机制。Service组件的Service.onBind()方法会返回实现IBinder接口的类。当Service发布远程API时,开发者通常使用AIDL文件生成IBinder类。

使用Binder通信时,客户端需要知道远程Binder对象的地址。然而,Binder的设计要求只有实现类,比如要调用的Service才知道地址。开发者使用Intent解析来进行Binder寻址。客户端使用action字符串或者组件名来构建Intent对象,然而使用它初始化与远程应用程序的通信,然而,Intent只是实际Binder地址的抽象描述,为了能够建立通信,还需要翻译成实际的地址。

ServiceManager是一个特殊的Binder节点,它巡行在Android系统服务内,管理所有的地址解析,是唯一一个有全局地址的Binder节点。因为所有的Android组件都使用Binder进行通信,它们需要使用ServiceManager进行注册,通过如上所述的地址来进行通信。

客户端想要和Service或者其他组件通信,需要隐式地通过Intent查询ServiceManager来接受Binder地址。

4. 完整是实例

AIDL设计到的知识都在前面做了详细的介绍,下面将跑一跑代码验证其可行性。

下面的代码片段是一个名为CustomData.aidl的AIDL文件示例。它应该和Java元代码文件放在同一个包里。

package com.example.liyuanjing.myapplication;
parcelable CustomData;

然后需要在AIDL文件中引入所有需要自定义的类,如下所示:

package com.example.liyuanjing.myapplication;
import com.example.liyuanjing.myapplication.CustomData;
interface ApiInterfaceV1 {
    boolean isPrime(long value);//用于检查数字是否为素数的简单远程方法
    void getAllDataSince(long timestamp, out CustomData[] result);//检索timestamp以后的所有CustomData对象,至多获取result.length个对象
    void storeData(in CustomData data);//存储CustomData对象 
}

写完上面的代码,忘记提醒开发者,原始类型参数不需要方法指示符。

切记,一旦实现了客户端代码,就不能在修改或者移除AIDL文件中的方法。可以在文件末尾添加新的方法,但是因为AIDL编译器会为每个方法生成标识符,所以不能修改现存的方法,否则不能向后兼容老版本。需要处理新版的API时,建议创建一个新的AIDL文件。这样做允许保持与老版本客户端的兼容。正如上面ApiInterfaceV1文件名,在天际新方法就可以创建一个V2结尾的文件,以此类推。

准备好AIDL文件后,服务器端需要实现

public class AidlService extends Service {
    private ArrayList<CustomData> mCustomDataCollection;

    @Override
    public void onCreate() {
        super.onCreate();
        this.mCustomDataCollection=new ArrayList<CustomData>();
    }

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

    private void getDataSinceImpl(CustomData[] result,Date since){
        int size=this.mCustomDataCollection.size();
        int pos=0;
        for (int i=0;i<size && pos<result.length;i++){
            CustomData storedValue=this.mCustomDataCollection.get(i);
            if(since.after(storedValue.getmCreated())){
                result[pos++]=storedValue;
            }
        }
    }

    private void storeDataImpl(CustomData data){
        int size=this.mCustomDataCollection.size();
        for (int i=0;i<size;i++){
            CustomData customData=this.mCustomDataCollection.get(i);
            if(customData.equals(data)){
                this.mCustomDataCollection.set(i,data);
                return ;
            }
        }
        this.mCustomDataCollection.add(data);
    }

    public static boolean isPrimeImpl(long number){
        return true;
    }
    private final ApiInterfaceV1.Stub mBinder=new ApiInterfaceV1.Stub(){
        @Override
        public boolean isPrime(long value) throws RemoteException {
            return isPrimeImpl(value);
        }

        @Override
        public void getAllDataSince(long timestamp, CustomData[] result) throws RemoteException {
            getDataSinceImpl(result,new Date(timestamp));
        }

        @Override
        public void storeData(CustomData data) throws RemoteException {
            storeDataImpl(data);
        }
    };
}

上面的代码中Service在代码末尾实现了ApiInterfaceV1.Stub.该对象也会在onBind()方法中返回给绑定到Service的客户端。注意,每次对ServiceAPI的调用都运行在自身的线程上,因为Binder提供了一个线程池用于执行所有的客户端调用。这意味着使用这种方法时,客户端不会阻塞Service所属的主线程.

服务器端配置文件配置服务如下:

<service
    android:name=".AidlService"
    android:enabled="true"
    android:exported="true" >
    <intent-filter>
        <action android:name="com.example.liyuanjing.myapplication.AIDL_SERVICE"/>
    </intent-filter>
</service>

android:exported="true"表示能被其他应用程序调用

android:enabled="true" 服务处于激活状态

以上是服务器端所需要的全部代码

下面的Activity展示了如何绑定到一个远程Service以及检索ApiInterfaceV1接口。如果是该API的唯一用户,可以同时管理客户端和服务器端的版本,那么这是首选的解决方案。


public class MainActivity extends Activity implements ServiceConnection {
    private Button start;
    private ApiInterfaceV1 mService;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.start=(Button)findViewById(R.id.start);
    }
    @Override
    protected void onResume() {
        super.onResume();
        bindService(new Intent("com.example.liyuanjing.myapplication.AIDL_SERVICE"),this,BIND_AUTO_CREATE);
    }

    @Override
    protected void onPause() {
        super.onPause();
        unbindService(this);
    }

    public void onCheckForPrime(View v)throws  Exception{
        long number=11;
        boolean isPrime=mService.isPrime(number);
        String message=isPrime?"你知道的这是正确啊":"你不知道的这是错误啊";
        Toast.makeText(this,message, Toast.LENGTH_LONG).show();
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        this.mService=ApiInterfaceV1.Stub.asInterface(service);
        this.start.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    onCheckForPrime(v);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        this.mService=null;
    }
}
	当然客户端只有上面的代码是远远不能调用服务器端的Service。需要将编译后的AIDL文件和Parcel包裹拷贝到客户端下,并且包的名称要和服务器端的一致否则会出现错误提示。
	编译后的AIDL文件在如下图所示的路径中:
	
	客户端拷贝后的项目缩略图如下图所示:
	
	一定要注意从服务器拷贝文件到客户端中包名必须一样否则不仅不能实现你所要的功能,而且会报错。
	
时间: 2024-11-10 00:59:12

AIDL实现Android IPC的相关文章

Android IPC机制(三)在Android Studio中使用AIDL实现跨进程方法调用

在上一篇文章Android IPC机制(二)用Messenger进行进程间通信中我们介绍了使用Messenger来进行进程间通信的方法,但是我们能发现Messenger是以串行的方式来处理客户端发来的信息,如果有大量的消息发到服务端,服务端仍然一个一个的处理再响应客户端显然是不合适的.另外,Messenger用来进程间进行数据传递但是却不能满足跨进程的方法调用,接下来我们来使用AIDL来实现跨进程方法调用,此前我们都是用Eclipse来实现的,这次我们看看在Android Studio中使用AI

android IPC通信(下)-AIDL

android IPC通信(上)-sharedUserId&&Messenger android IPC通信(中)-ContentProvider&&Socket 这篇我们将会着重介绍AIDL的使用方式和原理,要介绍AIDL先要简单介绍一下Binder,而且Messenger,ContentProvider和AIDL的最底层都是使用的Binder. Binder 直观来说,Binder是Android中的一个类,它实现了IBinder接口.从IPC角度来说,Binder是A

【Android - IPC】之AIDL简介

参考资料: 1.<Android开发艺术探索>第二章2.4.4 2.Android AIDL Binder框架解析:http://blog.csdn.net/lmj623565791/article/details/38461079 3.你真的理解AIDL中的in.out.inoutm么:http://www.open-open.com/lib/view/open1469494342021.html 4.慕课网<AIDL-小白成长记> 1. AIDL简介 Android系统规定:每

Android IPC通信以及AIDL技术运用

首先我们了解一下 IPC和AIDL IPC:进程间通信 AIDL:Android Interface Definition Language,即Android接口定义语言. 为什么使用: Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信. 为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现.与很多其他的基于RPC的解决方案一样,Android使用一种接

Android Programming: Pushing the Limits -- Chapter 7:Android IPC -- AIDL

服务端: 最终项目结构: 这个项目中,我们将用到自定义类CustomData作为服务端与客户端传递的数据. Step 1:创建CustomData类 package com.ldb.android.example.aidl; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; import java.util.ArrayList; import java.util.Date; impor

Android IPC机制(五)用Socket实现跨进程聊天程序

相关文章: Android IPC机制(一)开启多进程 Android IPC机制(二)用Messenger进行进程间通信 Android IPC机制(三)在Android Studio中使用AIDL实现跨进程方法调用 Android IPC机制(四)用ContentProvider进行进程间通信 1.Socket简介 Socket也称作"套接字",是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信.它分为流式套接字和

Android IPC机制(三):浅谈Binder的使用

一.前言 在上一篇博客Android IPC机制(二):AIDL的基本用法中,笔者讲述了安卓进程间通讯的一个主要方式.利用AIDL进行通讯.并介绍了AIDL的基本用法. 事实上AIDL方式利用了Binder来进行跨进程通讯.Binder是Android中的一种跨进程通讯方式.其底层实现原理比較复杂.限于笔者水平,不能展开详谈.所以这篇文章主要谈谈以AIDL为例,谈谈Binder的用法. 二.原理 上一篇文章中创建了一个IMyAidl.aidl文件,即接口文件,随即编译了该文件.生成了一个.jav

Android IPC之Messenger浅谈

之前写过一篇有关 IPC之AIDL浅谈的文章,详情请看Android IPC之AIDL浅谈.今天再来介绍另一种 IPC-Messenger. 一.概述. 首先看Messenger介绍, Reference to a Handler, which others can use to send messages to it. This allows for the implementation of message-based communication across processes, by c

Android Programming: Pushing the Limits -- Chapter 7:Android IPC -- Messenger

Messenger类实际是对Aidl方式的一层封装.本文只是对如何在Service中使用Messenger类实现与客户端的通信进行讲解,对Messenger的底层不做说明.阅读Android Programming: Pushing the Limits -- Chapter 7:Android IPC -- AIDL了解如何使用Aidl的方式实现服务端与客户端的通信. 在Service中使用Messenger,大部分代码还是跟Android的消息机制打交道,具体一点就是跟Handler,Mes