背景

一直以来都只是听说AIDL是跨进程的,但都由于项目中也没涉及到,所以也从来都没彻底去了解过,最近空闲下来去了解插件化开发原理,看着看着正好涉及到Ibinder以及android用到的AIDL,于是乎按图索骥一条条来探索到底是个怎么回事儿,按照读者推荐,我们就先从AIDL使用以及原理开始挖掘。

前言

对于跨进程,我们都知道android底层是linux,所以进程管理也是linux系统的那一套,即进程之间是相互独立的互不干扰的,数据是独享的,所以要进行进程间的通信也是通过老掉牙的方案Binder机制去搞,而android的AIDL底层也是基于Binder机制来搞的,只不过封装的比较好,android开发不需要知道Binder机制也能使用AIDL跨进程开发,只需要遵循一定的流程,一一步步创建就好;由于android的每个app对应一个独立进程(不是自己开启新的进程前提),要想跨应用访问共享数据,那就得掌握进程间的通信。接下来我们一步步教大家如何使用以及对其原理讲解。

资料

这个博主的一系列进程通信的讲解与比较是比较完美的,推荐想了解的要细读深究,并手动编写代码
http://blog.csdn.net/hitlion2008/article/details/9773251
Binde原理详解
http://weishu.me/2016/01/12/binder-index-for-newer/

AIDL使用简述

1、创建client/service的aidl文件(如IRemoteService.aidl),client/service使用的是同一份,分别放置在各自的src目录下
2、创建完后,重新build项目,会在app\build\generated\source\aidl目录下自动生成相应的java文件,
想深究的可以对着原理扒扒里边的源码,后边会简述
3、创建service,并实现IRemoteService.Stub,通过onBind()返回
4、创建client,并实现ServiceConnection,然后通过以下方式启动进行绑定,并建立进程间的通道

 Intent intent = new Intent(); intent.setClassName("包名", "包名+service的类名"); bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);

5、最后在ServiceConnection的onServiceConnected中通过IRemoteBankService.Stub.asInterface(service)获取client的代理,通过这个代理就可以和service相互通信了。

AIDL的创建步骤

1、创建所有的aidl文件,包括实现的进程间要通信的实体类也要定义

创建aidl文件 如图所示,名字随意命名,这里我命名为银行的aidl,名字为IRemoteBankService
创建完成后就会在src/main/aidl下自动生成和应用包名一致的包名,打开就看到我们创建的aidl文件,打开如下

// IRemoteBankService.aidlpackage com.example.zwr.myapplication;// Declare any non-default types here with import statementsinterface IRemoteBankService{    /**     * 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);}

这样我们就创建完毕!

2、在aidl文件中编写自定想要的接口。
3、编辑实体类.aidl,删除除了包名外的所有内容,写下parcelable 类名;(如parcelable User;)parcelable 中的p必须是小写的;
4、将IRemoteBankService.aidl定义的接口中所使用到的实体类,将对应的包名+类名导入IRemoteBankService.aidl;
(如:
import com.example.zwr.myapplication.User;
import com.example.zwr.myapplication.RemoteClientCallBack;

5、创建自己实现的实体类,这个类必须要是实现Parcelable
6、重新编译:即重新build就完毕了
注意:
自定义的接口方法的返回类型以及参数类型:类型有一定的限制
(1) 支持java的基本类型:float/int/String…..
(2)支持实现Parcelable的实体类
(3)支持Stub/aidl接口
基本类型不用说,看看实体类是怎么玩的
一般来说我们会定义自己传递的实体类,但在定义之前,我们要先在aidl/包名/创建实体类同名的aidl(如这里是User.aidl),然后在创建java/包名/实体类的.java,否则如果先创建类,有可能无法创建aidl,这是as工具的问题

AIDL的Demo详解

1、创建aidl
这里我的demo中是创建了3个aidl,所以先将这3个aidl文件创建

<1>、IRemoteBankService.aidl

创建完后得到一个类似java的接口,同样我们可以在里边自定义自己要通信的接口;比如我定义如下

这里创建了4个方法:
//—-以下方法都是客户端直接调用,将数据传给服务端,顺带从服务端拿取数据—–类似,客户端发送请求,拿取数据
/*客户端注册回调接口,用以服务端主动通过调用回调方法,向客户端发送消息—类似服务端主动推送数据给客户端/
/*存钱 存款/
boolean despoistMoney(int money);
/*取款/
int drawMoney(int money);
/*当前存取款用户/
User getUser();
//—-以上方法都是客户端直接调用,将数据传给服务端,顺带从服务端拿取数据—–类似,客户端发送请求,拿取数据

/*客户端注册回调接口,用以服务端主动通过调用回调方法,向客户端发送消息—类似服务端主动推送数据给客户端/
void registerClientOberser(RemoteClientCallBack clientCallBack);

正如注释写的,我们存取款以及获取用户信息都是通过定义的前3个方法获取,前3个方法相当于client向service发送请求,然后获取或者发送信息,主动方是在client,而最后一个方法是client首先向service注册一个观察者,然后当service有什么更新的状态想要通知client时都是通过这个观察者来通知更新,即主动权在service,由service主动推送消息,这样就达到了信息互通

以上3个aidl中,IRemoteBankService.aidl中声明的接口方法中用到了User和RemoteClientCallBack,所以需要导包:
import com.example.zwr.myapplication.User;
import com.example.zwr.myapplication.RemoteClientCallBack;

<2>、User.aidl

接下来看看User.aidl做了什么:
// User.aidl
package com.example.zwr.myapplication;

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

parcelable User;

其实创建aidl时会生成和之前一模一样的接口类型,这里我们把它都给删除,然后写下parcelable 类名;(这里是parcelable User;)
这样就算声明完了。

<3>RemoteClientCallBack.aidl

// RemoteClientCallBack.aidl
package com.example.zwr.myapplication;

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

interface RemoteClientCallBack {

void transferToClientByServer(String transferData);
}

这里声明了接口方法,但是参数是普通类型,不需要导入任何包;这个方法就是service主动将消息推送后,客户端回调的方法

以上就是aidl的玩法。

<4>、创建aidl使用参数的实体类

接下来我们要在java/aidl文件同包名/创建aidl相应的实体类User,如本demo:java\com\example\zwr\myapplication\User.java
如下所示

package com.example.zwr.myapplication;import android.os.Parcel;import android.os.Parcelable;/** * author : zhongwr on 2017/2/22 */public class User implements Parcelable {    private String name;    private String id;    /**进程Id*/    private String pId;    public User(String name, String id, String pId) {        this.name = name;        this.id = id;        this.pId = pId;    }    public User() {    }    @Override    public String toString() {        return "name = " + name + " id = " + id + " pid = " + pId;    }    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeString(this.name);        dest.writeString(this.id);        dest.writeString(this.pId);    }    protected User(Parcel in) {        this.name = in.readString();        this.id = in.readString();        this.pId = in.readString();    }    public static final Creator CREATOR = new Creator() {        @Override        public User createFromParcel(Parcel source) {            return new User(source);        }        @Override        public User[] newArray(int size) {            return new User[size];        }    };}

看着要生成这么多东东,其实我们不用自己手写,直接使用as自带的工具:
打开当前文件右键-》generate-》Parcelable
这样就会自动生成
这里我们要注意如下:
实现的Parcelable 类(User) 必须放在src/main/java目录下,包名与aidl的包名一致(这里是com.example.zwr.myapplication)
如图:

这样前提准备工作就基本完事,最后一步,重新build项目,然后不出什么意外的话会在pp\build\generated\source\aidl目录下自动生成相应的java文件

里边的内容先不关心,后续会讲解

2、服务端:创建RemoteBankService.java

这里就跟平时创建的service一样,继承service之后 主要是这个方法返回的IBinder

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

这里实现的是我们创建的aidl自动生成的java文件IRemoteBankService中的IRemoteBankService.Stub

private IRemoteBankService.Stub mRemoteBind = new IRemoteBankService.Stub() {        /**存钱*/        @Override        public boolean despoistMoney(int money) throws RemoteException {            Log.d(TAG, "despoistMoney pid = " + android.os.Process.myPid());            if (money > 0) {                return true;            }            return false;        }        /**取钱*/        @Override        public int drawMoney(int money) throws RemoteException {            Log.d(TAG, "drawMoney pid = " + android.os.Process.myPid());            clientCallBackInstance.transferToClientByServer("当前用户存钱成功             余额 :"+money+"当前进程Id = "+android.os.Process.myPid());            return money;        }        /**         * 用户信息         * @return         * @throws RemoteException         */        @Override        public User getUser() throws RemoteException {            Log.d(TAG, "getUser pid = " + android.os.Process.myPid());            return new User("张三", "" + System.currentTimeMillis(),             "" + android.os.Process.myPid());        }        /**         * 注册客户端的观察者,用以服务端更新后主动通知其更新         * @param clientCallBack         * @throws RemoteException         */        @Override        public void registerClientOberser(RemoteClientCallBack clientCallBack)         throws RemoteException {            clientCallBackInstance =clientCallBack;            Message msg = Message.obtain();            msg.obj = clientCallBack;            timeHandler.sendMessageDelayed(msg, 10000);        }    };    private RemoteClientCallBack clientCallBackInstance;

看到这个实现的Stub中实现了我们在aidl定义的接口方法,我们就是通过这些方法进行通信的

由于前三个是client主动调用的方法,而最后registerxxx()是client注册后,service主动推送消息,所以我们这里使用handler延时模拟推送。

service的完整代码如下

     package com.example.zwr.myapplication;import android.app.Service;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.os.*;import android.os.Process;import android.util.Log;/** * 独立进程运行的服务 */public class RemoteBankService extends Service {    private static final String TAG = "RemoteBankService";    public static void bindService(Context context, ServiceConnection connection) {        Log.d(TAG, "bindService pid = " + android.os.Process.myPid());        Intent intent = new Intent(context, RemoteBankService.class);//        intent.setClassName("com.example.zwr.myapplication","com.example.zwr.myapplication.RemoteBankService");        context.bindService(intent, connection, Context.BIND_AUTO_CREATE);    }    public static void doUnbindService(Context context, ServiceConnection connection) {        Log.d(TAG, "doUnbindService pid = " + android.os.Process.myPid());        if (connection != null) {            context.unbindService(connection);            context.stopService(new Intent(context, RemoteBankService.class));        }    }    public RemoteBankService() {    }    @Override    public void onCreate() {        Log.d(TAG, "onCreate pid = " + android.os.Process.myPid());        super.onCreate();    }    @Override    public void onDestroy() {        Log.d(TAG, "onDestroy pid = " + android.os.Process.myPid());        super.onDestroy();        Process.killProcess(Process.myPid());    }    @Override    public IBinder onBind(Intent intent) {        return mRemoteBind;    }    private IRemoteBankService.Stub mRemoteBind = new IRemoteBankService.Stub() {        /**存钱*/        @Override        public boolean despoistMoney(int money) throws RemoteException {            Log.d(TAG, "despoistMoney pid = " + android.os.Process.myPid());            if (money > 0) {                return true;            }            return false;        }        /**取钱*/        @Override        public int drawMoney(int money) throws RemoteException {            Log.d(TAG, "drawMoney pid = " + android.os.Process.myPid());            clientCallBackInstance.transferToClientByServer("当前用户存钱成功             余额 :"+money+"当前进程Id = "+android.os.Process.myPid());            return money;        }        /**         * 用户信息         * @return         * @throws RemoteException         */        @Override        public User getUser() throws RemoteException {            Log.d(TAG, "getUser pid = " + android.os.Process.myPid());            return new User("张三", "" + System.currentTimeMillis(),             "" + android.os.Process.myPid());        }        /**         * 注册客户端的观察者,用以服务端更新后主动通知其更新         * @param clientCallBack         * @throws RemoteException         */        @Override        public void registerClientOberser(RemoteClientCallBack clientCallBack)         throws RemoteException {            clientCallBackInstance =clientCallBack;            Message msg = Message.obtain();            msg.obj = clientCallBack;            timeHandler.sendMessageDelayed(msg, 10000);        }    };    private RemoteClientCallBack clientCallBackInstance;    private TimeHander timeHandler = new TimeHander();    static class TimeHander extends Handler{        public TimeHander(){            Looper looper = Looper.myLooper();            if(null == looper){                Looper.prepare();                Looper.loop();            }        }        @Override        public void handleMessage(Message msg) {            if(null!=msg.obj){                RemoteClientCallBack clientCallBackInstance                 = (RemoteClientCallBack)msg.obj;                try {                    clientCallBackInstance.transferToClientByServer(                    "已延期10s后发送 当前进程Id = "+ Process.myPid());                } catch (RemoteException e) {                    e.printStackTrace();                }            }        }    }}

这样service工作已完成99%了,最后一步就是要注册,这里我们起一个独立进程来跑;
AndroidManifest.xml:

<service     ndroid:name=".RemoteBankService"    android:enabled="true"    android:exported="true"    android:process=":remoteservice">service>

3、本应用的客户端Client:创建RemoteActivity
实现ServiceConnection:建立连接

  private IRemoteBankService iRemoteBankService;    private ServiceConnection serviceConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {//建立通信链接成功            Log.d(TAG, "onServiceConnected pid = " + android.os.Process.myPid());            iRemoteBankService = IRemoteBankService.Stub.asInterface(service);//跨进程的处理方式//            iRemoteBankService = (IRemoteBankService)service;//统一进程的处理方式            try {                iRemoteBankService.registerClientOberser(remoteClientCallBack);            } catch (RemoteException e) {                e.printStackTrace();            }        }        @Override        public void onServiceDisconnected(ComponentName name) {//断开链接            Log.d(TAG, "onServiceDisconnected pid = " + android.os.Process.myPid());            iRemoteBankService = null;        }    };

当建立连接之后会回调onServiceConnected();在这里我们获取远程的IBinder,由于这个是服务端的IBinder,这里又是不同的进程所以不能直接强转成IRemoteBankService,否则会抛出类型转换异常所以要使用
iRemoteBankService = IRemoteBankService.Stub.asInterface(service);来获取客户端的Binder代理;

获取到iRemoteBankService 后我们将客户端的观察者注册到service中:
iRemoteBankService.registerClientOberser(remoteClientCallBack);
再来看看remoteClientCallBack是何方妖物:

private RemoteClientCallBack.Stub remoteClientCallBack = new RemoteClientCallBack.Stub() {        @Override        public void transferToClientByServer(final String transferData) throws RemoteException {            Log.d(TAG, "transferData = " + transferData +             " client Pid = " + android.os.Process.myPid());            //如果是service通过handler调用的这个的,由于service的进程调用,所以这个回调不是在            //主线程而是工作线程中,直接更新或toast会抛出如下异常,所以定要在主线程中更新            //Uncaught remote exception! (Exceptions are not yet supported across processes.)            runOnUiThread(new Runnable() {                @Override                public void run() {                    Toast.makeText(RemoteActivity.this, transferData, Toast.LENGTH_SHORT).show();                }            });        }    };

这里实现的就是我们定义的RemoteClientCallBack.aidl文件自动生成的RemoteClientCallBack.java文件中的Stub;到了这里其实这里注册观察者,可以理解为把client当作service,service当作client互换角色吧。
然后通过回调就可以互相通信互相拿到消息了。
这里你可能注意到了在回调方法中使用runOnUiThread()来Toast,这是因为,回调后不是主线程,而是从系统线程池中获取到一个空闲工作线程来传递消息的,直接Toast则报异常(Exceptions are not yet supported across processes.)

ok!主要代码都写完了,这下需要绑定启动服务然后操纵看看是不是能够通信了;完整代码如下:package com.example.zwr.myapplication;

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.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

public class RemoteActivity extends AppCompatActivity implements View.OnClickListener {    public static void startInstance(Context context){        context.startActivity(new Intent(context,RemoteActivity.class));    }    private static final String TAG = "RemoteActivity";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_remote);        initView();    }    private void initView() {        findViewById(R.id.tv_bindService).setOnClickListener(this);        findViewById(R.id.tv_despoistMoney).setOnClickListener(this);        findViewById(R.id.tv_drawMoney).setOnClickListener(this);        findViewById(R.id.tv_getuser).setOnClickListener(this);        Log.d(TAG, "initView pid = " + android.os.Process.myPid());    }    @Override    public void onClick(View v) {        switch (v.getId()) {            case R.id.tv_bindService:                RemoteBankService.bindService(this,serviceConnection);                break;            case R.id.tv_despoistMoney:                try {                   boolean  isDesMoney = iRemoteBankService.despoistMoney(5);                    Log.d(TAG,"isDesMoney1 = "+isDesMoney);                     isDesMoney = iRemoteBankService.despoistMoney(-5);                    Log.d(TAG,"isDesMoney2 = "+isDesMoney);                } catch (RemoteException e) {                    e.printStackTrace();                }                break;            case R.id.tv_drawMoney:                try {                    int  drawMoney = iRemoteBankService.drawMoney(5);                    Log.d(TAG,"drawMoney = "+drawMoney);                } catch (RemoteException e) {                    e.printStackTrace();                }                break;            case R.id.tv_getuser:                try {                    User user = iRemoteBankService.getUser();                    Log.d(TAG,"user = "+user.toString());                } catch (RemoteException e) {                    e.printStackTrace();                }                break;        }    }    private IRemoteBankService iRemoteBankService;    private ServiceConnection serviceConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {//建立通信链接成功            Log.d(TAG, "onServiceConnected pid = " + android.os.Process.myPid());            iRemoteBankService = IRemoteBankService.Stub.asInterface(service);//跨进程的处理方式//            iRemoteBankService = (IRemoteBankService)service;//统一进程的处理方式            try {                iRemoteBankService.registerClientOberser(remoteClientCallBack);            } catch (RemoteException e) {                e.printStackTrace();            }        }        @Override        public void onServiceDisconnected(ComponentName name) {//断开链接            Log.d(TAG, "onServiceDisconnected pid = " + android.os.Process.myPid());            iRemoteBankService = null;        }    };    private RemoteClientCallBack.Stub remoteClientCallBack = new RemoteClientCallBack.Stub() {        @Override        public void transferToClientByServer(final String transferData) throws RemoteException {            Log.d(TAG, "transferData = " + transferData + " client Pid = "             + android.os.Process.myPid());            //如果是service通过handler调用的这个的,由于service的进程调用,所以这个回调不是            //在主线  程而是工作线程中,直接更新或toast会抛出如下异常,所以定要在主线程中更新            //Uncaught remote exception! (Exceptions are not yet supported across processes.)            runOnUiThread(new Runnable() {                @Override                public void run() {                    Toast.makeText(RemoteActivity.this, transferData, Toast.LENGTH_SHORT).show();                }            });        }    };    @Override    protected void onDestroy() {        super.onDestroy();        RemoteBankService.doUnbindService(this,serviceConnection);    }}

来看看运行结果: 先绑定,在操作其它选项
client:

service:

通过日志可以发现,这两个是在独立进程中,而且是可以正常通信的
以上是同一个应用中进行的,下边我们看看在不同应用中的通信

4、新创建应用为ClientAIDLDEMO :为client
先创建完后,将创建的所有aidl文件以及相关的类全部复制到这个项目中
注意:包名也是要一致的
如图:

然后将RemoteActivity也一同复制过来,唯一要修改的只有绑定和解绑,修改为:通过包名隐式意图启动进行绑定:如下

    Intent intent = new Intent();    intent.setClassName(   "com.example.zwr.myapplication",  "com.example.zwr.myapplication.RemoteBankService");    bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);    Intent intent = new Intent();    intent.setClassName(    "com.example.zwr.myapplication", "com.example.zwr.myapplication.RemoteBankService");     unbindService(serviceConnection);     stopService(intent);       

然后运行:点击绑定,结果如同上边,就不再重复!
跨进程通信讲解完毕!,有不对的地方请不吝指正!

demo:
同一个应用demo
不同应用的demo

更多相关文章

  1. 用Tasker转发Android上收到的短信至Telegram
  2. 很通俗易懂的概念Activity,Window,DecorView
  3. adb shell 执行后台程序后断开adb后台进程被结束的解决办法
  4. 百度Android客户端研发面试经验
  5. 《今日求职》之拉勾网Android客户端产品说明
  6. MUI如何安卓离线打包,在Android(安卓)Studio创建Hello World离线
  7. Android组件间的交互和进程间IPC通信
  8. 百度ting!正式发布beta版Android手机客户端
  9. Android(安卓)AIDL运用总结

随机推荐

  1. Android覆盖升级以及apk签名
  2. 使用ant让Android自动打包的build.xml,自
  3. Android实用视图动画及工具系列之四:多状
  4. Nexus 6P 外媒评测:华为造出了最好的 Andr
  5. 谷安: 米国军方的 iOS、Android、Windows
  6. 在Android中监控来电和去电
  7. 【Android】Android NDK开发扫盲及最新CM
  8. Android转场动画和共享元素动画兼容5.0以
  9. (译文)如何成为一个更好的Android开发者:30
  10. 在Android中调用WebService