Android IPC機制

什么是IPC?
IPC是Inter-Process Communication的縮寫,含義為進程間通信或者跨進程之間進行數(shù)據(jù)交換的過程。說起進程間通信,首先我們要理解什么是進程,什么是線程,進程和線程是截然不同的兩個概念。按照操作系統(tǒng)的描述,線程是CPU調(diào)度的最小單元,同時線程是一種有限的系統(tǒng)資源。而進程一般指一個執(zhí)行單元,在PC和移動設備上指一個程序或者一個應用。一個進程可以包含多個進程,因此進程和線程的關系是被包含的關系。

多進程的使用場景:

例如:第一種情況是一個應用某些模塊因為特殊的原因需要運行在單獨的進程中,又或者為了加大一個應用可使用的內(nèi)存所以需要通過多進程來獲取多份內(nèi)存空間。 Android對單個應用使用的最大內(nèi)存做了限制,早期的一些版本可能是16MB,不同的設備有不同的大小。
另一種情況是當前應用需要向其他應用獲取數(shù)據(jù),由于是兩個應用,所以必須采用跨進程的方式來獲取所需的數(shù)據(jù),甚至我們通過系統(tǒng)提供的ContentProvider去查詢數(shù)據(jù)的時候,也是進程間的一種通信方式。只不過通信的細節(jié)被系統(tǒng)內(nèi)部屏蔽了。

android多進程模式

正常情況下,在android中多進程是指一個應用中存在多個進程的情況,首先在android中使用多進程只有一種辦法,那就是給4大組件(Activity,Service,Receiver,ContentProvider)在AndroidManifest.xml中指定android:process屬性,除此之外沒有其他辦法。 也就是說,我們無法給一個線程或一個實體類單獨指定其運行時所在的進程。 其實還有另一種非常規(guī)的多進程方法,那就是通過JNI在native層去fork出一個新的進程。待補充
下面代碼展示如何在android中創(chuàng)建多進程:

   <activity
            android:name=".MainActivity"
            android:configChanges="orientation|screenSize"
            android:label="@string/app_name"
            android:launchMode="standard" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".SecondActivity"
            android:configChanges="screenLayout"
            android:label="@string/app_name"
            android:process=":remote" />
        <activity
            android:name=".ThirdActivity"
            android:configChanges="screenLayout"
            android:label="@string/app_name"
            android:process="com.ryg.chapter_2.remote" />

上面的示例給SecondActivity和ThirdActivity指定了process屬性。并且屬性值不同。這意味著當前應用又增加了兩個新進程。假設當前應用的包名為"com.ryg.chapter_2",當SecondActivity啟動時,系統(tǒng)會為它創(chuàng)建一個單獨的進程,進程名為"com.ryg.chapter_2:remote";當ThirdActivity啟動的時候,也會為他創(chuàng)建一個單獨的進程,進程名為"com.ryg.chapter_2.remote"。同時入口Mainactivity,沒有為它指定process屬性,那么他就運行在默認的進程中,默認的進程名是包名。 下圖是運行效果:



可以看到進程列表中分別有com.ryg.chapter_2,com.ryg.chapter_2:remote,com.ryg.chapter_2.remote 3個不同的進程,而且進程id各不相同。這說明我們的應用成功的使用了多進程技術。

SecondActivity和ThirdActivity的android:process屬性分別為":remote"和"com.ryg.chapter_2.remote",那么這兩種方式的區(qū)別是什么呢?
兩方面:
1.":"的含義是指要在當前的進程名前面附加上當前的包名,這是一種簡寫的形式。對于SecondActivity它的包名應該是com.ryg.chapter_2:remote。 對應ThirdActivity的屬性名為"com.ryg.chapter_2.remote,這是一種完整的命名方式,不會附加包名信息。
2.進程名以":"開頭的進程屬性當前應用的私有進程,其他應用的組件不可以和它跑在同一個進程中,而進程名不以":"開頭的進程屬于全局進程,其他應用通過ShareUID方式可以和他跑在同一個進程中。

知識點:
我們知道Android系統(tǒng)會為每個應用分配一個唯一的UID,具有相同的UID的應用才能共享數(shù)據(jù),這里要說明的是,兩個應用通過ShareUID跑在同一個進程中是有要求的,需要這兩個應用有相同的ShareUID并且簽名相同才可以。在這種情況下,它們可以互相訪問對方的私有數(shù)據(jù),比如data目錄,組件信息等。不管它們是否跑在同一個進程中。當然如果它們跑在同一個進程中,那么除了能共享data目錄,組件信息,還可以共享內(nèi)存數(shù)據(jù),或者說它們看起來就像是是跑在一個應用中的兩個部分。

多線程模式的運行機制:

android中的多線程模式有很多的問題,例如:

我們新建一個類叫UserManager,這個類中有一個public的靜態(tài)變量:

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

然后我們在MainActivity中給它賦值為2,打印出來。然后再啟動SecondActivity,同樣打印它的值。



可以看到,圖中的打印結果和我們想象的完全不同。正常情況下SecondActivity中打印的sUserId 應該是2才對。 這就是一個多進程帶來的問題。

出現(xiàn)這種情況的原因是SecondActivity運行在一個單獨的進程中,我們知道android為每一個應用都分配了一個獨立的虛擬機。或者說為每一個進程都分配了一個獨立的虛擬機。不同的虛擬機在內(nèi)存中有不同的地址空間,這就導致了在不同的虛擬機中訪問同一個類的對象會產(chǎn)生多份的副本。例如我們的這個例子,在進程"com.ryg.chapter_2"和進程com.ryg.chapter_2:remote中都存在一個UserManager 類,并且這兩個類是互不干擾的。在MainActivity中修改了sUserId 的類只是修改了com.ryg.chapter_2進程對應的內(nèi)存的值。 這也就是解釋了為什么SecondActivity類中sUserId 的值沒有變化了。

所有運行在不同進程中的四大組件,只要他們之間需要通過內(nèi)存來共享數(shù)據(jù),都會分享失敗,這也是多進程所帶來的主要影響。正常情況下,四大組件中間不可能不通過一些中間層來共享數(shù)據(jù),那么簡單地指定指定進程名來開啟的多進程都會無法正確運行。當然,特殊情況下,某些組件直接不需要共享數(shù)據(jù),這個時候可以通過直接指定進程名來共享數(shù)據(jù)。但是這種業(yè)務場景不常見,幾乎所有的情況都需要共享數(shù)據(jù)。

一般來說,使用多進程會造成如下幾方面的影響:
1.靜態(tài)成員變量和單例模式完全失效
2.線程同步機制完全失效
3.SharePreferences的可靠性下降
4.Application會多次創(chuàng)建

分析

  1. 第一方面之前已經(jīng)進行過了分析,不同的進程對應著不同的虛擬機也就對應著不同的地址空間。所以靜態(tài)成員變量和單例都不是全局唯一的了。
  2. 第2個問題本質(zhì)上和第一個問題是類似的,既然都不是一塊內(nèi)存了,那么不管是鎖對象還是鎖全局類都無法保證線程同步,因為不同進程鎖的不是同一對象。
  3. 第3個問題是因為SharePreferences不支持兩個進程同時去執(zhí)行寫操作,否則會導致一定幾率的數(shù)據(jù)丟失,這是因為SharePreference底層是通過讀/寫XML文件實現(xiàn)的,并發(fā)寫顯然可能出現(xiàn)問題,甚至并發(fā)讀都有可能出現(xiàn)問題。
  4. 第4個問題當一個組件跑在一個新的進程中時,由于系統(tǒng)要在創(chuàng)建新的進程同時分配獨立的虛擬機,所以這個過程其實就是啟動一個應用的過程。因此,相當于系統(tǒng)又把這個應用重新啟動了一遍。既然都重新啟動了,那么自然就創(chuàng)建了新的Application。這個問題其實可以這么理解,運行在同一個進程中的額組件屬于同一個虛擬機和同一個Application的。同理運行在不同進程中的組件是屬于兩個不同的虛擬機和Application.
    下面實例測試一下:
    首先在Application的onCreate方法中打印出當前進程的名字,然后連續(xù)啟動三個同一個應用內(nèi)屬于不同進程的Activity,按照期望,Application的onCreate應用執(zhí)行三次并打印出三次進程名不同的log,代碼如下:
 public class MyApplication extends Application {

    private static final String TAG = "MyApplication";

    @Override
    public void onCreate() {
        super.onCreate();
        String processName = MyUtils.getProcessName(getApplicationContext(),
                Process.myPid());
        Log.d(TAG, "application start, process name:" + processName);
    }

}

運行后的log為:



通過log可以看出,Application執(zhí)行了三次onCreate,并且每次的進程名稱和進程id都不一樣,它們的進程名和我們?yōu)锳ctivity指定的android:process屬性一致。 這也就證明在多進程模式中,不同的進程的組件的確會擁有獨立的虛擬機,application以及內(nèi)存空間。這會給實際的開發(fā)帶來很多的困惱,是尤其需要注意的。

我們可以這樣理解一個應用中的多進程,它就相當于兩個不同的應用采用了ShareUID的模式,這樣能夠更加直接的理解多進程模式的本質(zhì)。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,558評論 25 708
  • 一、 什么是IPC? IPC,全稱Inter-Process Communication,字面意思就是進程間通信或...
    kuwork閱讀 7,996評論 0 8
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,992評論 19 139
  • 01 在朋友的微博上看到一張照片,被秒到了:畫面是一個女人在擁擠的地鐵里旁若無人地看書。 朋友配的一句話是,認真讀...
    許你三生煙火閱讀 797評論 1 9
  • 不經(jīng)歷風雨 怎能見彩虹 換言而知 不經(jīng)歷人心的險惡 怎么能尋覓到三兩真心好友 ...
    做一位陽光明媚的女子閱讀 361評論 0 0