Android进程间通信之----Aidl传递对象

来源:互联网 发布:百乐淘宝旗舰店 编辑:程序博客网 时间:2024/06/05 14:42

转载请注明出处 CSDN废墟的树

前言

有关Android进程间通信之Aidl编程的基本使用步骤已经在上一篇博客中有讲解,Android studio 下的aidl编程实现Android的夸进程间通信。上一篇博客中只是演示了怎么利用Aidl实现跨进程间传递Java基本类型,以及Aidl传递Bitamap对象。可能在一些场景下你需要跨进程传递一个对象,那么Aidl是否能传递一个对象呢?答案是肯定的,网上也有很多相关的资料,之所以写这篇博客:一是当作自己学习笔记咯,二是把自己遇到的问题分享出来。

Aidl传递对象简介

由于Aidl只支持Java基本类型数据传递,因此是不能直接传递一个复杂类型对象的,所以为了解决这个问题,Android提供了一套机制—-将需要传递的对象序列化,然后在反序列化。

  • 序列化:把Java对象转换为字节序列的过程。
  • 反序列化:把字节序列恢复为Java对象的过程。

Java有一套自己的序列化机制Serializable,不过Android也自己实现了一套自己的序列化机制Parcelable。有关Serializable和Parcelable的区别请自行网上搜一把。总之:Aidl可以实现跨进程传递序列化之后的对象,接下来详细介绍实现的过程。

Aidl传递对象步骤

服务端Aidl

基本步骤和上一篇博客一样,在main目录下新建一个aidl目录,然后新建一个aidl接口类IMyAidlInterface.aidl。现在假如我们需要传递的对象是一个Students对象,那么在aidl目录下新建Students类,并实例化该类。最后在同样的目录下新建Students的aidl文件Students.aidl。目录结构如下:

这里写图片描述

相关代码如下:

Students

package com.example.xjp.aidla;import android.os.Parcel;import android.os.Parcelable;/** 1. Created by 850302 on 2016/4/29. */public class Students implements Parcelable {    private int id;    private String name;    private String className;    private int age;    protected Students(Parcel in) {        id = in.readInt();        name = in.readString();        className = in.readString();        age = in.readInt();    }    public Students(int id, String name, String className, int age) {        this.id = id;        this.name = name;        this.className = className;        this.age = age;    }    public Students(){}    public static final Creator<Students> CREATOR = new Creator<Students>() {        @Override        public Students createFromParcel(Parcel in) {            return new Students(in);        }        @Override        public Students[] newArray(int size) {            return new Students[size];        }    };    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeInt(id);        dest.writeString(name);        dest.writeString(className);        dest.writeInt(age);    }    public void readFromParcel(Parcel source) {        id = source.readInt();        name = source.readString();        className = source.readString();        age = source.readInt();    }    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public String getClassName() {        return className;    }    public void setClassName(String className) {        this.className = className;    }    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }}

Students类继承了Parcelable接口类,并且实现了其中的方法。值得注意的是writeToParcel方法和readFromParcel方法里面的写和读取顺序是需要一一对应的,就比如writeToParcel方法里写的顺序是

@Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeInt(id);//1:写一个int类型的值        dest.writeString(name);//2:写一个String类型的值        dest.writeString(className);//3:写一个String类型的值        dest.writeInt(age);//4:写一个int类型的值    }

以上写数值的顺序是

  1. 写一个int类型的值
  2. 写一个String类型的值
  3. 写一个String类型的值
  4. 写一个int类型的值

那么readFromParcel方法里的读取顺序也应该是按照以上顺序读取,否则读取的数据会错乱。

  1. 读取一个int类型的值
  2. 读取一个String类型的值
  3. 读取一个String类型的值
  4. 读取一个int类型的值

代码如下:

public void readFromParcel(Parcel source) {        id = source.readInt();        name = source.readString();        className = source.readString();        age = source.readInt();    }

Students.aidl

package com.example.xjp.aidla;parcelable Students;

该类很简单,仅仅是申明了 Students为parcelable类型。**注意:**parcelable开头是小写p,不是Parcelable。别问我为什么是这样,我也不知道 ^_^,只能说这是游戏规则。

IMyAidlInterface.aidl

// IMyAidlInterface.aidlpackage com.example.xjp.aidla;import com.example.xjp.aidla.Students;interface IMyAidlInterface {    int add(int arg1, int arg2);    String inStudentInfo(in Students student);    String outStudentInfo(out Students student);    String inOutStudentInfo(inout Students student);}

以上方法的参数都是Students,但是每个参数前面都有一个修饰符:in,out,inout,且这些修饰符是必须的,否则会报错。那么他们代表什么意思呢?

  • in:参数由客户端设置,或者理解成客户端传入参数值。
  • out:参数由服务端设置,或者理解成由服务端返回值。
  • inout:客户端输入端都可以设置,或者理解成可以双向通信。

值得注意的是:由于IMyAidlInterface接口类中使用到了Students类,所以你得主动import引入该类

import com.example.xjp.aidla.Students;

否则会报错,即使是Students类和IMyAidlInterface在同一个报名下也得引入。

服务端MyService

package com.example.xjp.aidl;import android.app.ActivityManager;import android.app.Service;import android.content.Context;import android.content.Intent;import android.os.IBinder;import android.os.RemoteException;import android.util.Log;import com.example.xjp.aidla.IMyAidlInterface;import com.example.xjp.aidla.Students;/** * Created by 850302 on 2016/4/28. */public class MyServer extends Service {    IMyAidlInterface.Stub mStub = new IMyAidlInterface.Stub() {        @Override        public int add(int arg1, int arg2) throws RemoteException {            return arg1 + arg2;        }        @Override        public String inStudentInfo(Students student) throws RemoteException {            String msg = "table1" + "\n" + "----------------------------------------------" + "\n" + "|" +                    " id " + "|" + " " +                    "age " +                    "|" + " name " + "|" + " className " + "|" + "\n" +                    "----------------------------------------------" + "\n" + "|  " + student.getId() + " " +                    "|  " + student                    .getAge() + "  |  " + student.getName() + "   |     " + student.getClassName() + "   | " +                    "\n" + "----------------------------------------------";            return msg;        }        @Override        public String outStudentInfo(Students student) throws RemoteException {//            student.setClassName("090412");//            student.setName("Tom2");//            String msg = "Id = " + student.getId() + " age = " + student.getAge() + " ClassName = " +//                    student.getClassName() + " Name = " + student.getName();            String msg = "table2" + "\n" + "----------------------------------------------" + "\n" + "|" +                    " id " + "|" + " " +                    "age " +                    "|" + " name " + "|" + " className " + "|" + "\n" +                    "----------------------------------------------" + "\n" + "|  " + student.getId() + " " +                    "|  " + student                    .getAge() + "  |  " + student.getName() + "   |     " + student.getClassName() + "   | " +                    "\n" + "----------------------------------------------";            return msg;        }        public String inOutStudentInfo(Students student) throws RemoteException {            student.setClassName("090411");            student.setAge(22)            String msg = "table3" + "\n" + "----------------------------------------------" + "\n" + "|" +                    " id " + "|" + " " +                    "age " +                    "|" + " name " + "|" + " className " + "|" + "\n" +                    "----------------------------------------------" + "\n" + "|  " + student.getId() + " " +                    "|  " + student                    .getAge() + "  |  " + student.getName() + "   |     " + student.getClassName() + "   | " +                    "\n" + "----------------------------------------------";            return msg;        }    };    @Override    public void onCreate() {        super.onCreate();        Log.e("xjp", "the remote Process Name is ==>" + getCurProcessName(this));    }    @Override    public IBinder onBind(Intent intent) {        Log.e("xjp", "the remote onBind......");        return mStub;    }    @Override    public void onRebind(Intent intent) {        Log.e("xjp", "the remote onRebind......");        super.onRebind(intent);    }    @Override    public boolean onUnbind(Intent intent) {        Log.e("xjp", "the remote onUnbind......");        return super.onUnbind(intent);    }    @Override    public void onDestroy() {        super.onDestroy();        Log.e("xjp", "the remote onDestroy......");    }    /**     * get current process name     *     * @param context     * @return     */    private String getCurProcessName(Context context) {        int pid = android.os.Process.myPid();        ActivityManager mActivityManager = (ActivityManager) context                .getSystemService(Context.ACTIVITY_SERVICE);        for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager                .getRunningAppProcesses()) {            if (appProcess.pid == pid) {                return appProcess.processName;            }        }        return null;    }}

服务端的工作是根据客户端传递过来的Students信息来生成对应的Student的一张信息表。

在AndroidManifest.xml文件里配置Service:

  <service        android:name="com.example.xjp.aidl.MyServer">        <intent-filter>           <action android:name="com.xjp.myService"></action>        </intent-filter>  </service>

当然你可以配置属性android:process,也可以不配置。不配置也就是默认配置时,该Service的进程名就是当前应用的包名,如果配置的话就会覆盖默认进程名。

OK,到此服务端所有的工作都准备好了,此时你去编译整个工程,发现编译出错,错误提示:找不到 符号类Students。

这里写图片描述

卧槽,懵圈了,该定义的都定义了,该import的也引入了,怎么会报错了?不知道Eclipse中是否有这个问题,反正Android Studio是有的,然后就网上搜吧,这方面的资料不多,有给出如下解答可以解决问题的。配置当前应用的 build.gradle文件,在该文件中添加如下配置即可。

sourceSets {        main {//            manifest.srcFile 'src/main/AndroidManifest.xml'            java.srcDirs = ['src/main/java', 'src/main/aidl']//            resources.srcDirs = ['src/main/java', 'src/main/aidl']//            aidl.srcDirs = ['src/main/aidl']//            res.srcDirs = ['src/main/res']//            assets.srcDirs = ['src/main/assets']        }    }

注:我的AS是1.3的,所以只配置了一行 java.srcDirs = [‘src/main/java’, ‘src/main/aidl’] 就解决问题了。这一行的意思是指定源文件的路径,把aidl包含进去了。

此时你再去编译就OK啦!

客户端aidl

客户端aidl就简单啦,直接把刚才的服务端的整个aidl目录拷贝到客户端即可。这里重复利用服务端的aidl代码,无需重写。

客户端

package com.example.xjp.myaidldemocustomer;import android.app.Activity;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 android.view.View;import android.widget.ImageView;import android.widget.TextView;import com.example.xjp.aidla.IMyAidlInterface;import com.example.xjp.aidla.Students;public class MainActivity extends Activity {    private IMyAidlInterface mStub;    private TextView txt;    private ImageView img;    private View unBindService;    private boolean isBind;    private ServiceConnection serviceConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            mStub = IMyAidlInterface.Stub.asInterface(service);        }        @Override        public void onServiceDisconnected(ComponentName name) {            Log.e("xjp", "the onServiceDisconnected");            mStub = null;        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        txt = (TextView) findViewById(R.id.text);    }    public void bindService(View v) {        isBind = true;        if (unBindService != null) unBindService.setEnabled(true);        Intent intent = new Intent();        intent.setAction("com.xjp.myService");        intent.setComponent(new ComponentName("com.example.xjp.aidl", "com.example.xjp.aidl.MyServer"));        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);    }    public void getTableInfo(View v) {        if (mStub == null) {            Log.e("xjp", "the mStub is null");        } else {            try {                String info1 = mStub.inStudentInfo(new Students(1, "Jim", "090415", 18));                String info2 = mStub.outStudentInfo(new Students(2, "Lida", "090416", 17));                String info3 = mStub.inOutStudentInfo(new Students(3, "Tom", "090417", 16));                txt.setText(info1 + "\n" + "===========line=========" + "\n" + info2 + "\n" +                        "===========line=========" + "\n" + info3);            } catch (RemoteException e) {                e.printStackTrace();            }        }    }    public void unbindService(View v) {        if (isBind) {            isBind = false;            unbindService(serviceConnection);            unBindService = v;            v.setEnabled(false);        }    }    @Override    protected void onDestroy() {        if (isBind) unbindService(serviceConnection);        super.onDestroy();    }}

客户端代码也很简单,实现ServiceConnection 连接服务的回调得到远程 binder,之后实现绑定服务方法,解绑服务方法,远程调用方法等。

同样客户端也需要配置 build.gradle文件,配置和服务端一样:

 sourceSets {        main {            java.srcDirs = ['src/main/java', 'src/main/aidl']        }    }

自此,整个Aidl传递对象的步骤基本完成。运行结果如下:

这里写图片描述

由以上三张表格的输出信息可以看出来:

String inStudentInfo(in Students student);//对应table1信息String outStudentInfo(out Students student);//对应table2信息String inOutStudentInfo(inout Students student);//对应table3信息

table1:表格信息是由客户端传入过去,且显示的信息也是客户端设置的信息,由此也证明in修饰符表示值由客户端设置。
table2:表格信息为空的,此时虽然客户端传入了Students参数,但是不生效,从此也证明了out修饰符表示由服务端设置值。
table3:表格信息和客户端传入的有变化,id=3,name=Tom没有改变,age=16变成22,className=“090417”变成了“090411”。这些改变都是在服务端修改的,也侧面说明了inout修饰符表示客户端和服务端都可以设置值。

总结

Aidl传递对象就实现了,需要注意点有如下:

  1. 传递的对象参数前面需要 in,out,inout三个当中的其中一个修饰符,否则会报错。
  2. 对象Students序列化重写writeToParcel和readFromParcel方法中写数据操作和读取操作顺序必须保持一致,否则会报错。
  3. 在使用到Students对象的地方都需要引入 import com.example.xjp.aidla.Students;包路径。否则也会报错找不到 Students类。
  4. 编译代码之前需要在 build.gradle配置文件中添加如下配置
sourceSets {        main {            java.srcDirs = ['src/main/java', 'src/main/aidl']        }    }
3 0
原创粉丝点击