两个应用实现基于AIDL技术和binder连接池的跨进程通信

最近需要实现一个以跨进程通信技术来获取项目中网络实时链接状态,借此机会学习并实现了以AIDL技术的IPC。此文主要讲解如何在两个应用中通过ADIL实现IPC,其实包括binder连接池的实现方案;任玉刚的《Android开发艺术探索》中的实现都是基于一个APP实现的,对于很多初学者来说并不能非常容易的理解。特别是binder连接池部分。并且本人发现网络上大部分文章都是互相抄袭,并没有真正理解或者自己去实现。所以本文主要是基于《Android开发艺术探索》的IPC例子,利用binder连接池来实现两个APP的跨进程通信。以下内容需要先熟悉普通AIDL的实现方式。可以先去阅读书本中的内容。

先实现服务端APP:

1、我们提供两个AIDL接口(ISecurityCenter和ICompute),来实现多业务模块都需要使用AIDL的情况。ISecurityCenter是提供加解密功能的,而ICompute接口是用来计算加法功能的。
ISecurityCenter.aidl:

两个应用实现基于AIDL技术和binder连接池的跨进程通信

// Icompute.aidl
package qlk.wt.com.app1;

// Declare any non-default types here with import statements

interface Icompute {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    int add(int a,int b);
}


ICompute.adil

// Icompute.aidl
package qlk.wt.com.app1;

// Declare any non-default types here with import statements

interface Icompute {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    int add(int a,int b);
}

现在业务模块的AIDL接口已经实现了,但是与普通AIDL实现方式不同,这里并不需要为每个ADIL分别创建一个Service,而是采用bind连接池的技术实现,binder连接池类似于设计模式中的工厂方法,binder连接池属于工厂,这里大家估计都应该明白了吧,我们需要再增加一个IBinderPool.aidl,为IBinderPool创建一个service,客户端通过绑定该service实现对多业务的AIDL的通信。所以上面的两个AIDL接口的具体实现都需要在服务端实现。

SecurityCenterImpl.java :

package qlk.wt.com.app1;

import android.os.RemoteException;

/**
 * Created by qianlikang on 2018/1/3.
 */

public class SecurityCenterImpl extends ISecurityCenter.Stub {
    private static final char SECRET_CODE = '^';

    @Override
    public String encrypt(String content) throws RemoteException {
        char[] chars = content.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            chars[i]^=SECRET_CODE;
        }
        return new String(chars);
    }

    @Override
    public String decrypt(String password) throws RemoteException {
        return encrypt(password);
    }
}


ComputeImpl.java :

package qlk.wt.com.app1;

import android.os.RemoteException;

/**
 * Created by qianlikang on 2018/1/3.
 */

public class ComputeImpl extends Icompute.Stub {

    @Override
    public int add(int a, int b) throws RemoteException {
        return a+b;
    }
}


接下来在服务端继续增加binder连接池的的AIDL文件IBinderPool.aidl,并且为IBinderPool.aidl 创建远程service 并实现IBinderPool.

IBinderPool.aidl:

// IBinderPool.aidl
package qlk.wt.com.app1;

// Declare any non-default types here with import statements

interface IBinderPool {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    IBinder queryBinder(int binderCode);
}

BinderPoolService :   
这里需要注意的是:该Service与书中的不同点是,IBinderPool.adil的接口实现并没有用BinderPool.java中的内部类BinderPoolImpl,目的就是为了实现两个APP。因为BinderPool.java是需要写在客户端的,服务端是没法获取到的

package qlk.wt.com.app1;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;

/**
 * Created by qianlikang on 2018/1/3.
 */

public class BinderPoolService extends Service {

    public static final int BINDER_COMPUTE = 0;
    public static final int BINDER_SECURITY_CENTER = 1;

    private Binder mBinderPool = new IBinderPool.Stub(){
        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            switch (binderCode) {
                case BINDER_SECURITY_CENTER:
                    binder = new SecurityCenterImpl();
                    break;
                case BINDER_COMPUTE:
                    binder = new ComputeImpl();
                    break;
                default:
                    break;
            }
            return binder;
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinderPool;
    }
}

当Binder连接池连接上远程服务时,会根据不同模块的标示即:binderCode,返回不同的Binder对象,不同Binder对象的具体实现上面就是SecurityCenterImpl的实现和ComputeImpl的实现BinderPoolService 需要在AndroidManifest中注册;这里需要注意的是,因为这里是两个APP,所以不需要增加Android:process=" "  标签来模拟跨进程。这里增加了一个action过滤条件,目的是为了隐式启动安全性考虑,这块书中有讲到。

<service
    android:name=".BinderPoolService"
    android:process=":remote"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="qlk.wt.com.app1.action.BinderPoolService"/>
    </intent-filter>
</service>


目前为止整个服务端的开发就完成了,下面给出整个工程结构图:

两个应用实现基于AIDL技术和binder连接池的跨进程通信


实现客户端APP2:
1、连同包结构一起拷贝到APP2中,一定要保证包结构一致。拷贝完成后记得rebuild一下。
2、增加BinderPool.java,所谓的binder连接池其实就是类似设计模式中的工厂方法。
       BinderPool.java:这里需要注意的是,与书中不同的是IBinderPool.aidl的接口具体实现是在服务端的BinderPoolService 中    成,并没有采用书中的内部类方式,目的就是实现两个APP的跨进程,其本质就是应该在服务端的BinderPoolService中去对 IBinderPool.aidl的接口进行具体实现,而不是在客户端的BinderPool.java 中以内部类方式实现!BinderPool.java一定是客户端编写的。这里尤其注意,初学者很可能迷糊!

package qlk.wt.com.app2;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
import qlk.wt.com.app1.IBinderPool;

/**
 * Created by qianlikang on 2018/1/3.
 */

public class BinderPool {
    private static final String TAG = "BinderPool";
    private static final String ACTION = "qlk.wt.com.app1.action.BinderPoolService";
    private static final String PACKAGENAME = "qlk.wt.com.app1";
    public static final int BINDER_COMPUTE = 0;
    public static final int BINDER_SECURITY_CENTER = 1;
    private Context mContext;
    private IBinderPool mBinderPool;
    private static volatile BinderPool sInstance;
    private CountDownLatch mConnectBinderPoolCountDownLatch;

    private BinderPool(Context context) {
        mContext = context.getApplicationContext();
        connectBinderPoolService();
    }

    public static BinderPool getInsance(Context context) {
        if (sInstance == null) {
            synchronized (BinderPool.class) {
                if (sInstance == null) {
                    sInstance = new BinderPool(context);
                }
            }
        }
        return sInstance;
    }

    private synchronized void connectBinderPoolService() {
        mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
        Intent service = new Intent();
        service.setAction(ACTION);
        service.setPackage(PACKAGENAME);
        mContext.bindService(service, mBinderPoolConnection,
                Context.BIND_AUTO_CREATE);
        try {
            mConnectBinderPoolCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * query binder by binderCode from binder pool
     *
     * @param binderCode the unique token of binder
     * @return binder who's token is binderCode<br>
     * return null when not found or BinderPoolService died.
     */
    public IBinder queryBinder(int binderCode) {
        IBinder binder = null;
        try {
            if (mBinderPool != null) {
                binder = mBinderPool.queryBinder(binderCode);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return binder;
    }

    private ServiceConnection mBinderPoolConnection = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // ignored.
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinderPool = IBinderPool.Stub.asInterface(service);
            try {
                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mConnectBinderPoolCountDownLatch.countDown();
        }
    };

    private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            Log.w(TAG, "binder died.");
            mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
            mBinderPool = null;
            connectBinderPoolService();
        }
    };

}

3、创建一个activity:
BinderPoolActivity :  在这个activity中去操作服务端的两个业务模块的AIDL。这里其实要明白一点,BinderPool.java中的具体操作完全可以放到activity中实现,增加BinderPool.java其实就是一种工厂方法设计模式,目的就是降低代码耦合性,便于维护。

package qlk.wt.com.app2;

import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import qlk.wt.com.app1.ISecurityCenter;
import qlk.wt.com.app1.Icompute;
import qlk.wt.com.app1.R;

/**
 * Created by qianlikang on 2018/1/3.
 */

public class BinderPoolActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                doWork();
            }
        }).start();

    }

    private void doWork() {

        BinderPool mBinderPool = BinderPool.getInsance(BinderPoolActivity.this);
        IBinder securityBinder = mBinderPool.queryBinder(BinderPool.BINDER_SECURITY_CENTER);
        ISecurityCenter iSecurityCenter = ISecurityCenter.Stub.asInterface(securityBinder);
        String msg = "helloword-安卓";
        System.out.println("content:" + msg);
        try {
            String password = iSecurityCenter.encrypt(msg);
            System.out.println("encrypt:" + password);
            System.out.println("decrypt:" + iSecurityCenter.decrypt(password));
        } catch (RemoteException e) {
            e.printStackTrace();
        }

        IBinder computeBinder = mBinderPool.queryBinder(BinderPool.BINDER_COMPUTE);
        Icompute icompute = Icompute.Stub.asInterface(computeBinder);
        try {
            System.out.println("3+5=" + icompute.add(3, 5));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

}


客户端APP2工程结构图:

两个应用实现基于AIDL技术和binder连接池的跨进程通信