《Android 開發藝術探索》讀書筆記(二)——IPC 機制
Android 多程序開發我在平時開發中還沒有遇到,但不代表不重要,仍需要了解一下基本概念,Android 的序列化機制和 Binder 是。
1 Android IPC 簡介
IPC 是 Inter Process Communication 的縮寫,意為程序間通訊或跨程序通訊,是指兩個程序之間進行資料交換的過程。程序一般指一個程式或一個應用,一個程序可以包含多個執行緒,執行緒是 CPU 排程的最小單元,最簡單的情況是一個程序(即一個應用)只包含一個執行緒,這個執行緒被稱為主執行緒,在 Android 中也被稱為 UI 執行緒,所有介面元素的相關操作必須在主執行緒中,如果在主執行緒中執行大量耗時任務,就會造成介面無法響應,嚴重影響使用者體驗,這種情況在 PC 上和移動端都有,在 Android 中這種情況被稱為 ANR(Application Not Responding),即應用無響應,為了避免這種問題我們就需要把耗時任務放到子執行緒中去執行。
IPC 不是 Android 特有的,任何一個作業系統都有相應的 IPC 機制,Windows 上可以通過剪貼簿、管道等來實現程序間通訊,Linux 可以通過命名管道、共享內容、訊號量來實現程序間通訊,Android 是一種基於 Linux 核心的移動作業系統,它的通訊方式並不能完全繼承自 Linux,所以它有自己的 IPC 機制,最有特色的就是 Binder 了,通過 Binder 可以輕鬆地實現程序間通訊,除此之外 Android 還支援 Socket,Socket 都可以實現任意兩個終端之間的通訊,更別說同一終端的兩個程序之間通訊了。
說到 IPC 就要提到多程序了,因為只有在多程序的場景下才需要考慮程序間通訊,畢竟“間”是對於至少兩個以上的主語來說的。多程序的情況一般分為兩種,一種情況是一個應用因為某些原因自身需要採用多程序模式實現,如有些模組由於特殊原因需要執行在單獨的程序中,或者為了加大一個應用可使用的記憶體所以需要通過多程序來獲取多份記憶體空間(Android 對單個應用的最大記憶體做了限制,早期一些版本一般為 16MB)。另一種情況是當前應用需要向其他應用獲取資料,由於是兩個應用,所以必須採用跨程序的方式來獲取需要的資料,當我們通過系統提供的 ContentProvider 去查詢資料時其實也是一種程序間通訊,只是通訊細節被系統遮蔽了。
如果採用了多程序的設計,那麼應用內就必須妥善處理程序間通訊帶來的各種問題。
2 Android 中的多程序模式
2.1 開啟多程序模式
多個應用自然是多程序,正常情況下,Android 中的多程序指的是一個應用中存在多個程序的情況。在 Android 中使用多程序的方法就是在 AndroidManifest.xml 中給四大元件指定 android:process 屬性,所以我們無法給一個執行緒或者實體類指定其執行時的程序。除此之外還有一種方法開啟多程序,就是通過 JNI 在 native 層 fork 一個新的程序,這屬於特殊情況。
下面演示開啟多程序:
<activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".SecondActivity" android:process=":remote" /> <activity android:name=".ThirdActivity" android:process="com.qinshou.demo1.remote" />
定義了三個 Activity,在 MainActivity 中啟動 SecondActivity,在 SecondActivity 中啟動 ThirdActivity,使用 adb shell "ps|grep com.qinshou.demo1" 檢視當前工程的程序:
可以看到 MainActivity 沒有指定程序名,所以它執行在預設程序中,預設程序名為包名,這三個程序的程序 id 分別為 8668、8698、8730,程序名也不同,說明我們成功開啟了多程序。
SecondActivity 和 ThirdActivity 中指定的 process 的方式是不一樣的,SecondActivity 為 ":remote",這是一種簡寫,它會在前面加上包名,所以 SecondActivity 的完整程序名為 "com.qinshou.demo1:remote"。ThirdActivity 為 "com.qinshou.demo1.remote",這就是完整的命名方式,不會在前面附加包名了。
使用 ":" 開頭簡寫指定程序名的程序屬於當前應用的私有程序,其他應用的元件不可以和它在同一個程序中。而使用完整命名的程序屬於全域性程序,其他應用可以通過 shareUID 的方式和它執行在同一程序。
兩個應用若想通過 shareUID 執行在同一程序也是有要求的,需要兩個應用有相同的 ShareUID 並且簽名相同。這樣一來它們之間可以互相訪問對方的私有資料,如 data 目錄、元件資訊等,如果在同一程序中甚至還可以共享記憶體資料。
2.2 多程序模式的執行機制
開啟多程序很簡單,只需要給四大元件指定 android:process 屬性即可,但是其實要處理的問題很多,一般來說,使用多程序會造成如下問題:
1)靜態成員和單例模式失效;
2)執行緒同步機制完全失效;
3)SharedPreferences 的可靠性下降;
4)Application 會多次建立。
雖然多程序可能導致的問題很多,但是仍然要正視它,為了解決這些問題系統提供很多跨程序通訊方法,雖然不能直接共享記憶體,但是可以跨程序傳遞資料,所以理解各種 IPC 方式是很重要的。
3 IPC 基礎概念介紹
IPC 主要包含三方面內容,Serializable 介面、Parcelable 介面及 Binder,Serializable 介面和 Parcelable 介面可以完成物件的序列化過程,當我們需要通過 Intent 或 Binder 傳遞資料時、需要把物件持久化到裝置上或者通過網路傳輸給其他客戶端時就需要使用 Parcelable 或者 Serializable。
3.1 Serializable 介面
Serializable 是 Java 提供的一個序列號介面,它跟 Cloneable 一樣只是一個標識介面,並沒有需要實現的方法,所以使用 Serializable 來實現物件的序列化非常簡單,只需要在類中宣告一個標識即可自動實現預設的序列化操作,看看示例程式碼:
public class Person implements Serializable {
private static final long serialVersionUID = 10000L;
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Person person1 = new Person("張三", 23);
Log.i("MainActivity", "person1.hashCode()--->" + person1.hashCode());
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
new FileOutputStream(
new File(MainActivity.this.getCacheDir() + "/Person.txt")
)
);
objectOutputStream.writeObject(person1);
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream objectInputStream = new ObjectInputStream(
new FileInputStream(
new File(MainActivity.this.getCacheDir() + "/Person.txt")
)
);
Person person2 = (Person) objectInputStream.readObject();
Log.i("MainActivity", person2.toString());
Log.i("MainActivity", "person2.hashCode()--->" + person2.hashCode());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
上面 Person 類實現了 Serializable 介面,並指定了一個 serialVersionUID 常量作為序列化的一個標識,然後使用 ObjectOutputStream 和 ObjectInputStream 即可儲存和恢復 Person 物件,恢復後的內容一樣,但是從 hashCode 可以看出它們倆並不是同一個物件。
事實上 serialVersionUID 並不是必須的,不宣告它同樣可以實現序列化,但這可能對反序列化過程產生影響,系統既然提供了這個 serialVersionUID 那麼它必然是有用的,它是用來輔助序列化和反序列化過程的,只有序列化後的資料中的 serialVersionUID 與當前類中的 serialVersionUID 相同才能被正常反序列化。
序列化的時候系統會將 serialVersionUID 寫入序列化的檔案(或者其他中介)中,當反序列化的時候系統會去檢測檔案中的 serialVersionUID,看它是否和目標類的 serialVersionUID 一致,如果一致就說明序列化的類的版本與當前目標類的版本是相同的,才能成功序列化,否則就說明序列化的類和當前目標類之間發生了某些變換,如成員變數的個數、型別發生了改變,這個時候就無法正常序列化了。
一般來說我們應該手動指定 serialVersionUID 的值,如果不手動指定,它會根據當前類的結構自動生成 hash 值並賦值給 serialVersionUID。當類結構發生改變時,hash 值肯定會發生變化,所以導致反序列化失敗,導致程式崩潰,所以手動指定 serialVersionUID 就能很大程度上避免反序列化過程的失敗,即使類發生了一些變化,如增減成員變數,它仍然能夠最大限度的恢復序列化的資料。但是如果類結構發生了非常規改變,如修改了類名、修改了成員變數的型別,儘管 serialVersionUID 驗證通過了,但是反序列化仍然會失敗。
需要注意的是靜態成員變數屬於類不屬於物件所以不會參與序列化過程,還有使用 transient 關鍵字修飾的成員變數也不會參與反序列化過程。
序列化和反序列化的過程也是可以修改的,只需要重寫兩個方法即可:
private void writeObject(java.io.ObjectOutputStream objectOutputStream) throws IOException {
objectOutputStream.defaultWriteObject();
}
private void readObject(java.io.ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
objectInputStream.defaultReadObject();
age = 25;
}
objectOutputStream.defaultWriteObject() 和 objectInputStream.defaultReadObject() 表示呼叫預設的序列化和反序列化過程,如果這兩句都不寫則序列化和反序列化的都是空物件了,在反序列化的時候又把 age 給修改了一下,設定為 25 了,列印如下:
可以看到自定義的修改起了作用,序列化的 age 是 23,反序列化出來變成 25 了。
3.2 Parcelable 介面
Parcelable 同 Serializable 一樣可以實現序列化並都可以用於 Intent 之間傳遞資料,Serializable 是 Java 的序列化介面,而 Parcelable 是 Android 特有的序列化方式,它比起 Serializable 效果要高很多,兩者效能相比 Parcelable 要高約 10 倍,但是使用也比較麻煩一點。Parcelable 主要用於記憶體序列化上,如果要將儲存到裝置中或者網路傳輸則過程會更復雜,所以這兩種情況還是推薦使用 Serializable 介面。
使用 Parcelable 介面實現序列化時類的寫法如下:
public class Person implements Parcelable {
private String name;
private int age;
private int sex; // 0 表示男,1 表示女
public Person() {
}
public Person(String name, int age, int sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
dest.writeInt(sex);
}
//成員內部類名必須為 CREATOR
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
@Override
public Person createFromParcel(Parcel source) {
Person person = new Person();
person.name = source.readString();
person.age = source.readInt();
person.sex = source.readInt();
return person;
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
實現 Parcelable 介面後需要重寫 describeContents() 方法,該方法表示內容描述,通常都是返回 0;還需要重寫 writeToParcel(Parcel dest, int flags) 方法,該方法表示序列化過程,內部由一系列 wirte 方法完成;還需要定義一個 Parcelable.Creator<T> 成員內部類,類名必須為 CREATOR,它的內部實現反序列化過程,由一系列 read 方法完成。
Android 中有很多類已經實現了 Parcelable 介面,這表明它們都是可序列化的,還有 List 和 Map 也是可以序列化的,前提是它們中的元素也必須是可序列化的。
3.3 Binder
Binder 剛開始看著比較複雜就跳過了,在後面看完 AIDL 後又回過頭來看它的,所以這裡的記錄會和後面的有些重複。
Binder 是一個很深入的話題,它的底層非常複雜。書中主要介紹了 Binder 的使用和上層原理,Binder 其實就是 Android 的一個類,它實現了 IBinder 介面。從 IPC 的角度來說,Binder 是一種跨程序通訊方式。Binder 也可以理解為一種虛擬物理裝置,它的裝置驅動是 /dev/binder,該通訊方式是 Android 特有的,在 Linux 上沒有。從 Framework 角度來說,Binder 是 ServiceManager 連線各種 Manager(ActivityManager、WindowManager 等)相應 ManagerService 的橋樑。從 Android 應用層來說,Binder 是客戶端和服務端通訊的媒介,當呼叫 bindService() 方法時,服務端會返回一個包含了服務端業務呼叫的 Binder 物件,通過這個 Binder 物件,客戶端就可以獲取服務端的資料或者服務端提供的服務(包括普通服務和 AIDL 服務)。
Binder 主要用在 Service 中,包括 AIDL 和 Messenger,普通服務不涉及程序間通訊所以較為簡單。Messenger 底層實現其實就是 AIDL,所以使用 AIDL 來分析 Binder 的工作機制。
新建一個 Book 類:
public class Book implements Parcelable {
private int id;
private String bookName;
public Book() {
}
public Book(int id, String bookName) {
this.id = id;
this.bookName = bookName;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", bookName=" + bookName +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(bookName);
}
public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel source) {
Book book = new Book();
book.id = source.readInt();
book.bookName = source.readString();
return book;
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
}
然後新建 Book.aidl:
package com.qinshou.ipc;
parcelable Book;
IBookManager.adil:
package com.qinshou.ipc;
import com.qinshou.ipc.Book;
interface IBookManager{
List<Book> getBookList();
void addBook(in Book book);
}
它們的結構應該是這樣的:
Book 是一個表示圖書資訊的類,它實現了 Parcelable 介面,Book.aidl 是表示要在 AIDL 中使用 Book 類的宣告,只要 AIDL 中要使用到一個 Java 類都要建立一個與之對應的 aidl 宣告檔案。IBookManager.aidl 是我們定義的一個介面,裡面有兩個方法 getBookList() 和 addBook(),在 IBookManager.aidl 中可以使用到的實現了 Parcelable 介面的類(這裡是 Book 類)必須顯式 import 進來,即使它們和當前的 AIDL 檔案處於同一個包內。然後系統就會根據 IBookManager.aidl 自動生成 Binder 類。在 Android Studio 中是在 app-->build-->generated-->source-->aidl-->debug-->包名下,如果沒有的話可以 Make Project 一下。
IBookManager.java(自動生成的):
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.qinshou.ipc.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.qinshou.ipc.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.qinshou.ipc.IBookManager interface,
* generating a proxy if needed.
*/
public static com.qinshou.ipc.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.qinshou.ipc.IBookManager))) {
return ((com.qinshou.ipc.IBookManager) iin);
}
return new com.qinshou.ipc.IBookManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(descriptor);
java.util.List<com.qinshou.ipc.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(descriptor);
com.qinshou.ipc.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.qinshou.ipc.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.qinshou.ipc.IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public java.util.List<com.qinshou.ipc.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.qinshou.ipc.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.qinshou.ipc.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.qinshou.ipc.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.qinshou.ipc.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.qinshou.ipc.Book book) throws android.os.RemoteException;
}
該類是一個介面,繼承自 IInterface,所有可以在 Binder 中傳輸的介面都需要繼承自 IInterface。可以看到在末尾有我們在 aidl 檔案中定義的兩個方法,其他的東西雖然命名看起來很亂,但是其實邏輯很清晰,在末尾有兩個靜態常量標識我們定義的方法,表示 id,用於在 transact 過程中確定客戶端請求的到底是哪個方法。然後有一個內部類 Stub,它其實就是一個 Binder 類,當客戶與服務端位於同一程序時,它們之間方法的呼叫不會走跨程序的 transact 過程,如果客戶端與服務端位於不同程序,方法的呼叫則需要走 transact 過程,這個邏輯由 Stub 的內部代理類 Proxy 完成。所以這個介面的核心實現就是內部類 Stub 和 Stub 的內部代理類 Proxy。下面瞭解一下該介面中各個方法和變數的含義:
DESCRIPTOR
Binder 的唯一標識,一般用當前 Binder 的類名錶示。
asInterface(android.os.IBinder obj)
用於將服務端的 Binder 物件轉換成客戶端所需要的 AIDL 介面型別的物件,該轉換過程是區分單程序和多程序的,如果客戶端與服務端位於同一程序,則返回服務端的 Stub 物件本身,如果客戶端與服務端位於不同程序則返回系統封裝後的 Stub.Proxy 物件。
asBinder()
返回當前 Binder 物件。
onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
該方法執行在服務端中的 Binder 執行緒池中,當客戶端每次發起跨程序請求時,遠端請求會通過系統底層封裝後交給此方法處理,通過 code 來確定客戶端請求的目標方法,然後從 data 中取出目標方法所需要的引數(如果目標方法需要引數的話),然後執行目標方法。當目標方法執行完成後就向 reply 中寫入返回值(如果目標方法有返回值的話)。如果該方法返回 false,那麼客戶端的請求就會失敗,利用這一特性可以來做許可權驗證,畢竟我們也不想隨便一個程序都能呼叫我們的方法。
Proxy#getBookList()
該方法執行在客戶端,當客戶端遠端呼叫該方法時,會首先建立該方法所需要的輸入型 Parcel 物件 _data、輸出型 Parcel 物件 _reply 和返回值物件 List,然後把引數資訊寫入 _data 中(該方法沒有,下面的 addBook() 方法就有 Book 引數),接著呼叫 transact 方法來完成 RPC(遠端過程呼叫)請求,同時將當前執行緒掛起,然後服務端的 onTransact() 方法會被呼叫,直到 RPC 過程結束返回後,才繼續執行當前執行緒,並從 _reply 中取出 RPC 過程的返回結果,最後返回 _reply 中的資料(如果有返回值的話,該方法是有的,下面的 addBook() 方法就沒有)。
Proxy#addBook(com.qinshou.ipc.Book book)
原理同 Proxy#getBookList() 一樣,只是該方法有引數,沒有返回值而已。
通過分析這些方法,可以說已經很清晰的瞭解了 Binder 的工作機制,再來一張圖說明一下:
需要注意的兩點:
1)因為客戶端發起遠端請求時,當前執行緒會被掛起直到服務端程序返回資料,所以如果一個遠端方法是耗時方法,那麼客戶端發起該遠端請求時就不能在 UI 執行緒中。
2)服務端的 Binder 方法會執行在 Binder 執行緒池中,所以 Binder 方法無論是否耗時都應該採用同步的方式實現。
通過分析 Binder 的工作機制後,我們其實可以不用寫 AIDL 檔案就可以自己手寫 Binder,所以說 AIDL 檔案其實就是用來幫助我們生成 Binder 類的,這裡我就沒有去研究手寫 Binder 的過程了,這只是為了讓我們更加理解 Binder 而已,一般情況下是不會去手寫 Binder 的,
Binder 還有兩個重要方法 linkToDeath() 和 unlinkToDeath()。Binder 是執行在服務端程序中的,如果服務端程序異常終止,這時客戶端與服務端的 Binder 連線就會斷裂,也稱為 Binder 死亡,這時候再去呼叫服務端的遠端方法就會失敗,如果客戶端不知道 Binder 死亡的話那麼客戶端的功能就會受到影響。所以 Binder 提供了兩個配對的方法 linkToDeath() 和 unlinkToDeath(),用來設定和解綁 Binder 的死亡監聽。如果客戶端通過 linkToDeath() 方法為 Binder 設定了死亡代理,那麼在 Binder 死亡時客戶端就會收到通知。
具體操作如下:
定義一個 DeathRecipient 物件:
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
/**
* Create By:禽獸先生
* Create On:2019/1/5 0:46
* Description: 當 Binder 死亡時就會回撥 binderDied() 方法,我們就可以在該方法中解綁
* 之前的 Binder 死亡代理並重新繫結遠端服務
*/
@Override
public void binderDied() {
if (mBookManager == null) {
return;
}
//取消死亡監聽
mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
//重置 IBookManager
mBookManager = null;
Log.i("Demo1", "binderDied,Thread--->" + Thread.currentThread().getName());
//TODO 在下面重新繫結遠端服務
}
};
然後在客戶端繫結遠端服務成功時設定死亡代理即可:
//將服務端返回的 Binder 物件轉換成 AIDL 介面所屬的型別
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
//設定死亡監聽
service.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
linkToDeath() 方法的第二個引數是個標記為,一般設為 0 即可。除了設定死亡代理,我們也可以呼叫 Binder 的 isBinderAlive() 方法主動判斷 Binder 是否死亡。
Binder 看完後,IPC 的基礎知識就完了,感覺 Binder 看第一眼感覺很複雜,但是其實搞清楚工作原理後邏輯還是很清晰的,畢竟程式碼量不多,只是變數命名方式看著不那麼習慣。
4 Android 中的 IPC 方式
跨程序通訊方式有很多,如在 Intent 中附加 extras 來傳遞資訊,或者通過共享檔案的方式來共享資料,或者使用 Binder 方式來跨程序通訊,或者使用 ContentProvider(它本身就支援跨程序訪問),或者通過網路通訊也是可以實現資料傳遞的。實現 IPC 的方式很多,當時它們在使用方法和側重點上都有區別。
4.1 使用 Bundle
四大元件中的三大元件(Activity、Service、BroadcastReceiver)都支援在 Intent 中傳遞 Bundle 資料,由於 Bundle 實現了 Parcelable 介面,所以它可以很方便的在不同程序間傳輸。所以當我們在一個程序中啟動另一個程序的 Activity、Service、Receiver 時就可以在 Bundle 中附加需要傳輸的資料。當然這些資料必須是它支援的,如基本型別、實現了 Parcelable 介面的物件、實現了 Serializable 介面的物件等。
使用 Bundle 是最簡單的一種程序間通訊方式,下面是 Demo 演示:
Demo1,:
package com.qinshou.demo1;
import android.content.ComponentName;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.qinshou.demo2", "com.qinshou.demo2.MainActivity"));
intent.putExtra("name", "張三");
intent.putExtra("age", 23);
startActivity(intent);
}
}
Demo2:
package com.qinshou.demo2;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = getIntent();
Log.i("Demo2MainActivity", "name--->" + intent.getStringExtra("name"));
Log.i("Demo2MainActivity", "age--->" + intent.getIntExtra("age", 0));
}
}
這裡有兩個工程,一個叫 Demo1,一個叫 Demo2,在 Demo1 的 MainActivity 中啟動了 Demo2 的 MainActivity 並傳輸了一些資料給 Demo2 的 MainActivity,執行後列印如下:
可以看到 Demo2 中成功接收到了 Demo1 傳遞的資料。
4.2 使用檔案共享
共享檔案也是一種不錯的程序間通訊方式,兩個程序通過讀寫同一個檔案來交換資料,如 Demo1 程序將資料寫到一個檔案中,Demo2 程序來讀取該檔案獲取資料,Android 對於併發讀寫檔案沒有限制,甚至多個程序同時寫同一個檔案都是可能的,所以這有可能出現併發問題,但是通過檔案交換資料仍然是一種方式,使用很簡單,除了交換一些文字資訊之外也交換序列化物件的資料,下面是 Demo 演示:
Demo1 中的 Person 類:
package com.qinshou.ipc;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.Serializable;
/**
* Create By:禽獸先生
* Create On:2018-12-31 23:50
* Description:
*/
public class Person implements Serializable {
private static final long serialVersionUID = 10000L;
private String name;
private int age;
private int sex; // 0 表示男,1 表示女
public Person() {
}
public Person(String name, int age, int sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
Person 類還是那個 Person 類,實現了 Serializable 介面,但是請注意它的包名,這樣 Demo1 中的 Person 類的全限定名為 com.qinshou.ipc.Person。
Demo1 中的 MainActivity:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PermissionUtil.requestPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE, new OnRequestPermissionResultCallBack() {
@Override
public void onSuccess() {
new Thread(new Runnable() {
@Override
public void run() {
ObjectOutputStream objectOutputStream = null;
try {
Person person = new Person("張三", 23, 0);
File cacheDir = new File(Environment.getExternalStorageDirectory() + "/Cache");
if (!cacheDir.exists()) {
cacheDir.mkdir();
}
File file = new File(cacheDir + "/cache.txt");
objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
objectOutputStream.writeObject(person);
Log.i("Demo1MainActivity", "person--->"+person.toString());
Log.i("Demo1MainActivity", "hashcode--->"+person.hashCode());
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (objectOutputStream != null) {
objectOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
public void onError(List<String> deniedPermissionList) {
}
});
}
}
MainActivity 中建立了一個 Person 物件,並存儲到 SD 卡下的 Cache 目錄,檔名為 cache.txt。
Demo2 中的 Person 類:
package com.qinshou.ipc;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.Serializable;
/**
* Create By:禽獸先生
* Create On:2018-12-31 23:50
* Description:
*/
public class Person implements Serializable {
private static final long serialVersionUID = 10000L;
private String name;
private int age;
private int sex; // 0 表示男,1 表示女
public Person() {
}
public Person(String name, int age, int sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
注意它的包名與 Demo1 中的包名一樣,所以全限定名也是 com.qinshou.ipc.Person。
Demo2 中的 MainActivity:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PermissionUtil.requestPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE, new OnRequestPermissionResultCallBack() {
@Override
public void onSuccess() {
new Thread(new Runnable() {
@Override
public void run() {
ObjectInputStream objectInputStream = null;
try {
File cacheDir = new File(Environment.getExternalStorageDirectory() + "/Cache");
if (!cacheDir.exists()) {
cacheDir.mkdir();
}
File file = new File(cacheDir + "/cache.txt");
objectInputStream = new ObjectInputStream(new FileInputStream(file));
Person person = (Person) objectInputStream.readObject();
Log.i("Demo2MainActivity", "person--->" + person.toString());
Log.i("Demo2MainActivity", "hashcode--->" + person.hashCode());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (objectInputStream != null) {
objectInputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
@Override
public void onError(List<String> deniedPermissionList) {
}
});
}
}
MainActivity 中從 SD 卡下的 Cache 目錄讀取 cache.txt 檔案中的資料,並轉換成 Person 物件。
先執行 Demo1,然後執行 Demo2,執行列印如下:
可以看到在 Demo2 中成功恢復了資料,但是 hashCode 並不一樣,說明這兩個並不是同一個物件。通過檔案共享這種方式來共享資料對檔案格式是沒有要求的,可以是 txt 文字檔案,也可以是 xml 檔案,只要讀寫雙方約定好資料格式即可,需要注意的是,如果是儲存自定義的物件的時候,兩個程序的物件的全限定名必須一樣,不然會導致強制轉換錯誤。
使用檔案共享也是有侷限性的,比如併發性問題,如果併發讀,那麼我們可能讀取出來的資料不是最新的,如果是併發寫就嚴重了,會導致儲存資料異常,所以我們要儘量避免併發讀寫的情況,或者考慮執行緒同步來限制多執行緒的寫操作。所以檔案共享這種程序間通訊方式適用於對資料同步要求不高的程序間通訊。
4.3 使用 Messenger
Messenger 可以理解為信使,通過它可以在不同程序中傳遞 Message 物件,因為 Message 中可以攜帶 Bundle,所以可以藉助 Message 可以實現程序間的資料傳遞。
Messenger 是一種輕量級的 IPC 方案,它的底層是由 AIDL 實現。Message 的使用方法很簡單,它對 AIDL 進行了封裝,而且它一次只處理一個請求,所以在服務端不用考慮執行緒同步的問題,實現 Messenger 的基本實現步驟主要步驟如下:
1)服務端程序
在服務端建立一個 Service 來處理客戶端的連線請求,同時建立一個 Handler 並通過它來建立一個 Messenger 物件,然後在 Service 的 onBind() 方法中返回這個 Messenger 的物件的 Binder 即可。
2)客戶端程序
在客戶端繫結服務端的 Service,繫結成功後用服務端返回的 IBinder 物件建立一個 Messenger,通過這個 Messenger 就可以向服務端傳送訊息了,傳送的訊息為 Message 物件。如果需要服務端迴應客戶端,就需要像服務端一樣建立一個 Handler 接收回傳訊息,並通過這個 Handler 建立 Messenger 物件,然後將這個 Messenger 物件通過 Message 的 replyTo 引數傳遞給服務端,服務端就可以通過這個 replyTo 引數的 Messenger 傳送回傳訊息給客戶端。
用程式碼來說明吧,下面是 Demo 演示:
Demo2 為服務端,建立一個 MessengerService:
public class MessengerService extends Service {
//處理客戶端傳送的訊息
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
//獲取客戶端傳送的訊息
String message = msg.getData().getString("msg");
Log.i("Demo2", "message--->" + message);
//獲取客戶端接受回傳訊息的 Messenger
Messenger messenger = msg.replyTo;
//建立回傳訊息
Message replyMessage = Message.obtain(null, 1);
Bundle bundle = new Bundle();
bundle.putString("msg", "你好張三,我是李四,請問有什麼可以幫你的。");
replyMessage.setData(bundle);
try {
//傳送回傳訊息
messenger.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
break;
}
}
}
//建立一個與 MessengerHandler 的 Messenger
private Messenger mMessenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
//返回 Messenger 中的 Binder
return mMessenger.getBinder();
}
}
在 AndroidManifest.xml 中註冊該服務並支援其他程序呼叫:
<service
android:name=".MessengerService"
android:enabled="true"
android:exported="true"/>
Demo1 為客戶端:
public class MainActivity extends AppCompatActivity {
//處理服務端回傳的訊息
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
//獲取服務端回傳的訊息
String message = msg.getData().getString("msg");
Log.i("Demo1", "message--->" + message);
default:
super.handleMessage(msg);
break;
}
}
}
//建立一個與 MessengerHandler 的 Messenger
private Messenger mMessenger = new Messenger(new MessengerHandler());
//與服務端 Service 連線的 Connection
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i("Demo1", "onServiceConnected");
//連線成功之後根據服務端返回的 binder 物件建立 Messenger 物件
Messenger messenger = new Messenger(service);
//建立傳送訊息
Message message = Message.obtain();
message.what = 0;
Bundle bundle = new Bundle();
bundle.putString("msg", "你好,我是張三,收到請回答。");
message.setData(bundle);
//設定傳送訊息中接收回傳訊息的 Messenger
message.replyTo = mMessenger;
try {
//傳送訊息
messenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i("Demo1", "onServiceDisconnected");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.qinshou.demo2", "com.qinshou.demo2.MessengerService"));
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mServiceConnection != null) {
unbindService(mServiceConnection);
}
}
}
先執行 Demo2 然後再執行 Demo1,列印結果如下:
Messenger 跨程序通訊的工作原理圖如下:
4.4 使用 AIDL
上面說了 Messenger 是對 AIDL 的一種封裝,那麼為什麼還要學習使用 AIDL 進行跨程序通訊呢?這是因為 Messenger 是序列的,如果有大量的訊息同時傳送到服務端,則服務端仍只能一個個的處理,這時候用 Messenger 就不太合適了,而且 Messenger 的主要作用是傳遞訊息,如果需要跨程序呼叫服務端的方法,這時候 Messenger 也是無法做到的,所以就需要 AIDL 了。它的使用也是主要分為服務端和客戶端。
1)服務端
服務端首先建立一個 AIDL 檔案,將需要暴露給客戶端的介面在這個 AIDL 檔案中宣告,然後需要建立一個 Service 用來監聽客戶端的連線請求,最後在 Service 中實現這個 AIDL 即可。
2)客戶端
客戶端需要繫結服務端的 Service,繫結成功後將服務端返回的 Binder 物件轉成 AIDL 介面所屬的型別,然後就可以呼叫 AIDL 中的方法了。
文字仍然很抽象,上程式碼:
仍然是以 Demo2 作為服務端,首先宣告一個可序列化的 Book 物件:
public class Book implements Parcelable {
private int id;
private String bookName;
public Book() {
}
public Book(int id, String bookName) {
this.id = id;
this.bookName = bookName;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", bookName=" + bookName +
'}';
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(bookName);
}
public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel source) {
Book book = new Book();
book.id = source.readInt();
book.bookName = source.readString();
return book;
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
}
建立字尾為 AIDL 檔案 IBookManager.aidl,在裡面宣告一個介面和兩個介面方法:
package com.qinshou.ipc;
import com.qinshou.ipc.Book;
interface IBookManager{
List<Book> getBookList();
void addBook(in Book book);
}
由於 IBookManager 中使用到了 Book 物件,所以需要建立一個 Book.aidl 表明它是 Parcelable 型別:
package com.qinshou.ipc;
parcelable Book;
建立一個 BookManagerService 用於給客戶端連線,並在其中實現 IBookManager 這個介面:
public class BookManagerService extends Service {
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
Log.i("Demo2", "有客戶端呼叫 getBookList() 方法,mBookList.size()--->" + mBookList.size());
Log.i("Demo2","mBookList--->" + mBookList.toString());
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
Log.i("Demo2", "有客戶端呼叫 addBook() 方法,book--->" + book);
mBookList.add(book);
}
};
public BookManagerService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.i("Demo2", "mBookList--->" + mBookList.getClass().getCanonicalName());
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "Ios"));
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
如果不能匯入 IBookManager 的話可以在寫完 AIDL 後可以 Make Project 一下,最後在 AndroidManifest.xml 中註冊該服務並支援其他程序呼叫:
<service
android:name=".BookManagerService"
android:enabled="true"
android:exported="true"/>
在 AIDL 檔案中並不是所有的資料型別都可以使用,它支援的資料型別如下:
1)基本資料型別:byte、short、int、long、float、double、boolean、char;
2)String 和 CharSequence;
3)List:只支援 ArrayList,而且裡面的每個元素都必須是 AIDL 支援的型別;
4)Map:只支援 HashMap,而且裡面的 key 和 value 都必須是 AIDL 支援的型別;
5)Parcelable:所有實現了 Parcelable 介面的物件;
6)AIDL:所有的 AIDL 介面本身也可以在 AIDL 檔案中使用;
需要注意的幾點:
1)自定義的實現了 Parcelable 介面的物件和 AIDL 物件必須顯式 import 進來,即使它們和當前的 AIDL 檔案處於同一個包內。
2)如果 AIDL 檔案中用到了自定義的實現了 Parcelable 介面的物件,則必須新建一個和它同名的 AIDL 檔案,在該檔案中宣告它為 Parcelable 型別,如上面的 Book.aidl。
3)AIDL 中除了基本資料型別,其他型別的引數都必須標上方向:in(資料只能由客戶端流向服務端)、out(資料只能由服務端流向客戶端)或者 inout(資料可在服務端與客戶端之間雙向流通),這要根據實際需要去選擇,不能一概使用 inout,因為這在底層實現是有開銷的。
4)AIDL 介面中只支援方法,不支援宣告靜態常量。
服務端和客戶端的包結構必須保持一致,不然會導致反序列化失敗。為了方便 AIDL 開發,建議把所有與 AIDL 相關的類和檔案全部放入一個包中,這樣當需要接入一個新的客戶端時只需要將這個包複製到客戶端工程下即可,但是其實 Java 類和 AIDL 介面等不能在同一個包下,所以複製到客戶端時其實需要複製兩個包。
Demo2 包結構如下:
到時候只需要將 aidl 和 java 中的 ipc 複製到客戶端即可,到時候 Demo1 的包結構如下:
接下來實現客戶端 Demo1,客戶端比較簡單,只需要在 MainActivity 中繫結服務端的 Service,然後將服務端返回的 Binder 物件轉成 AIDL 介面所屬的型別就可以呼叫 AIDL 中的方法了:
public class MainActivity extends AppCompatActivity {
//與服務端 Service 連線的 Connection
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//將服務端返回的 Binder 物件轉換成 AIDL 介面所屬的型別
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
//儲存 IBookManager
mBookManager = bookManager;
//呼叫服務端方法
List<Book> bookList = bookManager.getBookList();
Log.i("Demo1", bookList.getClass().getCanonicalName());
Log.i("Demo1", bookList.toString());
//呼叫服務端方法
bookManager.addBook(new Book(3, "Android 開發藝術探索"));
List<Book> newBookList = bookManager.getBookList();
Log.i("Demo1", newBookList.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.setComponent(new ComponentName("com.qinshou.demo2", "com.qinshou.demo2.BookManagerService"));
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
//解綁服務
unbindService(mServiceConnection);
}
}
先執行 Demo2 再執行 Demo1 後列印結果如下:
列印中可以看到,服務端雖然使用了 CopyOnWriteArrayList,但是傳輸到客戶端後為 ArrayList,這說明 AIDL 只能使用 ArrayList,也說明了客戶端成功呼叫了服務端的方法。
4.4.1 擴充套件一——註冊監聽,觀察者模式
假如有一種需求,使用者不想時不時的查詢圖書列表,那樣太累了,而是在希望圖書館在有新書到來時通知他,這是一種典型的觀察者方式,那麼應該怎麼做呢?
首先需要提供一個 AIDL 介面 IOnNewBookArrivedListener.aidl,每個客戶端都可以實現該介面表示註冊一個監聽,開啟新書到來時的提醒功能,當然也可以取消該監聽。這裡需要 AIDL 介面是因為上面說到了 AIDL 中只能使用實現了 Parcelable 的物件,但是 AIDL 介面在 AIDL 檔案中都可以使用。
提醒功能用程式來說就是每當有新書新增時,就回調所有 IOnNewBookArrivedListener 中的 onNewBookArrived 方法,並將新書 book 作為引數回撥。
增加 IOnNewBookArrivedListener.aidl:
package com.qinshou.ipc;
import com.qinshou.ipc.Book;
interface IOnNewBookArrivedListener{
void onNewBookArrived(in Book newBook);
}
在 IBookManager.aidl 中增加註冊監聽和登出監聽的介面:
package com.qinshou.ipc;
import com.qinshou.ipc.Book;
import com.qinshou.ipc.IOnNewBookArrivedListener;
interface IBookManager{
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookArrivedListener onNewBookArrivedListener);
void unregisterListener(IOnNewBookArrivedListener onNewBookArrivedListener);
}
BookManagerService 中實現 registerListner 和 unregisterListener 介面,並開啟一個執行緒模擬增加新書的情景,在增加新書時回撥所有 IOnNewBookArrivedListener 的 onNewBookArrived 方法:
public class BookManagerService extends Service {
private AtomicBoolean mIsServiceDestroy = new AtomicBoolean(false);
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private CopyOnWriteArrayList<IOnNewBookArrivedListener> mOnNewBookArrivedListenerList = new CopyOnWriteArrayList<>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
Log.i("Demo2", "有客戶端呼叫 getBookList() 方法,mBookList.size()--->" + mBookList.size());
Log.i("Demo2","mBookList--->" + mBookList.toString());
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
Log.i("Demo2", "有客戶端呼叫 addBook() 方法,book--->" + book);
mBookList.add(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener onNewBookArrivedListener) throws RemoteException {
Log.i("Demo2", "有客戶端呼叫 registerListener() 方法,onNewBookArrivedListener--->" + onNewBookArrivedListener);
if (!mOnNewBookArrivedListenerList.contains(onNewBookArrivedListener)) {
mOnNewBookArrivedListenerList.add(onNewBookArrivedListener);
Log.i("Demo2", "註冊監聽成功");
}else{
Log.i("Demo2", "該 Listener 已存在,註冊監聽失敗");
}
}
@Override
public void unregisterListener(IOnNewBookArrivedListener onNewBookArrivedListener) throws RemoteException {
Log.i("Demo2", "有客戶端呼叫 unregisterListener() 方法,onNewBookArrivedListener--->" + onNewBookArrivedListener);
if (mOnNewBookArrivedListenerList.contains(onNewBookArrivedListener)) {
mOnNewBookArrivedListenerList.remove(onNewBookArrivedListener);
Log.i("Demo2", "登出監聽成功");
}else{
Log.i("Demo2", "該 Listener 不存在,登出監聽失敗");
}
}
};
public BookManagerService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.i("Demo2", "mBookList--->" + mBookList.getClass().getCanonicalName());
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "Ios"));
new Thread(new ServiceWorker()).start();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onDestroy() {
super.onDestroy();
mIsServiceDestroy.set(true);
}
/**
* Create By:禽獸先生
* Create On:2019/1/2 23:07
* Description: 開啟一個執行緒模擬增加新書的場景
*/
private class ServiceWorker implements Runnable {
@Override
public void run() {
//每過 5s 增加一本書
while (!mIsServiceDestroy.get()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBookList.size() + 1;
Book newBook = new Book(bookId, "新書" + bookId);
try {
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
for (int i = 0; i < mOnNewBookArrivedListenerList.size(); i++) {
IOnNewBookArrivedListener onNewBookArrivedListener = mOnNewBookArrivedListenerList.get(i);
if (onNewBookArrivedListener != null) {
onNewBookArrivedListener.onNewBookArrived(book);
}
}
}
}
客戶端 Demo1 修改一下實現:
public class MainActivity extends AppCompatActivity {
//定義一個變數儲存 IBookManager,用於程式銷燬時登出觀察者監聽
private IBookManager mBookManager;
//與服務端 Service 連線的 Connection
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {