Android Binder Analysis(3)

Android Binder Analysis(3)

@(数据库系统)[Binder, AIDL, Messenger]

前文中我们描述了Binder的基本概念,及其在Binding Service时的两种应用场景,Extends Binder ClassMessenger,接下来我们来看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呢?AIDLAndroid Interface Definition Language的缩写,即Android接口描述语言。设计这门语言的主要目的是为了实现进程间通信,只有当你允许不同的客户端访问你的服务并且服务需要处理多线程问题时你才必须使用AIDL。利用AIDL可以定义不同进程间通信所必备的通信边界(即通信协议),通过这些通信边界,不同进程间可以互相共享数据,甚至调用一些特定方法。AIDL分同步实现和异步实现两种,同步实现是一种阻塞形式,就想我们在上篇中介绍IBinder的描述一样,客户端需等待服务的返回以唤醒自己的线程继续执行;异步就是回调的形式,客户端发出请求传到服务并不需要等待服务返回,服务执行后后主动告知客户端,引起客户端处理请求结果,就像setXXXListener的过程一样。

既然定义中描述AIDL是一种语言,那么自然有其语法和数据类型,其语法与java相似,支持的数据类型分以下几种:

  • Java中的八种基本数据类型,包括 byteshortintlongfloatdoublebooleanchar
  • String 类型
  • CharSequence类型
  • 实现Parcelable接口的实体类,List类型,Map类型

对于AIDL而言,其新建的文件为.aidl后缀并非.java后缀,而且在使用基本数据类型的时候是不需要导包的,但是除了这些基本类型之外,其他类型在使用之前必须导包,就算目标文件与当前正在编写的 .aidl 文件在同一个包下,而在 Java 中,这种情况是不需要导包的。

了解了AIDL的语法,乘热打铁,赶紧来编写一个同步AIDLdemo吧,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.gradleandroid域中添加如下代码:

这里写图片描述

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)。


感谢伙伴们阅读这份博文。转载请注明出处:小海窝珍惜作者劳动成果,谢谢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章