Binder AIDL的使用
参考Demo:https://github.com/gqq519/BinderAIDL
- Binder是Android的一个类,实现了IBinder接口
- IPC角度来说,Binder是Android的一种跨进程通信方式,可以理解为一种虚拟的物理设备,设备驱动是/dev/binder。
- 从Framework角度说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager等)和相应的ManagerService的桥梁。
- 从应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回服务端业务调用的Binder对象,客户端由此获取服务端提供的服务或数据。服务包括普通服务和基于AIDL的服务。
Android中,Binder主要用于Service中,包括AIDL和Messenger,普通Service不涉及进程间通信,不涉及Binder的核心,而Messenger底层其实也是AIDL实现的,所以拿AIDL来了解Binder的工作机制。
AIDL简述
AIDL:Android Interface Definition Language,通过编写aidl文件,系统会编译生成Binder接口,用于进程间通信。
AIDL支持的数据格式:
- Java的基本数据类型
- String和CharSequence
- List和Map:
- 元素必须是 AIDL 支持的数据类型
- 具体的类里则必须是 ArrayList 或者 HashMap
- 其他AIDL生成的接口
- 实现Parcelable的类
创建AIDL示例
1. 创建工程
2. 创建要操作的实体类,需要实现Parcelable接口,跨进程使用
public class User implements Parcelable {
private int id;
private String name;
public User(int id, String name) {
this.id = id;
this.name = name;
}
// 反序列化方法
protected User(Parcel in) {
id = in.readInt();
name = in.readString();
}
// 序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
}
// 内容描述
@Override
public int describeContents() {
// 一般返回0,另一个特殊返回CONTENTS_FILE_DESCRIPTOR,为有FileDescriptor,放入Parcelable需指定。。
// 然而。。好像并没有什么用,所以返回0就好了
return 0;
}
// 反序列化
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
// 反序列化对象
return new User(in);
}
@Override
public User[] newArray(int size) {
// 反序列化数组
return new User[size];
}
};
}
3. 创建实体类的映射aidl文件
右键新建AIDL文件User.aidl(名称与实体类保持一致),会在main下生成aidl目录,包名与java包名一致。
User.aidl文件为实体类的映射文件,需要声明映射的实体类和类型:
// User.aidl
// 包名与实体类包名一致
package com.gqq.binderaidl;
parcelable User;
4. 创建操作接口aidl文件
在aidl目录的包名下创建AIDL文件IUserManager.aidl,内部是一个接口,主动实现了void basicTypes()
方法,在接口中定义需要跨进程操作的接口:
// IUserManager.aidl
package com.gqq.binderaidl;
// Declare any non-default types here with import statements
import com.gqq.binderaidl.User;
interface IUserManager {
/**
* 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);
void addUser(in User user);
List<User> getUserList();
}
比如我们定义了两个方法用于操作:
- addUser()添加User
- getUserList()获取用户列表
注意:
- 定义的Parcelable实体类型,需要导入它的全路径,比如User,需要导入
import com.gqq.binderaidl.User;
- 方法参数中,除了基本数据类型外都需要标上类型:in(输入), out(输出), inout(输入输出)
5. Make Project,生成Binder的java文件
上述操作完成后,点击Build
-> Make Project
,完成后可以在build/generated/source/aidl/packageName/
下找到生成的java文件。
IUserManager的大致预览:
生成的代码主要给客户端使用,后续再介绍里面的内容吧~
至此,通信的媒介我们已经完成了。
注意:Make Project 出现错误可能的原因
- 映射的aidl:User.aidl和实体类User,名称要保持一致,包名要保持一致。
- 定义的AIDL接口文件的方法参数需要标上类型。
- AIDL接口文件中导入实体类的包名。
编写服务端代码
1. 创建Service
在项目中创建Service,需要实现onBind()方法,返回值为IBinder,根据上述生成的IUserManager.java,内部Stub继承自Binder,所以,onBind() 的返回值设置为AIDL的接口的实例。
public class UserService extends Service {
private List<User> users;
@Nullable
@Override
public IBinder onBind(Intent intent) {
users = new ArrayList<>();
// 返回AIDL生成的Binder实例
return new UserServiceImpl();
}
// 创建AIDL生成的Binder实例
public class UserServiceImpl extends IUserManager.Stub {
// 实现的AIDL接口的方法
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public void addUser(User user) throws RemoteException {
user.name += "-Service";
users.add(user);
}
@Override
public List<User> getUserList() throws RemoteException {
return users;
}
}
}
2. 清单注册Service
Service创建好之后在清单文件注册:
<service android:name=".UserService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.gqq.binderaidl.IUserManager" />
</intent-filter>
</service>
至此,服务端工作已完成。
编写客户端代码
1. 新建Client工程
客户端可以做为一个单独的App,或者跟服务端在不同的进程都可。
2. 拷生成文件到Client工程
在客户端工程下,将服务端生成的AIDL的java文件以及实体类User.java 一起拷过来,更改client的目录与Server保持一致(拷过来会报错)。
3. 绑定服务
public class MainActivity extends AppCompatActivity {
private IUserManager userManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 通过Server对外的接口操作数据并显示等
final TextView textView = findViewById(R.id.tv_show);
findViewById(R.id.btn_client).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (userManager == null) return;
try {
userManager.addUser(new User(1, "gqq"));
List<User> userList = userManager.getUserList();
textView.setText(userList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
Intent intent = new Intent();
intent.setAction("com.gqq.binderaidl.IUserManager");
// Android 5.0 以后必须显式启动,参考:https://blog.csdn.net/vrix/article/details/45289207
intent.setPackage("com.gqq.binderaidl");
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 绑定成功之后获取服务对象
userManager = IUserManager.Stub.asInterface(service);
Toast.makeText(MainActivity.this, "onServiceConnected", Toast.LENGTH_SHORT).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
userManager = null;
}
};
运行
首先运行服务端,再运行客户端,就可以通过客户端操作了。
升级:设置监听
假如现在用户希望服务端当有新用户时实时的告诉我,这个是一个典型的观察者模式,在实际中也用到很多。
1. 提供AIDL接口,作为监听
提供一个AIDL接口,客户端需要实现这个接口并注册提醒的功能,也可以随时取消这个提醒。使用AIDL接口是因为AIDL中无法使用普通接口。
服务端创建一个aidl文件:
package com.gqq.binderaidl;
import com.gqq.binderaidl.User;
// Declare any non-default types here with import statements
interface IOnNewUserArrivedListener {
/**
* 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);
void onNewUserArrived(in User user);
}
2. 在原用的IUserManager.aidl接口中添加注册和取消注册的方法
添加注册和反注册的方法,以便于客户端可以监听。
package com.gqq.binderaidl;
// Declare any non-default types here with import statements
import com.gqq.binderaidl.User;
import com.gqq.binderaidl.IOnNewUserArrivedListener;
interface IUserManager {
/**
* 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);
void addUser(in User user);
List<User> getUserList();
void registerListener(IOnNewUserArrivedListener listener);
void unregisterListener(IOnNewUserArrivedListener listener);
}
写完make project。
3. 完善服务端的Service
主要是实现Service中的IUserManager.Stub的实现,因为新增了两个方法。另外模拟场景:开启线程,每隔5s新增一个用户并通知客户端。
// ------------定义的变量---------------
private List<IOnNewUserArrivedListener> listeners = new ArrayList<>();
private boolean isServiceDestoryed = false;
// ------------重写方法,开启工作线程---------
@Override
public void onCreate() {
super.onCreate();
new Thread(new ServiceWorker()).start();
}
@Override
public void onDestroy() {
super.onDestroy();
isServiceDestoryed = true;
}
// -------------工作的线程-----------------
public class ServiceWorker implements Runnable {
@Override
public void run() {
while (!isServiceDestoryed) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int userId = users.size() + 1;
User user = new User(userId, "new User:"+userId);
try {
onNewUserArrived(user);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
private void onNewUserArrived(User user) throws RemoteException {
users.add(user);
for (int i = 0; i < listeners.size(); i++) {
IOnNewUserArrivedListener onNewUserArrivedListener = listeners.get(i);
onNewUserArrivedListener.onNewUserArrived(user);
}
}
// --------------重写的方法实现------------------
@Override
public void registerListener(IOnNewUserArrivedListener listener) throws RemoteException {
if (!listeners.contains(listener)) {
listeners.add(listener);
} else {
Log.i("TAG", "listener already exists");
}
}
@Override
public void unregisterListener(IOnNewUserArrivedListener listener) throws RemoteException {
if (listeners.contains(listener)) {
listeners.remove(listener);
} else {
Log.i("TAG", "not found");
}
}
服务端的修改已经完成。
4. 客户端注册监听并处理接收
把服务端新加的aidl文件生成的java文件复制到客户端项目中,客户端注册监听,并在页面退出时解除注册。同时在onUserArrived方法中接收到数据后要回到主线程显示等,所以借助Handler实现。
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Toast.makeText(MainActivity.this, "onServiceConnected", Toast.LENGTH_SHORT).show();
userManager = IUserManager.Stub.asInterface(service);
try {
// 注册监听
userManager.registerListener(listener);
service.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
Log.i("TAG", "RemoteException");
e.printStackTrace();
}
}
// 注册的监听
private IOnNewUserArrivedListener listener = new IOnNewUserArrivedListener.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public void onNewUserArrived(User user) throws RemoteException {
handler.obtainMessage(MESSAGE_WHAT_ARRIVED, user).sendToTarget();
}
};
// Handler的处理
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_WHAT_ARRIVED:
Log.i("TAG", "received new user" + msg.obj);
List<User> userList = null;
try {
userList = userManager.getUserList();
textView.setText(userList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
};
// 取消监听
@Override
protected void onDestroy() {
if (userManager != null && userManager.asBinder().isBinderAlive()) {
try {
userManager.unregisterListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(connection);
super.onDestroy();
}
升级:取消监听
在上述的场景设置中,当退出客户端页面,取消注册,通过日志可以查看到,当取消注册的时候会发现unregisterListener中remove的时候发生了异常,因为在多线程中,Binder会把客户端传递过来的对象重新转化并生成一个新的对象。对象是不能跨进程传递的,我们跨进程传递的时候都是把对象进行序列化和反序列化。那如何实现取消注册呢?需要借助RemoteCallbackList
。
RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口,可以从源码中看出:
public class RemoteCallbackList<E extends IInterface>
// Callback中封装了真正的远程listener
ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
跨进程的对象虽然不一样,但Binder是同一个,利用Binder来实现,客户端解除注册的时候遍历所有的服务端的listener,将具有相同Binder 的listener删除即可,这个是RemoteCallbackList所做的事情。同时,还有一个功能,当客户端进行终止后,它会自动解除客户端所注册的listener。
代码改善
利用RemoteCallbackList实现解除注册:用RemoteCallbackList代替List<>
private RemoteCallbackList<IOnNewUserArrivedListener> listeners = new RemoteCallbackList<>();
修改注册和解注册的方法:
@Override
public void registerListener(IOnNewUserArrivedListener listener) throws RemoteException {
listeners.register(listener);
// 在此处打印下注册的监听的数量
int i = listeners.beginBroadcast();
listeners.finishBroadcast();
Log.i("TAG", "registerListener listener size:"+ i);
}
@Override
public void unregisterListener(IOnNewUserArrivedListener listener) throws RemoteException {
listeners.unregister(listener);
// 注意:在此处打印下注册的监听的数量
int i = listeners.beginBroadcast();
listeners.finishBroadcast();
Log.i("TAG", "unregisterListener listener size:"+ i);
}
修改onNewUserArrived方法:
private void onNewUserArrived(User user) throws RemoteException {
users.add(user);
int size = listeners.beginBroadcast();
for (int i = 0; i < size; i++) {
IOnNewUserArrivedListener onNewUserArrivedListener = listeners.getBroadcastItem(i);
if (onNewUserArrivedListener != null) {
onNewUserArrivedListener.onNewUserArrived(user);
}
}
listeners.finishBroadcast();
}
注意:RemoteCallbackList并不是一个List,无法像操作List一样操作它,要像上述的方式一样去遍历它,其中beginBroadcast()
与finishBroadcast()
必须配对使用,哪怕是想要获取RemoteCallbackList的元素个数。
升级:耗时处理
客户端调用服务端的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端挂起。如果服务端的方法是耗时的操作,客户端在UI线程的话,就会导致ANR。客户端的onServiceConnected和onServiceDisconnected运行在UI线程中,不能直接在里面调用服务端的耗时方法。服务端的方法本身运行在Binder线程池中,可以做大量的耗时操作,不用再开启线程去进行异步操作。模拟一下耗时的操作:
// 服务端模拟耗时操作的方法
@Override
public List<User> getUserList() throws RemoteException {
SystemClock.sleep(5 * 1000);
return users;
}
客户端点击按钮去直接调用服务端的方法获取list数据,多次点击就会出现ANR,那么就需要把调用放到非UI线程,比如:
findViewById(R.id.btn_client).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (userManager == null) return;
new Thread(new Runnable() {
@Override
public void run() {
try {
List<User> userList = userManager.getUserList();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
}
}
同样,我们不可以在服务端UI线程中调用客户端耗时的操作,另外AIDL接口方法都运行在Binder线程池中,访问UI需要切换线程。例如客户端的onNewUserArrived方法。
升级:服务重连
Binder在通信中可能意外死亡,往往由于服务端进程意外停止,需要重新连接服务。
1. Binder设置DeathRecipient监听
设置DeathRecipient监听,当Binder死亡时,会收到binderDied方法的回调,可以在binderDied方法中重连服务。
2. 在OnServiceDisconnected中重连远程服务
区别:onServiceDisconnected在客户端UI线程被回调,binderDied在客户端的binder线程池中被回调,即binderDied中不能访问UI。
升级:权限验证
定义权限非本节重点:定义权限参考
首先在服务端的AndroidMenifest中声明所需权限
<permission
android:name="com.gqq.binderaidl.permission.ACCESS_USER_SERVICE"
android:protectionLevel="normal" />
<uses-permission android:name="com.gqq.binderaidl.permission.ACCESS_USER_SERVICE" />
第一种方法:在onBind中进行验证(permission验证)
@Override
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.gqq.binderaidl.permission.ACCESS_USER_SERVICE");
if (check == PackageManager.PERMISSION_DENIED) {
Log.i(TAG, "onbind check=" + check);
return null;
}
return mBinder;
}
第二种方法:在onTransact中进行验证(包名验证)
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws RemoteException {
// 权限验证
int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
if(check == PackageManager.PERMISSION_DENIED){
L.d("Binder 权限验证失败");
return false;
}
// 包名验证
String packageName=null;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if(packages!=null && packages.length>0){
packageName = packages[0];
}
if(!packageName.startsWith("com.ryg")){
L.d("包名验证失败");
return false;
}
return super.onTransact(code, data, reply, flags);
};
客户端AndroidMenifest中声明:
<uses-permission android:name="com.gqq.binderaidl.permission.ACCESS_USER_SERVICE" />
总结
主要是AIDL的整体使用流程。
再贴一遍代码地址,欢迎指正!