Android 关于AIDL通信,RemoteCallbackList实现Server回调Client

Android 接口定义语言 (AIDL)

Android 接口定义语言 (AIDL) 与您可能使用过的其他接口语言 (IDL) 类似。您可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,因此 Android 会使用 AIDL 为您处理此问题。

AIDL文件创建和使用

  • 先创建两个Android项目,分别为Server进程和Client进程

这里先创建Server进程,包名为com.enjoy.myplugin;创建Client进程,报名为com.enjoy.plugin;

Server进程目录结构如下;

Android 关于AIDL通信,RemoteCallbackList实现Server回调Client_第1张图片

Client进程的目录结构如下:

Android 关于AIDL通信,RemoteCallbackList实现Server回调Client_第2张图片

简单做个说明,IAidlCbListener.aidl是用于注册回掉的接口,IMyAidlInterface.aidl是Server进程提供的对外开放的接口,也就是用于Client可以调用的接口;由于AIDL只能传递Java基本类型和list,map等几个类型,而Person.aidl是为了传递对象而声明的自定义类型。

需要强调的是,自定义的类型和所有的AIDL文件都需要在Server进程和Client进程中保持同步,也就是包名和内容要保证一致。

  • 接下来看AIDL文件内容:
  1. IAidlCbListener.aidl
    // IAidlCbListener.aidl
    package com.enjoy.myplugin;
    
    // Declare any non-default types here with import statements
    
    interface IAidlCbListener {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
        void showServerMsg(String msg);
    }

     

  2. IMyAidlInterface.aidl
    // IMyAidlInterface.aidl
    package com.enjoy.myplugin;
    
    import com.enjoy.myplugin.IAidlCbListener;
    import com.enjoy.myplugin.Person;
    // Declare any non-default types here with import statements
    
    interface IMyAidlInterface {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
        void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                double aDouble, String aString);
    
        String getData();
        void sendData(String data);
        void addPerson(in Person person);
        void notifyClient(String notifyContent);
    
        void registerCb(IAidlCbListener icbi);
        void unRegisterCb(IAidlCbListener icbi);
    }
    

     

  3. Person.aidl
    // Person.aidl
    package com.enjoy.myplugin;
    
    // Declare any non-default types here with import statements
    
    parcelable Person;
    

     

自定义的传递数据类型Person.java

package com.enjoy.myplugin;

import android.os.Parcel;
import android.os.Parcelable;

import androidx.annotation.NonNull;

public class Person implements Parcelable {

    public int age;
    public String addr;

    public Person(){}

    public Person(int age, String addr){
        this.age = age;
        this.addr = addr;
    }

    protected Person(Parcel in) {
        this.age = in.readInt();
        this.addr = in.readString();
    }

    public static final Creator CREATOR = new Creator() {
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in);
        }

        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(age);
        dest.writeString(addr);
    }

    @NonNull
    @Override
    public String toString() {
        return "Person: age = "+age+",addr = "+addr;
    }
}

自定义的类型需要将其序列化,才能在进程之间传递。

说明:

上述要用到的AIDL和java文件,都是Server和Client之前交互需要用到的,故在Server和Client进程中,这些文件要同时存在,并且要保证包名和内容一致

Server进程中创建AIDL对应的Service,并实现AIDL中对外提供的接口方法

AidlServerService.java

package com.enjoy.myplugin;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import com.enjoy.myplugin.Person;


public class AidlServerService extends Service {
    private static final String TAG = "Rayman AidlServerService";

    private RemoteCallbackList mCbs = new RemoteCallbackList<>();
    private com.enjoy.myplugin.Person newPerson = new com.enjoy.myplugin.Person();

    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    class MyBinder extends IMyAidlInterface.Stub{

        public String mData;

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
            Log.d(TAG, "basicTypes: anInt = "+anInt);
        }

        @Override
        public String getData() throws RemoteException {
            notifyClient("invoke notifyClient...");
            return mData;
        }

        @Override
        public void sendData(String data) throws RemoteException {
            mData = data;
        }

        @Override
        public void addPerson(Person person) throws RemoteException {
            newPerson = person;
        }

        @Override
        public void notifyClient(String notifyContent) throws RemoteException {
            mCbs.beginBroadcast();
            //遍历所有注册的Listener,逐个调用它们的实现方法,也就是通知所有的注册者
            for(int i=0;i在IPC的过程中,可能会遇到以下情况需要考虑:

在AIDL中客户端向服务端注册一个回调方法时,服务端要考虑客户端是否意外退出(客户端因为错误应用Crash,或者被Kill掉了),服务端还不知道去回调客户端,出现错误

客户端和服务端进程状态

在进程间通信过程中,很可能出现一个进程死亡的情况。如果这时活着的一方不知道另一方已经死了就会出现问题。那我们如何在A进程中获取B进程的存活状态呢?

android肯定给我们提供了解决方式,那就是Binder的linkToDeath和unlinkToDeath方法,linkToDeath方法需要传入一个DeathRecipient对象,DeathRecipient类里面有个binderDied方法,当binder对象的所在进程死亡,binderDied方法就会被执行,我们就可以在binderDied方法里面做一些异常处理,释放资源等操作了

Android SDK提供一个封装好的对象:RemoteCallbackList,帮我自动处理了Link-To-Death的问题。

这里,简单介绍一下RemoteCallbackList:

public class RemoteCallbackList
extends Object

java.lang.Object
   ↳ android.os.RemoteCallbackList

负责维护远程接口列表的繁琐工作,通常用于执行从Service到其客户端的回调 。特别是:

  • 跟踪一组已注册的IInterface回调,注意通过其基础唯一性IBinder (通过调用)进行识别IInterface#asBinder
  • 将附加IBinder.DeathRecipient到每个已注册的接口,以便在其过程消失时可以将其从列表中清除。
  • 对接口的基础列表执行锁定以处理多线程传入的调用,并以线程安全的方式遍历该列表的快照而无需保持其锁定。

要使用此类,只需与服务一起创建一个实例,然后在客户端注册和取消注册服务时调用其register(E)unregister(E)方法。回调到注册客户端,使用beginBroadcast(), getBroadcastItem(int)finishBroadcast()

如果注册的回调过程消失了,该类将负责自动将其从列表中删除。如果要在这种情况下做其他工作,可以创建一个实现该onCallbackDied(E)方法的子类。

RemoteCallbackList帮我们避免了IPC两个进程在调用过程中发生意外crash,导致回调失败或者进程crash的问题。

Client进程实现对Server进程AIDL对应Service的绑定,调用其提供的接口

在Client进程的MainActivity中实现如下代码:

package com.enjoy.plugin;

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.util.Log;

import com.enjoy.myplugin.IAidlCbListener;
import com.enjoy.myplugin.IMyAidlInterface;
import com.enjoy.myplugin.Person;

public class MainActivity extends BaseActivity {

    private static final String TAG = "Rayman plugin_MainActivity";

    IMyAidlInterface myAidlInterface = null;
    private MyCbListener myCbListener = new MyCbListener();

    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if(myAidlInterface != null){
                //Server端意外died
                try {
                    myAidlInterface.unRegisterCb(myCbListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                myAidlInterface.asBinder().unlinkToDeath(mDeathRecipient,0);
                myAidlInterface = null;
                //TODO:bindService again
                Intent intent = new Intent("android.intent.action.aidl");
                intent.setComponent(new ComponentName("com.enjoy.myplugin","com.enjoy.myplugin.AidlServerService"));
                bindService(intent,mConn, Context.BIND_AUTO_CREATE);
            }
        }
    };

    private ServiceConnection mConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            if(myAidlInterface != null){
                try {
                    //设定死亡接收器,这个是针对IBinder对象的
                    service.linkToDeath(mDeathRecipient,0);
                    //调用Server进程通过AIDL提供的接口方法
                    myAidlInterface.sendData("testaidl...");
                    Log.d(TAG, "onServiceConnected: myCbListener = "+myCbListener);
                    if(myCbListener == null){
                        myCbListener = new MyCbListener();
                    }
                    //调用Server进程通过AIDL提供的接口方法
                    myAidlInterface.addPerson(new Person(32,"Rayman"));
                    //注册回调监听器
                    myAidlInterface.registerCb(myCbListener);
                    //在log中调用getData方法
                    Log.d(TAG, "onServiceConnected: data = "+myAidlInterface.getData());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            myAidlInterface = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("Rayman", "onCreate: this is plugin Activity...");

        Intent intent = new Intent("android.intent.action.aidl");
        intent.setComponent(new ComponentName("com.enjoy.myplugin","com.enjoy.myplugin.AidlServerService"));
        bindService(intent,mConn, Context.BIND_AUTO_CREATE);

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        try {
            if(myAidlInterface != null && myAidlInterface.asBinder().isBinderAlive()){
                myAidlInterface.unRegisterCb(myCbListener);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        unbindService(mConn);
    }

    //实现回调监听器
    class MyCbListener extends IAidlCbListener.Stub{

        @Override
        public void showServerMsg(String msg) throws RemoteException {
            Log.d(TAG, "showServerMsg: "+msg);
        }
    }
}

在Client进程的MainActivity中绑定Service并调用Server进程的通过AIDL提供的接口方法。

这里需要说明的是在MainActivity中,通过DeathRecipient对象,并调用IBinder的linkToDeath和unLinkToDeath方法,实现了万一Server进程Died之后,对Service进行重新注册。

简单介绍一下linkToDeath和unlinkToDeath:

/**
     * Interface for receiving a callback when the process hosting an IBinder
     * has gone away.
     * 
     * @see #linkToDeath
     */
    public interface DeathRecipient {
        public void binderDied();
    }

    /**
     * Register the recipient for a notification if this binder
     * goes away.  If this binder object unexpectedly goes away
     * (typically because its hosting process has been killed),
     * then the given {@link DeathRecipient}'s
     * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
     * will be called.
     * 
     * 

You will only receive death notifications for remote binders, * as local binders by definition can't die without you dying as well. * * @throws RemoteException if the target IBinder's * process has already died. * * @see #unlinkToDeath */ public void linkToDeath(@NonNull DeathRecipient recipient, int flags) throws RemoteException; /** * Remove a previously registered death notification. * The recipient will no longer be called if this object * dies. * * @return {@code true} if the recipient is successfully * unlinked, assuring you that its * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method * will not be called; {@code false} if the target IBinder has already * died, meaning the method has been (or soon will be) called. * * @throws java.util.NoSuchElementException if the given * recipient has not been registered with the IBinder, and * the IBinder is still alive. Note that if the recipient * was never registered, but the IBinder has already died, then this * exception will not be thrown, and you will receive a false * return value instead. */ public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);

使用它比较简单,只需要实现DeathRecipient的bindDied方法。

linkToDeath和unLinkToDeath是成对出现的,可以参照上面Client中MainActivity的实现,在ServiceConnection中onServiceConnected的时候link,在bindDied方法中unlink。linkToDeath是为IBinder对象设置死亡代理,unLinkToDeath是解除之前设置的死亡代理,并可以在此时做重新绑定的动作。

运行代码看结果

把Server进程和Client继承都编译出来,并安装到同一部手机或者虚拟机,先启动Server进程,然后再启动Client进程,看到如下结果:

请注意,这个log内容只是Client进程的,Server进程的log没有再截图中。从log可以看出,在client进程中调用的Server进程提供的AIDL接口方法,都成功调用了,并且Server端回调Client进程设置的监听器也执行成功,说明当前代码OK。

总结:

  1. 注意理解Binder机制
  2. AIDL只提供了有限的传输数据类型,自定义的传输类型需要序列化
  3. RemoteCallbackList很好的解决了IPC过程中可能出现的某一方进程Crash,引起另一方Exception的问题
  4. DeathRecipient理解和调用(解决Server端进程意外终止,Client端获得的Binder对象丢失问题)

OK,结束

你可能感兴趣的:(Android 关于AIDL通信,RemoteCallbackList实现Server回调Client)