Android AIDL (二) 客户端编码与定向Tag

620 阅读4分钟

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

四、客户端编码

客户端需要再创建一个新的工程,包名命名为 com.czy.client

首先,需要把服务端的AIDL文件以及Book类复制过来,将 aidl 文件夹整个复制到和Java文件夹同个层级下,不需要改动任何代码。

img

之后,需要创建和服务端Book类所在的相同包名来存放Book类

img

修改布局文件,添加两个按钮

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">
​
    <Button
        android:id="@+id/btn_getBookList"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="获取书籍列表" />
​
    <Button
        android:id="@+id/btn_addBook_inOut"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="InOut 添加书籍" />
​
</LinearLayout>
public class MainActivity extends AppCompatActivity {
​
    private final String TAG = "Client";
​
    private BookController bookController;
​
    private boolean connected;
​
    private List<Book> bookList;
​
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bookController = BookController.Stub.asInterface(service);
            connected = true;
        }
​
        @Override
        public void onServiceDisconnected(ComponentName name) {
            connected = false;
        }
    };
​
    private View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.btn_getBookList:
                    if (connected) {
                        try {
                            bookList = bookController.getBookList();
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                        log();
                    }
                    break;
                case R.id.btn_addBook_inOut:
                    if (connected) {
                        Book book = new Book("这是一本新书 InOut");
                        try {
                            bookController.addBookInOut(book);
                            Log.e(TAG, "向服务器以InOut方式添加了一本新书");
                            Log.e(TAG, "新书名:" + book.getName());
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
            }
        }
    };
​
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_getBookList).setOnClickListener(clickListener);
        findViewById(R.id.btn_addBook_inOut).setOnClickListener(clickListener);
        bindService();
    }
​
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (connected) {
            unbindService(serviceConnection);
        }
    }
​
    private void bindService() {
        Intent intent = new Intent();
        intent.setPackage("com.czy.server");
        intent.setAction("com.czy.server.action");
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
    }
​
    private void log() {
        for (Book book : bookList) {
            Log.e(TAG, book.toString());
        }
    }
​
}

两个按钮分别用于获取服务端的书籍列表和添加书籍。

在添加书籍时,服务端还改变了Book对象的Name属性,据此观察客户端和服务端数据的变化情况:

首先点击获取书籍列表,数据获取无误

img

再点击按钮添加书籍,可以看到,服务端对数据的修改也同时同步到了客户端这边

img

到此为止,客户端和服务端之间的通信已经实现了,客户端获取到了服务端的数据,也向服务端传送了数据。

五、定向Tag

最后,我再来讲下三种定向Tag之间的差别。

上边使用的是 InOut 类型,服务端对数据的改变同时也同步到了客户端,因此可以说两者之间数据是双向流动的。

In 类型的表现形式是:数据只能由客户端传向服务端,服务端对数据的修改不会影响到客户端。

Out类型的表现形式是:数据只能由服务端传向客户端,即使客户端向方法接口传入了一个对象,该对象中的属性值也是为空的,即不包含任何数据,服务端获取到该对象后,对该对象的任何操作,就会同步到客户端这边。

这里再来实际演示一下

先修改服务器端的 BookController.aidl 文件,向之添加两个新方法

package com.czy.server;
import com.czy.server.Book;

interface BookController {

    List<Book> getBookList();

    void addBookInOut(inout Book book);

    void addBookIn(in Book book);

    void addBookOut(out Book book);

}

则 AIDLService 类的 BookController.Stub 对象就需要修改为如下所示:

private final BookController.Stub stub = new BookController.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return bookList;
        }

        @Override
        public void addBookInOut(Book book) throws RemoteException {
            if (book != null) {
                book.setName("服务器改了新书的名字 InOut");
                bookList.add(book);
            } else {
                Log.e(TAG, "接收到了一个空对象 InOut");
            }
        }

        @Override
        public void addBookIn(Book book) throws RemoteException {
            if (book != null) {
                book.setName("服务器改了新书的名字 In");
                bookList.add(book);
            } else {
                Log.e(TAG, "接收到了一个空对象 In");
            }
        }

        @Override
        public void addBookOut(Book book) throws RemoteException {
            if (book != null) {
                Log.e(TAG, "客户端传来的书的名字:" + book.getName());
                book.setName("服务器改了新书的名字 Out");
                bookList.add(book);
            } else {
                Log.e(TAG, "接收到了一个空对象 Out");
            }
        }

    };

同步修改客户端的 BookController.aidl 文件

向布局文件多增添两个按钮,分别用于添加不同的 定向Tag 的数据

private View.OnClickListener clickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.btn_getBookList:
                    if (connected) {
                        try {
                            bookList = bookController.getBookList();
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                        log();
                    }
                    break;
                case R.id.btn_addBook_inOut:
                    if (connected) {
                        Book book = new Book("这是一本新书 InOut");
                        try {
                            bookController.addBookInOut(book);
                            Log.e(TAG, "向服务器以InOut方式添加了一本新书");
                            Log.e(TAG, "新书名:" + book.getName());
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                case R.id.btn_addBook_in:
                    if (connected) {
                        Book book = new Book("这是一本新书 In");
                        try {
                            bookController.addBookIn(book);
                            Log.e(TAG, "向服务器以In方式添加了一本新书");
                            Log.e(TAG, "新书名:" + book.getName());
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
                case R.id.btn_addBook_out:
                    if (connected) {
                        Book book = new Book("这是一本新书 Out");
                        try {
                            bookController.addBookOut(book);
                            Log.e(TAG, "向服务器以Out方式添加了一本新书");
                            Log.e(TAG, "新书名:" + book.getName());
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    break;
            }
        }
    };

此外,还有个地方需要修改下,即Book类。因为 Out 类型的确会使得客户端传一个不包含任何数据的对象回服务端,但该对象却不是直接就等于 null ,所以说明系统还是需要实例化 Book 类,但当前 Book 类只有一个有参构造函数,所以还需要修改 Book 类,为之添加一个无参构造函数以供系统使用

分别点击三个按钮,可以看到,服务端在获取到客户端以Out方式传来的Book对象时,的确是不包含书名这个属性值

img

关于AIDL的知识在这里将告一段落。