Android進程間通信機制(一)——基礎篇
在正式瞭解Android的IPC機制之前我們瞭解以下幾個問題:
- 什麼是進程間通信
- Android中一個應用實現多進程的方式
- IPC基礎:Serializable接口、Parcelable接口和Binder
一、什麼是進程間通信
進程間通信(Inner-Process Comunication,簡稱IPC),就是指不同進程之間的信息傳遞。
進程是系統進行資源分配和調度的基本單位,是操作系統的結構的基礎;一個應用至少有一個進程(當然也可以擁有多個進程),一個進程中有包含了多個線程(線程是CPU調度的最小單位),進程相當於是線程的容器,線程可以使用操作系統分配個進程的資源。
IPC機制是現代操作系統都存在的機制,而Android是基於Linux內核的OS,在Android中特有的方式Binder。
二、Android中創建多進程
Android中在一個應用中創建多個進程的方式只有一種:在AndroidManifest.xml文件中申明四大組件的標籤增加*“android:process=”""*屬性即可。如下所示:
1.私有進程
<service
android:name=".service.MyAIDLService"
android:enabled="true"
android:exported="true"
android:process=":aidl_test" />
2.全局進程
<service
android:name=".service.MyAIDLService"
android:enabled="true"
android:exported="true"
android:process="com.kanlulu.aidl:aidl_test_test" />
通過第一種方式創建的多進程以":“開頭的”:aidl_test“(省略了主進程它的全稱爲"com.kanlulu.aidl_test:aidl_test”) 是私有進程,其他應用組件不能和它運行在同一個進程中。
通過第二種寫了完整名稱的方式"com.kanlulu.aidl:aidl_test_test"是全局進程,可以使用相同ShareUID的方式運行在同一進程中(簽名也需要一樣)。
使用多進程可以帶來的好處:
- 創建一個新的進程,可以獲得更多的內存空間;
- 主進程被殺死後,子進程任然可以運行(推送);
- 子進程崩潰,主進程可以正常運行;
- …
使用多進程也會帶來一些問題和需要注意事項:
- 1.靜態成員和單例模式完全失效;
- 2.線程同步機制會完全失效;
- 3.SharedPreferenced的可靠性會降低;
- 4.Application會多次創建。
首先要說明的是同一個應用中創建的子進程在內存中的地址和主進程是不一樣的,所以第1條中靜態成員在不同進程間修改的其實是位於不同內存地址的副本,單例模式也是一樣的。第2條原因和第一條是類似的;至於第3條,SharedPreferenced的併發訪問會導致不可預料的錯誤。另外需要注意第四條,創建子進程時也會重新創建一次Application。
三、序列化Serializable接口和Parcelable接口
序列化是指將運行時的對象轉換成二進制,然後保存到流、內存或者通過網絡傳輸給其他端。
1、Serializable接口
Serializable接口是Java提供的序列化接口。
public interface Serializable {
}
下面是一個實現序列化接口的實體類:
package com.kanlulu.aidl_test.bean;
import java.io.Serializable;
public class Persion implements Serializable {
private String name;
private int age;
private String desc;
private static final long serialVersionUID = 8829975621220483374L;
public Persion() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
我們只需要實現Serializable
接口即可,另外還需要注意serialVersionUID屬性;這個屬性不是必須要創建的。
serialVersionUID:
簡單來說它的作用就是用來序列化時的校驗。只有當我們序列化前的serialVersionUID和我們反序列化是的serialVersionUID是一致的,反序列化才能成功,否則會報錯:InvalidClassException
。
如果我們不自己創建serialVersionUID,但我們在反序列化時更改了對象屬性名稱或類型時就會導致反序列化失敗。
serializable的序列化和反序列化分別通過ObjectOutputStream
和ObjectInputStream
來實現的,代碼如下:
package com.kanlulu.aidl_test.utils;
import android.text.TextUtils;
import android.util.Log;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerialUtils {
private static final String TAG = "SerialUtils";
/**
* 序列化
*
* @param object 序列化對象
* @param path 序列化對象存儲路徑
* @return
*/
public synchronized static boolean saveObject(Object object, String path) {
if (object == null || TextUtils.isEmpty(path)) {
return false;
}
ObjectOutputStream outputStream = null;
try {
outputStream = new ObjectOutputStream(new FileOutputStream(path));
outputStream.writeObject(object);
outputStream.close();
return true;
} catch (IOException e) {
Log.e(TAG, e.getMessage());
} finally {
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
* @param path
* @param <T>
* @return
*/
@SuppressWarnings("unchecked")
public synchronized static <T> T readObject(String path) {
if (TextUtils.isEmpty(path)) {
return null;
}
ObjectInputStream inputStream = null;
Object object = null;
try {
inputStream = new ObjectInputStream(new FileInputStream(path));
object = inputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
Log.e(TAG, e.getMessage());
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return (T) object;
}
}
2、Parcelable接口
Parcelable接口是Android所特有的序列化接口,在序列化中原始對象會被轉換成Parcel對象;Android對於Parcel描述:
Container for a message that can be sent through an IBinder.
可以通過IBinder發送的消息的容器。
package com.kanlulu.aidl_test.bean;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by kanlulu
* DATE: 2018/11/30 15:54
*/
public class Animal implements Parcelable {
private String name;
private int age;
private String desc;
public Animal() {
}
/**
* 需要自己創建包含全部屬性的構造方法
*/
public Animal(String name, int age, String desc) {
this.name = name;
this.age = age;
this.desc = desc;
}
/**
* 自動生成的
*/
protected Animal(Parcel in) {
name = in.readString();
age = in.readInt();
desc = in.readString();
}
/**
* 自動生成的
* 反序列化
*/
public static final Creator<Animal> CREATOR = new Creator<Animal>() {
@Override
public Animal createFromParcel(Parcel in) {
return new Animal(in);
}
@Override
public Animal[] newArray(int size) {
return new Animal[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "Animal{" +
"name='" + name + '\'' +
", age=" + age +
", desc='" + desc + '\'' +
'}';
}
@Override
public int describeContents() {
//幾乎都返回 0,除非當前對象中存在文件描述符時爲 1
return 0;
}
/**
* 序列化
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
dest.writeString(desc);
}
}
Serializable和Parcelable的區別
1.在使用的便捷性上來說,Serializable只需要實現該接口後額外創建一個serialVersionUID屬性即可,而使用Parcelable接口需要實現四個方法,比較繁瑣。
2.通常如果我們需要保存數據到SD卡,或者需要進行網絡傳輸數據,建議使用Serializable接口。
3.如果我們需要傳遞內存中的數據,建議使用Parcelable。
四、什麼是Binder
Android中有多種IPC機制,如AIDL、Messager、ContentProvider。而這些IPC機制的底層都是用Binder來實現的。
Binder的設計使用的是Client-Server結構,客戶端進程通過獲取服務端進程的代理,並向代理接口方法中讀寫數據來完成進程間的通信。它的傳輸過程只需要一次拷貝,爲發送添加UID/PID身份,安全性更高。
對於Server而言,Binder可以看成是Server實現特定服務訪問的接入地址,Client通過訪問這個地址實現對Server的請求;對於Client而言,Binder可以看成是通向Server的管道入口,client要想和某個server通信首先必須建立管道和管道入口。
與其他IPC不同,Binder使用面向對象的思想描述作爲訪問接入點的Binder和它在Client中的入口:Binder是一個實體位於Server中的對象,Client通過Binder的引用訪問Server。
Binder的通信框架定義了四個模型:Client、Server、ServiceManager和Binder驅動。其中Client、Server和ServiceManager運行在用戶空間,Binder驅動運行在內核空間。他們的關係類似與互聯網中:Server是服務器,Client是客戶終端,ServiceManager是域名服務器(DNS),驅動是路由器。