Android IPC機制理解

什麼是IPC機制?

IPC機制,含義爲進程間通訊或者跨進程通訊,是指兩個進程之間進行數據交換的過程。android

任何一個操做系統,線程是CPU能夠操做的最小單元,同時線程是一種有限的系統資源。而進程通常指一個執行單元,在PC和移動設備上指一個程序或者一個應用。一個進程能夠包含多個線程,所以進程和線程是包含與被包含的關係。數組

Android多進程模式

正常狀況下,在Android中多進程是指一個應用中存在多個進程的狀況,所以這裏不討論兩個應用之間的多進程狀況。首先,在Android中使用多進程只有一種方法,那就是給四大組件(Activity、Service、Receiver、ContentProvider)在AndroidMenifest中指定android:process屬性,除此以外沒有其餘辦法,也就是說咱們沒法給一個線程或者一個實體類指定其運行時所在的進程。其實還有另外一種很是規的多進程方法,那就是經過JNI在native層去fork一個新的進程,可是這種方法屬於特殊狀況,也不是經常使用的建立多進程的方式,所以咱們暫時不考慮這種方式。緩存

<activity
        android:name="com.ryg.chapter_2.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="com.ryg.chapter_2.SecondActivity"
        android:configChanges="screenLayout"
        android:label="@string/app_name"
        android:process=":remote" />
    <activity
        android:name="com.ryg.chapter_2.ThirdActivity"
        android:configChanges="screenLayout"
        android:label="@string/app_name"
        android:process="com.ryg.chapter_2.remote" />

上述代碼中就出現了三個進程,上面的示例分別爲SecondActivity和ThirdActivity指定了process屬性,而且它們的屬性值不一樣,這意味着當前應用又增長了兩個新進程。安全

  • 假設當前應用的包名爲「com.ryg.chapter_2」,
  • 當SecondActivity啓動時,系統會爲它建立一個單獨的進程,進程名爲「com.ryg.chapter_2:remote」;
  • 當ThirdActivity啓動時,系統也會爲它建立一個單獨的進程,進程名爲「com.ryg.chapter_2.remote」。
  • 同時入口Activity是MainActivity,沒有爲它指定process屬性,那麼它運行在默認進程中,默認進程的進程名是包名。

可是,實際使用中多進程是有不少問題須要處理的,例如如下問題:網絡

  • (1)靜態成員和單例模式徹底失效。
  • (2)線程同步機制徹底失效。
  • (3)SharedPreferences的可靠性降低。
  • (4)Application會屢次建立。

    簡單來講,第(1),(2)問題都是由於多進程的內存塊不一樣,因此數據共享也會出現問題;第三個是由於出現多進程,那麼對SharedPreferences同時讀寫,會出現併發

數據不一樣步的安全問題;第(4),每一次開啓多進程,至關於重啓了一個應用程序,因此application必定會重走一次。app

IPC機制實現的基礎

1、Serializable接口

首先,Serializable接口是一個空接口,做用是用來處理序列化的,爲對象提供標準的序列化和反序列化操做。ide

讓一個對象實現序列化,只須要這個類實現Serializable接口並聲明一個serialVersionUID便可,實際上,甚至這個serialVersionUID也不是必需的,咱們不聲明這個serialVersionUID一樣也能夠實現序列化,可是會對反序列化形成必定不肯定因素。高併發

例如:性能

public class User implements Serializable {
         private static final long serialVersionUID = 519067123721295773L;
         public int userId;
         public String userName;
         public boolean isMale;
        ...
    }

實際上,進行對象的序列化和反序列化也很是簡單,只須要採用ObjectOutputStream和ObjectInputStream便可輕鬆實現:

//序列化過程
    User user = new User(0,"jake",true);
    ObjectOutputStream out = new ObjectOutputStream(
                new FileOutputStream("cache.txt"));
    out.writeObject(user);
    out.close();
    //反序列化過程
    ObjectInputStream in = new ObjectInputStream(
                new FileInputStream("cache.txt"));
    User newUser = (User) in.readObject();
    in.close();

所謂的序列化,就是把實現了Serializable接口的User對象寫到文件中就能夠快速恢復了,恢復後的對象newUser和user的內容徹底同樣,可是二者並非同一個對象。

那麼如今,serialVersionUID的做用就體現出來了,這個serialVersionUID是用來輔助序列化和反序列化過程的,原則上序列化後的數據中的serialVersionUID只有和當前類的serialVersionUID相同纔可以正常地被反序列化。

serialVersionUID的詳細工做機制是這樣的:序列化的時候系統會把當前類的serialVersionUID寫入序列化的文件中(也多是其餘中介),當反序列化的時候系統會去檢測文件中的serialVersionUID,看它是否和當前類的serialVersionUID一致,若是一致就說明序列化的類的版本和當前類的版本是相同的,這個時候能夠成功反序列化;不然就說明當前類和序列化的類相比發生了某些變換,好比成員變量的數量、類型可能發生了改變,這個時候是沒法正常反序列化的。

2、Parcelable接口

Parcelable也是一個接口,只要實現這個接口,一個類的對象就能夠實現序列化並能夠經過Intent和Binder傳遞。

public class User implements Parcelable {
         public int userId;
         public String userName;
         public boolean isMale;
         public Book book;
         public User(int userId,String userName,boolean isMale) {
             this.userId = userId;
             this.userName = userName;
             this.isMale = isMale;
         }
         public int describeContents() {
             return 0;
         }
         public void writeToParcel(Parcel out,int flags) {
             out.writeInt(userId);
             out.writeString(userName);
             out.writeInt(isMale ? 1 : 0);
             out.writeParcelable(book,0);
         }
         public static final Parcelable.Creator<User> CREATOR = new Parcelable.
         Creator<User>() {
             public User createFromParcel(Parcel in) {
                 return new User(in);
             }
             public User[] newArray(int size) {
                 return new User[size];
             }
         };
         private User(Parcel in) {
             userId = in.readInt();
             userName = in.readString();
             isMale = in.readInt() == 1;
             book = in.readParcelable(Thread.currentThread().getContextClass-
             Loader());
         }
    }
        

這裏先說一下Parcel,Parcel內部包裝了可序列化的數據,能夠在Binder中自由傳輸。從上述代碼中能夠看出,在序列化過程當中須要實現的功能有序列化、反序列化和內容描述。

  • 序列化功能由writeToParcel方法來完成,最終是經過Parcel中的一系列write方法來完成的;
  • 反序列化功能由CREATOR來完成,其內部標明瞭如何建立序列化對象和數組,並經過Parcel的一系列read方法來完成反序列化過程;
  • 內容描述功能由describeContents方法來完成,幾乎在全部狀況下這個方法都應該返回0,僅噹噹前對象中存在文件描述符時,此方法返回1。

Parcelable和Serializable和區別

Serializable是Java中的序列化接口,其使用起來簡單可是開銷很大,序列化和反序列化過程須要大量I/O操做。而Parcelable是Android中的序列化方式,所以更適合用在Android平臺上,它的缺點就是使用起來稍微麻煩點,可是它的效率很高,這是Android推薦的序列化方式,所以咱們要首選Parcelable。Parcelable主要用在內存序列化上,經過Parcelable將對象序列化到存儲設備中或者將對象序列化後經過網絡傳輸也都是能夠的,可是這個過程會稍顯複雜,所以在這兩種狀況下建議你們使用Serializable。

Android中的IPC方式

1、使用Bundle

四大組件中的三大組件(Activity、Service、Receiver)都是支持在Intent中傳遞Bundle數據的,因爲Bundle實現了Parcelable接口,因此它能夠方便地在不一樣的進程間傳輸。基於這一點,當咱們在一個進程中啓動了另外一個進程的Activity、Service和Receiver,咱們就能夠在Bundle中附加咱們須要傳輸給遠程進程的信息並經過Intent發送出去。固然,咱們傳輸的數據必須可以被序列化,好比基本類型、實現了Parcellable接口的對象、實現了Serializable接口的對象以及一些Android支持的特殊對象

2、使用文件共享

共享文件也是一種不錯的進程間通訊方式,兩個進程經過讀/寫同一個文件來交換數據,好比A進程把數據寫入文件,B進程經過讀取這個文件來獲取數據。

可是,咱們知道,經過文件共享這種方式來共享數據對文件格式是沒有具體要求的,好比能夠是文本文件,也能夠是XML文件,只要讀/寫雙方約定數據格式便可。經過文件共享的方式也是有侷限性的,好比並發讀/寫的問題。

固然,SharedPreferences是個特例,衆所周知,SharedPreferences是Android中提供的輕量級存儲方案,它經過鍵值對的方式來存儲數據,在底層實現上它採用XML文件來存儲鍵值對,每一個應用的SharedPreferences文件均可以在當前包所在的data目錄下查看到。通常來講,它的目錄位於/data/data/package name/shared_prefs目錄下,其中package name表示的是當前應用的包名。從本質上來講,SharedPreferences也屬於文件的一種,可是因爲系統對它的讀/寫有必定的緩存策略,即在內存中會有一份SharedPreferences文件的緩存,所以在多進程模式下,系統對它的讀/寫就變得不可靠,當面對高併發的讀/寫訪問,Sharedpreferences有很大概率會丟失數據,所以,不建議在進程間通訊中使用SharedPreferences。

3、使用Messenger

Messenger是一種輕量級的IPC方案,它的底層實現是AIDL。

注意:這裏的Messenger,不一樣於handler,handler是沒法在進程間通訊的。

實現一個Messenger有以下幾個步驟,分爲服務端和客戶端。

服務端

咱們須要在服務端建立一個Service來處理客戶端的鏈接請求,同時建立一個Handler並經過它來建立一個Messenger對象,而後在Service的onBind中返回這個Messenger對象底層的Binder便可。

public class MessengerActivity extends Activity {
         private static final String TAG = " MessengerActivity";
         private Messenger mService;
         private ServiceConnection mConnection = new ServiceConnection() {
             public void onServiceConnected(ComponentName className,IBinder
             service) {
                 mService = new Messenger(service);
                 Message msg = Message.obtain(null,MyConstants.MSG_FROM_CLIENT);
                 Bundle data = new Bundle();
                 data.putString("msg","hello,this is client.");
                 msg.setData(data);
                 try {
                     mService.send(msg);
                 } catch (RemoteException e) {
                     e.printStackTrace();
                 }
             }
             public void onServiceDisconnected(ComponentName className) {
             }
         };
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.activity_messenger);
             Intent intent = new Intent(this,MessengerService.class);
             bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
         }
         @Override
         protected void onDestroy() {
             unbindService(mConnection);
             super.onDestroy();
         }
    }

客戶端

客戶端進程中,首先要綁定服務端的Service,綁定成功後用服務端返回的IBinder對象建立一個Messenger,經過這個Messenger就能夠向服務端發送消息了,發消息類型爲Message對象。
若是須要服務端可以迴應客戶端,就和服務端同樣,咱們還須要建立一個Handler並建立一個新的Messenger,並把這個Messenger對象經過Message的replyTo參數傳遞給服務端,服務端經過這個replyTo參數就能夠迴應客戶端。

public class MessengerActivity extends Activity {
         private static final String TAG = " MessengerActivity";
         private Messenger mService;
         private ServiceConnection mConnection = new ServiceConnection() {
             public void onServiceConnected(ComponentName className,IBinder
             service) {
                 mService = new Messenger(service);
                 Message msg = Message.obtain(null,MyConstants.MSG_FROM_CLIENT);
                 Bundle data = new Bundle();
                 data.putString("msg","hello,this is client.");
                 msg.setData(data);
                 try {
                     mService.send(msg);
                 } catch (RemoteException e) {
                     e.printStackTrace();
                 }
             }
             public void onServiceDisconnected(ComponentName className) {
             }
         };
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.activity_messenger);
             Intent intent = new Intent(this,MessengerService.class);
             bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
         }
         @Override
         protected void onDestroy() {
             unbindService(mConnection);
             super.onDestroy();
         }
    }

4、使用AIDL通訊

上面的Messenger來進行進程間通訊的方法,若是大量的消息同時發送到服務端,服務端仍然只能一個個處理,若是有大量的併發請求,那麼用Messenger就不太合適了。
因此,咱們可能須要跨進程調用服務端的方法,這種情形用Messenger就沒法作到了,可是咱們可使用AIDL來實現跨進程的方法調用。

這裏,就不寫出AIDL的實現方式了,這個示例網絡資源不少。

5、使用ContentProvider

ContentProvider是Android中提供的專門用於不一樣應用間進行數據共享的方式,從這一點來看,它天生就適合進程間通訊。和Messenger同樣,ContentProvider的底層實現一樣也是Binder。

6、使用使用Socket

咱們經過Socket來實現進程間的通訊。Socket也稱爲「套接字」,是網絡通訊中的概念,它分爲流式套接字和用戶數據報套接字兩種,分別對應於網絡的傳輸控制層中的TCP和UDP協議。TCP協議是面向鏈接的協議,提供穩定的雙向通訊功能,TCP鏈接的創建須要通過「三次握手」才能完成,爲了提供穩定的數據傳輸功能,其自己提供了超時重傳機制,所以具備很高的穩定性;而UDP是無鏈接的,提供不穩定的單向通訊功能,固然UDP也能夠實現雙向通訊功能。在性能上,UDP具備更好的效率,其缺點是不保證數據必定可以正確傳輸,尤爲是在網絡擁塞的狀況下。

總之,上面六種均可以實現進程間通訊的,具體能夠根據本身的需求來選擇合適的方式。

相關文章
相關標籤/搜索