AIDL

作者: LeoFranz | 来源:发表于2018-07-29 16:06 被阅读0次

    AIDL学习笔记
    AIDL是一门接口定义语言,用于Android进程间交互使用;
    1、为什么用aidl
    2、它相对其他IPC方案有什么优劣
    3、未来有更好的方案吗

    因为Android系统中每一个进程都有独立Dalvik VM实例,有自己的内存区域,彼此互不干扰也无法互相直接通信。AIDL就是在需要通信的双方定义一些公共的方法接口和需要传递的数据类型。通信两端具体实现接口的一方为服务端,调用接口的一方成为客户端,客户端调用服务端的相应方法达到进程间通信的目的。具体的实现方式是服务的调用。

    语法特性:
    文件类型:用AIDL书写的文件的后缀是 .aidl,而不是 .java。

    数据类型:AIDL默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的,但是除了这些类型之外的数据类型,在使用之前必须导包,就算目标文件与当前正在编写的 .aidl 文件在同一个包下——在 Java 中,这种情况是不需要导包的。比如,现在我们编写了两个文件,一个叫做 Book.java ,另一个叫做 BookManager.aidl,它们都在 com.leo.aidldemo 包下 ,现在我们需要在 .aidl 文件里使用 Book 对象,那么我们就必须在 .aidl 文件里面写上 import com.leo.aidldemo.Book; 哪怕 .java 文件和 .aidl 文件就在一个包下。
    默认支持的数据类型包括:
    Java中的八种基本数据类型,包括 byte,short,int,long,float,double,boolean,char。
    String 类型。
    CharSequence类型。
    List类型:只支持ArrayList,List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable(下文关于这个会有详解),List可以使用泛型。
    Map类型:只支持HashMap,Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable。Map是不支持泛型的。

    定向tag:这是一个极易被忽略的点——这里的“被忽略”指的不是大家都不知道,而是很少人会正确的使用它。在我的理解里,定向 tag 是这样的:AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。请不要滥用定向 tag ,而是要根据需要选取合适的——要是不管三七二十一,全都一上来就用 inout ,等工程大了系统的开销就会大很多——因为排列整理参数的开销是很昂贵的。

    实现原理:
    由于不同的进程有着不同的内存区域,并且它们只能访问自己的那一块内存区域,所以我们不能像平时那样,传一个句柄过去就完事了——句柄指向的是一个内存区域,现在目标进程根本不能访问源进程的内存,那把它传过去又有什么用呢?所以我们必须将要传输的数据转化为能够在内存之间流通的形式。这个转化的过程就叫做序列化与反序列化。简单来说是这样的:比如现在我们要将一个对象的数据从客户端传到服务端去,我们就可以在客户端对这个对象进行序列化的操作,将其中包含的数据转化为序列化流,然后将这个序列化流传输到服务端的内存中去,再在服务端对这个数据流进行反序列化的操作,从而还原其中包含的数据——通过这种方式,我们就达到了在一个进程中访问另一个进程的数据的目的。

    而通常,在我们通过AIDL进行跨进程通信的时候,选择的序列化方式是实现 Parcelable 接口。

    实现步骤:
    1、通常我们需要n个定义parcelable对象和1个定义具体方法的aidl文件
    实例如下:
    // BookInterface.aidl
    //这个文件的作用是引入了一个序列化对象 Book 供其他的AIDL文件使用
    //注意:Book.aidl与Book.java的包名应当是一样的
    package leo.com.bookstoredemo;

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

    parcelable Book;

    // BookStoreInterface.aidl
    package leo.com.bookstoredemo;

    // Declare any non-default types here with import statements
    //导入所需要使用的非默认支持数据类型的包
    import leo.com.bookstoredemo.Book;

    interface BookStoreInterface {
    /**
    * 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);
    //所有的返回值前都不需要加任何东西,不管是什么数据类型
    //传参时除了Java基本类型以及String,CharSequence之外的类型
    //都需要在前面加上定向tag,具体加什么量需而定
    List<Book> getBooks();
    void addBook(in Book book);
    }

    2、注意点:
    2.1 默认生成的模板类的对象只支持为 in 的定向 tag 。为什么呢?因为默认生成的类里面只有 writeToParcel() 方法,而如果要支持为 out 或者 inout 的定向 tag 的话,还需要实现 readFromParcel() 方法。
    2.2 如果AS无法识别Java或者aidl文件,则应该检查aidl文件的包结构是否和Java相同,如果还有问题,应该设置sourceset区分aidl和Java文件位置
    sourceSets {
    main {
    java.srcDirs = ['src/main/java']
    aidl.srcDirs = ['src/main/aidl']
    }
    }
    此外,服务端和客户端的aidl文件目录结构必须完全相同,比如服务端的book.aidl在com.leo.bookstoredemo目录下,客户端也必须如此。客户端需要反序列化服务端中和AIDL相关的类,如果两端文件路径不一致,就不能反序列化成功。
    还有,如果需要在接口方法中使用非默认支持的数据类型,比如Book.java,则定义parcelable对象的aidl文件也必须叫做Book.aidl否则编译器是无法识别的。
    2.3 AIDL方法是在服务端的Binder线程池中执行的,如果有多个客户端同时链接,那么服务端要做好线程同步问题——对于List,可以使用CopyOnWriteArrayList,问题来了,不是ArrayList为什么可行?因为首先其仍然是List的实现类,关键在Binder中会根据List规则访问数据并返回一个ArrayList给客户端。
    2.4服务端的方法可能通过很久才有相应,需要做好应对ANR的措施
    当定义完aidl文件且这一步没问题后,通过clean或者build项目后,可以让AS生成和aidl文件同名的Java文件,这就是具体的接口实现类

    3、现在贴上服务端的代码

    package leo.com.bookstoredemo;
    
    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;
    
    public class BookStoreService extends Service {
    
        private static final String TAG = "BookStoreService";
    
        private List<Book> mBooks = new ArrayList<>();
    
        private static final Object sObject = new Object();
    
        private BookStoreInterface.Stub mBookStoreManager = new BookStoreInterface.Stub(){
            @Override
            public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
    
            }
    
            @Override
            public List<Book> getBooks() throws RemoteException {
                synchronized (sObject) {
                    if(mBooks != null){
                        return mBooks;
                    } else {
                        mBooks = new ArrayList<>();
                        return mBooks;
                    }
                }
            }
    
            @Override
            public void addBook(Book book) throws RemoteException {
                synchronized (sObject) {
                    if(mBooks == null) {
                        mBooks = new ArrayList<>();
                    }
                    if(book == null) {
                        book = new Book("A Book",10);
                        Log.e(TAG,"the Book added in is null !");
                    }
                    if(!mBooks.contains(book)){
                        book.setmBookPrice(book.getmBookPrice() * 2);
                        mBooks.add(book);
                        Log.i(TAG,"now adding the book and change its price to "+book.getmBookPrice());
                    }
                }
    
            }
        };
    
        @Override
        public void onCreate() {
            super.onCreate();
            Book originalBook = new Book("tap dancing to work",30);
            mBooks.add(originalBook);
            Log.i(TAG,"original book list is: "+originalBook.toString());
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            Log.i(TAG,"Bookstore's manager has been connected: "+mBookStoreManager.toString());
            return mBookStoreManager;
        }
    }
    4、客户端的代码
    package leo.com.bookstorecustomer;
    
    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.support.v7.app.AppCompatActivity;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    
    import java.util.List;
    
    import leo.com.bookstoredemo.Book;
    import leo.com.bookstoredemo.BookStoreInterface;
    
    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "MainActivity";
    
        private BookStoreInterface mBookStoreInterface = null;
    
        private boolean mHasBound = false;
    
        private List<Book> mBooks = null;
    
        private Button mAddBookButton = null;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mAddBookButton = findViewById(R.id.add_book);
            mAddBookButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if(mHasBound && mBookStoreInterface != null) {
                        Book book = new Book("fucking android",50);
                        try {
                            mBookStoreInterface.addBook(book);
                            Log.i(TAG,"the book has been added to list "+book.toString());
                        }catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            attemptToBindService();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unbindService(mConnectionService);
            mBookStoreInterface = null;
            mHasBound = false;
        }
    
        private void attemptToBindService() {
            Intent intent = new Intent("com.leo.bookstore.manager");
            intent.setPackage("leo.com.bookstoredemo");
            bindService(intent,mConnectionService, Context.BIND_AUTO_CREATE);
        }
    
        private ServiceConnection mConnectionService = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
                Log.i(TAG,"Service connected !");
                mBookStoreInterface = BookStoreInterface.Stub.asInterface(iBinder);
                mHasBound = true;
                if(mBookStoreInterface != null) {
                    try {
                        mBooks = mBookStoreInterface.getBooks();
                        Log.i(TAG,"customer has connected bookstore and get a book list: "+mBooks.toString());
                    }catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName componentName) {
                mBookStoreInterface = null;
                mHasBound = false;
                Log.i(TAG,"Service disconnected");
            }
        };
    }
    通过log可以看到整个通信的流程和定向tag的作用
    07-29 15:21:21.245  3079  3079 I BookStoreService: original book list is: Book{mBookName='tap dancing to work', mBookPrice=30}
    07-29 15:21:21.245  3079  3079 I BookStoreService: Bookstore's manager has been connected: leo.com.bookstoredemo.BookStoreService$1@986cd9a
    07-29 15:21:21.256  2838  2838 I MainActivity: Service connected !
    07-29 15:21:21.257  2838  2838 I MainActivity: customer has connected bookstore and get a book list: [Book{mBookName='tap dancing to work', mBookPrice=30}]
    
    
    07-29 15:22:14.991  3079  3092 I BookStoreService: now adding the book and change its price to 100
    07-29 15:22:14.991  2838  2838 I MainActivity: the book has been added to list Book{mBookName='fucking android', mBookPrice=50}
    
    完整demo地址  https://github.com/LeeFranz/AndroidIPC
    
    

    相关文章

      网友评论

        本文标题:AIDL

        本文链接:https://www.haomeiwen.com/subject/edzzmftx.html