學習筆記之IPC - Android進程通信

IPC

IPC就是 inter-process communication的縮寫,含義是 跨進程通信,在學習這之前,我們需要聊什麼是進程。進程是系統運行程序的最小單位,是程序的一個運行實例。一個進程的創建是通過Process.start()方法來完成的,其中的參數可以定製啓動後最先執行的線程,通常是指定一個ActivityThread(主線程),process會通過socket把創建進程的請求發送給zygote,最終由zygote來fork一個新的進程,也就是我們創建的APP的實例。具體的進程分析可以參考以下兩篇文章:Android process 的啓動流程Android進程系列第二篇—Zygote進程的啓動流程

安卓中的多進程

爲什麼要使用多進程
  1. 我們開發應用時也遇到過需要常駐後臺的應用,例如:跑步、音樂、手機管家之類的軟件,核心服務不會因爲主應用被後臺清理後功能停止;
  2. 我們如果要做一個大而全的應用,例如裏邊有地圖、大圖瀏覽、webview、監控服務、下載服務等喫內存的大戶,這個時候內存分配給單一進程的空間可能會不夠,因爲安卓分配給進程的閾值可能是48M、24M、16m等,超過閾值可能會導致主進程OOM。
    合理使用多進程,我們可以很大程度上避免應用的奔潰以及提升用戶體驗;
使用的風險

多進程固然效果不錯,但是如果使用不當,也會造成很多麻煩,例如:

  • 多虛擬機潛在問題:多進程創建之後,會創建獨立的運行空間,並且有獨立的虛擬機,很多Java的特性會在多進程中失效:
  • 靜態變量和單利模式完全失效:因爲不同進程之間的內存空間是不同的,所以虛擬機的方法區內的靜態變量也是相互獨立的,因爲單例模式是基於靜態變量的,所以單利也會失效,所以在訪問多進程的相同變量時值可能不同;
  • 線程同步可能完全失效:多進程是不同的虛擬機的,而線程同步是通過虛擬機調度完成的,所以會失效;
  • Application會多次創建

進程的創建等於創建一個新的應用實例,所以會再創建一個新的application,onCreate方法會被調用多次,所以不要在application中創建過多的靜態變量,這樣回導致內存增加;

  • 文件讀寫問題

文件讀寫是泛指,其中包括:數據庫、文件、sp等,由於Java中文件鎖和隊列機制都是虛擬機級別的,所有不同進程訪問同一個文件時,鎖是不管用的。

實例

安卓中常用的創建對進程的方式爲在AndroidManifest文件中,給activity、service、brodcastreceiver、contentprovider等四大組件設置process屬性,其實設置屬性也有兩種寫法,分別是":remote" 和直接寫進程名:process=“com.xxxxx.xxx:remote”; process=“com.xxxx.xxx”,如下:

<activity android:name=".SecondActivity"
            android:process=":remote">
        </activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

項目運行之後,從mainactivity跳轉到secondactivity,然後通過命令:adb shell ps -ef|grep com.xxxx.xxxx 查看當進程:
在這裏插入圖片描述

  • 這兩種命名方式的區別:最明顯的應該就是名字上了,直接 :remote 這樣的命名方式最簡單,其次就是用冒號這種形式創建的進程是屬於私有進程的,其他應用的組件不可以運行在該進程;

IPC概念介紹

接下來看一下IPC,前面已經大概說了什麼是IPC,也就是概念,現在我們聊聊ipc中涉及到的幾個概念:Serializable、Parcelable以及Binder。爲什麼要了解Serializable和Parcelable接口呢?因爲我們知道Binder是用來通信的,通信就涉及到信息傳遞(數據傳遞),我們傳遞的信息不是無規則的,相傳什麼就傳什麼,而Binder的數據傳輸就用到上述兩個接口,咱們一個個來看:

  • Serializable接口
    它是java提供的一個序列化接口,什麼又是序列化?請看文章:對Java Serializable(序列化)的理解和總結,其實大概意思就是將一個對象轉換爲字節序列的過程就是序列化。序列化和反序列化過程就是將對象I/O流的讀寫。我們再實現Serializable接口的時候一般系統會提示生成一個serialVersionUID的變量,這個變量的作用就是防止對象結構變化之後反序列化失敗。
  • Parcelable接口
    實現Parcelable接口是另一種序列化方式,安卓獨有的,它的序列化過程比較繁瑣,具體請看代碼:
public class User implements Parcelable {
    public int userId;
    public String userName;
    public boolean isMale;
    public Book book;

    protected User(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        isMale = in.readByte() != 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(userId);
        dest.writeString(userName);
        dest.writeByte((byte) (isMale ? 1 : 0));
    }

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

    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
}

我們注意到繼承完之後自動重寫了writeToParcel方法,這個方法就是序列化的主要方法,Parcel 這個對象內部包裝了可序列化的數據,可以在Binder中任意傳輸。

區別
實現Serializable接口的方式進行了I/O操作,而Parcelable則是在內存中序列化,直接降對象序列化到磁盤或者序列化之後通過網絡傳輸,這是其一,其二,Serializable使用簡單,只需要繼承繼承該接口即可,而Parcelable使用起來比較麻煩。

  1. Binder
    Binder是一個繼承了IBinder接口的一個類,它是Android中一種跨進程通信的方式。Android系統的虛擬地址內存分爲用戶空間和內核空間,用戶空間爲私有空間,內核空間爲共享空間,Binder的機制就是通過共享內核空間實現進程通信;Binder把進程A生成的IPC數據(在用戶空間生成),傳遞給BinderDriver,Binder Driver在內核空間運行,之後Binder Driver再把IPC數據傳遞給進程B。IPC數據由4部分組成,Handle、RPC數據、RPC代碼、Binder協議,Handle是服務號,用來區分不同的服務,RPC代碼和RPC數據分別是B應用待調用的函數和函數的參數,Binder協議表示IPC數據的處理方法,包括兩種,從IPC層傳遞到Binder Driver和從Binder Driver傳遞到IPC層。
    binder機制

安卓中的IPC方式

Android中實現IPC的方式有很多種,實質上其實多進程之間的數據傳遞,只要實現了這個目的就等於實現了IPC,一下就介紹幾種方式:

  1. Bundle 這是最簡單的方式,因爲前邊我們介紹瞭如何在安卓開啓多進程,那我們啓動多進程時,可以直接帶上數據,這樣就可以簡單實現多進程的通信。
startActivity(new Intent(...).putExtra("data",bundle));
  1. 文件共享: 我們可以讓不同的進程訪問同一個文件(xml、文本、序列化對象等)實現進程通信,但是我們也知道不同進程之間是不能保證文件鎖的一致的,這樣會導致數據不一致的問題,所以不推薦使用。
  2. 使用消息機制 Massager:其實這種實現的方式底層也是AIDL:

客戶端消息處理:

public class ClientHandler extends Handler {
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        if (msg.what == 2) {
            // 獲取到服務端返回的信息並打印出來
            String str = msg.getData().getString("reply");
            if (BuildConfig.DEBUG) Log.e("ClientMHandler", str);
        }
    }
}

服務端消息處理

public class ServiceHandler extends Handler {
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
        if (msg.what == 1) {
            Log.e("ServiceHandler",msg.getData().getString("msg"));
            // 獲取客戶端的信使實例
            Messenger client = msg.replyTo;
            // 新建返回消息
            Message message = Message.obtain();
            message.what = 2;
            Bundle bundle = new Bundle();
            bundle.putString("reply", "收到客戶端的消息了!!");
            message.setData(bundle);
            try {
                // 給客戶端發送消息
                client.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }
    }
}

服務端

public class MessengerService extends Service {
    // service啓動時就創建一個信使
    Messenger messenger = new Messenger(new ServiceHandler());
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        // service和client的綁定
        return messenger.getBinder();
    }
}

客戶端

public class MainActivity extends AppCompatActivity {

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 通過與服務端綁定,獲取Binder,也就是service,然後通過service創建一個和service通信的信使
            Messenger messenger = new Messenger(service);
            //創建消息
            Message message = Message.obtain();
            message.what = 1;
            Bundle bundle = new Bundle();
            bundle.putString("msg", "你好服務端!");
            message.setData(bundle);
            // 創建一個客戶端的信使,傳過去,然後服務端才能根據這個信使給客戶端發消息
            message.replyTo = new Messenger(new ClientHandler());
            try {
                messenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_start_service).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setClass(MainActivity.this, MessengerService.class);
                bindService(intent,conn, Service.BIND_AUTO_CREATE);
            }
        });
    }


}

以上是binder的簡單使用,我們可以再結合AIDL的只是對其進行優化,稍後專門做一下AIDL的用法;
5. ContentProvider :其實ContentProvider 也是IPC機制,底層也是通過AIDL實現的多進程通信,不過由於ContentProvider 的特殊機制,它的職責計較專一,就是爲其他程序提供數的。
6. socket: 學名“套接字”,是網絡通信中的概念,它分爲流式套接字和數據報套接字相中,分別對應網絡的傳輸控制層中的TCP和UDP協議,TCP是基於“三次握手”建立的連接,可提供穩定的雙向傳輸,而UDP是無連接的單向傳輸沒有像TCP一樣的超時重傳的機制,所以他的連接可能會出現數據丟失的問題,並且連接也不夠穩定,但是因爲沒有握手機制,所以它的效率是要高於TCP的。因爲是基於網絡層的,所以不受限於應用的用戶空間,所以它也是跨進程通信的手段之一,後續會詳細對socket做介紹;

參考鏈接

Android Binder機制

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