Android Binder系列之AIDL使用(2)

前言

那么Binder到底是什么呢?对于应用层的人来说他就是一个跨进程通信的方式,我们知道Android系统中每个App都运行在自己独立的进程里,当然一个App也可以有多个进程,这个我们下一篇再细说,而进程之间是不能直接通信,各个App之间虽然是相互独立的但是也不能完全不交流,比如我在简书…不…在csdn看到一个非常有意思的文章,我想把它分享到微信,这个时候就要进程间通信了。

那AIDL又是什么呢?由于Binder使用起来很麻烦,需要创建很多类和接口,于是谷歌为了让我们能够方便的使用Binder就指定了一个框架或者说是插件来让我们间接的使用Binder,这样做的好处是我们不用对Binder进行什么了解,直接按照谷歌给的模版用最少的代码量写出满足需求的代码。

AIDL使用(不同应用间)

这里我们创建两个项目,一个叫server,一个叫client,server提供接口,client调用接口,也就相当于简书是client调用微信server的接口。
很多文章都说Binder通信采用C/S架构,到底是什么意思呢,其实可以理解为谁主动调用谁就是客户端client,谁被动接受谁就是服务端server。

server端

创建第一个应用server。

1.创建 aidl 文件

创建第一个AIDL文件IMyAidlInterface:
在这里插入图片描述

在这里插入图片描述

2.创建传输数据的实体类

在进程间通信不是你想传什么就能传,对数据类型有一定的要求,默认情况下,AIDL 支持下列数据类型:

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

在创建传输数据的实体类之前要先创建一个AIDL文件声明即将要创建的People(这一步操作必须要在前面,否则会导致后面的文件由于重名创建不了)
在这里插入图片描述
将自动生成的代码改成:

package com.czy.server;

parcelable People;

接着创建我们需要传递数据的Bean对象People:

package com.czy.server;

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

/**
 * @author mashanshui
 * @date 2020/4/13 0013
 * @desc TODO
 */
public class People implements Parcelable {
    private String name;
    private int age;

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

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

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


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

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

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

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

这个时候可能会报错,先不管。

3.定义数据接口

第一步的时候创建了IMyAidlInterface.aidl但是没有写代码,他是定义数据接口的,什么是数据接口,我写完你就知道了:

package com.czy.server;
import com.czy.server.People;
// Declare any non-default types here with import statements

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

    People getPeople();
    void addPeople(in People people);
}

在addPeople的参数中有一个in,这个东西代表数据流向,后面我会详细说。

到这里clean一下项目,如果前面有报错的,到这里就该没了。

4.开启服务

Binder的通信是需要借助四大组件之一的Service,这里我们创建一个Service,并在里面加入如下的代码:

package com.czy.server;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class MyService extends Service {
    private static final String TAG = "MyService";

    private People people;

    public MyService() {
        people = new People("爸爸", 25);
    }

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

    private IMyAidlInterface.Stub binder = new IMyAidlInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
            Log.e(TAG, "basicTypes: anInt=" + anInt + " aLong=" + aLong + " aBoolean=" + aBoolean +
                    " aFloat=" + aFloat + " aDouble=" + aDouble + " aString=" + aString);
        }

        @Override
        public People getPeople() throws RemoteException {
            return people;
        }

        @Override
        public void addPeople(People people) throws RemoteException {
            Log.e(TAG, "addPeople: " + people.toString());
        }
    };
}
          <service
            android:name=".MyService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.czy.server.action" />

                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>

在这里我们实现了之前定义的三个接口,client的跨进程请求最终会在这里返回。
到这里server端就完成了。

client端

创建第client。

1.复制server的代码

在这里插入图片描述
总共复制了三个文件,整个AIDL和数据类People,注意包名一定要和server端保持完全一致。复制完成之后clean一下项目。

2.创建连接

由于server是以Service的方式注册的,所以client需要以Service的方式建立连接。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private IMyAidlInterface myAidlInterface;

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

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.addPeople).setOnClickListener(clickListener);
        findViewById(R.id.getPeople).setOnClickListener(clickListener);
        Intent intent = new Intent();
        intent.setPackage("com.czy.server");
        intent.setAction("com.czy.server.action");
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    private View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.getPeople:
                    try {
                        People people = myAidlInterface.getPeople();
                        Log.e(TAG, "从server端获取: " + people);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                case R.id.addPeople:
                    People people = new People("妈妈", 28);
                    try {
                        myAidlInterface.addPeople(people);
                        Log.e(TAG, "向server端添加:" + people.toString());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        }
    };
}

这里我们添加了两个Button,getPeople和addPeople用来调用接口,然后通过bindService和ServiceConnection与server端的MyService建立连接,通过Service获取到IMyAidlInterface.stub的实例之后(其实不是实例,跨进程怎么可能获取到实例呢),就可以调用他的方法了。

测试

代码写完了,我们来进行测试吧。
将两个App都运行到手机上,首先打开server,然后打开client,这时候点击client的getPeople按钮,log如下:
在这里插入图片描述
再次点击addPeople按钮,log如下:
在这里插入图片描述

AIDL使用(同个应用内)

上面讲了使用AIDL在不同应用间进行进程通信,下面来介绍一下同个应用内的不同进程间的通信,其实使用和上面的是一样,我们选择server端作为例子,在server端创建一个Activity并指定运行在其他进程,将client端的连接服务调用接口的代码(也就是MainActivity)放到这个Activity中,下面来看一下具体操作:

1.创建应用内进程

在server中创建一个Activity名为Main2Activity,并设置他的运行进程为remote:

public class Main2Activity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
    }
}
        <activity
            android:process=":remote"
            android:name=".Main2Activity" />

2.绑定服务

将client端的连接服务调用接口的代码(也就是MainActivity)放到Main2Activity中:

public class Main2Activity extends AppCompatActivity {
    private static final String TAG = "Main2Activity";
    private IMyAidlInterface myAidlInterface;

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

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        findViewById(R.id.addPeople).setOnClickListener(clickListener);
        findViewById(R.id.getPeople).setOnClickListener(clickListener);
        Intent intent = new Intent();
        intent.setPackage("com.czy.server");
        intent.setAction("com.czy.server.action");
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }

    private View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.getPeople:
                    try {
                        People people = myAidlInterface.getPeople();
                        Log.e(TAG, "从server端获取: " + people);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                case R.id.addPeople:
                    People people = new People("妈妈", 28);
                    try {
                        myAidlInterface.addPeople(people);
                        Log.e(TAG, "向server端添加:" + people.toString());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        }
    };
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/getPeople"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:text="getPeople"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/addPeople"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="addPeople"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/getPeople" />

</androidx.constraintlayout.widget.ConstraintLayout>

测试

在MainActivity中添加代码跳转到Main2Activity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.textview).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, Main2Activity.class));
            }
        });
    }
}

跳转到Main2Activity之后会运行在新的进程:
在这里插入图片描述
点击getPeople按钮:
在这里插入图片描述
点击addPeople按钮:
在这里插入图片描述

断开监听

我们如果要调用远程接口也就是进程间通信,必须要建立连接,如果建立连接之后过了一段时间再次去访问,这时连接已经断开的话就会报错,所以我们需要判断连接状态,很简单直接调用binder的isBinderAlive方法:

myAidlInterface.asBinder().isBinderAlive()

但是如果在需要通信的时候才去判断连接状态,如果断开就去重新连接再进行通信会消耗时间,有没有一种方法可以监听连接状态,在断开后自动去重连,这就要用到IBinder.DeathRecipient:

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            try {
                myAidlInterface.asBinder().linkToDeath(deathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            myAidlInterface.asBinder().unlinkToDeath(deathRecipient, 0);
        }
    };

通过linkToDeath绑定一个观察者deathRecipient,当断开连接后就会回掉到binderDied中,注意要在回调中解除绑定。

tag数据流向

还记得之前定义AIDL接口时方法参数前的tag吗

void addPeople(in People people)

在Binder中数据流向有三种in、out和inout,如果不指定默认是in。
AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。

  • in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;
  • out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;
  • inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

通俗点来讲:

  • addPeople(in People people):people这个参数到达服务端后的任何修改都和客户端无关,不会同步到服务端。
  • addPeople(out People people):people这个参数无论有没有值到达服务端都是一个空对象,服务端对这个对象的修改会同步到客户端。
  • addPeople(inout People people):people这个参数既能完整的到达服务端,在服务端的修改又能同步到客户端。

总结

这一篇文章讲的主要是AIDL的使用,很基础简单的知识点,并没有接触到Binder的相关内容,下一篇文章我们会分析AIDL的核心,也就是AIDL帮助我们做了哪些事,以及我们自己怎么不借助AIDL完成这些事。

发布了65 篇原创文章 · 获赞 24 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/shanshui911587154/article/details/105490635