標籤:hashmap ddb 書籍 parcel lin java 處理 nts 存在
參考資料:
1、《Android開發藝術探索》第二章2.4.4
2、Android AIDL Binder架構解析:http://blog.csdn.net/lmj623565791/article/details/38461079
3、你真的理解AIDL中的in、out、inoutm麼:http://www.open-open.com/lib/view/open1469494342021.html
4、慕課網《AIDL-小白成長記》
1、 AIDL簡介
Android系統規定:每個應用程式獨自擁有一份虛擬機器,一個應用程式和另一個應用程式沒有辦法直接進行通訊,這樣保證了進程之內資料的安全性,也保證一個應用程式崩潰之後不會導致其他應用程式跟著崩潰。兩個進程如果想要通訊,只能通過Android系統底層進行通訊。
AIDL(Android Interface Definition Language,Android介面定義語言)是Android中的一種IPC(Inter-Process Communication,跨進程通訊)的方式,它允許我們編寫一個介面,作為用戶端和服務端通訊的橋樑。AIDL是Android基於IDL自己定義的一種用戶端和服務端交流的語言規範。
2、 AIDL使用方法
在這個章節中,我們通過一個執行個體學習AIDL的用法。
在這個執行個體中有服務端和用戶端,服務端有操作Book的功能,用戶端調用服務端的功能。
由於Book不是基礎資料型別 (Elementary Data Type),因此想要讓Book在多進程之間傳遞,需要對Book進行序列化,代碼如下:
package my.itgungnir.aidl;import android.os.Parcel;import android.os.Parcelable;/** * Book實體類 */public class Book implements Parcelable { private String booName; private int pirce; public Book(String booName, int pirce) { this.booName = booName; this.pirce = pirce; } protected Book(Parcel in) { booName = in.readString(); pirce = in.readInt(); } public static final Creator<Book> CREATOR = new Creator<Book>() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(booName); dest.writeInt(pirce); }}
我們的思路是:在服務端中定義操作Book列表的方法(添加書籍、查詢所有書籍),然後在用戶端中遠程調用服務端的這兩個方法操作書籍。
下面提供這個執行個體的編寫代碼和詳細說明。
2.1、 AIDL介面的建立
首先來介紹一下AIDL介面的定義文法:
1、AIDL檔案支援的資料類型:
(1)基礎資料型別 (Elementary Data Type)(int、long、char、boolean、double等);
(2)String和CharSequence;
(3)List:只支援ArrayList,裡面每個元素必須都能夠被AIDL支援;
(4)Map:只支援HashMap,裡面的每個元素必須都能夠被AIDL支援,包括key和value;
(5)Parcelable:所有實現了Parcelable介面的對象;
(6)AIDL:所有的AIDL介面本身也可以在AIDL檔案中使用。
2、自訂的Parcelable對象和AIDL對象必須要顯式import進來,不管它們是否和當前的AIDL檔案位於同一個包內。
3、如果AIDL檔案中用到了自訂的Parcelable對象,那麼必須重建立一個和它同名的AIDL檔案,並在其中聲明它為Parcelable類型。
4、AIDL介面的方法中,除了基本類型的參數,其他類型的參數都必須標上資料流向:in表示資料只能從用戶端流向服務端;out表示資料只能從服務端流向用戶端;inout表示資料可以在服務端和用戶端之間雙向流通。注意:基礎資料型別 (Elementary Data Type)預設且只能是in流向。
5、AIDL介面中只支援方法,不支援聲明靜態常量。
6、AIDL的包結構在服務端和用戶端要保持一致,否則運行會出錯,這是因為用戶端需要還原序列化服務端中和AIDL介面相關的所有類,如果類的完整路徑不一樣的話,就無法完成還原序列化,程式也就無法正常運行。
接下來我們來建立一個AIDL介面IBookManager.aidl,其中用到了Book類,因此我們還需要建立一個Book.aidl檔案。
Book.aidl檔案中的代碼如下:
package my.itgungnir.aidl;parcelable Book;
IBookManager.aidl檔案中的代碼如下:
package my.itgungnir.aidl;import my.itgungnir.aidl.Book;interface IBookManager { List<Book> getBookList(); void addBook(in Book book);}
可以看到,這兩個AIDL檔案中的文法完全遵循上面說的AIDL文法。
2.2、 服務端的實現
定義了AIDL介面之後,我們就可以實現這個介面了。建立一個Service類BookManagerService,代碼如下:
import android.app.Service;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.os.RemoteException;import android.support.annotation.Nullable;import java.util.ArrayList;import java.util.List;import my.itgungnir.aidl.Book;import my.itgungnir.aidl.IBookManager;/** * 定義服務端Service,實現AIDL介面 */public class BookManagerService extends Service { private CopyOnWriteArrayList<Book> bookList = new CopyOnWriteArrayList<>(); private Binder binder = new IBookManager.Stub() { @Override public List<Book> getBookList() throws RemoteException { return bookList; } @Override public void addBook(Book book) throws RemoteException { bookList.add(book); } }; @Override public void onCreate() { super.onCreate(); bookList.add(new Book("Android", 20)); bookList.add(new Book("iOS", 17)); } @Nullable @Override public IBinder onBind(Intent intent) { return binder; }}
在這個類中,我們建立了一個Stub的對象,在這個對象的兩個方法中進行資料的處理,最後在onBind()方法中返回這個Stub。
編寫這個類的時候需要注意:這個類中我們並沒有使用ArrayList,而是一個CopyOnWriteArrayList類(這個類並不繼承自ArrayList)。這個CopyOnWriteArrayList支援是一種支援並發讀/寫的List。由於AIDL是在服務端的Binder線程池中執行的,因此當多個用戶端同時串連的時候,會存在多個線程同時訪問的情形,所以我們要在AIDL方法中處理線程同步,而CopyOnWriteArrayList可以自動進行線程同步。
另外,前面說道,AIDL中只支援ArrayList,但CopyOnWriteArrayList並不繼承自ArrayList,為什麼AIDL還是會支援呢?這是因為AIDL中所支援的是抽象的List,而List只是一個介面,因此雖然服務端返回的是CopyOnWriteArrayList,但是在Binder中會按照List的規範去訪問資料並最終形成一個新的ArrayList傳遞給用戶端。和這個類類似的是Map中的ConcurrentHashMap。
編寫完了Service類,我們需要在Manifest檔案中註冊這個Service,代碼如下:
<service android:name=".BookManagerService"> <intent-filter> <action android:name="my.itgungnir.aidl.book_server" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter></service>
至此,服務端的代碼就編寫完成了。
2.3、 用戶端的實現
在用戶端,我們先要綁定服務端的服務,將服務端返回的Binder介面轉換成AIDL介面,然後就可以調用服務端的方法了。
需要注意的是,AIDL檔案和AIDL中用到的Parcelable類,在服務端和用戶端都需要有一份,且包路徑必須一致。
用戶端的MainActivity中的代碼如下:
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 java.util.List;import my.itgungnir.aidl.Book;import my.itgungnir.aidl.IBookManager;public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { IBookManager manager = IBookManager.Stub.asInterface(service); try { // 查詢所有圖書 List<Book> bookList = manager.getBookList(); Log.i(TAG, "query book list: " + bookList.toString()); // 添加新書 Book newBook = new Book("Html", 40); manager.addBook(newBook); Log.i(TAG, "add book: " + newBook.toString()); // 查詢所有圖書 bookList = manager.getBookList(); Log.i(TAG, "query book list: " + bookList.toString()); } 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); Intent intent = new Intent(); intent.setAction("my.itgungnir.aidl.book_server"); intent.setPackage("my.itgungnir.server"); bindService(intent, connection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { unbindService(connection); super.onDestroy(); }}
由於這裡把服務端和用戶端放在兩個不同的Module中,因此需要為Intent對象設定package屬性為服務端的包名。
可以看到,在用戶端,我們調用了服務端的getBookList()方法,將儲存在服務端List中的Book對象擷取過來,並通過Log日誌列印到控制台。這樣,就可以在控制台中看到我們列印出來的資訊了,如下:
my.itgungnir.client I/MainActivity: query book list: [Book{booName=‘Android‘, pirce=20}, Book{booName=‘iOS‘, pirce=17}]my.itgungnir.client I/MainActivity: add book: Book{booName=‘Html‘, pirce=40}my.itgungnir.client I/MainActivity: query book list: [Book{booName=‘Android‘, pirce=20}, Book{booName=‘iOS‘, pirce=17}, Book{booName=‘Html‘, pirce=40}]
【Android - IPC】之AIDL簡介