Android 進程間通信(一) -- Android 多進程模式

系列文章
Android 進程間通信(一) – Android 多進程模式
Android 進程間通信(二) – 理解 Binder 的機制
Android 進程間通信(三) --通過 AIDL 理解Binder,並手寫Binder服務

一、IPC 簡介

IPC 爲 Inter-Process Communication 的縮寫,意思爲 進程間通信或跨進程通信,是指兩個進程之間的數據交換的過程。

說起進程間的通信,首先要先認識進程和線程;
按照操作系統中的描述,線程是 CPU 調度的最小單元,同時線程是一種悠閒的系統資源;而進程一般指一個執行單元,在Android 中指一個應用。一個進程可以只有一個線程,也可以包含多個線程;

在Android中,主線程也叫UI線程,一些界面元素,都在該線程中操作,所以,一般一些的耗時操作都應該放在其他線程,不然會有 ANR。

對於 Android 來說,它是基於 Linux 操作系統的,但是卻不是用管道,信號量等來進行進程通信,而是自己特色的 Binder ,通過 Binder 就能輕鬆實現進程間的通信。

當然,除了使用 Binder,也可以使用 socket,廣播、contentprovider 等手段進行進程間通信。

二、Android 中的多進程模式

在進行跨進程通信之前,需要先學習一下 Android的多進程模式。

首先在 Android 通過給四大組件指定 android:process 屬性,除此之外沒有其他辦法;也就是說我們無法給一個線程或者一個實體類指定其運行時所在的進程。淡然,其實還有一種非 常規的多進程辦法,就是通過 JNI 在 native 層去fork一個新的進程,但這種比較特殊,也不是常見的創建多進制的方式,因此這裏暫時不考慮。

如何在 Android 創建多進程呢?看下面的例子,這裏新建兩個 activity,並在 android:process 明明成不同的進程:
在這裏插入圖片描述
在 SecondActivity 的時候,process爲 “:remote” ,首先 “:” 冒號是指在 當前的進程名前面加上當前的包名;當系統啓動 SecondActivity 時,會創建一個單獨的進程,進程名爲:com.example.ipcdemo:remote;

而在 ThirdActivity 的時候,則是直接填寫完成進程名 com.example.ipcdemo.remote 。

當我們把兩個activity 都啓動之後,我們用 ps | grep com.example.ipcdemo 看看:
在這裏插入圖片描述
可以看到,進程確實是不一樣的。

上面說到,進程以 “:” 開頭的進程屬於當前應用的私有進程,其他應用的組件不可以和它跑在同個進程中,而進程名不以 “:” 開頭的進程屬於全局進程,其他應用通過 ShareUID方法就可以和它泡在同一個進程中。

我們知道,Android 系統會爲我們的應用分配一個唯一的 UID,具有相同 UID 才能共享數據,但要共享數據,除了 UID 要相同,簽名也要一致纔行;這種情況下,他們可以互相訪問對方的私有數據,比如 data 目錄,組件信息等。

啥意思呢?比如你其中一個 moudle 有個私有字符串在 data/data/ 目錄下。其他進程一般情況下是不能訪問這個目錄的,但是如果你的其中一個 moudle 的 shareUID 和它相同,且簽名相同,雖然進程不相同,卻能正常訪問的。
關於 ShareUID 共享數據的,可以參考這篇文章:https://blog.csdn.net/yanjianjunaaa/article/details/13095087

2.1 多進程模式的限制

上面中,我們已經通過 process 開啓了多進程,接着,我們寫一個工具類,裏面有一個成員變量

public class UserManager {
    public static int testNum = 1;
}

我們在 mainactivity 的時候,設置爲 2 :

UserManager.testNum = 2;
Log.d(TAG, "zsr onCreate: "+UserManager.testNum);

然後在 SecondActivity 的時候,再打印一下這個值:
在這裏插入圖片描述
可以看到,多進程下,數據並不能共享,爲啥是這樣?

原因是 SecondActivity 是在另外的進程裏,我們知道 Android 爲每一個應用程序都分配了一個單獨的虛擬機,或者說爲每一個進程都分配了一個獨立的虛擬機,每個虛擬機在內存上分配上有不同的地址空間,這就導致不同的虛擬機中訪問同一個對象會產生多個副本。

就上面的問題,com.example.ipcdemo 和 com.example.ipcdemo:remote 這兩個進程都存在一個 UserManager 的類,mainActivity 在 com.example.ipcdemo 這個進程,修改 testNum 隻影響當前進程的,而 secondActivity 屬於com.example.ipcdemo:remote 進程,相互之間不影響。

一般來說,使用多進程會造成如下幾方面的問題:

  1. 靜態成員和單例模式失效 : 因爲屬於不同內存地址空間了,肯定不一樣
  2. 線程同步機制完全失效 :因爲對象都不同了,線程同步的鎖對象或者鎖全局都無法保證線程同步了。
  3. SharePreferences 的可靠性下降 :因爲SharePreferences 底層是通過讀/寫 XML 文件的,併發可能會出現數據不同步問題。
  4. Application 會多次創建 : 這個也好理解,都不同進程了,虛擬機都要爲它重新分配內存,所以 application 會多次創建。

2.2 Serializeble 和 Parcelable

在進程的通知中,我們還可以使用 Serializeble 和 Parcelable ,他們都能完成對象象的序列化過程。

2.2.1 Serializeble

需要注意的是 Serializeble ,它是 java 的提供一個序列化接口,它是一個口接口,實現起來比較簡單,只要在 數據類中繼承它即可:

public class UserBean implements Serializable {
    public static final long serialVersionUID = 8308345L;
    
    public String name;
    public int age;
}

但爲什麼有個 serialVersionUID 呢?其實這個不寫也可以,也可以實現序列化,只不過在反序列化的時候會有問題,這個等一下說。

如何進行序列化和反序列化呢?其實也很簡單,只要使用 ObjectOutPutStream 和 ObjecInputStream 即可:

        try {

            String path = Environment.getExternalStorageDirectory().getAbsolutePath();
            File file = new File(path,"cache.txt");
             if (file.exists()){
                file.delete();
            }
            /**
             * 序列化
             */
            UserBean zhangsan = new UserBean("zhangsan", 100);
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(file));
            //寫入數據
            outputStream.writeObject(zhangsan);
            outputStream.flush();
            outputStream.close();


            /**
             * 反序列化
             */
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
            UserBean bean = (UserBean) ois.readObject();

            Log.d(TAG, "zsr onCreate: "+bean.toString());


        } catch (Exception e) {
            Log.d(TAG, "zsr onCreate: "+e.toString());
            e.printStackTrace();
        }

剛纔說到 serialVersionUID 會影響反序列化,那其實我們把寫入數據的屏蔽掉,然後去掉 serialVersionUID ,再加上個字段 lastName:

    //public static final long serialVersionUID = 8308345L;

    public String name;
    public int age;
    public String lastName;

發現報了以下的錯誤:

local class incompatible: stream classdesc serialVersionUID = 8308345, local class serialVersionUID = 6474696687691921132

但是,把 serialVersionUID 還原,又能正常反序列化了。

所以,從這裏知道了,在當前類序列化的時候,會把 serialVersionUID 寫入到序列化的文件中(也可能是其他中介),當反序列化的時候,系統就會去檢測,serialVersionUID 是否一致,如果一致 則說明這個是可以被序列化的。

2.2.2 Parcelable

由於 Serializeble 是Java 層的接口,且每次都是 IO 流,開銷過大。 所以 Android 團隊也開發了屬於Android 自身的序列化接口 Parcelable,這個寫法會比較複雜一點,但是現在使用 Android studio ,基本都可以幫助我們自己實現,而Parcelable是在內存上去序列化的,速度比Serializeble 快些。
這裏就不過多介紹了。

下一章我們通過 AIDL 來學習 Binder 的機制。

參考Android藝術開發探索第二章

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