上一篇博客,介绍了Android中使用AIDL进行进程间通信的基本用法,
传送门:Android中AIDL的使用(一)
本篇是后续的一些内容,主要包括:
- Binder中设置死亡代理
- AIDL客户端订阅服务端消息
- 进程间操作对象
OK,下面进入正题
Binder中设置死亡代理
使用AIDL进行进程间通信,Binder是运行在服务端进程的,如果服务端进程因为某些原因被终止,那么Binder会断开,此时客户端调用服务端就会失败。那么有什么办法在连接断开时能及时通知客户端进行重连呢,Binder中提供了两个方法linkToDeath和unlinkToDeath,使用linkToDeath,我们给Binder设置一个死亡代理,就能及时收到连接断开的通知了。
首先, 新建一个IBinder.DeathRecipient对象,并实现内部的binderDied()方法,Binder死亡时,系统会自动调用这个方法。
/** 定义一个死亡代理对象 */
private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (manager == null) return;
manager.asBinder().unlinkToDeath(deathRecipient, 0);
manager = null;
mBound = false;
// 重连
attemptToBindService();
}
};
在 onServiceConnected 中设置这个死亡代理
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
setLog("service connected");
manager = StudentManager.Stub.asInterface(service);
mBound = true;
// 设置死亡代理
service.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
这样,就给Binder设置了死亡代理,当服务断开时,客户端能及时收到消息,进项相应操作。
AIDL客户端订阅服务端消息
还是在Student这个例子中,如果有多个老师去管理学生,每个老师不可能时时去刷新学生列表,我们希望当有一名老师添加了一个学生时,服务端能给每个老师发送一条消息进行通知,我们来实现它。
首先,我们定义一个AIDL接口,每个客户端需要实现这个接口,并且去服务端进行注册。
新建一个IStudentAddListener.aidl文件,导入所需要的Student对象,定义一个学生增加时的方法:
package com.aidl;
import com.aidl.Student;
interface IStudentAddListener {
void onStudentAdd(in Student s);
}
在原有的接口中增加注册和解除注册的方法
package com.aidl;
import com.aidl.Student;
import com.aidl.IStudentAddListener;
interface StudentManager {
//所有的返回值前都不需要加任何东西,不管是什么数据类型
List<Student> getStudents();
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定
void addStudent(in Student s);
// 注册增加学生的监听
void registerListener(IStudentAddListener listener);
// 解除注册增加学生的监听
void unregisterListener(IStudentAddListener listener);
}
在Service中实现方法,我们使用CopyOnWriteArrayList代替List,因为AIDL的方法运行在服务端进程,数据可能被多个客户端同时操作,所以我们需要在服务端处理线程同步的问题,CopyOnWriteArrayList是支持并发读写的,使用它可以进行自动的线程同步。
package com.aidl;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class StudentService extends Service {
private static final String TAG = "StudentService";
private CopyOnWriteArrayList<Student> students = new CopyOnWriteArrayList<>();
private CopyOnWriteArrayList<IStudentAddListener> listeners = new CopyOnWriteArrayList<>();
private StudentManager.Stub stub = new StudentManager.Stub() {
@Override
public List<Student> getStudents() throws RemoteException {
Log.i(TAG, "getStudents()");
return students;
}
@Override
public void addStudent(Student s) throws RemoteException {
Log.i(TAG, "addStudent()");
students.add(s);
onNewStudent(s);
}
@Override
public void registerListener(IStudentAddListener listener) throws RemoteException {
if (!listeners.contains(listener)) {
listeners.add(listener);
} else {
Log.i(TAG, "listener 已存在");
}
}
@Override
public void unregisterListener(IStudentAddListener listener) throws RemoteException {
if (listeners.contains(listener)) {
listeners.remove(listener);
} else {
Log.i(TAG, "listener 没有找到");
}
}
};
/**
* 增加学生之后,通知各个客户端
*/
private void onNewStudent(Student student) {
try {
for (int i = 0; i < listeners.size(); i++) {
listeners.get(i).onStudentAdd(student);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public IBinder onBind(Intent intent) {
testAddStudent();
return stub;
}
/**
* 新建一个线程,每隔5秒增加一名学生进行测试
*/
private void testAddStudent() {
new Thread() {
@Override
public void run() {
try {
while (true) {
sleep(5000);
stub.addStudent(new Student("小明", 20));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
}
在客户端注册这个监听
package com.aidl.client;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.aidl.IStudentAddListener;
import com.aidl.Student;
import com.aidl.StudentManager;
import java.util.List;
import java.util.Random;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private ScrollView scrollView;
private TextView tvLog;
private Button btAdd;
private Button btGet;
private StringBuffer log = new StringBuffer();
//由AIDL文件生成的Java类
private StudentManager manager = null;
//标志当前与服务端连接状况的布尔值,false为未连接,true为连接中
private boolean mBound = false;
private static final int NEW_STUDENT = 101;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case NEW_STUDENT:
setLog("服务端通知增加了一名学生");
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
scrollView = (ScrollView) findViewById(R.id.scrollView);
tvLog = (TextView) findViewById(R.id.log);
btAdd = (Button) findViewById(R.id.add);
btGet = (Button) findViewById(R.id.get);
btAdd.setOnClickListener(this);
btGet.setOnClickListener(this);
}
@Override
protected void onStart() {
super.onStart();
if (!mBound) {
attemptToBindService();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mBound) {
if (manager != null) {
try {
manager.unregisterListener(studentAddListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mServiceConnection);
mBound = false;
}
}
/**
* 检查连接状态
*
* @return
*/
private boolean checkConnect() {
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
setLog("正在尝试重连,请稍后再试");
return true;
}
if (manager != null) return true;
return false;
}
/**
* 定义一个死亡代理对象
*/
private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (manager == null) return;
manager.asBinder().unlinkToDeath(deathRecipient, 0);
manager = null;
mBound = false;
// 重连
attemptToBindService();
}
};
/**
* 增加学生的监听
*/
private IStudentAddListener studentAddListener = new IStudentAddListener.Stub() {
@Override
public void onStudentAdd(Student s) throws RemoteException {
mHandler.sendEmptyMessage(NEW_STUDENT);
}
};
/**
* 尝试与服务端建立连接
*/
private void attemptToBindService() {
Intent intent = new Intent();
intent.setAction("STUDENT_SERVICE");
intent.setPackage("com.aidl");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
setLog("service connected");
manager = StudentManager.Stub.asInterface(service);
mBound = true;
// 设置死亡代理
service.linkToDeath(deathRecipient, 0);
// 注册增加学生的监听
manager.registerListener(studentAddListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
setLog("service disconnected");
mBound = false;
}
};
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.add:
addStudent();
break;
case R.id.get:
getStudents();
break;
default:
break;
}
}
Random random = new Random();
private void addStudent() {
if (!checkConnect()) return;
try {
Student s = new Student("s" + random.nextInt(1000), random.nextInt(20));
manager.addStudent(s);
setLog("add student success");
} catch (Exception e) {
e.printStackTrace();
}
}
private void getStudents() {
if (!checkConnect()) return;
try {
List<Student> list = manager.getStudents();
setLog("get students success list.size()=" + list.size());
} catch (Exception e) {
e.printStackTrace();
}
}
private void setLog(String msg) {
log.append(msg + "\n");
tvLog.setText(log);
scrollView.fullScroll(View.FOCUS_DOWN);
}
}
测试一下
OK,成功!
进程间操作对象
关闭我们的客户端,我们在onDestroy中添加了解除注册的代码,但是在服务端的log中发现
这样,由于没有找到我们要解除的listener,导致不能完成解除。这是由于多进程通信中,Binder会把我们传过来的对象,重新转化为一个新的对象,虽然客户端是传的同一个对象,但是到服务端就成了不同的对象,所以不能这样直接解除注册。
解决办法是使用RemoteCallbackList来管理我们的AIDL接口。
看下我们修改之后的StudentService
package com.aidl;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class StudentService extends Service {
private static final String TAG = "StudentService";
private CopyOnWriteArrayList<Student> students = new CopyOnWriteArrayList<>();
// private CopyOnWriteArrayList<IStudentAddListener> listeners = new CopyOnWriteArrayList<>();
private RemoteCallbackList<IStudentAddListener> listeners = new RemoteCallbackList<>();
private StudentManager.Stub stub = new StudentManager.Stub() {
@Override
public List<Student> getStudents() throws RemoteException {
Log.i(TAG, "getStudents()");
return students;
}
@Override
public void addStudent(Student s) throws RemoteException {
Log.i(TAG, "addStudent()");
students.add(s);
onNewStudent(s);
}
@Override
public void registerListener(IStudentAddListener listener) throws RemoteException {
listeners.register(listener);
Log.i(TAG, "listener register");
// if (!listeners.contains(listener)) {
// listeners.add(listener);
// } else {
// Log.i(TAG, "listener 已存在");
// }
}
@Override
public void unregisterListener(IStudentAddListener listener) throws RemoteException {
listeners.unregister(listener);
Log.i(TAG, "listener register");
// if (listeners.contains(listener)) {
// listeners.remove(listener);
// } else {
// Log.i(TAG, "listener 没有找到");
// }
}
};
/**
* 增加学生之后,通知各个客户端
*/
private void onNewStudent(Student student) {
try {
int N = listeners.beginBroadcast();
for (int i = 0; i < N; i++) {
listeners.getBroadcastItem(i).onStudentAdd(student);
}
listeners.finishBroadcast();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public IBinder onBind(Intent intent) {
testAddStudent();
return stub;
}
/**
* 新建一个线程,每隔5秒增加一名学生进行测试
*/
private void testAddStudent() {
new Thread() {
@Override
public void run() {
try {
while (true) {
sleep(5000);
stub.addStudent(new Student("小明", 20));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
}
注意:
- RemoteCallbackList并不是一个List
- RemoteCallbackList的beginBroadcast()和finishBroadcast()必须成对使用
现在我们看下日志
源码地址:
服务端:https://github.com/liurui-36/StudentAIDLServer
客户端:https://github.com/liurui-36/StudentAIDLClient