Service全面解析——深入理解AIDL

本文主要梳理Service中远程服务相关内容,重点学习adil相关用法,若对Servcie的基础不扎实,建议先去阅读Servcie全面解析——本地服务全面解析

AIDL简述

AIDL的定义是 Android Interface Definition Language,即Android接口定义语言。没错,AIDL是一门语言,那么它就包含一系列的语法定义以及它的各类用法。

Android为何要设计这门语言?

下面是官方文档中对于AIDL的介绍

Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. 

注意:只有当你允许来自不同应用程序的客户端访问你的IPC服务并希望处理服务中的多线程时,才需要使用AIDL。 如果你不需要在不同的应用程序间执行并发IPC,你应该通过实现一个Binder来创建你的接口,或者,如果你想执行IPC,但不需要处理多线程,可以使用Messenger来实现你的接口。

可见AIDL适用于Android中的进程间通信,并且需要执行并发IPC时的场景。而Messenger在Android中进行进程间通信,是同步进行的,无法处理并发情况。

AIDL语法

AIDL中支持的类型有如下几种:

  1.  Java中所有基本数据类型,char,byte,short,int,long,float,double,boolean
  2. String类型
  3. CharSequence
  4. List,List当中的数据元素必须能够被AIDL支持
  5. Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
  6. Parcelable:实现了Parcelable接口的对象
  7. AIDL接口:所有的AIDL接口本身也可以在AIDL文件中使用。
  8. 定向tag:其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。

AIDL编写流程

AIDL的实现分以下步骤:

  1. 创建实体类实现Parcelable接口进行序列化和反序列化
  2. 创建AIDL文件
  3. 在服务端创建service,创建Binder对象并返回
  4. 在客户端实现ServiceConnection对象,获取服务端提供的AIDL类实体类,用于交互

AIDL具体实现

  • 创建NBAStar类实现Parcelable接口进行序列化和反序列化
public class NBAStar implements Parcelable {
    private String name;
    private int age;
    private String team;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getTeam() {
        return team;
    }

    public void setTeam(String team) {
        this.team = team;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeString(team);
    }


    public static final Creator<NBAStar> CREATOR = new Creator<NBAStar>() {
        @Override
        public NBAStar createFromParcel(Parcel source) {
            NBAStar NBAStar = new NBAStar();
            NBAStar.setName(source.readString());
            NBAStar.setAge(source.readInt());
            NBAStar.setTeam(source.readString());
            return NBAStar;
        }

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

    @Override
    public String toString() {
        return name + "," + age + "," + team + ";";
    }
}
Parcelable是 android为我们提供的序列号接口,实现 Parcelable主要做了三个事情:序列化、 描述、 反序列化,分别对应实现writeToParcel()方法,describeContents()方法,以及内部类Creater中的createFromParcel()方法和newArray()方法,具体的序列化这里不多作讲解。

  • 创建AIDL文件


创建好的目录如上图所示,其中IAidlInterfase.aidl文件是AIDL接口文件,用于客户端的调用

// IAidlInterface.aidl
package review.heal.com.review;

// Declare any non-default types here with import statements
import review.heal.com.review.NBAStar;

interface IAidlInterface {
   List<NBAStar> getAllStars();

   void addStar(in NBAStar NBAStar);
}

实现Parcelable接口的NBAStar类需要一个实体映射的.aidl文件NBAStar.aidl,在NBAStar.aidl中需要声明实体类和类型

// IAidlInterface.aidl
package review.heal.com.review;

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

parcelable NBAStar;

注意:NBAStar.aidl的包名要和实体类NBAStar的包名保持一致

在创建完以上文件之后,在Android Studio中选择Build->Make Project,此时将会出现一个错误

Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
> Unable to find source java class:
 '/Users/Shared/app/AndroidApp/remoteService/app/src/main/aidl/review/heal/com/review/NBAStar.java' 

出现这个错误的原因,是因为在Android Studio中使用Gradle来构建Android项目,而Gradle会默认使用sourceSet来配置不同文件的访问路径,Gradle默认将Java文件访问路径设置在Java包下,若Java文件被放置在aidl包下,那么Android Studio将无法找到此java文件。问题的解决方法实在build.gradle中添加下面语句

 sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }
    }

意思是将Java代码的访问路径设置成Java包和aidl包,这样便能在aidl包中找到java文件了。

此时再在Android Studio中选择Build->Make Project,Android Studio就会帮我们生成IAidlInterface文件。


以下代码由系统自动生成,这些代码用来设定基于IAidlInterface接口的Binder IPC连接,以便客户端与远程服务通信。

public interface IAidlInterface extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements review.heal.com.review.IAidlInterface {
        private static final java.lang.String DESCRIPTOR = "review.heal.com.review.IAidlInterface";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an review.heal.com.review.IAidlInterface interface,
         * generating a proxy if needed.
         */
        public static review.heal.com.review.IAidlInterface asInterface(android.os.IBinder obj) {
            ......
            return new review.heal.com.review.IAidlInterface.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            ......
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements review.heal.com.review.IAidlInterface {
            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;
            }
//所有的返回值前都不需要加任何东西,不管是什么数据类型

            @Override
            public java.util.List<review.heal.com.review.NBAStar> getAllStars() throws android.os.RemoteException {
                ......
                return _result;
            }

            @Override
            public void addStar(review.heal.com.review.NBAStar NBAStar) throws android.os.RemoteException {
                ......
            }
        }

        static final int TRANSACTION_getAllStars = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addStar = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
//所有的返回值前都不需要加任何东西,不管是什么数据类型

    public java.util.List<review.heal.com.review.NBAStar> getAllStars() throws android.os.RemoteException;

    public void addStar(review.heal.com.review.NBAStar NBAStar) throws android.os.RemoteException;
}

  • 在服务端创建service,创建Binder对象并返回
public class NBAStarAidlService extends Service {
    private List<NBAStar> nbaStars;

    private IBinder iBinder = new IAidlInterface.Stub() {
        //返回所有NBAStar
        @Override
        public List<NBAStar> getAllStars() throws RemoteException {
            return nbaStars;
        }

        //增加NBAStar
        @Override
        public void addStar(NBAStar NBAStar) throws RemoteException {
            nbaStars.add(NBAStar);
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        nbaStars = new ArrayList<>();
        NBAStar nbaStar = new NBAStar();
        nbaStar.setName("科比");
        nbaStar.setAge(30);
        nbaStar.setTeam("洛杉矶湖人");
        nbaStars.add(nbaStar);
        return iBinder;
    }
}

同时要在AndroidMainfest文件中声明服务

 <service android:name=".NBAStarAidlService" android:process=":remote">
            <intent-filter>
                <action android:name="review.heal.com.review.IAidlInterface"/>
            </intent-filter>
        </service>
服务端主要做的事,是实现了 IAidlInterface的Stub接口,并在onBind()方法中返回IBinder,客户端获取IBinder对象进行交互。

  • 在客户端实现ServiceConnection对象,获取服务端提供的AIDL类实体类,用于交互

首先需要将服务端的IAidlInterfase.aidl、NBAStar.aidl以及NBAStar实体类移植到客户端上,要保证包名跟服务端的一致,同样在Android Studio中选择Build->Make Project,生成IAidlInterface文件。

然后实例化ServiceConnection,获取服务端IBinder对象

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iAidlInterface = IAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

通过IAidlInterface的Stub抽象类调用asInterface()方法,可以将IBinder对象转换成IAidlInterface的实例,调用bindService()方法绑定远程服务

bindService(new Intent(IAidlInterface.class.getName()), serviceConnection, Context.BIND_AUTO_CREATE);

在Android5.0之后,上面的绑定调用会提示 Service Intent must be explicit  错误,也就是说,在Android5.0之后,服务必须显式的开启。以下方法是一个隐调用转换为显式调用

    public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
        PackageManager pm = context.getPackageManager();
        List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);

        if (resolveInfo == null || resolveInfo.size() != 1) {
            return null;
        }

        ResolveInfo serviceInfo = resolveInfo.get(0);
        String packageName = serviceInfo.serviceInfo.packageName;
        String className = serviceInfo.serviceInfo.name;
        ComponentName component = new ComponentName(packageName, className);

        Intent explicitIntent = new Intent(implicitIntent);

        explicitIntent.setComponent(component);

        return explicitIntent;
    }
       bindService(new Intent(createExplicitFromImplicitIntent(MainActivity.this, new Intent(IAidlInterface.class.getName()))), serviceConnection, Context.BIND_AUTO_CREATE)

这样客户端便完成了远程绑定服务端,调用IAidlInterface的getAllStars()方法

try {
         if (iAidlInterface != null) {
          Log.e("aidl", "接收aidl数据:" + iAidlInterface.getAllStars());
          }
     } catch (RemoteException e) {
          Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
     }

调用结果:

review.heal.com.remoteservice E/aidl: 接收aidl数据:[科比,30,洛杉矶湖人;]

调用IAidlInterface的addStar()方法后再调用getAllStars()方法

try {
       if (iAidlInterface != null) {
           NBAStar nbaStar = new NBAStar();
           nbaStar.setName("麦迪");
           nbaStar.setAge(31);
           nbaStar.setTeam("休斯顿火箭");
           iAidlInterface.addStar(nbaStar);
        }
     } catch (RemoteException e) {
        Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
     }

调用结果:

review.heal.com.remoteservice E/aidl: 接收aidl数据:[科比,30,洛杉矶湖人;, 麦迪,31,休斯顿火箭;]
以上就是AIDL实现跨进程通信的全过程



猜你喜欢

转载自blog.csdn.net/weixin_41975165/article/details/80064022