AIDL中的in、out、inout的区别

一、概念说明

定向tag是AIDL中语法的一部分,其中in、out、inout是三个定向tag。
在官网上关于Android定向tag的定义是这样的:

All non-primitive parameters require a directional tag indicating which way the data goes . Either in , out , or inout . Primitives are in by default , and connot be otherwise .

意思就是所有非基本类型的参数都需要一个定向tag来表明数据是如何走向的,要不是in,out或者inout。基本数据类型默认是in,而且不能是其他tag。

根据上述的声明,我们大概猜一下,得出这样的结论:
定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可以在服务端与客户端之间双向流通。其中的数据流向是针对在客户端中的那个传入方法的对象而言的。

对于in,服务端将会收到客户端对象的完整数据,但是客户端对象不会因为服务端对传参的修改而发生变动。类似的行为在Java中的表现是,在Java方法中,对传进来的参数进行了深复制,传进来的参数不会受到深复制后的对象的影响。这和in的行为有点类似。

对于out,服务端将会收到客户端对象,该对象不为空,但是它里面的字段为空,但是在服务端对该对象作任何修改之后客户端的传参对象都会同步改动。类似的行为在Java中的表现是,在Java方法中,对传进来的参数进行忽略,并new一个新对象,所有的操作都是围绕着这个新对象进行的,最后将该新对象赋值给传参对象。

对于inout ,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。类似的行为在Java中的表现是,在Java方法中,对传进来的参数进行修改并返回。

二、实验论证

下面写一个demo来验证上面的结论。
aidl文件:(Book.aidl)

 

// IBook.aidl
package com.example.runningh.mydemo.binder;
import com.example.runningh.mydemo.binder.Book;

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

parcelable Book;

aidl文件:(IBookManager.aidl)

 

// IBookManager.aidl
package com.example.runningh.mydemo.binder;
import com.example.runningh.mydemo.binder.Book;

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

interface IBookManager {
    List<Book> getBooks();

    void addBookWithInTag(in Book book); //测试定向tag in

    void addBookWithOutTag(out Book book); //测试定向tag out

    void addBookWithInOutTag(inout Book book); //测试定向tag inout
}

Java文件:(Book.java)

 

package com.example.runningh.mydemo.binder;

import android.os.Parcel;
import android.os.Parcelable;

/**
 * Created by hwldzh on 2018/5/20
 * 类描述:
 */
public class Book implements Parcelable {
    public String bookName;
    public int price;

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(bookName);
        dest.writeInt(price);
    }

    public static Creator<Book> CREATOR =  new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

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

    private Book(Parcel in) {
        readFromParcel(in);
    }

    public Book(String name, int price) {
        this.bookName = name;
        this.price = price;
    }

    public Book() {

    }

    public void readFromParcel(Parcel in) {
        bookName = in.readString();
        price = in.readInt();
    }

    public String toString() {
        return "[bookName=" + bookName + ", bookPrice=" + price + "]";
    }
}

编译demo项目,生成IBookManager.java文件。下面看一下客户端的代码:

 

package com.example.runningh.mydemo.binder;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;

import com.example.runningh.mydemo.R;

/**
 * Created by hwldzh on 2018/5/20
 * 类描述:
 */
public class TestTagActivity extends Activity implements View.OnClickListener {
    public static final String TAG = "ABC";
    private IBookManager bookManager;

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

        findViewById(R.id.test_tag_in).setOnClickListener(this);
        findViewById(R.id.test_tag_out).setOnClickListener(this);
        findViewById(R.id.test_tag_inout).setOnClickListener(this);

        Intent intent = new Intent(this, TestTagService.class);
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                bookManager = IBookManager.Stub.asInterface(service);
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                bookManager = null;
            }
        }, BIND_AUTO_CREATE);
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.test_tag_in:
                if (bookManager != null) {
                    try {
                        Book book = new Book("Android", 30);
                        bookManager.addBookWithInTag(book);
                        Log.i(TAG, "Test in tag. book Info: " + book.toString());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case R.id.test_tag_out:
                if (bookManager != null) {
                    try {
                        Book book = new Book("Android", 30);
                        bookManager.addBookWithOutTag(book);
                        Log.i(TAG, "Test out tag. book Info: " + book.toString());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                break;
            case R.id.test_tag_inout:
                if (bookManager != null) {
                    try {
                        Book book = new Book("Android", 30);
                        bookManager.addBookWithInOutTag(book);
                        Log.i(TAG, "Test inout tag. book Info: " + book.toString());
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                break;
            default:
                break;
        }
    }
}

客户端的代码比较简单,主要是三个按钮,点击后触发三个定向tag的不同操作,执行完毕后打印传递参数Book对象的信息。
接着我们看一下服务端的代码:

 

package com.example.runningh.mydemo.binder;

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

import java.util.ArrayList;
import java.util.List;

/**
 * Created by hwldzh on 2018/5/20
 * 类描述:
 */
public class TestTagService extends Service {
    public static final String TAG = "ABC";
    private IBookManager.Stub bookManager;
    private List<Book> list = new ArrayList<>();

    @Override
    public void onCreate() {
        super.onCreate();
        list.add(new Book("FirstBook", 30));

        bookManager = new IBookManager.Stub() {
            @Override
            public List<Book> getBooks() throws RemoteException {
                return list;
            }

            @Override
            public void addBookWithInTag(Book book) throws RemoteException {
                if (book == null) {
                    Log.i(TAG, "book is null");
                    book = new Book();
                }
                book.price = 100;
                list.add(book);
                Log.i(TAG, "add Book with in tag.list=" + list.toString());
            }

            @Override
            public void addBookWithOutTag(Book book) throws RemoteException {
                if (book == null) {
                    Log.i(TAG, "book is null");
                    book = new Book();
                }
                book.price = 100;
                list.add(book);
                Log.i(TAG, "add Book with out tag.list=" + list.toString());
            }

            @Override
            public void addBookWithInOutTag(Book book) throws RemoteException {
                if (book == null) {
                    Log.i(TAG, "book is null");
                    book = new Book();
                }
                book.price = 100;
                list.add(book);
                Log.i(TAG, "add Book with inout tag.list =" + list.toString());
            }
        };
    }

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

服务端的代码也挺简单的,就是初始化一个IBookManager.Stub对象的IBinder接口并返回给客户端。
现在客户端分别点击”测试定向tag in",“测试定向tag out",“测试定向tag inout”按钮,然后得到如下的Log信息:

 

点击定向tag in。服务端:
origin Book Info=[bookName=Android, bookPrice=30]
add Book with in tag.list=[[bookName=FirstBook, bookPrice=30], [bookName=Android, bookPrice=100]]
点击定向tag in。客户端:
Test in tag. book Info: [bookName=Android, bookPrice=30]

点击定向tag out。服务端: 
origin Book Info=[bookName=null, bookPrice=0]
add Book with out tag.list=[[bookName=FirstBook, bookPrice=30], [bookName=Android, bookPrice=100], [bookName=null, bookPrice=100]]
点击定向tag out。客户端: 
Test out tag. book Info: [bookName=null, bookPrice=100]

点击定向tag inout。服务端: 
origin Book Info=[bookName=Android, bookPrice=30]
add Book with inout tag.list =[[bookName=FirstBook, bookPrice=30], [bookName=Android, bookPrice=100], [bookName=null, bookPrice=100], [bookName=Android, bookPrice=100]]
点击定向tag inout。客户端: 
Test inout tag. book Info: [bookName=Android, bookPrice=100]

从上面信息可以得到如下结论:
当我们点击“测试定向tag in”按钮时,服务端接收到了客户端传过来的Book对象的完整信息,即[bookName=Android, bookPrice=30] ,执行完毕后客户端的Book对象的信息没有改变,依然是[bookName=Android, bookPrice=30]。
当我们点击“测试定向tag out”按钮时,服务端接收到了客户端传过来的Book对象,但是对象的字段都是未初始化的,即[bookName=null, bookPrice=0],执行完毕后客户端的Book对象的信息同步改变了,即和服务端的对象信息进行了同步:[bookName=null, bookPrice=100]。
当我们点击“测试定向tag inout”按钮时,服务端接收到了客户端传过来的Book对象的完整信息,同时服务端的对象的改变也会同步到客户端的对象中。
这个结论的得出和我们上述的推论是一致的,下面我们从源码中看一下这三个方法的逻辑流程,进一步论证我们的实验结果。

三、理论支持

首先我们看一下客户端的源码是怎么处理addBookWithInTagaddBookWithOutTagaddBookWithInOutTag这三个方法的(注意三个方法中注释的对比):

 

@Override 
public void addBookWithInTag(com.example.runningh.mydemo.binder.Book book) throws android.os.RemoteException
{
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
         //判断book对象是否为空,不为空则将book对象写入到_data中,_data是客户端到服务端的输入流数据
        if ((book!=null)) {
            _data.writeInt(1);
            book.writeToParcel(_data, 0);
        }
        else {
            _data.writeInt(0);
        }
        //调用远程服务端的方法,将数据流传送过去
        mRemote.transact(Stub.TRANSACTION_addBookWithInTag, _data, _reply, 0);
        //注意这里并没有对服务端返回的数据流进行读取
        _reply.readException();
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
}

@Override 
public void addBookWithOutTag(com.example.runningh.mydemo.binder.Book book) throws android.os.RemoteException
{
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        //并不关心客户端传递参数的对象,因为都没有将对象写入到_data数据流
        mRemote.transact(Stub.TRANSACTION_addBookWithOutTag, _data, _reply, 0); 
        _reply.readException();
         //从服务端的数据流中读取数据并解析成Book对象返回到传参对象
        if ((0!=_reply.readInt())) {
            book.readFromParcel(_reply);
        }
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
}

@Override 
public void addBookWithInOutTag(com.example.runningh.mydemo.binder.Book book) throws android.os.RemoteException
{
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        //判断book对象是否为空,不为空则将book对象写入到_data中,_data是客户端到服务端的输入流数据
        _data.writeInterfaceToken(DESCRIPTOR);
        if ((book!=null)) {
            _data.writeInt(1);
            book.writeToParcel(_data, 0);
        }
        else {
            _data.writeInt(0);
        }
        //调用远程服务端的方法,将数据流传送过去   
        mRemote.transact(Stub.TRANSACTION_addBookWithInOutTag, _data, _reply, 0);
        _reply.readException();
        //从服务端的数据流中读取数据并解析成Book对象返回到传参对象
        if ((0!=_reply.readInt())) {
            book.readFromParcel(_reply);
        }
    }
    finally {
        _reply.recycle();
        _data.recycle();
    }
}

结论
定向 tag in 表示数据只能由客户端流向服务端,服务端将会收到客户端对象的完整数据,但是客户端对象不会因为服务端对传参的修改而发生变动。
out 表示数据只能由服务端流向客户端,服务端将会收到客户端对象,该对象不为空,但是它里面的字段为空,但是在服务端对该对象作任何修改之后客户端的传参对象都会同步改动。
inout 则表示数据可以在服务端与客户端之间双向流通。其中的数据流向是针对在客户端中的那个传入方法的对象而言的。服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

四、参考文章:

这篇文章的其实是对AIDL的in、out、inout这三个定向tag的一个总结,之前一直不了解这三个定向tag的意思,知道看到你真的理解AIDL中的in,out,inout么?这篇文章后,顺着作者的思路下来,并且自己做一遍实验和看一遍源码,才对这三个定向tag有一个清晰的了解。


 

<think>好的,我现在需要解释一下AIDL中的inoutinout参数。首先,我得回顾一下AIDL的基本概念,它是Android中用于进程间通信的接口定义语言。用户可能已经知道AIDL的作用,但需要更深入了解参数修饰符的区别和使用场景。 首先,要明确inoutinout这三个关键字的作用。我记得in参数是单向输入的,数据从客户端流向服务端,服务端修改后客户端不会看到变化。out参数则相反,数据由服务端填充,客户端传入的参数初始值不会被服务端使用,但服务端设置的修改会返回给客户端。inout结合了两者,客户端和服务端的修改都会同步到对方那里。 接下来需要确认这些参数在底层是如何实现的。可能涉及到Parcel的读写顺序。例如,in参数在序列化时只会写入数据,服务端反序列化后读取,处理完后可能不会写回。而out参数在客户端序列化时可能不会写入数据,但服务端处理完后会写入结果,客户端反序列化时读取。inout则是先写入再读取,这样双方都能传递数据。 然后要考虑数据类型是否支持。基本类型如int、long等默认只能是in,因为它们的传递是值拷贝。如果要使用outinout,必须使用对象类型,比如自定义的Parcelable对象。例如,用户定义一个Student类实现Parcelable,然后在AIDL接口中使用out Student stu,这样服务端可以修改stu的内容并返回给客户端。 可能用户会混淆这些参数的使用场景,需要举例说明。比如,如果有一个方法需要传入参数并获取处理后的结果,应该用inout;如果只是传递输入数据,用in;如果只需要获取输出,用out。此外,要注意性能问题,因为进程间通信有开销,特别是inout会涉及两次序列化,可能影响效率。 还要检查是否有常见的误区。比如,误以为基本类型可以用out修饰,但实际上不行,必须用对象类型。另外,在服务端实现AIDL接口时,对于out参数,需要显式地创建对象并赋值,因为客户端传入的可能是null,服务端需要生成实例并填充数据。 最后,总结一下各个参数的特点和使用注意事项,确保用户能够根据实际需求选择合适的修饰符,并正确实现对应的Parcelable对象,避免在跨进程通信时出现数据不一致或空指针异常的问题。</think>在Android AIDL(Android Interface Definition Language)中,`in`、`out` 和 `inout` 是用于修饰接口方法参数的关键字,它们控制数据在跨进程通信(IPC)中的传递方向。以下是逐步解释: --- ### **1. 基本概念** AIDL的`in`、`out`、`inout` 决定了参数在客户端和服务端之间的传递方向: - **`in`**:数据从客户端流向服务端(单向输入)。 - **`out`**:数据从服务端流向客户端(单向输出)。 - **`inout`**:数据双向流动(输入+输出)。 --- ### **2. 底层原理** - **序列化与反序列化**:AIDL通过`Parcel`对象实现数据传输。参数的传递本质是客户端和服务端对`Parcel`的读写顺序不同。 - **`in`参数**: - 客户端将参数写入`Parcel`。 - 服务端从`Parcel`读取参数,但不会回写修改。 - **`out`参数**: - 客户端不写入参数初始值。 - 服务端从`Parcel`读取空对象,处理后写入新值。 - 客户端反序列化时读取服务端写入的值。 - **`inout`参数**: - 客户端写入初始值,服务端读取后修改并回写新值。 - 客户端再次反序列化获取更新后的值。 --- ### **3. 具体行为示例** #### **`in` 参数** - 客户端传递数据,服务端接收副本,修改不影响客户端: ```java // AIDL定义 void updateValue(in int value); // 客户端调用 int value = 10; service.updateValue(value); // 服务端修改value不会影响客户端的value ``` #### **`out` 参数** - 服务端填充数据,客户端忽略初始值: ```java // AIDL定义 void getResult(out Result result); // 客户端调用 Result result = null; service.getResult(result); // 客户端result被服务端填充 ``` #### **`inout` 参数** - 客户端和服务端均可修改数据: ```java // AIDL定义 void compute(inout Data data); // 客户端调用 Data data = new Data(5); service.compute(data); // 客户端和服务端均可修改data ``` --- ### **4. 使用注意事项** 1. **数据类型限制**: - 基本类型(如`int`)只能使用`in`(因为传递的是值拷贝)。 - 自定义对象(需实现`Parcelable`)才能使用`out`或`inout`。 2. **性能影响**: - `in`参数最轻量,仅单向传输。 - `inout`参数涉及双向序列化,性能开销较大。 3. **空指针问题**: - `out`参数在客户端可为`null`,服务端需创建新对象。 - `inout`参数客户端必须初始化对象。 --- ### **5. 代码示例** 假设有一个`User`类实现`Parcelable`: ```java // AIDL接口 interface IUserService { void getUserInfo(in User input, out User output, inout User inoutData); } ``` - `input`:客户端传入数据,服务端修改不影响客户端。 - `output`:客户端传入`null`,服务端填充后返回。 - `inoutData`:客户端和服务端均可修改,最终同步到双方。 --- ### **6. 总结** | 修饰符 | 数据方向 | 适用类型 | 性能开销 | |--------|--------------------|----------------|----------| | `in` | 客户端 → 服务端 | 基本类型、对象 | 低 | | `out` | 服务端 → 客户端 | 对象 | 中 | | `inout`| 双向 | 对象 | 高 | 根据实际需求选择修饰符,避免不必要的性能损耗。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值