Android开发经验谈Android学习之旅程序员

Android学习之旅-使用 AIDL 实现进程间通信01[艺术

2018-12-07  本文已影响6人  TengFeiGo

什么是 AIDL

AIDL 全称是“Android”接口定义语言,在 Android 中进程之间是不能直接通过内存共享的方式进行数据通信,为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。我们知道4个Android应用程序组件中的3个(Activity、BroadcastReceiver和ContentProvider)都可以进行跨进程访问,另外一个Android应用程序组件Service同样可以。因此,可以将这种可以跨进程访问的服务称为AIDL(Android Interface Definition Language)服务。
AIDL底层其实也是通过binder来进行进程间通信的,只要Android提供了AIDL这个工具来自动生成代码便捷了开发者而已,相关代码在 build->generated->source->aidl 文件夹下,这里只是举个例子,后面我会新建一个 aidl 文件一步一步的来,并且从源码分析 aidl 生成的java文件,顺带也复习一下相关的知识点。

aidl文件目录

如何使用 AIDL

image.png

为了真实演示客户端与服务端的通信,我新建了两个module,分别为client和server,在client中创建 AIDL 文件,如图所示,创建ok后在 main 文件夹下会生成一个 aidl 的文件夹,如图所示,可以看到 aidl 文件的包名和 java 目录下的包名是一致的,这一点要值得注意。


image.png

新建 Book.java:

public class Book implements Parcelable {

    private int bookId;
    private String bookName;

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

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

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

    protected Book(Parcel in) {
        this.bookId = in.readInt();
        this.bookName = in.readString();
    }

    public static final 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];
        }
    };
}

所有在进程间通信的实体类必须实现 Parcelable 接口,也就是序列化,在 IBookManager.aidl 中新建两个方法供调用。

// IBookManager.aidl
package com.example.tengfei.aidl;
import com.example.tengfei.aidl.Book;

interface IBookManager {

    List<Book> getBookList();

    void addBook(in Book book);
}

你可以发现我在这里需要用到Book实体类,而这也是 aidl 的特殊之处,你还需要定义一个 Book.aidl ,尽管你已经定义了一个Book.java,在 aidl 中如果你需要用到任意一个实体类,那么此类首先必须序列化,其次必须同时定义一个此类的 aidl 文件,注意前面的 parcelable,是小写的,这是一个坑,务必要注意了。
同时需要注意在 AIDL 中支持以下数据类型:
1.基本数据类型
2.String 和 CharSequence
3.List:只支持 ArrayList 并且List的每一个数据都必须要被 AIDL 支持
4.Map:只支持 HashMap 并且 Map 里的每一个数据都必须要被 AIDL 支持
5.所有的实体类数据都被被 Parcelable 序列化
6.所有 AIDL 接口本身也可以在 AIDL 文件中使用

// Book.aidl
package com.example.tengfei.aidl;

parcelable Book;

其实到了这一步在 client 中 build 文件下已经生成了相关的代码可以供我们调用,但先不要关注这些代码,接下来新建服务端module,让我们首先实现一个简单的进程间通信的小demo,注意了,为了方便你的操作,你可以将与 aidl 相关的类全部拷贝到一个包下面,但是需要注意的是在AndroidStudio中如果将你的java文件也就是后缀是java的文件放在aidl文件夹中,但是编译的时候会报错提示你java文件找不到,解决办法是把java文件放在java文件夹下面,保持其包名不变与Book.aidl一致,第二种解决办法是修改 build.gradle 文件:在 android{} 中间加上下面的内容:

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

在写服务端 aidl 文件的时候直接将这个包给copy过去就可以了,也避免了不必要的错误,接下来直接将我们在client中定义的 aidl 相关文件复制到 server 中,客户端与服务端的 aidl 相关文件的包名必须一致,但是我将Book.java放到java文件夹下,并确保它与Book.aidl包名一致

服务端中aidl文件java类的位置

在服务端中定义一个 Service

public class MyService extends Service {

    private CopyOnWriteArrayList<Book> copyOnWriteArrayList = new CopyOnWriteArrayList<>();

    private Binder binder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() {
            return copyOnWriteArrayList;
        }

        @Override
        public void addBook(Book book) {
            copyOnWriteArrayList.add(book);
        }
    };

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

    @Override
    public void onCreate() {
        super.onCreate();
        copyOnWriteArrayList.add(new Book(0x000,"基督山恩仇记"));
        copyOnWriteArrayList.add(new Book(0x001,"海底两万里"));

    }
}

之所以用到 CopyOnWriteArrayList 是因为 AIDL 方法是运行在binder线程池中的,当多个客户端同时连接的时候,会出现多个线程同时访问的情况。
在客户端中绑定服务端

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "MainActivityTAG";

    private IBookManager iBookManager;

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iBookManager = IBookManager.Stub.asInterface(service);
            try {
                iBookManager.addBook(new Book(0x002, "神秘岛"));
                iBookManager.addBook(new Book(0x003, "Android开发艺术探索"));


            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.bt_client_bind).setOnClickListener(this);
        findViewById(R.id.bt_client_add).setOnClickListener(this);
        findViewById(R.id.bt_client_get).setOnClickListener(this);

    }

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

    @Override
    public void onClick(View v) {
        int i = 0;
        switch (v.getId()) {
            case R.id.bt_client_bind:
                i++;
                Intent intent = new Intent("com.example.tengfei.server.MyService");
                intent.setPackage("com.example.tengfei.server");
                bindService(intent, connection, Context.BIND_AUTO_CREATE);
                break;
            case R.id.bt_client_add:
                try {
                    iBookManager.addBook(new Book(i, "#" + i));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            case R.id.bt_client_get:
                List<Book> bookList = null;
                try {
                    bookList = iBookManager.getBookList();
                    if ( bookList != null && !bookList.isEmpty()) {
                        for (Book book : bookList) {
                            Log.i(TAG, book.toString());
                        }
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
        }
    }
}

代码很简单,首先定义一个 ServiceConnection ,在 onServiceConnected 回调方法中可以获取服务端返回的 IBinder 对象,也就是我们自定义Service中的 onBind 方法返回的对象,拿到这个对象后将它转换成我们所需要的 AIDL 接口类型的对象,紧接着就可以通过 IBookManager 来调用远程服务端的方法,在这里有个小插曲,我绑定服务时报了 Service Intent must be explicit ,后来我查资料发现在 Android5.0 版本以上时不允许隐式启动 Service,其实你可以在setAction之后调用 setPackage 将你要启动Service所在的应用的包名给传递进去,就可以正常绑定服务了,其实学习就是这样,你不要亲自动手实践的话很多问题你压根就发现不了,所以我选择在学习完艺术探索之后亲自实践一遍,并将所学的给记录下来增强自己的理解。

参考资料

1、百度百科 什么是 AIDL
2、Android开发艺术探索
3、如何解决Android 5.0中出现的警告:Service Intent must be explicit

上一篇下一篇

猜你喜欢

热点阅读