Android进程间通信之AIDL

作者: 饱醉豚我去年买了个表 | 来源:发表于2017-03-24 14:58 被阅读1073次

    本文例子中的源码地址: 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<String>)。另一端实际接收的具体类始终是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()。

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


    aidl.gif

    服务端代码

    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端,否则看不到效果

    相关文章

      网友评论

        本文标题:Android进程间通信之AIDL

        本文链接:https://www.haomeiwen.com/subject/wnllnttx.html