AndroidIPC通信之AIDL

欢迎关注我的个人公众号,大家一起来交流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.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.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就先介绍到这里,内容很多但不是很难。
 
 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值