Android IPC機(jī)制

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

多進(jìn)程的使用場(chǎng)景:

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

android多進(jìn)程模式

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

   <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屬性。并且屬性值不同。這意味著當(dāng)前應(yīng)用又增加了兩個(gè)新進(jìn)程。假設(shè)當(dāng)前應(yīng)用的包名為"com.ryg.chapter_2",當(dāng)SecondActivity啟動(dòng)時(shí),系統(tǒng)會(huì)為它創(chuàng)建一個(gè)單獨(dú)的進(jìn)程,進(jìn)程名為"com.ryg.chapter_2:remote";當(dāng)ThirdActivity啟動(dòng)的時(shí)候,也會(huì)為他創(chuàng)建一個(gè)單獨(dú)的進(jìn)程,進(jìn)程名為"com.ryg.chapter_2.remote"。同時(shí)入口Mainactivity,沒(méi)有為它指定process屬性,那么他就運(yùn)行在默認(rèn)的進(jìn)程中,默認(rèn)的進(jìn)程名是包名。 下圖是運(yùn)行效果:



可以看到進(jìn)程列表中分別有com.ryg.chapter_2,com.ryg.chapter_2:remote,com.ryg.chapter_2.remote 3個(gè)不同的進(jìn)程,而且進(jìn)程id各不相同。這說(shuō)明我們的應(yīng)用成功的使用了多進(jìn)程技術(shù)。

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

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

多線程模式的運(yùn)行機(jī)制:

android中的多線程模式有很多的問(wèn)題,例如:

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

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

然后我們?cè)贛ainActivity中給它賦值為2,打印出來(lái)。然后再啟動(dòng)SecondActivity,同樣打印它的值。



可以看到,圖中的打印結(jié)果和我們想象的完全不同。正常情況下SecondActivity中打印的sUserId 應(yīng)該是2才對(duì)。 這就是一個(gè)多進(jìn)程帶來(lái)的問(wèn)題。

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

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

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

分析

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

}

運(yùn)行后的log為:



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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容