第二章 IPC機制詳解(1)

本文為Android開發藝術探索的筆記,僅供學習

第二章知識點大綱

1 IPC 含義就是進程間通信或者跨進程通信

線程是CPU調度的最小單位,同時線程也是一種有限的系統資源。
進程一般指的是一個執行單元,在PC和移動設備上指一個程序或者是一個應用。

一個進程可以有很多個線程,但只有一個線程的時候即為主線程,在android里也稱為UI線程。UI線程里才能去操作界面元素。很多時候,一個進程需要執行大量耗時任務,如果這些任務放在主線程里,就會造ANR(應用程序無響應)。解決這個問題就需要用到線程,需要建立子線程通過Handle去操作些耗時操作或更新UI。

2 多進程模式分析

2.1 android中的多進程模式

想要使用多進程,只需在四大組件的XML文件里使用android:process的屬性即可。

<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" />
            </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" />

讓我們來看看這三個Activity分別對應的進程是什么


可以看到有三個進程,分別表示在xml里聲明的三個Activity,一次對應MainActivity,SecondActivity,ThirdActivity
SecondActivity的進程名字是com.ryg.chapter_2:remote ,:的前面部分是當前進程是在的包名。
ThirdActivity所對應的進程com.ryg.chapter_2 .remote ,這個命名方式是完整的命名方式,不會附加包名。

開頭的進程是私有進程,其他應用組件是不會和它跑在同一個進程里的,而不是以:開頭的則是全局進程其他應用可以通過ShareUID的方式,可以跑在同一個進程里。

注:Android的系統會給每一個應用分配一個唯一的UID,具有相同的UID才能共享數據。若兩個相同的UID想要跑在同一個進程里是有要求的,必須要有相同簽名可以。這種情況下,他們可以互相訪問私有數據,還可以共享數據。

2.2 多進程模式的運行機制

首先舉個例子好讓大家理解,在上面的基礎上,我們再添加一個類,并且聲明一個全局變量如下圖
public class UserManager{ public int sUserId = 1;}

那么我們需要在MainActivity中去修改sUserId ,把值改為2 并且打印出來,在SecondActivity將sUserId打印出來,結果


按照常理來說SecondActivity應該輸出2,因為MainActivity已經把值改為2了,而事實卻是相反的,因為Android中每一個進程,都會為其分配一個DVM(虛擬機),所以每一個進程都是相對獨立的,而MainActivity修改的值,僅對同一進程有效。SecondActivity和MainActivity并不在同一個進程中,所以MainActivity修改的值SecondActivity會接收不到。

所以這就是多進程帶來的主要影響有以下幾點

  1. 靜態成員和單例模式完全失效
  2. 線程同步機制完全失效
  3. SharePreferences的可靠性降低
  4. Application會被多次創建

第一個影響就是上面的例子,第二個影響也一樣,既讓都不在同一個虛擬機中,不再同一個進程中線程又怎么去同步呢。第三個影響就是Sharepreferences本生不支持兩個進程同事去寫,這樣有一點的幾率會造成數據的丟失。第四個影響每一個應用對于一個進程,對于一個虛擬機,對于一個Application,所以開啟多個進程會開啟多個虛擬機開啟多個Application 是一樣的道理。

3 IPC的基礎概念

主要包括三個,serializable接口,parcelable接口以及binder

3.1 serializable的接口

Serializable 是java提供的一個序列化的接口,就是對對象進行序列化和反序列化的操作。首先想要實現序列化的操作需要讓類去實現Serialezable接口并聲明一個serialVersionUID即可,事實上serialVersionUID并不是必須的,不聲明serialVersionUID也可以實現序列化。

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

就完成對象的序列化操作,幾乎所有的工作都讓系統去自動完成。
如何對對象進行序列化和反序列化的操作,只需采用ObjectInputStream和ObjectOutputStream

//序列化的過程
               String CHAPTER_2_PATH = Environment.getExternalStorageDirectory().getPath()
            + "/singwhatiwanna/chapter_2/";
               String CACHE_FILE_PATH = CHAPTER_2_PATH + "usercache";

                User user = new User(1, "hello world", false);
                File dir = new File(MyConstants.CHAPTER_2_PATH);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
                ObjectOutputStream objectOutputStream = null;
                try {
                    objectOutputStream = new ObjectOutputStream(
                            new FileOutputStream(cachedFile));
                    objectOutputStream.writeObject(user);
                    Log.d(TAG, "persist user:" + user);
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    MyUtils.close(objectOutputStream);
                }
//反序列化過程
                User user = null;
                File cachedFile = new File(MyConstants.CACHE_FILE_PATH);
                if (cachedFile.exists()) {
                    ObjectInputStream objectInputStream = null;
                    try {
                        objectInputStream = new ObjectInputStream(
                                new FileInputStream(cachedFile));
                        user = (User) objectInputStream.readObject();
                        Log.d(TAG, "recover user:" + user);
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    } finally {
                        MyUtils.close(objectInputStream);
                    }
                }

這就是一個簡單的使用,將對象輸出到txt中就是序列化,將txt的數據讀取出來就是反序列化。


android中Intent 是可以來傳遞一個序列化的對象


好了 之前說serialVersionUID 沒有也可以序列化,但到底有什么用呢?

  1. serialVersionUID就像是標識符,當你序列化的時候serialVersionUID的值也會被寫入,在反序列化的時候 系統會通過對比類里的serialVersionUID與文件中的serialVersionUID 是否一致來進行反序列化,但一致的時候反序列化成功不一致則不成功。
  2. 當你沒聲明serialVersionUID的時候,在反序列化的時候 修改了類那么反序列化會不成功 因為當前類與序列化時候的類不一樣導致了反序列化的失敗。(因為不聲明serialVersionUID,系統會自動聲明serialVersionUID,因為你進行類修改所以會導致 前后兩次serialVersionUID的值不一樣,所以反序列化失敗)
  3. 但是如果你手動聲明了serialVersionUID,當你修改了序列化對象,對其進行增刪改屬性之后再進行反序列化,此時反序列化會是成功的,也是無論修改這樣serialVersionUID的值都是一樣的,系統會最大程度的去回復數據。

3.2 Parcelable的接口

Parcelable也是序列化的一種方式,使用Parcelable只需去實現Parcelable接口即可

public class User implements Parcelable {
    public int userId;
    public String userName;
    public boolean isMale;
    public User() {
    }
    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);
    }
    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;
    }
}


其實下列方法都會自動生成,不需要手打

既讓Serializable 和Parcelable都可以序列化,那就說說兩者之間的區別,Serializable 在序列化和反序列化的時候,需要進行大量 I/O操作,很耗時。Parcelable是android的序列化方式,他很高效,但是使用起來很麻煩。
Parcelable的性能比Serializable好,在內存開銷方面較小,所以在內存間數據傳輸時推薦使用Parcelable,如activity間傳輸數據,而Serializable可將數據持久化方便保存,所以在需要保存或網絡傳輸數據時選擇Serializable,因為android不同版本Parcelable可能不同,所以不推薦使用Parcelable進行數據持久化

3.3 Binder

Binder就是android的一個類,它實現了IBinder的接口。從IPC角度上來說,就是Binder是Android的一種跨進程通信的方式。從Android Framework的角度來分析就是Binder 是ServiceManager用來連接各種Manager的(ActivityManager WindowManager)橋梁。從Android應用層的角度來分析就是客戶端和服務端進行通信的媒介,當bindService的時候,服務端會調用一個Binder對象,通過這個對象,客戶端就可以獲取到一些數據。

在Android的開發中,Binder主要用在Service中,包括AIDL和Messenger。普通的Service中的Binder不涉及到進程通信,所以適用AIDL來分析Binder的工作機制。

先演示一下AIDL的建立和通過AIDL生成JAVA文件去看看Binder的機制

其中Book.java是自定義類,用途就是個Bean。如果需要使用自定義類 必須要建立一個同名的aidl文件。
系統可能會找不到我們創建的類,那么就是要一下聲明

sourceSets {
    main {
        manifest.srcFile ='src/main/AndroidManifest.xml'
        java.srcDirs = ['src/main/java', 'src/main/aidl']
        resources.srcDirs = ['src/main/java', 'src/main/aidl']
        aidl.srcDirs = ['src/main/aidl']
        res.srcDirs = ['src/main/res']
        assets.srcDirs = ['src/main/assets']
    }
}

接下來 我們來看看生成的JAVA文件 去看看Binder的機制,先看看生成的java文件,代碼確實比較多,我就截取一個方法為例子,我直接在代碼上進行解釋


public interface IBookManager extends android.os.IInterface
{
/** Stub是運行在服務端中的 */
public static abstract class Stub extends android.os.Binder implements com.example.gyh.myapplication.IBookManager
{
//這個屬性就是Binder的唯一標識,也就是包名
private static final java.lang.String DESCRIPTOR = "com.example.gyh.myapplication.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
//該方法就是為了將服務端的Binder轉化為用戶端的AIDL接口類型的對象,這種轉化過程是區分進程的,若是同一個進程則就返回服務端本生的Stub對象,不是則轉化成Stub.Proxy對象。
public static com.example.gyh.myapplication.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.gyh.myapplication.IBookManager))) {
return ((com.example.gyh.myapplication.IBookManager)iin);
}
return new com.example.gyh.myapplication.IBookManager.Stub.Proxy(obj);
}
//該方法就是返回Binder對象
@Override public android.os.IBinder asBinder()
{
return this;
}
//該方法是運行在服務端的Binder線程池中的,當客戶端發起跨進程請求的時候,遠程請求會通過系統底層的封裝后交由改方法進行處理。現在來解釋一下里面的參數代表著什么意思
code表示客戶端請求的方式是什么,data保存著方法的參數,reply是要返回的值,flags為false的時候,客戶端請求失敗,這樣利用這參數做權限驗證。
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.example.gyh.myapplication.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.gyh.myapplication.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
//該類是運行在客戶端中的
private static class Proxy implements com.example.gyh.myapplication.IBookManager
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public void addBook(com.example.gyh.myapplication.Book book) throws android.os.RemoteException
{
//首先要創立三個對象,_data,_reply,_result , 然后如果有參數的話把參數寫到_data中,接著調用transact方法去發起遠程過程調用(RPC),同時將現場掛起(就是暫停的意思);然后服務端的ontransact調用,直到RPC過程返回后,該線程繼續。并從_reply中取出返回值,并且賦予_result去返回。

android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
//transact回去調用stub中的ontransact方法,找到對應的方法也就是ontransact里的addBook方法,取出返回的_reply,從中取出客戶端需要的返回值
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public void addBook(com.example.gyh.myapplication.Book book) throws android.os.RemoteException;
}

通過上面的分析,我們可以知道客戶端發起遠程請求時,當前線程會被掛起直到服務端進程返回結果,這是一個耗時操作,所以不能再UI線程中發起。其實服務端的Binder是運行在Binder線程池中的,所以不管Binder是否耗時都應該是同步的。下圖解釋



那么我再來總的概括一下,當我們和服務器建立連接之后,通過服務器返回的Binder,將Binder轉化為客戶端的AIDL接口對象,因為是跨進程所以返回的是Stub.Proxy對象,通過這個對象去調用相應的方法如addBook,addBook方法中則會通過tranasct方法去調用Stub中onTranasct方法中對應的addBook,并通過_reply返回相應的數據給客戶端。這樣一來整個跨進程通信就結束了。


Binder的死亡代理

Binder的兩個很重要的方法linkToDeath和unlinkToDeath,當我們服務端的進程由于某種原因斷裂,那么對導致我們遠程調用失敗。但更為重要的是我們不知道Binder是否斷裂,那么客戶端的功能就會收到影響,所以Binder給我們配置了兩個方法,通過linkToDeath我們可以設置一個死亡代理,當Binder死亡的時候,可以給客戶端發來消息,從而我們可以重新開啟服務。然后如何去設置這個代理呢?我們先來看看demo


首先聲明一個DeathRecipent對象,DeathRecipent是一個接口,其內部只有一個方法binderDied,當我們實現該方法的時候,當Binder死亡的時候系統就會回調改方法,然后我們可以移除之前綁帶的Binder去重新開去新的Binder

linkToDeath方法的第二個參數是標記位,我們直接可以設置為0,這樣我們就設置好了死亡代理。另外我們可以通過Binder的isBinderAlive的方法去判斷Binder是否死亡。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容