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


感謝夥伴們閱讀這份博文。轉載請註明出處:小海窩珍惜作者勞動成果,謝謝。

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