欢迎关注我的个人公众号,大家一起来交流Android开发知识

AndroidIPC通信之AIDL-编程知识网

一、废话

已经有很长一段时间没有写博客了,好吧!我承认我颓废了。

什么是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文件:

AndroidIPC通信之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文件一同复制到客户端的工程下,注意从服务端复制的代码,包名不能改变。工程结构如下:

AndroidIPC通信之AIDL-编程知识网

我们在客户端代码里面加入两个按钮,一个按钮添加学生信息,一个按钮用于获取学生信息。

客户端与服务端通过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文件:

AndroidIPC通信之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();     } }

现在我们可以先打开服务端,再打开客户端进行通信可以看到每隔两秒钟会收到订阅消息

AndroidIPC通信之AIDL-编程知识网

但是当我们关闭页面进行取消注册监听的时候,却发现之前的监听没有找到。

这是因为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就先介绍到这里,内容很多但不是很难。