欢迎关注我的个人公众号,大家一起来交流Android开发知识
一、废话
已经有很长一段时间没有写博客了,好吧!我承认我颓废了。
什么是IPC通信?IPC通信:是指两个进程之间进行数据通信的过程。(进程是资源分配的基本单位(CPU、内存),它拥有独立的地址空间,线程是CPU执行的最小单位,一个进程可以有很多个线程组成)。Binder机制解决了Android进程间通信的问题,当然Socket同样也可以实现。
什么时候使用IPC通信呢?有一些原因需要采用多进程来实现,你比如说某些模块需要在单独的进程中运行,还有些原因比如Android早些版本对单个应用做了最大内存限制,可能是16MB左右,为了加大某个应用的使用内存。还有一种情况,是为了访问其他应用的某些数据:比如我们通过ContentProvider访问系统层的联系人,短信等等信息。
二、还是废话
如何开启多进程模式?我们讨论的是一个应用多个进程的情况下。只有一个办法就是:四大组件在Manifest中指定android:process属性。看似很简单,其实很复杂,这里面牵扯到数据如何进行通信。因为我们知道,进程拥有独立的地址空间,拥有自己的一块内存。而android会为每个应用分配一个独立的虚拟机,而每个虚拟机会映射到分配的地址空间上,这就会导致我们在不同进程中访问同一个对象会产生多个副本,发生数据不同步的现象。
在多进程进行数据通信往往会产生一下几个问题:
1、静态成员和单例完全失效
2、线程同步机制失效
3、SharedPreferencesk可靠性降低
4、Application会创建多次
其实本质问题就是不在同一个存储空间上了。
三、言归正传
说了那么多废话,我们来说一下AIDL:
1、首先我们要了解一下AIDL,它是android定义的一种接口语言。既然是语言我们就要了解它支持哪些数据类型,怎么使用。它支持java中的八中数据类型和String类型,CharSequence、List、Map类型(注意List支持泛型,Map不支持)除了这几种数据类型之外的数据类型,都需要进行导包,不管这个类是否在同一个包文件下,这是与java不同的地方。
2、我们知道AIDL文件是以.aidl为结尾的文件。aidl文件大致可以分为两类:
①一类是我们定义的序列化对象,供其他的aidl文件调用,因为这个对象不是aidl支持数据类型中的对象,所以必须把它序列化成一个aidl文件,在其他aidl文件中通过导包进行引用。
②还有一类就是我们定义的接口方法。
3、如何使用
这里我们以其他的数据类型为例,因为默认的数据类型不需要进行创建第一类aidl文件比较简单,所以就不做讨论。
我们以两个应用为例进行跨进程通信,一个为server端,一个为client端。server端中创建一个Student类,Student类中有姓名和年龄,客户端进行对Student类进行获取Student的信息,添加信息操作。
首先在server端中我们创建一个Student对象实现Pacelable接口,代码如下:
package com.sheca.aidlserver;
import android.os.Parcel;
import android.os.Parcelable;
/**
* @author xuchangqing
* @time 2019/11/21 13:59
* @descript
*/
public class Student implements Parcelable {
private String mName;
private int mAge;
public Student(){}
protected Student(Parcel in) {
mName = in.readString();
mAge = in.readInt();
}
public static final Creator<Student> CREATOR = new Creator<Student>() {
@Override
public Student createFromParcel(Parcel in) {
return new Student(in);
}
@Override
public Student[] newArray(int size) {
return new Student[size];
}
};
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
public int getAge() {
return mAge;
}
public void setAge(int age) {
mAge = age;
}
@Override
public String toString() {
return "Student{" +
"mName='" + mName + '\'' +
", mAge=" + mAge +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
dest.writeInt(mAge);
}
}
这里实现pacelable接口默认实现writeTopacel方法,我们先解释一下aidl数据流向的问题,在aidl中tag有三种类型:in、out和inout,这三种类型都是站在服务端的角度考虑的, in表示数据流向从客户端流向服务端,out表示从服务端流向客户端,inout表示是双向数据通道。关于这个AIDL的tag问题我们后文继续详述,这里只做了解即可。AIDL默认的数据类型是in类型,如果要支持out或者inout类型,还要实现readFromParcel方法。不然会报错,这个方法readFrmoParcel其实已经在Api更新的时候取消了,不需要外部强制重写。定义如下
public void readFromParcel(Parcel dest){
mName=dest.readString();
mAge=dest.readInt();
}
然后我们在这个Student类的包名下创建一个Student的aidl文件,这个文件就是我们所说的第一种aidl文件,供其他aidl文件使用。系统会自动生成一个同样包名的aidl文件:
然后我们修改这个文件的代码如下:
// StudentAIDL.aidl
package com.sheca.aidlserver;
// Declare any non-default types here with import statements
parcelable Student;
仅接着我们创建第二个aidl文件StudentManager,供客户端调用。
// StudentManager.aidl
package com.sheca.aidlserver;
// Declare any non-default types here with import statements
import com.sheca.aidlserver.StudentAIDL;
interface StudentManager {
List<Student> getStudent();
void addStudent(in Student student);
}
StudentManager中封装了两个方法,一个是获取学生信息,一个添加学生信息。
注意:因为Student类不属于AIDL支持的数据类型,我们把它转换成了AIDL支持的数据类型即生成一个Student的AIDL文件,所以我们要导入的包是aidl文件类型的包。
服务端创建一个service
package com.sheca.aidlserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import java.util.ArrayList;
import java.util.List;
/**
* @author xuchangqing
* @time 2019/11/21 16:36
* @descript
*/
public class StudentServer extends Service {
private List<Student> mStudentList= new ArrayList<>();
@Override
public IBinder onBind(Intent intent) {
return mStudentBinder;
}
StudentManager.Stub mStudentBinder=new StudentManager.Stub(){
@Override
public List<Student> getStudent() throws RemoteException {
return mStudentList;
}
@Override
public void addStudent(Student student) throws RemoteException {
mStudentList.add(student);
}
};
}
然后在Mainfest中配置这个Service。
<service android:name=".StudentServer"
>
<intent-filter>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="xcq_server_service"/>
</intent-filter>
</service>
这样我们的服务端代码已经全部定义好了。下面开始编写客户端的代码,
客户端需要把之前在服务端创建的所有的AIDL文件和Bean文件一同复制到客户端的工程下,注意从服务端复制的代码,包名不能改变。工程结构如下:
我们在客户端代码里面加入两个按钮,一个按钮添加学生信息,一个按钮用于获取学生信息。
客户端与服务端通过bindservice()建立连接,在onServiceConnected()方法中,将service转换成定义的AIDL对象的实例,通过这个实例调用;
package com.sheca.aidlclient;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.sheca.aidlserver.Student;
import com.sheca.aidlserver.StudentManager;
import java.util.List;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView mGetData;
private TextView mAddData;
private TextView mContent;
private StudentManager mManager;
private List<com.sheca.aidlserver.Student> mStudent;
private int i=0;
private TextView mBind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initService();
initView();
}
private void initService() {
Intent intent = new Intent();
intent.setPackage("com.sheca.aidlserver");
intent.setAction("xcq_server_service");
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
private void initView() {
mGetData=(TextView)findViewById(R.id.tv_getdata);
mAddData=(TextView)findViewById(R.id.tv_setdata);
mContent=findViewById(R.id.tv_content);
mBind=findViewById(R.id.tv_bind);
mGetData.setOnClickListener(this);
mAddData.setOnClickListener(this);
mBind.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.tv_getdata:
if(mManager!=null){
Log.e("Connect","开始获取数据");
try {
mStudent= mManager.getStudent();
mContent.setText("mStudent"+mStudent.get(mStudent.size()-1));
} catch (RemoteException e) {
e.printStackTrace();
}
}else{
Log.e("Connect","未绑定");
}
break;
case R.id.tv_setdata:
if(mManager!=null) {
Log.e("Connect","开始添加数据");
i++;
Student student = new Student();
student.setName("张三" + i);
student.setAge(23);
try {
mManager.addStudent(student);
} catch (RemoteException e) {
e.printStackTrace();
}
}else{
Log.e("Connect","未绑定");
}
break;
case R.id.tv_bind:
Intent intent = new Intent();
intent.setPackage("com.sheca.aidlserver");
intent.setAction("xcq_server_service");
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
break;
}
}
ServiceConnection mConnection =new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mManager=StudentManager.Stub.asInterface(service);
Log.e("Connect","isConnext"+name);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}
这样的一个简单的AIDL通信就建立完成了。
为什么AIDL可以完成跨进程的通信呢?我们生成的AIDL文件,它帮助我们做了一些什么事情呢?
我们在工程的/build/generated/aidl_source_output_dir/debug/compileDebugAidl/文件夹下可以查看到系统自动编译出的AIDL文件:
在这里,我们看一下源码:
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.sheca.aidlserver;
public interface StudentManager extends android.os.IInterface
{
/** Default implementation for StudentManager. */
public static class Default implements com.sheca.aidlserver.StudentManager
{
@Override public java.util.List<com.sheca.aidlserver.Student> getStudent() throws android.os.RemoteException
{
return null;
}
@Override public void addStudent(com.sheca.aidlserver.Student student) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.sheca.aidlserver.StudentManager
{
private static final java.lang.String DESCRIPTOR = "com.sheca.aidlserver.StudentManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.sheca.aidlserver.StudentManager interface,
* generating a proxy if needed.
*/
public static com.sheca.aidlserver.StudentManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.sheca.aidlserver.StudentManager))) {
return ((com.sheca.aidlserver.StudentManager)iin);
}
return new com.sheca.aidlserver.StudentManager.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getStudent:
{
data.enforceInterface(descriptor);
java.util.List<com.sheca.aidlserver.Student> _result = this.getStudent();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addStudent:
{
data.enforceInterface(descriptor);
com.sheca.aidlserver.Student _arg0;
if ((0!=data.readInt())) {
_arg0 = com.sheca.aidlserver.Student.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addStudent(_arg0);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.sheca.aidlserver.StudentManager
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public java.util.List<com.sheca.aidlserver.Student> getStudent() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.sheca.aidlserver.Student> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getStudent, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getStudent();
}
_reply.readException();
_result = _reply.createTypedArrayList(com.sheca.aidlserver.Student.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addStudent(com.sheca.aidlserver.Student student) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((student!=null)) {
_data.writeInt(1);
student.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_addStudent, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().addStudent(student);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.sheca.aidlserver.StudentManager sDefaultImpl;
}
static final int TRANSACTION_getStudent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addStudent = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public static boolean setDefaultImpl(com.sheca.aidlserver.StudentManager impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.sheca.aidlserver.StudentManager getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public java.util.List<com.sheca.aidlserver.Student> getStudent() throws android.os.RemoteException;
public void addStudent(com.sheca.aidlserver.Student student) throws android.os.RemoteException;
}
很长,不过不用着急,我们一步步分析。首先我们定义的AIDL接口继承了android.os.IInterface 这个接口。
这个接口是做什么的呢?我们查看一下这个接口的源码,发现只有一个方法asBinder();
package android.os;
/**
* Base class for Binder interfaces. When defining a new interface,
* you must derive it from IInterface.
*/
public interface IInterface
{
/**
* Retrieve the Binder object associated with this interface.
* You must use this instead of a plain cast, so that proxy objects
* can return the correct result.
*/
public IBinder asBinder();
}
如果想获取和该接口关联的Binder对象。你必须使用这个方法而不是使用一个简单的类型转化。这样代理对象才能返回正确的结果。我们可以看出这是一个类型转换的接口,它将服务或者服务代理通过asBinder()转换成一个IBinder对象。
回到StudentManager接着往下看,在这个接口中我们可以查看到我们在aidl中定义的两个方法getStudent()和addStudent(),同时使用两个整型的id标记这两个方法,在transact()方法中区分调用哪个方法。接着它声明了一个Stub类,在这个类中如果客户端与服务端处在同一个进程中,就不会走transact过程,当两者处于不同进程时,方法会调用transact过程,这个过程是由下面的Proxy类来判断完成的。
我们接着往下看有一个asInterface(android.os.IBinder obj)这个方法,它是将服务端返回的binder对象转换成客户端所需要的aidl类型,这个转换过程是区分进程的,如果客户端与服务端在统一进程中那么返回的对象是Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。
另外还有一个方法是OnTranscat()方法,这个方法运行在服务端的Binder线程池中,当客户端发起请求时,远程请求会通过系统底层封装后交给该方法响应。我们还可以在这个方法中进行权限校验,这个我们后面再说。
通过以上的描述我们清楚的了解了AIDL的工作原理。在Binder中还有两个重要的方法linkToDeath和UnlinkToDeath来判断Binder是否死亡。
这里不做过多的描述,感兴趣的同学可以写个demo尝试一下。另外有个注意事项需要说一下我们知道
服务务端的方法是在binder线程池中运行的,如果多个客户端对接一个服务端可能存在并发问题。因此我们存储学生信息的容器ArrayList可以换成CopyOnWriteArrayList<>它支持并发操作,类似的HashMap也可以更换成ConCurrentHashMap
四、AIDL接口回调回传数据
现在我们来说一个问题:如果我们客户端不想通过调用getStudent()方法来查询学生的信息,而是一旦有学生被添加了就通知给客户端,该如何做呢?
我们知道这是一个典型的观察者模式。首先我们定义一个AIDL接口作为通知的接口回调(AIDL接口不能使用普通接口),然后让客户端订阅这个接口,当然还要定义一个取消订阅的接口,供客户端取消订阅。
// IStudentAddInterface.aidl
package com.sheca.aidlserver;
// Declare any non-default types here with import statements
import com.sheca.aidlserver.StudentAIDL;
interface IStudentAddInterface {
void isStudentAdded(in Student student);
}
// StudentManager.aidl
package com.sheca.aidlserver;
// Declare any non-default types here with import statements
import com.sheca.aidlserver.StudentAIDL;
import com.sheca.aidlserver.IStudentAddInterface;
interface StudentManager {
List<Student> getStudent();
void addStudent(in Student student);
void registerListener(IStudentAddInterface listener);
void unRegisterListener(IStudentAddInterface listener);
}
服务端的代码除了实现新增的两个注册和取消注册的接口,在onCreate()中开启一个线程每隔两秒新增一个学生,再通知注册这个接口的客户端。
package com.sheca.aidlserver;
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;
/**
* @author xuchangqing
* @time 2019/11/21 16:36
* @descript
*/
public class StudentServer extends Service {
private boolean isDestroyed =true;
private CopyOnWriteArrayList<Student> mStudentList= new CopyOnWriteArrayList<>();
private CopyOnWriteArrayList<IStudentAddInterface> mListenerList = new CopyOnWriteArrayList<>();
@Override
public IBinder onBind(Intent intent) {
return mStudentBinder;
}
StudentManager.Stub mStudentBinder=new StudentManager.Stub(){
@Override
public List<Student> getStudent() throws RemoteException {
return mStudentList;
}
@Override
public void addStudent(Student student) throws RemoteException {
mStudentList.add(student);
}
@Override
public void registerListener(IStudentAddInterface listener) throws RemoteException {
if(!mListenerList.contains(listener)){
mListenerList.add(listener);
}
Log.d("CONNECTION","注册订阅");
}
@Override
public void unRegisterListener(IStudentAddInterface listener) throws RemoteException {
if(mListenerList.contains(listener)){
mListenerList.remove(listener);
}
Log.d("CONNECTION","取消订阅");
}
};
@Override
public void onCreate() {
super.onCreate();
Log.d("CONNECTION","isDestroyed"+isDestroyed);
//开启线程每隔两秒新增一个学生
new Thread(new Runnable() {
@Override
public void run() {
while (isDestroyed) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Student mStudent = new Student();
mStudent.setAge(mStudentList.size() + 1);
Log.d("CONNECTION", "新学生" + mStudent.getAge());
//订阅消息通知
onNewStudentAdded(mStudent);
}
}
}).start();
}
private void onNewStudentAdded(Student mStudent) {
mStudentList.add(mStudent);
for (int i = 0; i < mListenerList.size(); i++) {
try {
IStudentAddInterface listener = mListenerList.get(i);
listener.isStudentAdded(mStudent);
//订阅消息
Log.d("CONNECTION","listener"+listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
isDestroyed=false;
}
}
我们再更改一下客户端的代码,客户端只需要在建立连接的时候注册改订阅即可,我们知道服务端的方法是在binder线程池中执行的,客户端在调用的时候可能会出现线程阻塞的状况。因此我们可以采用handler来转换一下线程。
当然我们还要在页面关闭的时候取消注册。
package com.sheca.aidlclient;
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.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.sheca.aidlserver.IStudentAddInterface;
import com.sheca.aidlserver.Student;
import com.sheca.aidlserver.StudentManager;
import java.util.List;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TextView mGetData;
private TextView mAddData;
private TextView mContent;
private StudentManager mManager;
private List<com.sheca.aidlserver.Student> mStudent;
private int i=0;
private TextView mBind;
private Handler mHandler = new Handler(Looper.myLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch(msg.what){
case 1000:
Log.e("Connection","Student"+msg.obj);
break;
}
}
};
private IStudentAddInterface isAddListener = new IStudentAddInterface.Stub() {
@Override
public void isStudentAdded(Student student) throws RemoteException {
mHandler.obtainMessage(1000, student).sendToTarget();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initService();
initView();
}
private void initService() {
Intent intent = new Intent();
intent.setPackage("com.sheca.aidlserver");
intent.setAction("xcq_server_service");
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
private void initView() {
mGetData=(TextView)findViewById(R.id.tv_getdata);
mAddData=(TextView)findViewById(R.id.tv_setdata);
mContent=findViewById(R.id.tv_content);
mBind=findViewById(R.id.tv_bind);
mGetData.setOnClickListener(this);
mAddData.setOnClickListener(this);
mBind.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.tv_getdata:
if(mManager!=null){
Log.e("Connect","开始获取数据");
try {
mStudent= mManager.getStudent();
mContent.setText("mStudent"+mStudent.get(mStudent.size()-1));
} catch (RemoteException e) {
e.printStackTrace();
}
}else{
Log.e("Connect","未绑定");
}
break;
case R.id.tv_setdata:
if(mManager!=null) {
Log.e("Connect","开始添加数据");
i++;
Student student = new Student();
student.setName("张三" + i);
student.setAge(23);
try {
mManager.registerListener(isAddListener);
} catch (RemoteException e) {
e.printStackTrace();
}
try {
mManager.addStudent(student);
} catch (RemoteException e) {
e.printStackTrace();
}
}else{
Log.e("Connect","未绑定");
}
break;
case R.id.tv_bind:
Intent intent = new Intent();
intent.setPackage("com.sheca.aidlserver");
intent.setAction("xcq_server_service");
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
break;
}
}
ServiceConnection mConnection =new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mManager=StudentManager.Stub.asInterface(service);
Log.e("Connect","isConnext"+name);
Student student = new Student();
student.setName("张三" + 3);
student.setAge(23);
try {
mManager.registerListener(isAddListener);
Log.e("Connect","注册订阅"+isAddListener);
} catch (RemoteException e) {
e.printStackTrace();
}
try {
mManager.addStudent(student);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onDestroy() {
try {
mManager.unRegisterListener(isAddListener);
} catch (RemoteException e) {
e.printStackTrace();
}
Log.e("Connect","取消订阅"+isAddListener);
unbindService(mConnection);
super.onDestroy();
}
}
现在我们可以先打开服务端,再打开客户端进行通信可以看到每隔两秒钟会收到订阅消息
但是当我们关闭页面进行取消注册监听的时候,却发现之前的监听没有找到。
这是因为Binder 会把客户端传递过来的对象重新转化并生成一个新的对象,虽然我们在注册和解注册过程中使用的是同一个客户端,但是通过 Binder 传递到服务端后,却会产生两个全新的对象。而对象是不能跨进程传输的,对象的跨进程传输本质上都是反序列化的过程,这就是为什么 AIDL 中的自定义对象都必须要实现 Parcelable 接口的原因。
那么该如何解决呢?系统给我们提供了一个
RemoteCallbackList专门用于删除跨进程listener的类。注意RemoteCallbackList它并不是一个list,它只是一个泛型
public class RemoteCallbackList<E extends IInterface>{}
从源码可以看出它支持AIDL所有的接口,工作原理很简单就是定义一个Map来存储调用的接口信息,键为Ibinder,值为Callback
/*package*/ ArrayMap<IBinder, Callback> mCallbacks
= new ArrayMap<IBinder, Callback>();
key和value的获取方式如下
*/
public boolean register(E callback, Object cookie) {
synchronized (mCallbacks) {
if (mKilled) {
return false;
}
// Flag unusual case that could be caused by a leak. b/36778087
logExcessiveCallbacks();
IBinder binder = callback.asBinder();
try {
Callback cb = new Callback(callback, cookie);
binder.linkToDeath(cb, 0);
mCallbacks.put(binder, cb);
return true;
} catch (RemoteException e) {
return false;
}
}
}
注意一点RemoteCallbackList 并不是一个List ,不能像 List 一样去操作它,遍历RemoteCallbackList 必须要以下面的方式进行,其中 beginBroadcast 和 finishBroadcast 必须配套使用,哪怕我们仅仅是想要获取 RemoteCallbackList 中的元素个数。
五、权限验证
增加权限验证有利于挺高程序的安全性,并不是所有的客户端想要与服务端建立连接我们都能够允许通过。
这里介绍两种常见的做法:
1、在onBInd()方法中进行验证,不通过直接返回null,这样客户端就无法绑定服务。常见的做法增加permission
我们现在服务端的Maniefest中声明权限
<!--定义权限-->
<permission
android:name="com.sheca.permission.ACCESS_SERVICE"
android:protectionLevel="normal"/>
然后在onBind()方法中进行判断
@Override
public IBinder onBind(Intent intent) {
int check = checkCallingOrSelfPermission("com.sheca.permission.ACCESS_SERVICE");
if (check == PackageManager.PERMISSION_DENIED) {
return null;
}
return mStudentBinder;
}
然后客户端使用的时候必须增加这个权限才能够使用,做法是在客户端的Mainefest中增加
<uses-permission android:name="com.sheca.permission.ACCESS_SERVICE"/>
2、onTransact()在这个方法里面我们可以进行权限校验,可以采用第一种的做法进行permission校验,我们还可以采用Uid和Pid进行验证,通过getCallingUid和getCallingPid可以拿到客户端的所属应用的uid和pid。通过这两个参数我们做验证,比如验证包名。当然还有其他的验证方法,比如对service增加权限等等,就不做一一介绍了。
aidl就先介绍到这里,内容很多但不是很难。