Android进程间通信之AIDL

本文例子中的源码地址: Github:进程间通信之AIDL

AIDL(Android 接口定义语言)是定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口,一个进程通常无法访问另一个进程的内存,但是可以通过AIDL进行进程间通信。**AIDL可以让客户端以IPC(进程间通信)方式访问服务端,并且服务端可以处理多线程;如果不需要处理多线程,则可以使用Messenger类来实现接口;如果只是需要本地的Service,不需要IPC过程,则只需要通过实现一个Binder类就可以了。**使用AIDL的前提必须了解绑定Service,Service为我们创建Binder驱动,Binder驱动是服务端与客户端通信的桥梁。AIDL通过我们写的.aidl文件,生成了一个接口,一个Stub类用于服务端,一个Proxy类用于客户端,不熟悉Service的可以看下官方文档:Service

定义AIDL接口步骤:

1.创建.aidl文件 在服务端的src/ 目录内创建.aidl类型的文件,如果客户端和服务端不在一个应用内,则需要将.aidl文件复制到客户端。 .aidl文件中可以定义一个或多个接口方法,方法参数和返回值可以是任意类型的,默认情况下,AIDL支持下面的几种类型:

  • Java 编程语言中的所有原语类型(如 int、long、char、boolean 等等)
  • String
  • CharSequence
  • List (List中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或声明的可打包类型。 可选择将List用作“通用”类(如List)。另一端实际接收的具体类始终是ArrayList,但生成的方法使用的是List接口)
  • Map (Map中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或声明的可打包类型。 不支持通用 Map(如 Map<String,Integer>形式的 Map)。 另一端实际接收的具体类始终是HashMap,但生成的方法使用的是 Map接口)

注意:如果在.aidl文件中使用的是自定义对象,即使此类型文件和.aidl文件在同一个文件夹中,也必须使用import语句导入,并且必须实现Parcelable接口。另外,非原语类型的参数需要指示数据走向的方向标记,可以是 in(输入)、out(输出) 或 inout(输入输出),默认是in,一定要正确规定方向,因为编组参数的开销极大。

比如声明一个自定义对象Rect.java:

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    public static final Parcelable.Creator<Rect> CREATOR = new
Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }
}
复制代码

创建一个声明可打包类的 .aidl 文件,与声明的自定义对象同名,并且这两个同名的类和.aidl文件放在同一个包下,如 Rect.aidl:

package android.graphics;

//注意这里是小写的parcelable来声明
parcelable Rect;
复制代码

上面把Rect类放在.aidl目录中时,编译会提示找不到这个类,因为Android Studio默认会去java目录下找,需要在build.gradle文件 android{ } 中间增加一段代码,让aidl目录里面的java文件也能被识别:

 sourceSets{
        main{
            java.srcDirs=['src/main/java','src/main/aidl']
        }
    }
复制代码

以下是一个 IRemoteService.aidl 文件示例:

// IRemoteService.aidl
package com.example.android;

// Declare any non-default types here with import statements

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    int getPid();

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}
复制代码

2.实现接口 SDK会根据.aidl文件自动生成一个接口,接口中有一个名为Stub的内部抽象类,用于扩展Binder类并实现AIDL中的方法,服务端需要覆写Stub类并实现方法。此外,Stub类中还有一个asInterface()方法,该方法带IBinder(通常便是传给客户端 onServiceConnected()回调方法的参数)并返回存根接口实例。

以下是一个使用匿名实例实现名为 IRemoteService 的接口(由以上 IRemoteService.aidl 示例定义)的示例:

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing
    }
};
复制代码

mBinder是 Stub类的一个实例(一个 Binder),用于定义服务的 RPC 接口。 在下一步中,将向客户端公开该实例,以便客户端能与服务进行交互。 在实现 AIDL 接口时应注意遵守以下这几个规则:

  • 由于不能保证在主线程上执行传入调用,因此一开始就需要做好多线程处理准备,并将您的服务正确地编译为线程安全服务。
  • 默认情况下,RPC 调用是同步调用。如果明知服务完成请求的时间不止几毫秒,就不应该从 Activity 的主线程调用服务,因为这样做可能会使应用挂起(Android 可能会显示“Application is Not Responding”对话框)— 您通常应该从客户端内的单独线程调用服务。
  • 引发的任何异常都不会回传给调用方。

3.向客户端公开接口 服务端实现Service并重写Onbind()以返回Stub类的实现,客户端就能与服务进行交互了。 以下是一个向客户端公开 IRemoteService 示例接口的服务示例:

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        return mBinder;
    }

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing
        }
    };
}
复制代码

现在,当客户端(如 Activity)调用bindService()以连接此服务时,客户端的onServiceConnected()回调会接收服务的onBind()方法返回的 mBinder实例。 客户端还必须具有对 interface 类的访问权限,因此如果客户端和服务在不同的应用内,则客户端的应用 src/目录内必须包含 .aidl文件(它生成android.os.Binder接口 — 为客户端提供对 AIDL 方法的访问权限)的副本。 当客户端在onServiceConnected()回调中收到IBinder时,它必须调用 YourServiceInterface.Stub.asInterface(service)以将返回的参数转换成YourServiceInterface类型。例如:

IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName className, IBinder service) {
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }

    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        mIRemoteService = null;
    }
};
复制代码

客户端执行IPC过程:

  1. 在项目 src/目录中加入 .aidl文件。
  2. 声明一个IBinder接口实例(基于 AIDL 生成)。
  3. 实现 ServiceConnection。
  4. 调用 Context.bindService(),以传入ServiceConnection实现。
  5. 在您的 onServiceConnected()实现中,将收到一个 IBinder实例(名为service)。调用YourInterfaceName.Stub.asInterface((IBinder)service) ,以将返回的参数转换为 YourInterface 类型。
  6. 调用在接口上定义的方法。始终捕获 DeadObjectException异常,它们是在连接中断时引发的;这将是远程方法引发的唯一异常。
  7. 如需断开连接,请使用您的接口实例调用 Context.unbindService()。

上面说的是整个流程,下面来举个栗子,先看效果图:

服务端代码

Apple.aidl:

package org.ninetripods.mq.multiprocess_sever;
parcelable Apple;
复制代码

其中Apple类已经实现Parcelable接口了,这里就不再贴出来了,接着声明IAidlCallBack.aidl 提供客户端调用的方法:

// IAidlCallBack.aidl
package org.ninetripods.mq.multiprocess_sever;

// Declare any non-default types here with import statements
import org.ninetripods.mq.multiprocess_sever.Apple;
interface IAidlCallBack {
    Apple getAppleInfo();
}
复制代码

然后编写RemoteService.java类:

public class RemoteService extends Service {
    public RemoteService() {
    }
    @Override
    public IBinder onBind(Intent intent) {
         //通过onBind()方法返回Binder实例供客户端调用
        return mBinder;
    }

    private IAidlCallBack.Stub mBinder = new IAidlCallBack.Stub() {
        @Override
        public Apple getAppleInfo() throws RemoteException {
            return new Apple("蛇果", 20f, getString(R.string.respose_info));
        }
    };
}
复制代码

最后在AndroidManifest.xml里声明一下:

<service
   android:name=".RemoteService"
   android:enabled="true"
   android:exported="true">
     <intent-filter>
       <action android:name="android.mq.common.service" />
       <category android:name="android.intent.category.DEFAULT" />
     </intent-filter>
 </service>
复制代码

客户端代码:AidlActivity.java

首先实现ServiceConnection,在onServiceConnected()中通过IAidlCallBack.Stub.asInterface(service) 拿到Binder实例,然后就可以调用服务端方法了,代码如下:

private ServiceConnection mCommonConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            isCommonBound = true;
            mCommonService = IAidlCallBack.Stub.asInterface(service);
            if (mCommonService != null) {
                try {
                    Apple apple = mCommonService.getAppleInfo();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isCommonBound = false;
            mCommonService = null;
            showMessage("\n连接远程服务端失败...");
        }
    };
复制代码

最后是绑定服务:

Intent commonIntent = new Intent();
commonIntent.setAction("android.mq.common.service");
commonIntent.setPackage("org.ninetripods.mq.multiprocess_sever");
bindService(commonIntent, mCommonConnection, Context.BIND_AUTO_CREATE);
复制代码

例子中还有个观察者模式,跟在同一进程中使用观察者模式是有区别的,要用到RemoteCallbackList,具体使用方法代码中有,就不再详述了~ 恩~整个过程差不多是这样了,完整代码已上传至 Github:进程间通信之AIDL,如果对您有帮助,给个star吧,感激不尽~(注:项目中有两个项目,请确保先启动Server端,否则看不到效果

转载于:https://juejin.im/post/5a33e7e35188256970780cfd

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值