Android IPC 机制

  • 2017-03-14
  • 10,586
  • 0

本文为《Android 开发艺术探索》 第二章IPC机制 笔记及实践

文章中涉及的代码:https://github.com/pqpo/ServiceAndIpcDemo  建议先clone一份到本地,运行起来之后再继续阅读。

其中项目中包括Service生命周期的部分不在本文 讨论范围之内,并且只演示了使用Binder进行进程间通讯,其他方式比较简单。

IPC:Inter-Process Communication,意为进程间通讯。思考一下什么是线程,什么是进程,线程间通讯是怎么完成的,需要注意什么?

Android 中的IPC方式:

  1. 使用Bundle:通过Intent传递数据,包括基本数据类型,序列化类型(Parcellable、Serializable),安卓支持的特殊对象。具体查看Bundle类对外提供的支持类型。
  2. 使用文件共享:两个进程通过读写一个文件来交换数据。并发读可能读到的不是最新的,并发写很可能数据会乱套。SharedPreferences(带有缓存的文件读写)在进程间通信中不介意使用。
  3. 使用Socket:通过TCP或者UDP协议进行进程间通信,比较消耗资源。
  4. 使用Binder:Android 底层提供的IPC方式,包括使用简单的Messenger,ContentProvider都可以进行进程间通信,底层也是由Binder实现。

本文主要介绍使用Binder进行进程间通信,下面先演示一下如何使用aidl来实现IPC:

1.新建三个文件 Program.java, Program.aidl(用到的Parcelable实体必须声明一个aidl文件), IProgramManager.aidl, IOnProgramListChangedListener.aidl:

 
//Program.java
public class Program implements Parcelable {

    public int programId;
    public String programName;

    public Program(int programId, String programName) {
        this.programId = programId;
        this.programName = programName;
    }
    //Parcelable 相关代码省略
}
 
//Program.aidl
package pw.qlm.ipctest;
parcelable Program;
 
//IProgramManager.aidl
package pw.qlm.ipctest;

import pw.qlm.ipctest.Program;
import pw.qlm.ipctest.IOnProgramListChangedListener;

interface IProgramManager {
    List getProgramList();
    void addProgram(String program);
    void removeProgram(String program);
    void registerOnProgramListChangedListener(IOnProgramListChangedListener listener);
    void unregisterOnProgramListChangedListener(IOnProgramListChangedListener listener);
}

package pw.qlm.ipctest;

import pw.qlm.ipctest.Program;

interface IOnProgramListChangedListener {
    void onChanged(String method, in Program list);
}

2.build 一下,使IDE根据aidl文件自动生成代码
3.创建实现类,ProgramManagerImpl.java,继承于IProgramManager.Stub,该类为IDE根据aidl文件生成的类,并实现方法:

 
//ProgramManagerImpl.java
public class ProgramManagerImpl extends IProgramManager.Stub {

    private final HashSet mProgramList = new HashSet<>();
    private AtomicInteger ids = new AtomicInteger(0);

    //夸进程监听器不能简单的使用List,因为不同的进程,list内保存的对象不是同一个
    private RemoteCallbackList callbacks = new RemoteCallbackList<>();

    @Override
    public List getProgramList() throws RemoteException {
        synchronized (mProgramList) {
            return Arrays.asList(mProgramList.toArray(new Program[]{}));
        }
    }

    //远程调用是运行与Binder线程池,故可以进行耗时操作;
    @Override
    public void addProgram(String program) throws RemoteException {
        //模拟耗时操作
        SystemClock.sleep(1000);
        synchronized (mProgramList) {
            Program program1 = new Program(ids.incrementAndGet(), program);
            if (mProgramList.add(program1)){
                onNotifyProgramListChanged("add", program1);
            }
        }
    }

    @Override
    public void removeProgram(String program) throws RemoteException {
        //略
    }

    @Override
    public void registerOnProgramListChangedListener(IOnProgramListChangedListener listener) throws RemoteException {
        if(listener != null) {
            callbacks.register(listener);
        }
    }

    @Override
    public void unregisterOnProgramListChangedListener(IOnProgramListChangedListener listener) throws RemoteException {
        if(listener != null) {
            callbacks.unregister(listener);
        }
    }

    private void onNotifyProgramListChanged(String method, Program program1) throws RemoteException {
        //beginBroadcast(),callbacks.finishBroadcast()必须成对出现
        int N = callbacks.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnProgramListChangedListener broadcastItem = callbacks.getBroadcastItem(i);
            broadcastItem.onChanged(method, program1);
        }
        callbacks.finishBroadcast();
    }

}

4.创建Service,给其他进程提供服务,ProgramManagerService.java:

 
//ProgramManagerService.java
public class ProgramManagerService extends Service {

    private static final String TAG = "IPC/ProgramService";

    IProgramManager programManager = new ProgramManagerImpl();

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind");
        //鉴权,防止任意客户端调用
        if (checkCallingOrSelfPermission("qw.qlm.ipctest.PERMISSION_CALL_REMOTE_SERVICE") == PackageManager.PERMISSION_DENIED) {
            return null;
        }
        return programManager.asBinder();
    }
}

5.远程连接服务:


private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.i(TAG, "binderDied : disconnected!" + " Thread:" + Thread.currentThread().getName());
            if (mProgramManager != null) {
                mProgramManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
                mProgramManager = null;
            }
            mHandler.sendEmptyMessage(MSG_CONNECT_TO_SERVICE);
        }
    };

//夸进程监听器也是一个Binder,简单的接口没有夸进程的能力
private IOnProgramListChangedListener mListener = new IOnProgramListChangedListener.Stub() {
    @Override
    public void onChanged(String method, Program program) throws RemoteException {
        Message.obtain(mHandler, MSG_CHANGED, method + " " + program + " success").sendToTarget();
    }
};

Intent intent = new Intent(this, ProgramManagerService.class);
mProgramServiceConnection = new ServiceConnection() {
      @Override
      public void onServiceConnected(ComponentName name, IBinder service) {
           if (service == null) {
               toast("permission denied!");
               return;
           }
           mProgramManager = IProgramManager.Stub.asInterface(service);
           Log.i(TAG, "connect success!");
           toast("connect success!");
           try {
               mProgramManager.asBinder().linkToDeath(mDeathRecipient, 0);
               mProgramManager.registerOnProgramListChangedListener(mListener);
           } catch (RemoteException e) {
               e.printStackTrace();
           }
     }

     @Override
     public void onServiceDisconnected(ComponentName name) {
          Log.i(TAG, "onServiceDisconnected : disconnected!" + " Thread:" + Thread.currentThread().getName());
                mProgramManager = null;
                bindToService();
          }
     };
bindService(intent, mProgramServiceConnection, Context.BIND_AUTO_CREATE);

6.使用远程接口:


//如果远程调用为耗时操作避免在UI线程中调用
mProgramManager.addProgram(programName);

注意点,对应以上各个步骤

  1. Parcelable实体需要在aidl文件中声明,IProgramManager.aidl中import语句不能少,不管在不在同一个包中; 如要注册监听,不能简单声明一个接口,而是要声明aidl文件 IOnProgramListChangedListener.aidl, 简单接口不具备远程通信的能力;
  2. 新建aidl文件之后必须build;
  3. 实现类运行与Binder线程池中,需要注意线程安全,并且允许进行耗时操作;夸进程监听器不能简单的使用List,因为不同的进程,list内保存的对象不是同一个,必须使用RemoteCallbackList;
  4. Service 中可以在onBind中进行鉴权;
  5. 可以设置死亡监听器 IBinder.DeathRecipient 用于恢复Service,该回调运行于Binder线程池;ServiceConnection 中的 onServiceDisconnected 运行于UI线程。夸进程接听器必须也是一个Binder对象:IOnProgramListChangedListener mListener = new IOnProgramListChangedListener.Stub();
  6. 远程调用可能是一个耗时操作,如果是耗时操作需要运行在后台线程;

为了更好的理解Binder机制,下面不使用aidl来实现IPC:

1. 新建远程调用接口,IConfigManager.java,继承IInterface:


public interface IConfigManager extends IInterface {

    void setValue(String value) throws RemoteException;
    String getValue() throws RemoteException;

}

2. 新建实现类,ConfigManagerImpl.java:


public class ConfigManagerImpl extends Binder implements IConfigManager {

    private static final java.lang.String DESCRIPTOR = "pw.qlm.ipctest.ipc.ConfigManagerImpl";

    private String value;
    private Context mContext;

    public ConfigManagerImpl(Context context) {
        this.attachInterface(this, DESCRIPTOR);
        mContext = context;
    }

    @Override
    public synchronized void setValue(String value) throws RemoteException{
        this.value = value;
    }

    @Override
    public synchronized String getValue() throws RemoteException {
        return value;
    }

    @Override
    public IBinder asBinder() {
        return this;
    }

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        //鉴权,同时判断权限与package name,返回FALSE,则该次远程调用会失败
        if (mContext.checkCallingOrSelfPermission("qw.qlm.ipctest.PERMISSION_CALL_REMOTE_SERVICE") == PackageManager.PERMISSION_DENIED) {
            return false;
        }
        String packageName = "";
        String[] packagesForUid = mContext.getPackageManager().getPackagesForUid(getCallingUid());
        if (packagesForUid != null && packagesForUid.length > 0) {
            packageName = packagesForUid[0];
        }
        if (packageName == null || !packageName.startsWith("pw.qlm")) {
            return false;
        }

        switch (code) {
            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTOR);
                return true;
            case ConfigManagerProxy.TRANSACT_getValue:
                data.enforceInterface(DESCRIPTOR);
                String result = getValue();
                reply.writeNoException();
                reply.writeString(result);
                return true;
            case ConfigManagerProxy.TRANSACT_setValue:
                data.enforceInterface(DESCRIPTOR);
                String value = data.readString();
                setValue(value);
                reply.writeNoException();
                return true;
        }
        return super.onTransact(code, data, reply, flags);
    }

    public static IConfigManager asInterface(IBinder binder) {
        if (binder == null) {
            return null;
        }
        IInterface iInterface = binder.queryLocalInterface(DESCRIPTOR);
        if (iInterface != null && iInterface instanceof IConfigManager) {
            return (IConfigManager) iInterface;
        }
        return new ConfigManagerProxy(binder);
    }

    private static class ConfigManagerProxy implements IConfigManager {

        private IBinder remote;

        public ConfigManagerProxy(IBinder remote) {
            this.remote = remote;
        }

        @Override
        public void setValue(String value) throws RemoteException {
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            try {
                data.writeInterfaceToken(DESCRIPTOR);
                data.writeString(value);
                remote.transact(TRANSACT_setValue, data, reply, 0);
                reply.readException();
            } finally {
                data.recycle();
                reply.recycle();
            }
        }

        @Override
        public String getValue() throws RemoteException {
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            String result = null;
            try {
                data.writeInterfaceToken(DESCRIPTOR);
                remote.transact(TRANSACT_getValue, data, reply, 0);
                reply.readException();
                result = reply.readString();
            } finally {
                data.recycle();
                reply.recycle();
            }
            return result;
        }

        @Override
        public IBinder asBinder() {
            return remote;
        }

        static final int TRANSACT_setValue = IBinder.FIRST_CALL_TRANSACTION + 0;
        static final int TRANSACT_getValue = IBinder.FIRST_CALL_TRANSACTION + 1;

    }

}

接下来的连接和调用就和使用aidl步骤中的4,5,6一致了,这里再重点分析一下Binder的实现类ConfigManagerImpl。

可以看到,该类是继承于Binder,表明该类具备远程调用的能力,并且实现了IConfigManager,说明该类拥有特点的业务能力,结合起来看也就是说ConfigManagerImpl具备远程调用业务接口的能力。那么这种能力是怎么实现的呢,主要都是Binder的功劳。
实现业务接口不用多说了,要注意方法调用在Binder线程池中,特别关注多线程调用的安全。


@Override
public synchronized void setValue(String value) throws RemoteException{
   this.value = value;
}
@Override
public synchronized String getValue() throws RemoteException {
   return value;
}

声明一个唯一标识量,一般用当前类名:


String DESCRIPTOR = "pw.qlm.ipctest.ipc.ConfigManagerImpl";

构造方法中调用以下方法,后面binder.queryLocalInterface(DESCRIPTOR)中会用DESCRIPTOR 标准量查询本地接口:


this.attachInterface(this, DESCRIPTOR);

我们通过下面的方法将一个binder对象转换为可以进行业务调用的对象:


public static IConfigManager asInterface(IBinder binder) {
     if (binder == null) {
        return null;
     }
     IInterface iInterface = binder.queryLocalInterface(DESCRIPTOR);
     if (iInterface != null && iInterface instanceof IConfigManager) {
        return (IConfigManager) iInterface;
     }
     return new ConfigManagerProxy(binder);
}

如果是本地进程,binder就是我们新建的ConfigManagerImpl,那么调用binder.queryLocalInterface(DESCRIPTOR)就会返回本身,可以查看Binder中的代码:


public IInterface queryLocalInterface(String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

其中mDescriptor, mOwner便是构造方法中设置进去的this.attachInterface(this, DESCRIPTOR);
如果是远程调用,那么该binder对象不是本身,实际上是BinderProxy对象,这个时候会返回一个代理对象ConfigManagerProxy,并将这个BinderProxy对象传给它的内部进行远程通信。例如:


@Override
public void setValue(String value) throws RemoteException {
     Parcel data = Parcel.obtain();
     Parcel reply = Parcel.obtain();
     try {
         data.writeInterfaceToken(DESCRIPTOR);
         data.writeString(value);
         remote.transact(TRANSACT_setValue, data, reply, 0);
         reply.readException();
     } finally {
         data.recycle();
         reply.recycle();
     }
}

可以看到支持远程通信的对象是有限的,也就是Parcel.write支持的对象。
这时候会将数据传递到Binder所在进程中回调执行onTransact(int code, Parcel data, Parcel reply, int flags);


@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTOR);
                return true;
            case ConfigManagerProxy.TRANSACT_getValue:
                data.enforceInterface(DESCRIPTOR);
                String result = getValue();
                reply.writeNoException();
                reply.writeString(result);
                return true;
            case ConfigManagerProxy.TRANSACT_setValue:
                data.enforceInterface(DESCRIPTOR);
                String value = data.readString();
                setValue(value);
                reply.writeNoException();
                return true;
        }
        return super.onTransact(code, data, reply, flags);
}

data代表输入参数,reply代表输出参数,开始时必须调用data.enforceInterface(DESCRIPTOR);结束时必须调用
reply.writeNoException();如果返回true则代表该次远程调用成功,否则失败,这里可以做鉴权:


//鉴权,同时判断权限与package name,返回FALSE,则该次远程调用会失败
if (mContext.checkCallingOrSelfPermission("qw.qlm.ipctest.PERMISSION_CALL_REMOTE_SERVICE") == PackageManager.PERMISSION_DENIED) {
    return false;
}
String packageName = "";
String[] packagesForUid = mContext.getPackageManager().getPackagesForUid(getCallingUid());
if (packagesForUid != null && packagesForUid.length > 0) {
    packageName = packagesForUid[0];
}
if (packageName == null || !packageName.startsWith("pw.qlm")) {
    return false;
}

以上是关于Binder使用的所有内容,有兴趣的同学还可以继续深入底层了解Binder驱动是如何进行进程间通信的。另外还需要思考一个问题,如果每个业务都新建一个Service肯定是不妥的,那么如何使用一个Service管理客户端中的多个Binder呢?具体实现查看项目中的BinderPoolService相关代码。

>> 转载请注明来源:Android IPC 机制

评论

还没有任何评论,你来说两句吧

发表评论