android AIDL实现进程聊天的小程序

aidl全称是Android Interface Definition Language,也就是Android接口定义语言。它用于进程间通信。

每个进程都有它独立的内存空间,不同进程想要相互直接访问内存是不行的,aidl就是为了实现进程间相互访问而创造的。

效果图

这里写图片描述

这里写图片描述

使用

aidl并不是支持任何类型的,我们首先应该知道aidl支持哪些数据类型,对其一开始不支持的数据类型我们需要做相应处理。

AIDL文件支持的数据类型

  • Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。
  • String 类型。
  • CharSequence类型。
  • List类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable(下文关于这个会有详解)。List可以使用泛型。
  • Map类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的。
  • 定向tag:这是一个极易被忽略的点——这里的“被忽略”指的不是大家都不知道,而是很少人会正确的使用它。在我的理解里,定向 tag
    是这样的:AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out
    表示数据只能由服务端流向客户端,而 inout
    则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag
    的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out
    的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag
    的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

如果不是这几种数据类型的话,必须实现Parcelable接口

  1. 首先我们先按照常规创建一个类,这里我创建一个Talk类,它包含通信端的名称和通信的内容。
public class Talk {

    //发送方名称
    private String name;

    //发送的信息
    private String message;

    public Talk() {
    }

    public String getName() {
        return name;
    }

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

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "Talk{" +
                "name='" + name + '\'' +
                ", message='" + message + '\'' +
                '}';
    }

}

这是一个很常见的类,没有什么可以讲的,但是aidl文件是不支持我们的Talk这种类型的,我们必须将其序列化才能使这种类型的数据在内存之间传递。

如何序列化呢,这就是我们接下来的一步

2.继承Parcelable接口,重写其中的方法

这里写图片描述

这里写图片描述

这个时候在生成的方法里面我们会注意到有一个writewToParcel的方法,这个方法即实现了我们定向tag里的in,可以从客户端写入数据到服务器,但我们如果还要实现out和inout的话那就不够用了,所以我们要自己书写readFromParcel方法来实现out和inout。

public void readFromParcel(Parcel parcel){
        name = parcel.readString();
        message = parcel.readString();
    }

完整的Talk代码

public class Talk implements Parcelable{

    //发送方名称
    private String name;

    //发送的信息
    private String message;

    public Talk() {
    }

    protected Talk(Parcel in) {
        name = in.readString();
        message = in.readString();
    }

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

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

    public String getName() {
        return name;
    }

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

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

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

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(name);
        parcel.writeString(message);
    }

    //为了实现out和inout,要自己书写此方法
    public void readFromParcel(Parcel parcel){
        name = parcel.readString();
        message = parcel.readString();
    }

    @Override
    public String toString() {
        return "Talk{" +
                "name='" + name + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}

类定义好了,也序列化了,接下来

创建aidl文件,引入我们的类

这里写图片描述

新建一个aidl文件和Talk的名字一样,注意,Book.aidl的包名也必须和Book.java的包名一致

//注意这里的包名
package com.example.aidldemo.bean;

parcelable Book;//parcelable要小写!!!

aidl文件分两种,一种是我们刚刚那样的,方便我们引入我们自己创建的类,另一种则用来声明具体的操作方法

创建TalkManager.aidl文件

创建aidl文件的方法和刚刚创建的步骤一样

// TalkManager.aidl
package com.example.myaidlserver.bean;

//一定要import,并写出完整路径!!!
import com.example.myaidlserver.bean.Talk;

interface TalkManager {

    //不论返回什么类型,都不用声明public,private
    //发送消息,为了使双方能够通信,所以使用inout
    void sendMessage(inout Talk talk);

    //接收消息
    Talk getMessage();
}

两个aidl文件创建好了,现在我们编译一下

点击这个小锤子

如果编译成功了,那么我们会发现androidstudio帮我们自动生成了一个文件,这个文件就是aidl的接口
这里写图片描述

接口有了,下面我们要具体实现接口中的方法了

新建一个Service类继承自Service

public class AIDLService extends Service{

    private final static String TAG = "AIDLService";

    //用于存放我们的Talk实例
    private List<Talk> talkList;

    //定义TalkManager.Stub的实例,在其中将接口的方法具体实现
    private TalkManager.Stub talkManager = new TalkManager.Stub() {
        @Override
        public void sendMessage(Talk talk) throws RemoteException {
            talkList.add(talk);
        }

        @Override
        public Talk getMessage() throws RemoteException {
            return talkList.get(talkList.size() - 1);
        }
    };

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

    //返回我们的talkmanager实例以便于操作我们的方法
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return talkManager;
    }
}

千万不要忘记在Manifest中声明

<service android:name=".service.AIDLService"
        android:exported="true">
        <intent-filter>
            <action android:name="com.example.myaidlserver"/>
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
</service>

在MainActivity中绑定服务,操作方法(如果对服务不熟悉的朋友请先了解一下Service)

public class MainActivity extends AppCompatActivity {

    private TextView textView;

    private Button button;

    private EditText editText;

    private String message;

    private TalkManager talkManager = null;

    //发送的信息
    private Talk talk;

    //接收到的信息
    private Talk talk2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        contact();
        if(talkManager != null){
            try {
                talk2 = talkManager.getMessage();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        if(talk2 != null){
            textView.append(talk2.toString() + "\n");
        }
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                talk = new Talk();
                message = editText.getText().toString();
                talk.setMessage(message);
                talk.setName("服务器");
                sendMessage(talk);
                textView.append(talk.toString() + "\n");
                editText.getText().clear();
            }
        });
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        if(talkManager != null){
            try {
                talk2 = talkManager.getMessage();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        if(talk2 != null){
            textView.append(talk2.toString() + "\n");
        }
    }

    public void initView(){
        textView = findViewById(R.id.textview);
        button = findViewById(R.id.button);
        editText = findViewById(R.id.edittext);
    }

    //绑定服务
    public void contact(){
        Intent intent = new Intent();
        intent.setAction("com.example.myaidlserver");
        intent.setPackage("com.example.myaidlserver");
        bindService(intent,serviceConnection, Context.BIND_AUTO_CREATE);
        Toast.makeText(this,"已连接",Toast.LENGTH_SHORT).show();
    }

    public void sendMessage(Talk talk){
        try {
            talkManager.sendMessage(talk);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    //在其中实例化我们的talkManager
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            talkManager = TalkManager.Stub.asInterface(iBinder);
        }

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

服务器端的代码写好了,现在服务器可以收和发了,接下来

客户端

客户端怎样才能利用aidl访问服务器的进程,与服务器进行通信呢?

首先,我们要先做aidl文件和java文件的移植

这里写图片描述

对于aidl文件,我们直接将整个aidl文件夹复制到客户端的main文件下就好

对于java文件,我们将我们的Talk.java文件复制到客户端的java文件夹下,这里有一定要注意的一点,我们的Talk类复制过去后也要和之前的包名保持一致,
这里写图片描述

然后编译一下

因为服务器和客户端通信的原理一样,所以我们之间把服务器端的MainActivity中的代码复制过来就好。

开始通信吧

服务器源码

客户端源码

猜你喜欢

转载自blog.csdn.net/qq_37918409/article/details/81944566