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.
* 邮箱:liweijie@qq.com
* 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.
* 邮箱:liweijie@qq.com
* 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.
* 邮箱:liweijie@qq.com
* 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"
android:exported="true">
<intent-filter>
<action android:name="app.androidbinder2.user_service.action"/>
</intent-filter>
</service>
复制代码
客户端
主要的代码如下:
//...省略代码
private UserService userServerService;
@Override
protected void onCreate(Bundle savedInstanceState) {
//...省略代码
}
ServiceConnection userConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
showToast("连接成功");
userServerService = UserService.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
showToast("与服务器断开连接");
}
};
public void startServer(View view) {
Intent startServer = new Intent();
startServer.setAction("app.androidbinder2.user_service.action");
startServer.setComponent(new ComponentName("app.androidbinder2", "app.androidbinder2.services.App2Service"));
bindService(startServer, userConnection, Service.BIND_AUTO_CREATE);
}
public void getUserName(View view) {
try {
//实际调用
String name = userServerService.getUserName(1);
showToast(name);
} catch (Exception e) {
e.printStackTrace();
showToast("出现错误");
}
}
//...省略代码
复制代码
AIDL的实际使用
我们就以上面定义的AIDL文件作为我们的例子。我们需要实现的一个需求是两个App之间通过AIDL实现数据互通,至于一个App之间多进程也是类似,后面再做例子。 其实上面已经是把例子的代码罗列的差不多了。这里列一下跨APP进程调用demo一般的步骤:
- 假如有需要自定义的数据需要通过实现Parcelable传递,那么先编写这种java文件
- 在客户单编写一份引入上面编写的Parcelable实体的AIDL文件,再编写一份AIDL接口(一般而言会把所有跨进程的接口编写在一起)
- 通过编译,当没有问题之后,需要把上面的需要跨进程的Parcelable的Java文件以及AIDL文件复制到Server端,包名需要一致,然后编译。
- 在Server端,新建继承Service的服务实现,然后在onBinder()中,返回实现我们编译生成的AIDL接口文件的Stub子类。
- 在Client端,通过ServiceConnection获取到Server端的Binder实例,进行调用。这里主要用到的是Stub的静态方法asInterface()。客户端就可以通过这个实例进行跨进程调用了。
同一个APP中跨进程通信
我们其实在实际中,使用最多的还是这一种情况,目前我所接触到的就是自己设计的IM系统中使用到。 他其实跟跨APP没有什么区别,同一个APP中跨进程,他只是需要一份AIDL文件即可。因为系统分配给一个进程的内存是有限的,而且默认的主进程处理的事务较多,在保活方面,在数据接收处理方面,使用多进程是有优势的。 Android的APP启用多进程方式是在清单文件中添加android:process
,设置该组件所处的进程名称既可。 比如
android:process=":local"//他设置所处的进程名称是包名+local,他表示的是该组件处于自己的私有进程,其他进程的组件不可以跑在同一个进程中。
android:process="com.app.sample.local"//这里的name就是进程名称。这种则是声明他是全局进程,其他应用的组件可以通过ShareUID来达到跟他位于同一个进程运行。
复制代码
以上就是AIDL的基本使用,当然我们在实际使用中,会更加的复杂,比如我们会在IM进程中开启多个线程来处理消息,接受消息,轮训消息等,而且需要通过回调的方式主动通知主进程,一般需要IM端一个Service,主进程端一个Service等,这些我们后面在做讲述。
本文如有错误或侵权还请指出或联系删除,谢谢。