Android:AIDL简单介绍+传递简单数据+传递复杂数据

对于AIDL的学习,这些也只能说是我在学习中的理解,有理解不到位或者错的地方也欢迎指正。


1.AIDL的简单介绍

AIDL的目的就是实现进程之间的通信,尤其是在涉及多进程并发情况下的进程间通信。可以将aidl理解为两个进程之间的桥梁,并制定规则,使其传输特定数据。

1.AIDL支持的数据类型有:
  1. 基本数据类型(int、long、char、boolean、double),定向 tag 默认且只能是 in
  2. string和charSequence,定向 tag 默认且只能是 in
  3. list:只支持arraylist,以及里面的所有元素必须被aidl支持
  4. map:只支持hashmap
  5. parcelable:所有实现parcelable接口的对象
  6. aidl:所有aidl接口本身也可以在aidl文件中使用

需要注意的是:在aidl中声明其他数据类型时,需要在前面加in(输入型参数)、out(输出型参数)、inout(输入输出型参数)

需要注意的是:除aidl所支持的数据类型外,如果要使用则必须导包,就算目标文件与当前文件在同一包下。

比如:
编写了两个文件,一个叫做 Book.java ,另一个叫做 BookManager.aidl,它们都在 com.xxx.aidldemo 包下 ,现在要在 .aidl 文件里使用 Book 对象,那么就必须在 .aidl 文件里面写上 import com.xxx.aidldemo.Book; 哪怕 .java 文件和 .aidl 文件就在一个包下。
2.定向tag:

表示在跨进程通信中数据的流向(流向是针对客户端的对象而言) 

in(输入型参数):客户端——>服务器

out(输出型参数):服务器——>客户端

inout(输入输出型参数):双向流通.

3.如果不包含非默认支持的数据类型,只需要编写一个AIDL文件,如果包含,通常需要写 n+1 个AIDL文件( n 为非默认支持的数据类型的种类数)
4.非默认支持数据类型的序列化操作

        1. 使数据类实现Parcelable接口

例:数据从客户端到服务端,可以在客户端对这个对象进行序列化(通常为实现Parcelable接口),将其中数据转化为序列化流,并传输到服务端内存中,再在服务端对这个数据进行反序列化操作,从而还原数据。

        2. 建立一个类,书写其成员变量,建立getter和setter并添加一个无参构造。令这个类implements Parcelable 。

需要注意的是:默认生成的模板类对象只支持为in的定向tag。因为默认生成的类中只有writeToParcel() 方法,如果要实现为 out 或者 inout 的定向 tag 的话,需要实现readFromParcel() 方法。

5.书写AIDL文件 

新建AIDL文件,AIDL文件大致分为两类:

一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型。

// Book.aidl
//用于引入了一个序列化对象Book,供其他的AIDL文件使用
//注意:Book.aidl与Book.java的包名应当是一样的
package com.example.ipcclient;
 
//注意parcelable是小写
parcelable Book;

一类是用来定义方法接口,以供系统完成跨进程通信。

可理解为通信接口文件,需服务器端及客户端各一个定义,需内容相同。

// BookManager.aidl
package com.example.ipcclient;
//导入所需要使用的非默认支持数据类型的包
import com.lypeer.ipcclient.Book;
 
interface BookManager {
 
    //所有的返回值前都不需要加任何东西,不管是什么数据类型
    List<Book> getBooks();
    Book getBook();
    int getBookCount();
 
    //传参时除了Java基本类型以及String,CharSequence之外的类型
    //都需要在前面加上定向tag,具体加什么按需而定
    void setBookPrice(in Book book , int price)
    void setBookName(in Book book , String name)
    void addBookIn(in Book book);
    void addBookOut(out Book book);
    void addBookInout(inout Book book);
}

 

2.AIDL传递简单数据

1.服务端
        a.创建AIDL文件 

        IPerson.aidl

// IPerson.aidl
package com.example.aidlserver;
import com.example.aidlserver.AIDLService;
interface IPerson {
    String queryPerson(int num);
}

创建完后进行build,生成IPerson.java文件

         b.自定义Service类

                i.继承Service类,定义一个PersonQueryBinder类用来继承IPerson.Stub类,实现了IPerson接口和IBinder接口。

                ii.实例化自定义的Stub类,重写onBind方法,返回binder对象。

        AIDLService.java

package com.example.aidlserver;

public class AIDLService extends Service {
    //实例化自定义的Stub类
    private IBinder binder = new PersonQueryBinder();
    private String[] names = {"第一个","第二个","第三个"};

    private String query(int num)
    {
        if(num > 0 && num < 6){
            return names[num - 1];
        }
        return null;
    }

    @Override
    public IBinder onBind(Intent intent) {
//重写onBind方法,返回binder对象。
//        return new PersonQueryBinder();
        return binder;
    }
    //定义一个PersonQueryBinder类用来继承IPerson.Stub类,实现了IPerson接口。
    private final class PersonQueryBinder extends IPerson.Stub {

        public String queryPerson(int num) throws RemoteException {
            return query(num);
        }
    }
}
        c.在AndroidManifest.xml文件中注册Service
        <service android:name=".AIDLService"
            android:exported="true">
            <intent-filter>
                <action android:name="任意名即可,如:LXYaidl" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>
2.客户端

需要将服务端的aidl文件直接复制过来(包括其中的包),客户端的内容直接在Activity中完成即可。

流程如下:

        a.自定义的PersonConnection类实现ServiceConnection接口
        b.以PersonConnection对象作为参数,调用bindService绑定远程Service
bindService(service,conn,BIND_AUTO_CREATE);
        c.绑定远程Service的ServiceConnection并不能直接获取Service的onBind( )方法

                返回的IBinder对象,只能返回onBind()方法所返回的代理对象,所需处理如下:

iPerson = IPerson.Stub.asInterface(service);

代码:MainActivity.java
package com.example.aidlclient;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private EditText edit_num;
    private Button btn_query;
    private TextView txt_name;
    private IPerson iPerson;
    private PersonConnection conn = new PersonConnection();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //控件绑定
        edit_num = findViewById(R.id.edit_num);
        btn_query = findViewById(R.id.btn_query);
        txt_name = findViewById(R.id.txt_name);

        //绑定远程Service
        Intent service = new Intent("LXY_aidl");
        service.setPackage("com.example.aidlserver");
//以PersonConnection对象作为参数,调用bindService绑定远程Service 
        bindService(service, conn, BIND_AUTO_CREATE);
        btn_query.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        String number = edit_num.getText().toString();
        int num = Integer.valueOf(number);
        try {
            txt_name.setText(iPerson.queryPerson(num));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        edit_num.setText("");
    }
//自定义的PersonConnection类实现ServiceConnection接口
    private final class PersonConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName name, IBinder service) {
            iPerson = IPerson.Stub.asInterface(service);
        }
        public void onServiceDisconnected(ComponentName name) {
            iPerson = null;
        }
    }
}
3.启动步骤及可能出现的bug

        启动步骤为:先启动服务端,再启动客户端

这个地方可能出现的bug如下: 

  • Android AIDL客户端调用服务端方法报错空指针,即找不到那个方法。

报错为:java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.String com.example.aidlserver.ILanguage.queryLanguage(int)' on a null object reference

 解决方法:

        1.在客户端清单文件中配置服务端service的包路径

        2.服务端的onBind方法中,返回值不可为空,返回的值为binder对象;或自定义的继承了IPerson.Stub类的类的new 方法。

  • 服务端和客户端AIDL文件所在包路径不一致,报错异常为: 

java.lang.SecurityException: Binder invocation to an incorrect interface

解决方法:修改服务端service对应的完整包名,客户端应与服务端的一致。

服务端:

 客户端:

3.AIDL传递复杂数据

1.基础操作:

        1.将writeToParcelreadFromPacel方法写入方法,将对象写入到包裹(parcel)中。

需要注意的是:写入顺序与读取顺序需要相同

        2.在该类中添加一个名为CREATORstatic final属性 改属性需要实现:android.os.Parcelable.Creator接口

        3.从接口中写两个方法:

        a.createFromParcel(Parcel source)方法:实现从source创建出JavaBean实例

        b.newArray(int size):创建一个数组。

        4.定向tag

2.服务端

代码:Step1:创建aidl文件实现接口.
1.Person.aidl
// Person.aidl
package com.example.aidl_p;
parcelable Person;

2. Salary.aidl
// Salary.aidl
package com.example.aidl_p;
parcelable Salary;
代码:Step2:分别建立类,需实现Parcelable接口,重写对应的方法
1.Person.java
package com.example.aidl_p;

public class Person implements Parcelable {
    private Integer id;
    private String name;

    public Person(Integer id,String name){
        this.id = id;
        this.name = name;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    protected Person(Parcel in) {
    }

    //写入数据到Parcel中的方法
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        //把对象所包含的数据写入到parcel中
        dest.writeInt(id);
        dest.writeString(name);
    }

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

    //必须提供一个名为CREATOR的static final属性 该属性需要实现
    //android.os.Parcelable.Creator<T>接口
    public static final Creator<Person> CREATOR = new Creator<Person>() {
        //从Parcel中读取数据,返回Person对象
        @Override
        public Person createFromParcel(Parcel in) {
            return new Person(in.readInt(),in.readString());
        }
        @Override
        public Person[] newArray(int size) {
            return new Person[size];
        }
    };
    //因为集合取出元素的时候是根据Person对象来取得
    //需要重写hashCode()和equals()方法
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result +((name == null)? 0 : name.hashCode());
        return result;
    }

    //hashCode 自动生成
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(id, person.id) &&
                Objects.equals(name, person.name);
    }


}
2.Salary.java 
package com.example.aidl_p;

public class Salary implements Parcelable {
    private String type;
    private Integer salary;

    public Integer getSalary() {
        return salary;
    }

    public void setSalary(Integer salary) {
        this.salary = salary;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    protected Salary(Parcel in) {
    }

    public Salary(String type,Integer salary){
        this.type = type;
        this.salary = salary;
    }

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

    public static final Creator<Salary> CREATOR = new Creator<Salary>() {
        //从Parcel中读取数据,返回Person对象
        @Override
        public Salary createFromParcel(Parcel in) {
            return new Salary(in.readString(),in.readInt());
        }

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

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeString(type);
        parcel.writeInt(salary);
    }

    public String toString(){
        return "工作:" + type + "    薪水: " + salary;
    }
}
3.ISalary.aidl  获取工资信息的方法
// ISalary.aidl
package com.example.aidl_p;
//无论在不在同一个包中都需要导包
import com.example.aidl_p.Salary;
import com.example.aidl_p.Person;

interface ISalary {
 //定义一个Person对象作为传入参数
    //接口中定义方法时,需要制定新参的传递模式,这里是传入,所以前面有一个in
//获取工资信息
    Salary getMsg(in Person owner);
}
4.AidlService.java 
核心Service:
定义一个SalaryBinder类继承Stub,从而实现ISalary和IBinder接口;定义一个存储信息的Map集合,
重新onBind方法,返回SalaryBinder类的对象实例
5.注册Service:
        <service android:name=".AidlService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.AIDLService" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>
3.客户端

代码:Step1:将服务端的aidl复制过来。
代码:Step2:定义一个ServciceConnection对象,重写对应方法,和普通数据的类似,接着bindService,然后再Button的点击事件中获取Salary对象并显示。
MainActivity.java
package com.example.aidl_kehu;

public class MainActivity extends AppCompatActivity {

    private ISalary salaryService;
    private Button btnquery;
    private EditText editname;
    private TextView textshow;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //返回的是代理对象,要调用这个方法
            salaryService = ISalary.Stub.asInterface(iBinder);         }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            salaryService = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnquery = findViewById(R.id.btnquery);
        editname = findViewById(R.id.editname);
        textshow = findViewById(R.id.textshow);

        ActionBar actionBar = getSupportActionBar();
        if(actionBar != null){
            actionBar.hide();
        }

        Intent service = new Intent("android.intent.action.AIDLService");
        service.setPackage("com.example.aidl_p");
        bindService(service,connection, BIND_AUTO_CREATE);

        btnquery.setOnClickListener(new View.OnClickListener() {
            @SuppressLint("SetTextI18n")
            @Override
            public void onClick(View view) {
                try {
                    String name = editname.getText().toString();
                    Salary salary = salaryService.getMsg(new Person(1,name));
                    textshow.setText(name+salary.toString());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        this.unbindService(connection);
    }

}

4.AIDL总结 

1.服务端

        a.创建aidl,创建同名类,在aidl中写parcelable接口。并创建aidl实现接口方法,且需要导包。

        b. 在类中生成set、get方法,创建对象,writeToParcel中把对象写进去,在createFromParcel中返回对象,创建toString() 方法用于输出。

        c.在Service中实现功能

2.客户端

        a.复制服务端的aidl和类,在MainActivity中实现功能

猜你喜欢

转载自blog.csdn.net/shanhe_yuchuan/article/details/135384500