Android IPC通信--基礎介紹

01.什麼是IPC

1.1什麼是RPC?

1.2什麼是DIL?

02.Android中如何控件進程

03.進程的重要性

04.Android中線程

05.開啓多進程

06.多進程影響

07.Serializable

08.Parcelable


本文屬於IPC進程通信的一些介紹和鋪墊,具體的IPC進程通信方式的實現請移步:IPC進程通信間方式

01.什麼是IPC

  • IPC(Inter-Process Communication)的含義即爲進程間通信或者翻譯爲跨進程通信,是指兩個進程之間進行數據交換的過程。
  • 一般情況下,在 Android系統中一個應用就只享有一個進程,在最簡單的情況下一個進程可以只包含有一個線程(當然,一般情況下是不可能的),即主線程,也稱爲 UI 線程
  • 有時候應用因爲某些原因需要採用多進程模式,此時如果要在應用內的不同進程間進行通信,就需要使用到 IPC 機制。或者是兩個不同的應用需要進行數據交換,此時也一樣需要依靠 Android 系統提供的 IPC 方案。

1.1什麼是RPC?

  • RPC 即 Remote Procedure Call (遠程過程調用) 是一種計算機通訊協議,它爲我們定義了計算機 C 中的程序如何調用另外一臺計算機 S 的程序,讓程序員不需要操心底層網絡協議,使得開發包括網絡分佈式多程序在內的應用程序更加容易。
  • RPC 是典型的 Client/Server 模式,由客戶端對服務器發出若干請求,服務器收到後根據客戶端提供的參數進行操作,然後將執行結果返回給客戶端。
  • RPC 位於 OSI 模型中的會話層

1.2什麼是DIL?

  • RPC 只是一種協議,規定了通信的規則。
  • 在實際工作中客戶端與服務端會有各種各樣的平臺,就好像日常開發一樣,爲了統一處理不同的實現,需要定義一個共同的接口,於是有了 IDL。
  • IDL 即 Interface Description Language (接口定義語言)。
  • 它通過一種中立的方式來描述接口,使得在不同平臺上運行的對象和用不同語言編寫的程序可以相互通信交流。比如,一個組件用 C++ 寫成,另一個組件用 Java 寫,仍然可以通信。
     

02.Android中如何控件進程

  • 如果需要控制某個組件所屬的進程,則可在清單文件中執行以下操作
    • 各類組件(Android四大組件)標籤:< activity >< service >< receiver > 和 < provider >等均支持 android:process屬性,此屬性可以指定該組件應在哪個進程運行,通過此屬性可以使每個組件均在各自的進程中運行,或者使一些組件共享一個進程,而其他組件則不共享。 此外,還可以通過該屬性使不同應用的組件在相同的進程中運行,但前提是這些應用共享相同的 Linux 用戶 ID 並使用相同的證書進行簽署
    • 此外,< application > 元素也支持 android:process 屬性,以設置適用於所有組件的默認值
  • 如果內存不足而系統又需要內存時,系統可能會在某一時刻關閉某一進程
    • Android 系統將權衡所有進程對用戶的相對重要程度來決定終止哪個進程,被終止的進程中運行的應用組件也會隨之銷燬, 當這些組件需要再次運行時,系統將爲它們重啓進程

03.進程的重要性

  • Android 系統會盡量長時間地保持應用進程,但爲了新建進程或運行更重要的進程,有時候就需要移除舊進程以回收內存。 爲了確定保留或終止哪些進程,系統會根據進程中正在運行的組件以及這些組件的狀態,將每個進程放入“重要性層次結構”中。
  • 必要時,系統會首先消除重要性最低的進程,然後是重要性略遜的進程,依此類推。重要性層次結構一共分爲五級,第一級最爲重要
    • 前臺進程
      • 即用戶當前操作所必需的進程。如果一個進程滿足以下任一條件,即視爲前臺進程:
        • 託管用戶正在交互的 Activity(已調用 Activity 的 onResume() 方法)
        • 託管某個 Service,該Service綁定到用戶正在交互的 Activity
        • 託管正在前臺運行的 Service(服務已調用 startForeground())
        • 託管正在執行生命週期回調的 Service(onCreate()、onStart() 或 onDestroy())
        • 託管正執行其 onReceive() 方法的 BroadcastReceiver
      • 通常,在任意給定時間前臺進程都爲數不多,只有在內存不足以支持它們同時繼續運行這一情況下系統纔會終止它們。 此時,設備往往已達到內存分頁狀態,因此需要終止一些前臺進程來確保用戶界面正常響應
    • 可見進程
      • 沒有任何前臺組件,但仍會影響用戶在屏幕上所見內容的進程。如果一個進程滿足以下任一條件,即視爲可見進程:
        • 託管不在前臺、但仍對用戶可見的 Activity(已調用其 onPause() 方法)。例如,如果前臺 Activity 啓動了一個覆蓋一部分屏幕的對話框時,就會出現這種情況
        • 託管綁定到可見或前臺Activity 的 Service
      • 可見進程被視爲是極其重要的進程,除非爲了維持所有前臺進程同時運行而必須終止,否則系統不會終止這些進程
    • 服務進程
      • 正在運行已使用 startService() 方法啓動的服務且不屬於上述兩個更高類別的進程。
      • 儘管服務進程與用戶所見內容沒有直接關聯,但是它們通常在執行一些用戶關心的操作(例如,在後臺播放音樂或從網絡下載數據)。因此,除非內存不足以維持所有前臺進程和可見進程同時運行,否則系統會讓服務進程保持運行狀態
    • 後臺進程
      • 包含目前對用戶不可見的 Activity 的進程(已調用 Activity 的 onStop() 方法)。
      • 這些進程對用戶體驗沒有直接影響,系統可能會隨時終止它們以回收內存供前臺進程、可見進程或服務進程使用。 通常會有很多後臺進程在運行,因此它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進程最後一個被終止。如果某個 Activity 正確實現了生命週期方法,並保存了其當前狀態,則終止其進程不會對用戶體驗產生明顯影響,因爲當用戶導航回該 Activity 時,Activity 會恢復其所有可見狀態
    • 空進程
      • 不含任何活動應用組件的進程。
      • 保留這種進程的的唯一目的是用作緩存,以縮短下次在其中運行組件所需的啓動時間。爲使總體系統資源在進程緩存和底層內核緩存之間保持平衡,系統往往會終止這些進程
  • 根據進程中當前活動組件的重要程度,Android 會將進程評定爲它可以達到的最高級別。
    • 例如,如果某進程託管着服務和可見Activity,則會將此進程評定爲可見進程。此外,一個進程的級別可能會因其他進程對它的依賴而有所提高,即服務於另一進程的進程其級別永遠不會低於其所服務的進程。 例如,如果進程 A 中的內容提供程序爲進程 B 中的客戶端提供服務,或者如果進程 A 中的服務綁定到進程 B 中的組件,則進程 A 始終被視爲至少與進程 B 同樣重要。
    • 由於運行Service的進程其級別高於託管後臺 Activity 的進程,因此啓動長時間運行操作的 Activity 最好爲該操作啓動Service,而不是簡單地創建工作線程,當操作有可能比 Activity 更加持久時尤要如此。例如,正在將圖片上傳到網站的 Activity 應該啓動Service來執行上傳操作,這樣一來,即使用戶退出 Activity,仍可在後臺繼續執行上傳操作。使用Service可以保證,無論 Activity 發生什麼情況,該操作至少具備“服務進程”優先級。 同理,廣播接收器也應使用Service,而不是簡單地將耗時冗長的操作放入線程中

04.Android中線程

  • 應用啓動時,系統會爲應用創建一個名爲“主線程”的執行線程。
    • 此線程非常重要,因爲它負責將事件分派給相應的用戶界面小部件,其中包括繪圖事件。此外,它也是應用與 Android UI 工具包組件(來自 android.widget 和 android.view 軟件包的組件)進行交互的線程。因此,主線程也稱爲 UI 線程
  • 系統不會爲每個組件實例創建單獨的線程。
    • 運行於同一進程的所有組件均在UI線程中實例化,並且對每個組件的系統調用均由該線程進行分派。 因此,響應系統回調的方法(比如報告用戶操作的 onKeyDown() 或生命週期回調方法)始終在進程的 UI 線程中運行
    • 例如,當用戶觸摸屏幕上的按鈕時,應用的 UI 線程會將觸摸事件分派給小部件,而小部件反過來又設置其按下狀態,並將失效請求發佈到事件隊列中。 UI 線程從隊列中取消該請求並通知小部件應該重繪自身
  • 如果 UI 線程需要處理所有任務,則執行耗時很長的操作將會阻塞整個 UI。
    • 一旦線程被阻塞,將無法分派任何事件,包括繪圖事件。如果 UI 線程被阻塞超過特定時間(目前大約是 5 秒鐘),用戶就會看到一個顯示“應用無響應”(ANR) 文本的對話框
    • 此外,Android UI 工具包並非線程安全工具包。因此不能通過工作線程操縱 UI,而只能通過 UI 線程操縱用戶界面。 因此,Android 的單線程模式必須遵守兩條規則:
      • 不要阻塞 UI 線程
      • 不要在 UI 線程之外訪問 Android UI 工具包

05.開啓多進程

  • 爲一個 Android 應用開啓多進程模式的方法有兩種。
    • 第一種方法是在 AndroidMenifest 中爲四大組件指定 android:process 屬性,爲其聲明要在哪個進程名下運行,即可開啓多進程。
    • 第二種方法是通過 JNI 在 native 層中 fork 一個新的進程。這裏只討論第一種方法。
  • Android 應用默認在命名爲包名的進程下運行,除非你爲其指定了 android:process 屬性 例如,這裏創建一個應用,包名爲 com.yc.ipc ,再指定四大組件之一的 Service 運行在其它進程下
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
    
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    
    <service
        android:name=".MyService"
        android:process="com.yc.process.test" />
  • 並在啓動 MainActivity 的同時啓動MyService,這樣,在系統進程列表中就可以看到這兩個相關的進程

06.多進程影響

  • 雖然開啓多進程的方法並不算麻煩,但當應用開啓了多進程後,其實會對來很多的負面影響,主要有以下幾個:
    • 靜態成員與單例模式失效
    • 線程同步機制失效
    • SharedPreferences 可靠性下降
    • Application 被創建多次
  • 爲了解決多進程帶來的問題,系統也爲開發者提供了很多的跨進程通信方式,比如文件共享、ContentProvider、Messenger、AIDL、Socket 等

07.Serializable

  • 跨進程通信的目的就是爲了進行數據交換,但並不是所有的數據類型都能被傳遞,除了基本數據類型外,還必須是實現了序列化和反序列化的數據類型纔可以,即實現了 Serializable 接口或 Parcelable 接口的數據類型
  • Serializable 接口是由 Java所提供的一個序列化接口,是一個空接口,爲對象提供了標準的序列化和反序列化接口。類只要實現了該接口,即可自動實現默認的序列化過程。
    package java.io;
    
    public interface Serializable {
    }
    
  • 此外,爲了輔助系統完成對象的序列化和反序列化過程,還可以聲明一個long型數據serivalVersionUID
    private static final long serivalVersionUID = 123456578689L;
    • 序列化時系統會把對象的信息以及 serivalVersionUID 一起保存到某種介質中(例如文件或內存中),當反序列化時就會把介質中的 serivalVersionUID 與類中聲明的 serivalVersionUID 進行對比,如果兩者相同則說明序列化的類與當前類的版本是相同的,則可以序列化成功。如果兩者不相等,則說明當前類的版本已經變化(可能是新增或刪減了某個方法),則會導致序列化失敗
  • 沒有聲明serivalVersionUID怎麼辦?
    • 如果沒有手動聲明 serivalVersionUID ,編譯工具則會根據當前類的結構自動去生成 serivalVersionUID ,這樣在反序列化時只有類的結構完全保持一致才能反序列化成功
    • 爲了當類的結構沒有發生結構性變化時依然能夠反序列化成功,一般是手動爲 serivalVersionUID 指定一個固定的值。這樣即使類增刪了某個變量或方法體時,依然能夠最大程度地恢復數據。當然,類的結構不能發生太大變化,否則依然會導致反序列化失敗
  • 某字段可以不用序列化麼
    • 此外,靜態成員變量屬於類不屬於對象,所以不會參與序列化過程,用 transient 關鍵字標記的成員變量也不會參與序列化過程

08.Parcelable

  • Parcelable 接口是由 Android 系統提供的序列化接口,官方也推薦使用 Parcelable 進行序列化操作,Bundle 、 Intent 和 Bitmap 等都實現了 Parcelable 接口。
  • Parcelable 接口相比 Serializable 更爲高效,但實現方式也相比麻煩些。實現 Parcelable 接口需要實現四個方法,用於進行序列化、反序列化和內容描述。一般我們也不需要手動實現 Parcelable 接口,可以通過 Android Studio的一個插件:Android Parcelable code generator 來自動完成

實現一個Parcelable接口,需要實現以下幾個方法:

1.構造函數:從序列化後的對象中創建原始對象

2.describeContents :接口內容的描述,一般默認返回0即可

3.writeToParcel:序列化的方法,將類的數據寫到parcel容器中

4.靜態的parcelable.Creator接口,這個接口包含兩個方法:

            1)createFormParcel:反序列化的方法,將Parcel還原成Java對象

            2)newArray:提供給外部類反序列化這個數組使用。

下面看個例子:

public class Book implements Parcelable{
    private String bookName;
    private String author;
    private int publishDate;

    public Book(){

    }

    //寫一個構造方法或者set方法來方便寫入數據
    public String getBookName(){
        return bookName;
    }

    public void setBookName(String bookName){
        this.bookName = bookName;
    }

    public String getAuthor(){
        return author;
    }

    public void setAuthor(String author){
        this.author = author;
    }

    public int getPublishDate(){
        return publishDate;
    }

    public void setPublishDate(int publishDate){
        this.publishDate = publishDate;
    }

    @Override
    public int describeContents(){
        return 0;
    }

    @Override
    public void writeToParcel(Parcel out, int flags){
        //該方法將類的數據寫入外部提供的Parcel中.即打包需要傳遞的數據到Parcel容器保存,
        //以便從parcel容器獲取數據
        out.writeString(bookName);
        out.writeString(author);
        out.writeInt(publishDate);
    }

    public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>(){

     @Override
        public Book[] newArray(int size){
            //從Parcel容器中讀取傳遞數據值,封裝成Parcelable對象返回邏輯層。
            return new Book[size];
        }

        @Override
        public Book createFromParcel(Parcel in){
            //從Parcel容器中讀取傳遞數據值,封裝成Parcelable對象返回邏輯層。
            return new Book(in);
        }
    };

    public Book(Parcel in){
        //如果元素數據是list類型的時候需要: lits = new ArrayList<?> in.readList(list);
        //否則會出現空指針異常.並且讀出和寫入的數據類型必須相同.如果不想對部分關鍵字進行序列化,可以使用transient關鍵字來修飾以及static修飾.
        bookName = in.readString();
        author = in.readString();
        publishDate = in.readInt();
    }
}

 

本文屬於IPC進程通信的一些介紹和鋪墊,具體的IPC進程通信方式的實現請移步:IPC進程通信間方式

文章參考了GitHub上的Demo和一些博客,用於個人總結和分享,若有不足之處,望指正。

參考博客

  1. https://blog.csdn.net/u011240877/article/details/72863432
  2. https://blog.csdn.net/zizidemenghanxiao/article/details/50341773?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.nonecase

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章