IPC第一篇:AIDL远程服务

IPC是(Inter-Process-Communication)的简称,中文名是:进程间的通信。顾名思义就是可以实现进程与进程之间数据交换的一种机制。

那AIDL又是什么东西呢?AIDL是(Android-Interface-Definition-Language)的简称,中文名是:安卓接口定义语言。

由于Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。

为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。我们知道4个Android应用程序组件中的3个(Activity、Broadcast和Content Provider)都可以进行跨进程访问,另外一个Android应用程序组件Service同样可以。因此,可以将这种可以跨进程访问的服务称为AIDL(Android Interface Definition Language)服务。(摘自搜狗百科)

      了解了基本概念之后,我们再说一下AIDL都支持哪些类型的数据:

1、基本数据类型(int、long、char、boolean、double)
2、String和CharSequence
3、List和Map集合
4、集合内元素必须是AIDL支持的数据类型
5、服务端具体使用的集合必须是ArrayList和HashMap
6、Parcelable:实现了Parcelable接口的对象
7、AIDL本身接口也可以在AIDl文件使用。

这里需要说明一下,如果想要在跨进程中传递模型数据,需要实现Parcelbale接口。只有实现了序列化,才可以传递,否则会失败。如果不了解Parcelable的同学可以去搜索一下Parcelable的相关知识。这里不做讲解。

那么如何实现一个自己的AIDL呢?

AIDL有几大步骤:

1、创建AIDL文件

2、在Service中实现AIDL

3、创建客户端,连接远程服务

接下来我们就按照步骤一步一步的实现一个自己的AIDL。

1、创建AIDL文件

首先我们新建一个安卓项目,然后在main目录右键,创建一个AIDL文件,我们先创建一个Person.aidl文件。

Person.aidl文件的内容很简单,内容如下:

// Person.aidl
package com.yangzhenyu.msgservice;

parcelable Person;

这里我解释一下,parcelable Person这行代码的意思是Person是一个序列化的类,可以被用来传递。接下来我们创建Person的实体类,内容也比较简单,直接粘代码了:

package com.yangzhenyu.msgservice;

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

import androidx.annotation.NonNull;

public class Person implements Parcelable {

    private String name;
    private int age;

    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }


    protected Person(Parcel in) {
        name = in.readString();
        age = in.readInt();
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {

        dest.writeString(name);
        dest.writeInt(age);
    }

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

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

    @NonNull
    @Override
    public String toString() {
        return "姓名:"+this.name+" 年龄:"+this.age;
    }
}

Person类有两个变量,一个是name,一个是age,然后实现了Parcelable接口,这一点很重要,上文中已经提到,只有实现了Parcelable的类,才可以在进程中传递,Person对外提供了一个两参的构造方法并且重写了toString方法。

接下来我们再声明一个IPersonManager的AIDL文件,IPersonManager.aidl文件其实是一个接口,里面封装了一些操作,这里我简单的封装了四个方法:

import com.yangzhenyu.msgservice.Person;

interface IPersonManager {

    List<Person> getPersonList();

    void addPerson(in Person person);

    Person getFirstPerson();

    void deletePerson(int index);
}

都是对Person这个类的一些简单操作,有一点需要注意,我们需要手动的将Person这个类的包导入一下。

有些同学可能注意到有的方法参数前面会有一个in标记,那个折又是什么意思呢?

声明方法时,如果不是基本数据类型,需要在参数前面增加一个tag,这个tag有三种,in,out,inout,这里表示的是这个参数可以支持的流向:

  • in: 代表这个对象能够从客户端传递到服务器

  • out: 代表这个对象能够作为返回值从服务器到客户端

  • inout: 代表这个对象能从客户端到服务器,也可以作为返回值从服务器到客户端

需要注意的是,创建AIDL文件的时候,会自动生成一个basicTypes方法,这个方法展示了AIDL默认支持的基本数据类型。我们可以删掉这个方法即可。因为我们要写自己的方法。

/**
     * 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);

目前位置我们AIDL文件的创建就到此结束了。接下来我们来实现Service。

2、在Service中实现AIDL文件

首先我们需要创建一个MyService继承自Service类,MyService自动重写了onBind方法,这个方法需要返回一个IBiner对象

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

接下来我们需要实现AIDL接口,在这里,我们在MyService中创建一个Person的数组mList,用来对接口方法做具体的实现

private Binder mBinder = new IPersonManager.Stub() {

        @Override
        public List<Person> getPersonList() throws RemoteException {
            return mList;
        }

        @Override
        public void addPerson(Person person) throws RemoteException {
            mList.add(person);
            Log.d(TAG,"插入成功:"+person.toString());
        }

        @Override
        public Person getFirstPerson() throws RemoteException {
            return mList.size()>0?mList.get(0):null;
        }

        @Override
        public void deletePerson(int index) throws RemoteException {
            if (mList.size()>index){
                Person person = mList.get(index);
                mList.remove(index);
                Log.d(TAG,"删除成功:"+person.toString());
            }
        }
    };

为了方便使用,我们在Service的构造方法中对mList进行了一次初始化的操作(这个操作可有可无,我这边是为了方便使用),然后在onBind方法中将mBinder对象返回。最后记得去AndroidMainfest.xml文件中声明service,

 <service android:name=".MyService"
            android:enabled="true"
            android:exported="true"
            android:process=":YangService">
            <intent-filter>
                <action android:name="YANGZHENYU.SERVICE"/>
            </intent-filter>
        </service>

注意:Service需要声明隐式意图,因为要给别的应用程序使用。还有一点需要留意,当你尝试运行项目的时候,可能会报找不到Person这个类,原因是Android Studio默认会去java目录里寻找Person类,我们将Person类放在了aidl目录下,之所以这么做是为了方便别的应用程序拷贝aidl相关的文件,因此为了解决报错问题,我们需要在gradle中声明Person的目录地址

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"
    ......
    ......
    sourceSets {
        main{
            java.srcDirs = ['src/main/java','src/main/aidl']
        }
    }
}

到这里,我们的服务器端已经完成了,现在可以运行项目将服务器端安装到手机里面了。接下来我们来实现客户端的代码。

3、创建客户端,连接远程服务

首先第一步我们需要将服务器端的AIDL文件全部拷贝过来,将它放置在与java目录同级的目录下就好,并且保持包名一致。

接下来我们在MainActivity中实现ServiceConnection这个接口

 private static ServiceConnection mConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mManager = IPersonManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

在这里我们主要实现onServiceConnected这个方法,我们通过IPersonManager.Stub.asInterface这个方法拿到服务器的IPersonManager对象,然后就可以操作这个对象里面的方法了。使用和正常的其他方法一样。在这里我在activity_main.xml中定义了五个按钮,分别是绑定远程服务、获取全部列表、插入人员、获取第一个人、删除一个人。绑定服务通过隐式意图来完成,注意一点,在Android5.0之后,隐式意图需要传递package,否则会报错。然后我们还需要在activity的onDestroy中解绑,否则会造成内存泄漏。

Intent intent = new Intent();
intent.setAction("YANGZHENYU.SERVICE");
intent.setPackage("com.yangzhenyu.msgservice");
bindService(intent,mConn,BIND_AUTO_CREATE);



。。。
。。。



@Override
protected void onDestroy() {
    super.onDestroy();
    unbindService(mConn);
}

完整的代码如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG="Client";
    private static IPersonManager mManager;
    private Button mBindBtn;
    private Button mObtainListBtn;
    private Button mInsertBtn;
    private Button mFirstBtn;
    private Button mDeleteBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBindBtn = findViewById(R.id.bind_btn);
        mObtainListBtn = findViewById(R.id.obtain_list_btn);
        mInsertBtn = findViewById(R.id.insert_btn);
        mFirstBtn = findViewById(R.id.first_btn);
        mDeleteBtn = findViewById(R.id.delete_btn);

        mBindBtn.setOnClickListener(this);
        mObtainListBtn.setOnClickListener(this);
        mInsertBtn.setOnClickListener(this);
        mFirstBtn.setOnClickListener(this);
        mDeleteBtn.setOnClickListener(this);

    }

    private static ServiceConnection mConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mManager = IPersonManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    @Override
    public void onClick(View v) {
        try {
            switch (v.getId()) {
                case R.id.bind_btn:
                    Intent intent = new Intent();
                    intent.setAction("YANGZHENYU.SERVICE");
                    intent.setPackage("com.yangzhenyu.msgservice");
                    bindService(intent,mConn,BIND_AUTO_CREATE);
                    break;
                case R.id.obtain_list_btn:
                    if (mManager!=null){
                        List<Person> list = mManager.getPersonList();
                        Log.d(TAG,list.toString());
                    }

                    break;
                case R.id.insert_btn:
                    if (mManager!=null){
                        mManager.addPerson(new Person("张麻子",38));
                    }
                    break;
                case R.id.first_btn:
                    if (mManager!=null){
                        Person person = mManager.getFirstPerson();
                        Log.d(TAG,"第一个人是:"+person.toString());
                    }
                    break;
                case R.id.delete_btn:
                    if (mManager!=null){
                        mManager.deletePerson(2);
                    }
                    break;
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConn);
    }
}

接下来我们就可以将客户端安装到手机上了

我们首先点击绑定远程服务,这个时候,客户端拿到IPersonManager对象,接下来,我们点击获取全部列表,此时控制台打印服务器端的初始化的list的内容:

com.yangzhenyu.msgclient D/Client: [姓名:小王 年龄:21, 姓名:大熊 年龄:26, 姓名:乐乐 年龄:23, 姓名:理想 年龄:20, 姓名:特斯拉 年龄:78, 姓名:爱因斯坦 年龄:90, 姓名:小丽 年龄:25]

接下来我们尝试插入一条数据,控制台然后再次获取全部列表查看,此时可以看到,插入的数据已经成功,我们在数组的最后插入了张麻子

//这个是在服务器端打印的内容
com.yangzhenyu.msgservice:YangService D/Server: 插入成功:姓名:张麻子 年龄:38

//这个是在客户端获取列表打印的内容,此时插入成功了

com.yangzhenyu.msgclient D/Client: [姓名:小王 年龄:21, 姓名:大熊 年龄:26, 姓名:乐乐 年龄:23, 姓名:理想 年龄:20, 姓名:特斯拉 年龄:78, 姓名:爱因斯坦 年龄:90, 姓名:小丽 年龄:25]
com.yangzhenyu.msgclient D/Client: [姓名:小王 年龄:21, 姓名:大熊 年龄:26, 姓名:乐乐 年龄:23, 姓名:理想 年龄:20, 姓名:特斯拉 年龄:78, 姓名:爱因斯坦 年龄:90, 姓名:小丽 年龄:25, 姓名:张麻子 年龄:38]

接下来我们再尝试获取第一个人和删除一个人(这里我直接在代码里写死了,删除第二条数据,也就是乐乐会被删除),也都可以正常完成操作。

//客户端打印的内容
com.yangzhenyu.msgclient D/Client: 第一个人是:姓名:小王 年龄:21


//服务器端打印的删除一个人的内容:
com.yangzhenyu.msgservice:YangService D/Server: 删除成功:姓名:乐乐 年龄:23


//再次获取列表,可以发现,数据确实已经删除了

com.yangzhenyu.msgclient D/Client: [姓名:小王 年龄:21, 姓名:大熊 年龄:26, 姓名:理想 年龄:20, 姓名:特斯拉 年龄:78, 姓名:爱因斯坦 年龄:90, 姓名:小丽 年龄:25, 姓名:张麻子 年龄:38]

到这里,我们已经正常的完成了AIDL的全部流程。

最后我们再简单的总结一下:

1、为了方便客户端使用AIDL文件,最好将aidl相关的类放在同一个地方,方便拷贝

2、如果找不到AIDL相关的类,需要在gradle中声明路径

3、安卓5.0之后隐式意图需要指定报名,否则会报错

以上就是如何实现AIDL的全部流程,如果还有疑问,欢迎交流。最后希望大家发现问题及时指正,多多包涵。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值