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)。
感謝夥伴們閱讀這份博文。轉載請註明出處:小海窩珍惜作者勞動成果,謝謝。