IPC 機制(上)

IPC 機制.png

2.1 Android IPC 簡介


IPC 是 Inter-Process Communication 的縮寫绢馍,含義為進程間通信或者跨進程通信蛮穿,是指兩個進程之間進行數(shù)據(jù)交換的過程。IPC 不是 Android 中所獨有的涂臣,任何一個操作系統(tǒng)都有響應的 IPC 機制。

2.2 Android 中的多進程模式


2.2.1 開啟多進程模式

在Android中使用多進程只有一種方法,即給四大組件在 AndroidManifest 中指定 android:process 屬性流炕。

      <activity android:name=".MainActivity">
            <intent-filter>
                ....
            </intent-filter>
        </activity>
        <activity android:name=".SecondActivity"
            android:process=":remote">
        </activity>
        <activity android:name=".ThirdActivity"
            android:process="kjn.com.chapter5.remote">
        </activity>

Android Studio 使用 adb 命令(使用 adb 命令需要先配置 adb 環(huán)境)查看進程

C:\Users\Administrator.KTS-20160106YSM\Desktop\Chapter5>adb shell ps
...
u0_a94    10485 1256  1407444 52272 SyS_epoll_ 00000000 S kjn.com.chapter5
u0_a94    10508 1256  1405248 50748 SyS_epoll_ 00000000 S kjn.com.chapter5:remote
u0_a94    10525 1256  1405496 52776 SyS_epoll_ 00000000 S kjn.com.chapter5.remote

上述代碼有兩種命名方式:

  • :remote: 冒號前面自動加上包名澎现,屬于當前應用的私有進程,其他組件不可以和它跑在同一個進程中每辟。
  • 包名.remote :完整的命名方式剑辫,屬于全局進程,其他應用通過 ShareUID 方式可以和它跑在同一個進程中影兽。

我們知道 Android 系統(tǒng)會為每一個應用分配一個唯一的 UID揭斧,具有相同 UID 的應用才能共享數(shù)據(jù),在這種情況下他們可以互相訪問對方的似有數(shù)據(jù)峻堰,比如 data 目錄讹开、組件信息等。這里要說明的是捐名,兩個應用通過 UID 跑在同一個進程中是要求有相同是 ShareUID 和簽名才行旦万,跑在同一個進程中,他們還可以共享內(nèi)存數(shù)據(jù)镶蹋,或者說它們看起來就像是一個應用的兩個部分成艘。

2.2.2 多進程模式的運行機制
  新建一個 UserManager 類,這類里只有一個靜態(tài)成員變量

public class UserManager {
   public static int sUserId = 1;
}
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        UserManager.sUserId = 2;
        Log.d("MainActivity", "UserManage.sUserId=" + UserManager.sUserId);
    }

在 MianActivity 中改變 sUserId 為 2贺归,然后跳轉(zhuǎn)到 SecondActivity 中淆两,并打印 sUserId 的值。

kjn.com.chapter2 D/MainActivity: UserManage.sUserId=2
kjn.com.chapter2:remote D/SecondActivity: UserManage.sUserId=1

上述問題出現(xiàn)的原因是 SecondActivity 運行在一個單獨的進程中拂酣,Android 會為每一個進程應用分配一個獨立的虛擬機秋冰,不同的虛擬機在內(nèi)存分配上有不同的地址空間,這導致不同的虛擬機在訪問同一個類的對象會產(chǎn)生多份副本婶熬,上面 UserManage 就是這種情況剑勾,在一個進程中修改 sUserId 只會影響當前進程。

所有運行不在不同進程中的四大組件赵颅,只要它們之間通過內(nèi)存來共享數(shù)據(jù)虽另,都會共享失敗,這也是多進程帶來的主要影響饺谬。一般來說捂刺,多進程會造成如下幾個方面的問題:

  • (1)靜態(tài)成員和單例模式完全失效。

  • 就是上面這個案例商蕴。

  • (2)線程同步機制完全失效叠萍。

    • 因為不在一塊內(nèi)存上,那么不管是鎖對象還是鎖全局類都無法包裝線程同步绪商,因為不同進程鎖的不是同一個對象苛谷。
  • (3)SharedPreferences 的可靠性下降。

  • 因為 SharedPreferences 不支持兩個進程同時去執(zhí)行操作格郁,否則會導致一定幾率的數(shù)據(jù)丟失腹殿,這是因為 SharedPreferences 底層是通過讀寫 XML 文件來實現(xiàn)的独悴,并發(fā)寫顯然是可能出問題的,甚至并發(fā)讀寫都有可能出問題锣尉。

  • (4)Application 會多次創(chuàng)建刻炒。

  • 因為當一個組件跑在一個新的進程中的時候,由于系統(tǒng)要在創(chuàng)建新的進程同事分配獨立的虛擬機自沧,所以這個過程相當于就是啟動一個應用的過程坟奥,就會有兩個 Application。

系統(tǒng)提供了很多跨進程通信方法拇厢,雖然說不能直接地共享內(nèi)存爱谁,我們還可以使用 Intent 來傳遞數(shù)據(jù),共享文件 和 SharedPreferences 孝偎,基于 Binder 的 Messenger 和 AIDL 以及 Socekt 等等访敌。

2.3 IPC 基礎概念介紹


2.3.1 Serializable 接口
  Serializable 是 Java所提供的一個序列化接口,它是一個空接口衣盾,為對象提供標準的序列化和反序列化操作寺旺。,具體實現(xiàn)可以參考以下代碼:

    //序列化
    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 方式序列化對象的典型過程势决,只需要把實現(xiàn) Serializable 接口的 User 對象寫到文件中就可以快速回復了阻塑,恢復之后的內(nèi)容是完全一樣的。

  private static final long serialVersionUID = 519067123721295773L;

serialVersionUID 的作用是:當序列化對比反序列的 serialVersionUID 一致的時候可以正常的反序列化果复。如果不指定叮姑,系統(tǒng)會計算當前類的 hash 值并賦值給 serialVersionUID,而當前類的有所改變据悔,比如增加或者刪除了某些成員變量,那么系統(tǒng)會重新計算當前類的 hash 值然后重新賦值給 耘沼,反序列化失敗這時候系統(tǒng)就會 crash极颓。修改類名和成員變量類型,就算 serialVersionUID 通過了群嗤,一樣會失敗菠隆。

盡量手動指定 serialVersionUID (比如 1 L)或者工具按當前目錄結(jié)構(gòu)自動生成。

  • 靜態(tài)成員變量屬于類不屬于對象狂秘,所以不會參與序列化過程
  • 其次用 transient 關鍵字標記的成員變量不參與序列化過程

2.3.2 Parcelable 接口

Parcel 內(nèi)部包裝了可序列化的數(shù)據(jù)骇径,可以在 Binder 中自由的傳遞。序列化功能由 writeToParcel 方法來完成者春,反序列化功能由 CREATOR 來完成破衔,其內(nèi)部標明了如何創(chuàng)建序列化對象和數(shù)組;內(nèi)容描述功能由 describeContents 方法來完成钱烟。
<div align = center>Parcelable 的方法說明</div>

方法 功能 標記位
createFromParce l (Parcel in) 從序列化后的對象中創(chuàng)建原始對象
newArray(int size) 創(chuàng)建指定長度的原始對象數(shù)組
User(Parcel in) 從序列化后的對象中創(chuàng)建原始對象
writeToParcel(Parcel out, int flags) 將當前對象寫入序列化結(jié)構(gòu)中晰筛,其中 flags 標識有兩種值:0或者 1(參見右側(cè)標記位)嫡丙。為 1 時標識當前對象需要作為返回值返回,不能立即釋放資源读第,幾乎所有情況都為 0 PARCELABLE_WRITE_REYURN_VALUE
describeContents() 返回當前對象的內(nèi)容描述曙博,如果含有文件描述符,返回 1(參見右側(cè)標記)怜瞒,否則返回 0父泳,幾乎所有情況都返回 0 CONTENTS_FILE_DESCRIPTOR

Intent、Bundle吴汪、Bitmap 等都實現(xiàn)了 Parcelable 接口都可以直接序列化惠窄,同時 List 和 map 也可以序列化,前提它們里面每個元素都是可序列化的浇坐。

  • Serializable 是 java 中的序列化接口睬捶,使用起來簡單但是開銷大,序列化和反序列化過程需要大量 I/O 操作近刘。
  • Parcelable 是Android 的序列化方式擒贸,因此適合用在 Android 平臺上,缺點是使用起來稍微麻煩觉渴,但是它的效率高介劫,因此我們首選 Parcelable。

Parcelable 和 Serializable 都是實現(xiàn)序列化并且都可以用于 Intent 間的數(shù)據(jù)傳遞案淋。Parcelable 主要用于內(nèi)存序列化上座韵,通過 Parcelable 將對象序列化到存儲設備中或者將對象序列化后通過網(wǎng)絡傳輸,但這個過程會稍顯復雜踢京,因此這兩種情況建議大家適用 Serializable 誉碴。

2.3.3 Binder

  • 實現(xiàn) IBinder 接口,主要應用在 Service 中瓣距,是客戶端和服務端進行通信的媒介黔帕,服務端向客戶端返回實現(xiàn)了業(yè)務接口的 Binder 對象(Service 的 onBind 方法)
  • 是 Android 中一種跨進程通信的方式
  • 可以理解成一個虛擬物理設備,設備驅(qū)動為 /dev/binder ?? 此處不理解...
  • 是連接各種 Manager(ActivityManager蹈丸,WindowManager 等)和相應的 ManagerService 的橋梁

AIDL

AIDL 的流程:

  • 創(chuàng)建一個 Service 和一個 AIDL 接口成黄;
  • 創(chuàng)建一個類繼承自 AIDL 接口中的 Stub 類并實現(xiàn)抽象方法
  • 在 Service 的 onBind 方法中返回這個類的對象
  • 客戶端綁定服務端 Service,建立鏈接就可以訪問遠程服務端的方法了逻杖。

AIDL 作為接口定義文件奋岁,與普通的接口定義方法有些許不同,然而大致是類似的荸百。

  1. AIDL 支持的類型
  • 基本數(shù)據(jù)類型
  • String 和 CharSequence
  • 實現(xiàn)了 Parcelable 接口的對象
  • AIDL 接口本身的類型
  • 對于集合闻伶,AIDL 僅支持兩種類型
    • ArrayList,且里面的每個元素必須被 AIDL 支持
    • HashMap管搪,且里面的每個 key和 value 必須被 AIDL 支持
  1. 如果在 AIDL 文件中出現(xiàn)了定義的 Parcelable 對象虾攻,需要新建一個與之同名的 AIDL 文件铡买,并聲明為 parcelable 類型。
package *.*.*;
parcelable Something;

3.定義業(yè)務接口霎箍,AIDL 中除了基本類型都要標識方向:in/out/inout奇钞,只支持方法,不支持靜態(tài)常量漂坏。

package *.*;
import *.Something; // 即便處于同一個包下景埃,也要 import 語句

interface ISomeManager {

 List<Something> getSomethingList();
 void doSomething(in Something sth); 
}

源碼分析

Android 開發(fā)中,Binder 主要用在 Service 中顶别,包括 AIDL 和 Messager聪蘸,其中普通 Service中的 Binder 不涉及進程間通信拗胜,無法觸及 Binder 的核心胆数,而 Messager 的底層其實是 AIDL刻蚯,所以這里選擇用 AIDL 來分析 Binder 的工作機制。

源碼鏈接

image.png

AIDL 類似一個接口類剩失,通過 build 一下 project屈尼,構(gòu)建工具(build-tools 文中的 aidl.exe 程序)會編譯出一個類,在 build/generated/source/aidl/debug/ 的路徑下拴孤,我們找到了 AIDL 文件生成的類 IBookManager.java脾歧,下面我們就來閱讀這些源碼,來一窺究竟演熟。

image.png
public interface IBookManager extends android.os.IInterface {
     public static abstract class Stub extends android.os.Binder implements com.ryg.chapter_2.aidl.IBookManager {
...

代碼很長鞭执,直接看目錄結(jié)構(gòu)( ctrl + 7 )


image.png

DESCRIPTOP 是 Binder 的唯一標識,一般用當前 Binder 的類名表示芒粹。

 private static final java.lang.String DESCRIPTOR = "com.ryg.chapter_2.aidl.IBookManager";

這里的 Stub 類就是我們要關注的地方兄纺,因為它就是 AIDL 的實現(xiàn)類:
首先看它的 asInterface 方法,因為我們要在 ServiceConnection 連接成功后化漆,根據(jù)此方法將服務器返回的 Binder 轉(zhuǎn)換為遠程業(yè)務接口:

 public static com.ryg.chapter_2.aidl.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.ryg.chapter_2.aidl.IBookManager))) {
                return ((com.ryg.chapter_2.aidl.IBookManager) iin);
            }
            return new com.ryg.chapter_2.aidl.IBookManager.Stub.Proxy(obj);
        }

語意很清晰囤热,首先拿一個字符串(接口類全名)到本地進程查詢(queryLocalInterface),查詢不到則說明這是一個跨進程調(diào)用获三;這里運用到了代理模式(Proxy),因為是跨進程锨苏,就需要額外的步驟疙教,就是將請求參數(shù)和返回值序列化的過程,而單進程就不需要這個步驟了:

@Override
public void addBook(com.ryg.chapter_2.aidl.Book book) 
        throws android.os.RemoteException {
    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);
        }
         /**
           * 客戶端發(fā)起請求伞租,當前線程阻塞等待返回
           * 所以請求遠程服務盡量不要在 UI 線程上發(fā)起
           */
        mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
        /** 
          * 客戶端請求發(fā)送出去后
          * 遠程請求通過底層封裝后回調(diào)服務端的 Binder 的 onTransact 方法進行處理
          */
        _reply.readException();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
}

在 public Boolean onTransact(int code贞谓,Parcelable data,Parcelable reply葵诈,int flags) 方法中裸弦,服務端通過 code 知道客戶端請求的目標方法祟同,從data中取出所需的參數(shù),調(diào)用對應的業(yè)務方法理疙,執(zhí)行完畢后將結(jié)果寫入到 reply 中晕城。返回 true 意味著請求成功。利用這個特性可以做驗證是否有權(quán)限調(diào)用該服務窖贤,見 BookManagerService

public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException {
    // 檢測清單是否聲明了以下權(quán)限
    int check = checkCallingOrSelfPermission(
        "com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE");
    Log.d(TAG, "check=" + check);
    if (check == PackageManager.PERMISSION_DENIED) {
        return false;
    }
    // 檢查應用包名是否以 "com.ryg" 開頭
    String packageName = null;
    String[] packages = getPackageManager().getPackagesForUid(
            getCallingUid());
    if (packages != null && packages.length > 0) {
        packageName = packages[0];
    }
    Log.d(TAG, "onTransact: " + packageName);
    if (!packageName.startsWith("com.ryg")) {
        return false;
    }

    return super.onTransact(code, data, reply, flags);
}

清單中定義權(quán)限

<uses-permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" />
  • 當客戶端發(fā)起遠程請求時砖顷,由于當前線程會被掛起直至服務端進程返回數(shù)據(jù),所以若果一個遠程方法是很耗時的赃梧,那么不能在 UI 線程中發(fā)起此遠程請求滤蝠;
  • 其次,由于服務端的 Binder 方法運行在 Binder 的線程池中授嘀,所以 Binder 方法不管是否耗時都應該采用同步的方式去實現(xiàn)物咳,因為它已經(jīng)運行在一個線程中了。
Binder 的工作機制

其實我們完全可以不提供 AIDL 文件即可實現(xiàn) Binder蹄皱,之所以提供 AIDL 文件览闰,是為了方便系統(tǒng)為我們生產(chǎn)代碼,系統(tǒng)根據(jù) AIDL 文件生成 Java 文件的格式是固定的夯接。

Binder兩種重要的方法 linkToDeath 和 unlinkToDeath

Binder運行在服務端焕济,如果由于某種服務端異常終止了的話會導致客戶端的遠程調(diào)用失敗、所以Binder提供了兩個配對的方法linkToDeath和unlinkToDeath盔几,通過linkToDeath方法可以給Binder設置一個死亡代理晴弃,當Binder死亡的時候客戶端就會收到通知,然后就可以重新發(fā)起連接從而恢復連接了逊拍。

如何給Binder設置死亡代理
1上鞠、聲明一個DeathRecipient對象、DeathRecipient是一個接口芯丧,其內(nèi)部只有一個方法bindDied芍阎,實現(xiàn)這個方法就可以在Binder死亡的時候收到通知了。

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if (mRemoteBookManager == null) return;
        mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
        mRemoteBookManager = null;
        // TODO:這里重新綁定遠程Service
    }
};

2缨恒、在客戶端綁定遠程服務成功之后谴咸,給binder設置死亡代理

mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市骗露,隨后出現(xiàn)的幾起案子岭佳,更是在濱河造成了極大的恐慌,老刑警劉巖萧锉,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件珊随,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機叶洞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門鲫凶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人衩辟,你說我怎么就攤上這事螟炫。” “怎么了惭婿?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵不恭,是天一觀的道長。 經(jīng)常有香客問我财饥,道長换吧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任钥星,我火速辦了婚禮沾瓦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谦炒。我一直安慰自己贯莺,他們只是感情好,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布宁改。 她就那樣靜靜地躺著缕探,像睡著了一般。 火紅的嫁衣襯著肌膚如雪还蹲。 梳的紋絲不亂的頭發(fā)上爹耗,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機與錄音谜喊,去河邊找鬼潭兽。 笑死,一個胖子當著我的面吹牛斗遏,可吹牛的內(nèi)容都是我干的山卦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼诵次,長吁一口氣:“原來是場噩夢啊……” “哼账蓉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逾一,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤剔猿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嬉荆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡酷含,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年鄙早,在試婚紗的時候發(fā)現(xiàn)自己被綠了汪茧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡限番,死狀恐怖舱污,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情弥虐,我是刑警寧澤扩灯,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站霜瘪,受9級特大地震影響珠插,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颖对,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一捻撑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缤底,春花似錦顾患、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至徙歼,卻和暖如春犁河,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鲁沥。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工呼股, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人画恰。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓彭谁,卻偏偏與公主長得像,于是被迫代替她去往敵國和親允扇。 傳聞我的和親對象是個殘疾皇子缠局,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

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