欢迎关注我的个人公众号,大家一起来交流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就先介绍到这里,内容很多但不是很难。