那么Binder到底是什么呢?对于应用层的人来说他就是一个跨进程通信的方式,我们知道Android系统中每个App都运行在自己独立的进程里,当然一个App也可以有多个进程,这个我们下一篇再细说,而进程之间是不能直接通信,各个App之间虽然是相互独立的但是也不能完全不交流,比如我在简书…不…在csdn看到一个非常有意思的文章,我想把它分享到微信,这个时候就要进程间通信了。
那AIDL又是什么呢?由于Binder使用起来很麻烦,需要创建很多类和接口,于是谷歌为了让我们能够方便的使用Binder就指定了一个框架或者说是插件来让我们间接的使用Binder,这样做的好处是我们不用对Binder进行什么了解,直接按照谷歌给的模版用最少的代码量写出满足需求的代码。
这里我们创建两个项目,一个叫server,一个叫client,server提供接口,client调用接口,也就相当于简书是client调用微信server的接口。
很多文章都说Binder通信采用C/S架构,到底是什么意思呢,其实可以理解为谁主动调用谁就是客户端client,谁被动接受谁就是服务端server。
创建第一个应用server。
在进程间通信不是你想传什么就能传,对数据类型有一定的要求,默认情况下,AIDL 支持下列数据类型:
在创建传输数据的实体类之前要先创建一个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 +
'}';
}
}
这个时候可能会报错,先不管。
第一步的时候创建了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一下项目,如果前面有报错的,到这里就该没了。
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。
总共复制了三个文件,整个AIDL和数据类People,注意包名一定要和server端保持完全一致。复制完成之后clean一下项目。
由于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在不同应用间进行进程通信,下面来介绍一下同个应用内的不同进程间的通信,其实使用和上面的是一样,我们选择server端作为例子,在server端创建一个Activity并指定运行在其他进程,将client端的连接服务调用接口的代码(也就是MainActivity)放到这个Activity中,下面来看一下具体操作:
在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" />
将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中,注意要在回调中解除绑定。
还记得之前定义AIDL接口时方法参数前的tag吗
void addPeople(in People people)
在Binder中数据流向有三种in、out和inout,如果不指定默认是in。
AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。
通俗点来讲:
这一篇文章讲的主要是AIDL的使用,很基础简单的知识点,并没有接触到Binder的相关内容,下一篇文章我们会分析AIDL的核心,也就是AIDL帮助我们做了哪些事,以及我们自己怎么不借助AIDL完成这些事。