Android Binder Analysis(3)
@(数据库系统)[Binder, AIDL, Messenger]
前文中我们描述了Binder
的基本概念,及其在Binding Service
时的两种应用场景,Extends Binder Class
和Messenger
,接下来我们来看Binder
的第三个应用场景:AIDL
。
AIDL在官网的解释如下:
Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. Regardless, be sure that you understand Bound Services before implementing an AIDL.
上面这段英文简要描述了AIDL
,那么什么是AIDL
呢?AIDL
是Android Interface Definition Language
的缩写,即Android
接口描述语言。设计这门语言的主要目的是为了实现进程间通信,只有当你允许不同的客户端访问你的服务并且服务需要处理多线程问题时你才必须使用AIDL
。利用AIDL
可以定义不同进程间通信所必备的通信边界(即通信协议),通过这些通信边界,不同进程间可以互相共享数据,甚至调用一些特定方法。AIDL
分同步实现和异步实现两种,同步实现是一种阻塞形式,就想我们在上篇中介绍IBinder的描述一样,客户端需等待服务的返回以唤醒自己的线程继续执行;异步就是回调的形式,客户端发出请求传到服务并不需要等待服务返回,服务执行后后主动告知客户端,引起客户端处理请求结果,就像setXXXListener
的过程一样。
既然定义中描述AIDL
是一种语言,那么自然有其语法和数据类型,其语法与java
相似,支持的数据类型分以下几种:
Java
中的八种基本数据类型,包括byte
,short
,int
,long
,float
,double
,boolean
,char
String
类型CharSequence
类型- 实现
Parcelable
接口的实体类,List
类型,Map
类型
对于AIDL
而言,其新建的文件为.aidl
后缀并非.java
后缀,而且在使用基本数据类型的时候是不需要导包的,但是除了这些基本类型之外,其他类型在使用之前必须导包,就算目标文件与当前正在编写的 .aidl
文件在同一个包下,而在 Java
中,这种情况是不需要导包的。
了解了AIDL
的语法,乘热打铁,赶紧来编写一个同步AIDL
的demo
吧,AIDL
项目编写的一般步骤如下:
- 在服务端创建
AIDL
文件并编译(编译的目的是生成中间文件)[PS:也可以在客户端创建AIDL
文件] - 拷贝
AIDL
文件及实体类到客户端(或者服务端,取决于你首次编写AIDL
在那一端,编译成功后拷贝至另一端即可) - 编写服务端代码
- 编写客户端代码,发送请求到服务器
在服务端创建AIDL
文件并编译
打开Android Studio
新建AIDLServerDemo Project
,等待项目构建完成,随后在app
上右键,新建->AIDL
,选择后如下图所示:
随后输入AIDL
文件名称确定即可,新建AIDL
(Book.aidl
)文件后的项目目录如下图所示:
展开AIDL
目录,打开新建的Book.aidl
文件,编写如下代码:
package com.example.zbtuo.serverdemo1.bean;
parcelable Book;
其中Book
是我们定义的一个实体类对象,实现了Parcelable
接口,内容如下:
package com.example.zbtuo.serverdemo1.bean;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by zbtuo on 17-5-16.
*
* Function Declaration :
*/
public class Book implements Parcelable {
private String bookName;
private String bookPrice;
private String bookAuthor;
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public String getBookPrice() {
return bookPrice;
}
public void setBookPrice(String bookPrice) {
this.bookPrice = bookPrice;
}
public String getBookAuthor() {
return bookAuthor;
}
public void setBookAuthor(String bookAuthor) {
this.bookAuthor = bookAuthor;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.bookName);
dest.writeString(this.bookPrice);
dest.writeString(this.bookAuthor);
}
public Book() {
}
protected Book(Parcel in) {
this.bookName = in.readString();
this.bookPrice = in.readString();
this.bookAuthor = 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];
}
};
@Override
public String toString() {
return "BookName:"+bookName+" BookAuthor:"+bookAuthor+" BookPrice:"+bookPrice;
}
}
随后我们新建BookManager.aidl
文件用于定义操作Book
可用的接口,BookManager.aidl
文件内容如下:
package com.example.zbtuo.serverdemo1;
import com.example.zbtuo.serverdemo1.bean.Book;
// Declare any non-default types here with import statements
interface BookManager {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
List<Book> getBooks();
Book getBook(String bookName);
int getBookCount();
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定
void addBook(in Book book);
}
build
该项目,可看到如下中间文件生成:
至此,我们完成了AIDL
开发的第一步,编写AIDL
文件并编译
拷贝AIDL文件及实体类到客户端
在Android Studio
中重新新建Android
项目作为客户端,并拷贝服务端实体类和aidl
文件到该项目中并编译,如下图所示:
编写服务端代码
随后为服务器端添加服务代码,并实现BookManager.aidl
中定义的Book
的操作接口,代码如下所示:
package com.example.zbtuo.serverdemo1;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.example.zbtuo.serverdemo1.bean.Book;
import java.util.ArrayList;
import java.util.List;
public class BookManagerService extends Service {
private List<Book> mBooks = new ArrayList<>();
public final String TAG = this.getClass().getSimpleName();
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBookManager;
}
@Override
public void onCreate() {
Book book = new Book();
book.setBookName("Android开发");
book.setBookPrice("28");
book.setBookAuthor("XXX");
mBooks.add(book);
super.onCreate();
}
private BookManager.Stub mBookManager = new BookManager.Stub() {
@Override
public List<Book> getBooks() throws RemoteException {
synchronized (this) {
Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());
if (mBooks != null) {
return mBooks;
}
return new ArrayList<>();
}
}
@Override
public Book getBook(String bookName) throws RemoteException {
synchronized (this){
if (mBooks == null || mBooks.size() <= 0 ){
return null;
}
for (int i=0; i < mBooks.size();i++){
if (mBooks.get(i).equals(bookName)){
return mBooks.get(i);
}
}
return null;
}
}
@Override
public int getBookCount() throws RemoteException {
synchronized (this) {
Log.e(TAG, "invoking getBookCount() method , now the list is : " + mBooks.toString());
if (mBooks != null) {
return mBooks.size();
}
return 0;
}
}
@Override
public void addBook(Book book) throws RemoteException {
synchronized (this) {
if(book != null){
mBooks.add(book);
Log.e(TAG,"invoking addBook() method , now the list is : " + mBooks.toString());
}
}
}
};
}
其中关于为什么要书写Binder
,我们在上篇中已经介绍过,这里不再赘述。
编写客户端代码发送请求到服务器
客户端的MainActivity.Java
文件内容如下所示:
package com.example.zbtuo.aidlclientdemo1;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import android.widget.Toast;
import com.example.zbtuo.serverdemo1.BookManager;
import com.example.zbtuo.serverdemo1.bean.Book;
import java.util.List;
public class MainActivity extends AppCompatActivity implements OnClickListener{
private TextView mTextView,mBookCount;
private BookManager mBookManager;
//标志当前与服务端连接状况的布尔值,false为未连接,true为连接中
private boolean mBound = false;
//包含Book对象的list
private List<Book> mBooks;
private int count = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
mBookCount = (TextView)findViewById(R.id.books);
mTextView.setOnClickListener(MainActivity.this);
mBookCount.setOnClickListener(MainActivity.this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.textview:
processAddBook();
break;
case R.id.books:
processGetBookCount();
break;
}
}
public void processGetBookCount(){
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null) return;
try {
mBookCount.setText(mBookManager.getBookCount()+"");
Log.e(getLocalClassName(), "call server method getBookCount "+mBookManager.getBookCount());
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void processAddBook(){
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null) return;
Book book = new Book();
book.setBookName("APP研发录"+count);
book.setBookPrice("30");
book.setBookAuthor("Author"+count);
count ++;
try {
mBookManager.addBook(book);
Log.e(getLocalClassName(), book.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void attemptToBindService() {
Intent intent = new Intent();
intent.setAction("com.example.zbtuo.aidl");
intent.setPackage("com.example.zbtuo.serverdemo1");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStart() {
super.onStart();
if (!mBound) {
attemptToBindService();
}
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.e(getLocalClassName(), "service connected");
mBookManager = BookManager.Stub.asInterface(iBinder);
mBound = true;
if (mBookManager != null) {
try {
mBooks = mBookManager.getBooks();
Log.e(getLocalClassName(), mBooks.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.e(getLocalClassName(), "service disconnected");
mBound = false;
}
};
}
使用bindService
方式获取服务,在服务连接成功后获得IBinder
对象,利用IBinder
对象向服务发出请求。
客户端的界面布局xml
内容如下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.zbtuo.aidlclientdemo1.MainActivity">
<TextView
android:id="@+id/textview"
android:layout_width="match_parent"
android:layout_marginTop="10dp"
android:gravity="center"
android:layout_height="60dp"
android:text="add book!"/>
<TextView
android:id="@+id/books"
android:text="Get Book Count"
android:gravity="center"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="60dp"/>
</LinearLayout>
AIDL开发之M3T
至此我们就完成了第一个同步AIDL Demo
的开发,各位亲们按照我提供的代码是否编写出了代码呢?我想大多数小伙伴的答案是NO,难道是我这个渣渣贴错代码了?明确告诉你不是的,哈哈哈,或许你遇到了这样的错:
又或者是这样的错:
针对AIDL
开发,我总结了几条注意事项,简称为M3T,分别代表:
- MUST:Package Name Of JAVA Bean MUST Be Same To AIDL
- MUST:AIDL File Of Client MUST Be Same To Server
- MUST:Non Primitive type MUST implement Parcelable Interface
- TAG:Directional TAG For Non-primitive Parameters
Package Name Of Java Bean MUST Be Same To AIDL
这个Must的意思是AIDL
文件中的Book.aidl
文件的包名必须和Java
实体Bean
的包名一致。
这里再拿出刚才的两张图,如果你编译时爆出下面错误:
赶紧check一下你的包名是否一致,如下图:
A,B两处的包名必须完全一致。
又或者你幸运的躲过了第一摆,进入了AIDL
文件拷贝过程,编译发现如下错误:
此时多半是因为你拷贝过来的AIDL
路径没有被当作源码路径导致的,此时可在build.gradle
的android
域中添加如下代码:
AIDL File Of Client MUST Be Same To Server
这个MUST的意思是在aidl
文件从客户端向服务器(或者服务器向客户端)拷贝的过程中,AIDL
文件在两端必须保持一致,不止每个文件的内容,还包含每个文件的包名类名等,如下图所示:
Client和Server必须完全一致,此时就要求我们适时调整JAVA Beans
目录。
Non Primitive type MUST implement Parcelable Interface
这个MUST的意思是说,用于Binder
传递的实体类必须实现Parcelable
接口。在跨进程过程中,数据必须从Application
传入Framework
进而传入Kernel
,在共享内存中share
给另一个进程,要实现这种操作,数据必须序列化,Parcelable
是一个用于序列化的接口,Android
特有,效率比Serializable
高,不会引起频繁GC,不适用于持久化数据,通常用于IPC
通信,实现Parcelable
接口必须实现Parcelable.Creator
接口且实现该接口的对象名必须为CREATOR
,而Serializable
是一个标记接口,实现方式简单,JAVA SE
本身支持,依赖于反射实现,通常用于持久化数据,这里一定要注意一点,Parcelable
实体类中默认是没有生成readFromParcel(Parcel dest)
函数的,切记切记,随后在讲TAG的时候会用到。
考虑到篇幅问题,我们在下一篇中介绍M3T的T (定向TAG)。
感谢伙伴们阅读这份博文。转载请注明出处:小海窝珍惜作者劳动成果,谢谢。