Binder总结篇2-Binder使用
在上一篇文章 Binder总结篇1-Binder原理 中,我们大概理解了Binder的运行原理,那么我们在什么样子的应用场景下会使用到Binder呢?
就我个人而言,是在IM系统开发当中使用到多进程开发,也就需要Binder来进行通信了,本文是编写实际的例子的,涉及到的点有:
- AIDL
包括支持的数据类型,定义以及使用等
- Service
包括一些启动,数据获取以及数据回传等。
本文的demo地址是: AndroidBinderSample
AIDL
关于AIDL的详细描述,可以看官网 Android 接口定义语言 (AIDL)
他是Android的接口定义语言,用来具体实现Binder通信过程的数据传递,格式跟java的接口代码的编写差不多。
他是使用.aidl文件结尾,存放在man文件夹下的aidl文件夹下,当然你可以通过在gradle中配置
aidl.srcDirs
来指定。
AIDL支持的数据类型
- Java的基本类型,也包括String类型和CharSequence类型
- List 和Map,其中List和Map中的元素必须是AIDL支持的数据类型,而且在Server端必须使用ArrayList或者是HashMap来接收。
- 其他的AIDL生成的接口
- 实现了Parcelable接口的实体,可以看 详细介绍Android中Parcelable的原理和使用方法
AIDL文件,总得来说,AIDL分为两类文件,一种是接口类型,就是需要被调用被实现的。一种是声明Parcelable数据,作用就是把对应的Java实现了Parcelable接口的类映射到AIDL中,然后被AIDL的接口文件引用,需要注意的是这个AIDL文件的包名需要与Java实现Parcelable文件对应的包名一致。
例如:
我们声明了一个JavaDomain,在包
app.androidbinder.domain
下,大致如下
package app.androidbinder.domain;
import android.os.Parcel;
import android.os.Parcelable;
/**
* 作者:黎伟杰 on 2018/8/13.
* 邮箱:[email protected]
* description:
* update by:
* update day:
*
* @author liweijie
*/
public class UserInfo implements Parcelable {
//省略代码
public UserInfo() {
}
protected UserInfo(Parcel in) {
//省略代码
}
public static final Creator<UserInfo> CREATOR = new Creator<UserInfo>() {
@Override
public UserInfo createFromParcel(Parcel in) {
return new UserInfo(in);
}
@Override
public UserInfo[] newArray(int size) {
return new UserInfo[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
//省略代码
}
public void readFromParcel(Parcel in) {
//省略代码
}
}
复制代码
然后在AIDL文件下也需要建立对应的包名,然后编写UserInfo.aidl,如下:
package app.androidbinder.domain;
/**
* 作者:黎伟杰 on 2018/8/14.
* 邮箱:[email protected]
* description:
* update by:
* update day:
*
* @author liweijie
*/
parcelable UserInfo;
复制代码
这样子,在别的AIDL中就可以引用这个
UserInfo
了。
定义接口类型的AIDL如下:
// UserService.aidl
package app.androidbinder;
import java.util.List;
import app.androidbinder.domain.UserInfo;
// Declare any non-default types here with import statements
interface UserService {
String getUserName(int userId);
void saveUser(in UserInfo param);
UserInfo getUserInfo(int userId);
List<UserInfo> queryUser();
//for in out inout
UserInfo handleIn(in UserInfo info);
UserInfo handleOu(out UserInfo info);
UserInfo handleInOut(inout UserInfo info);
}
复制代码
这就是两种AIDL文件类型以及对应的大致编写。
in、out、inout
在官方文档中支出,所有的非原语参数需要指示数据的方向标记,可以是in、out、inout。默认的原语是in,不能是其他流向。 这里指定的非原语是指:除了Java的基本类型外的其他参数,也就是对象。我们在AIDL使用的时候需要知道这个参数的流向。 那什么是数据的方向标记呢? 首先,数据的方向标记是针对客户端中的那个传入的方法参数而言。数据流向的标识符不能使用在返回参数上,只能使用在方法参数上面。
- in:他表示的是这个参数只能从客户端流向服务端,比如客户端传递了一个User对象给服务端,服务端会收到一个完整的User对象,然后假如在服务端对这个对象进行操作,那么这个改变是不会反映到客户端的,这个流向也就是只能从客户端到服务端。
- out:他表示,当客户端传递参数到服务端的时候,服务端将会收到一个空的对象,假如服务端对该对象进行操作,将会反映到客户端。比如,客户端传递一个User对象到服务端,服务端接收到的是一个空的User对象(不是null,只是有点像new一个User对象)。当服务端对这个User对象进行改变的时候,他的值变化将会反映到客户端。
- inout,它具有这二者的功能,也就是客户端传递对象到服务端,可以接收到完整的对象,同时服务端改变对象,也会反映到客户端。 总结来说,in类似于传值,out类似于传引用,只是out的引用值到了服务端为空,inout则具有二者的功能,默认的是in。
Service
多进程之间的通信离不开的是Service,Android四大组件之一,这里不过多的赘述,只是需要知道我们在多进程通信当中,服务端最少提供一个
Service
来,在
onBind()
方法中返回实现AIDL接口的
IBinder
对象。然后客户端通过
bindService()
来连接,通过在
ServiceConnection
的
onServiceConnected
方法,通过调用AIDL 生成的文件中的
Stub
的
asInterface()
来在客户端获取服务实例,继而调用服务端的方法。需要注意,在跨app的进程调用中,对外暴露的
Service
需要在清单文件中把
android:exported
设置为
true
。一般而言,我们还会配置一些fillter来进行过滤。
关于如何使用Service以及他的一些生命周期,一些方法区别(比如startService和bindService)请自己另外查阅文档,这里就不描述了。以下是本文Demo中的实例,使用的还是上面的AIDL,使用之前确保成功编译出对应的aidl生成文件:
服务端的Service
package app.androidbinder2.services;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import app.androidbinder.UserService;
import app.androidbinder.domain.UserInfo;
/**
* 作者:黎伟杰 on 2018/8/12.
* 邮箱:[email protected]
* description:
* update by:
* update day:
*
* @author liweijie
*/
public class App2Service extends Service {
/**
* 模拟一些测试数据
*/
private List<UserInfo> data = new ArrayList<>();
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
data.clear();
data.add(new UserInfo(1, "A", 20));
data.add(new UserInfo(2, "B", 30));
data.add(new UserInfo(3, "C", 40));
data.add(new UserInfo(4, "D", 50));
return userService;
}
private Binder userService = new UserService.Stub() {
@Override
public String getUserName(int userId) throws RemoteException {
for (UserInfo item : data) {
if (item.getUserId() == userId) {
return item.getUserName();
}
}
return null;
}
@Override
public void saveUser(UserInfo param) throws RemoteException {
data.add(param);
}
@Override
public UserInfo getUserInfo(int userId) throws RemoteException {
for (UserInfo item : data) {
if (item.getUserId() == userId) {
return item;
}
}
return null;
}
@Override
public List<UserInfo> queryUser() throws RemoteException {
return data;
}
@Override
public UserInfo handleIn(UserInfo info) throws RemoteException {
info.setUserName("嘿嘿嘿");
return info;
}
@Override
public UserInfo handleOu(UserInfo info) throws RemoteException {
if (info == null) {
Log.e("App2Service", "UserInfi server is null");
info = new UserInfo();
}
info.setUserName("嘻嘻嘻");
return info;
}
@Override
public UserInfo handleInOut(UserInfo info) throws RemoteException {
info.setUserName("哈哈哈");
return info;
}
};
}
复制代码
清单文件的配置是:
<service
android:name=".services.App2Service"