IPC基礎(chǔ)

1.IPC

Inter-Process Communication,即進(jìn)程間通信或者跨進(jìn)程通信。

2.進(jìn)程與線程

進(jìn)程與線程是不同的概念,按照操作系統(tǒng)中的描述,線程是CUP調(diào)度的最小單元,而進(jìn)程一般是指一個執(zhí)行單元,對于PC和移動設(shè)備來說,通常指一個程序或者應(yīng)用的一次動態(tài)的過程。一個進(jìn)程可以包含多個線程,即進(jìn)程與線程是包含與被包含的關(guān)系。

3.什么情況下會出現(xiàn)多進(jìn)程

①一個應(yīng)用存在多進(jìn)程;②多個應(yīng)用之間構(gòu)成多進(jìn)程。
在Android中使用多進(jìn)程常規(guī)的方法只有一種,那就是在注冊四大組件時在AndroidManifest.xml中為四大組件聲明屬性 android:process屬性來指定該組件在哪個進(jìn)程中工作。例如本例中創(chuàng)建了3個Activity,它的AndroidManifest.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jdqm.ipcdemo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

        <activity android:name=".SecondActivity"
            android:process=":remote" />

        <activity android:name=".ThirdActivity"
            android:process="com.jdqm.ipcdemo.remote"/>
    </application>

</manifest>

其中為SecondActivity指定在新的進(jìn)程 “包名:remote“中工作,ThirdActivity在 “com.jdqm.ipcdemo.remote”進(jìn)程中工作,啟動應(yīng)用,并依此啟動這個三個Activity,通過菜單Tools-->Android-->Android Devices monitor來查看當(dāng)前設(shè)備的工作進(jìn)程。

Android Devices monitor查看系統(tǒng)進(jìn)程信息

8600、8603、8614這三個進(jìn)程就是該應(yīng)用新建的進(jìn)程。除了以上方式外,我們還可以通過命令行的方式去查看進(jìn)程信息: adb shell ps | grep com.jdqm.ipcdemo,其中com.jdqm.ipcdemo 是包名。

命令行方式查看系統(tǒng)進(jìn)程信息

注:另外還有一種比較特殊的方式去創(chuàng)建多進(jìn)程,那就是在jni層去fork新的進(jìn)程。

在上面的例子中,android:process=":remote"與android:process="com.jdqm.ipcdemo.remote"有什么不同?
①前者會在冒號前添加當(dāng)前應(yīng)用的包名,即完整進(jìn)程名為:com.jdqm.ipcdeo:remote;
②前者是當(dāng)前應(yīng)用的私有進(jìn)程,其他的應(yīng)用不能訪問,而后者可以通過SharedUID方式與它跑在同一個進(jìn)程中。

4.使用多進(jìn)程會帶來什么問題?

①靜態(tài)成員和單例模式失效(每個進(jìn)程有獨立的虛擬機(jī));
②線程同步機(jī)制失效(既然不是同一塊內(nèi)存,不管是鎖對象還是鎖全局類都無法保證線程的同步);
④SharedPreferences可靠新下降(并發(fā)讀寫都有可能出現(xiàn)問題);
④Application會多次創(chuàng)建(系統(tǒng)創(chuàng)建一個新的進(jìn)程,就會分配一個新的虛擬機(jī),這個過程就是應(yīng)用啟動的過程);

5.進(jìn)程間通信有哪些方式?

Intent傳遞數(shù)據(jù),共享文件和SharedPreferences,基于Binder的Messenger和AIDL以及Socket等。

6.Android實現(xiàn)對象的序列化和反序列化的兩種方式

①實現(xiàn)erializable接口

public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;
   
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/mnt/sdcard/cache.txt"));
User u = new User("張三", 18);
out.writeObject(u);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("/mnt/sdcard/cache.txt"));

User u = (User) in.readObject();
in.close();

通過Serrializable接口實現(xiàn)序列化和反序列化比較簡單,只要該類實現(xiàn)Serializable接口即可,那其中的 “private static final long serialVersionUID = 1L”指定的這個值有什么用?serialVersionUID 的工作機(jī)制是這樣的:在序列化時會把這個serialVersionUID寫入到序列化文件中(也可能是其他中介),當(dāng)反序列化時系統(tǒng)會去檢查這個值與當(dāng)前類的serialVersionUID是否相等,若相等則說明序列化時該類的版本與當(dāng)前的版本是一致的,這個時候是能成功被反序列化的;否則說明當(dāng)前類的結(jié)構(gòu)發(fā)生了某些變換,比如類的成員變量的數(shù)量、類型發(fā)生了改變,這個時候是無法正常被反序列化的。當(dāng)然這個值如果不指定的話,系統(tǒng)會根據(jù)類的結(jié)構(gòu)計算一個Hash值,一旦這個類發(fā)送任何改變都會引起這個值得改變。有時候雖然我們的類發(fā)生了改變,但是我們?nèi)钥梢岳梅葱蛄谢瘉砘謴?fù)原來的數(shù)據(jù),這個時候必須要手動聲明serialVersionUID。例如:假設(shè)序列化的時候類的結(jié)構(gòu)是這樣子的:

public class Student implements Serializable{

    private static final long serialVersionUID = 1L;
    
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

通過序列化寫入文件

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/mnt/sdcard/student.txt"));
Student stu = new Student("小明", 18);
out.writeObject(stu);
out.close();

這個時候我們?nèi)ジ淖冾惖慕Y(jié)構(gòu),增加一個字段 "private float score;",這個時候類的結(jié)構(gòu)發(fā)生了改變,但是我們指定serialVersionUID 不變,“欺騙”系統(tǒng)。

public class Student implements Serializable{

    private static final long serialVersionUID = 1L;

    private String name;

    private int age;
   
     private float score;
   
     public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

在這種情況下去反序列化

ObjectInputStream in = new ObjectInputStream(new FileInputStream("/mnt/sdcard/student.txt"));

Student stu2 = (Student) in.readObject();
//name='小明', age=18, score=0.0
Log.d(TAG, "onCreate: " + stu2.toString()); 
in.close();

可以看到原來的兩個字段被正確的反序列化,而增加的字段則是一個默認(rèn)值。假設(shè)序列化時的serialVersionUID與反序列化時的不一致,則會報如下異常:

java.io.InvalidClassException: com.jdqm.ipcdemo.Student; Incompatible class (SUID): com.jdqm.ipcdemo.Student: static final long serialVersionUID =1L; but expected com.jdqm.ipcdeom.Student: static final long serialVersionUID =2L;

②Parcelable
只要實現(xiàn)這個接口的類的對象就可以通過Intent和Binder來傳遞。

public class Person implements Parcelable {

    private String name;

    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     *返回當(dāng)前對象的內(nèi)容描述
     * @return 0或者1,當(dāng)含有文件描述符時返回1,其他情況0,幾乎所有的情況都是返回0
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     *
     * @param dest
     * @param flags 這個標(biāo)記為可以是0或者1,為1時標(biāo)示當(dāng)前對象需要作為返回值返回,不能立即釋放。幾乎所有情況都是0
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }

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


        @Override
        public Person createFromParcel(Parcel source) {
            return new Person(source);
        }

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

    private Person(Parcel source) {
        name = source.readString();
        age = source.readInt();
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

7.Serializable與Parcelable有什么區(qū)別?

Serializable是java的序列化接口,使用起來簡單但是開銷大,會伴隨著大量的I/O操作,而Parcelable是Android中的序列化方式,稍顯復(fù)雜但是效率更高。Parcelable主要用于內(nèi)存序列化上,通過Parcelable將對象序列化到存儲設(shè)備上或者序列化后通過網(wǎng)絡(luò)傳輸也是可以的,但是過程會稍顯復(fù)雜,這兩種情況下建議使用Serializable。

8.Binder

Binder是一個類,實現(xiàn)了IBinder接口。從IPC的角度來說,Binder是Android的一種跨進(jìn)程通信方式。Binder還可以理解為一種虛擬的物理設(shè)備,它的設(shè)備驅(qū)動是/dev/binder。

9.Binder很重要的兩個方法linkToDeath和unlinkToDeath

當(dāng)我們發(fā)起RPC的過程中,由于某種原因服務(wù)端由于異常終止了,這個時候客戶端到服務(wù)端的Binder連接就斷了,這樣就導(dǎo)致遠(yuǎn)程調(diào)用失敗,如果我們不知道連接已經(jīng)斷了,那就會影響客戶端。為了解決這個問題,Binder提供一配對的方法linkToDeath和unlinkToDeath,通過linkToDeath我們可以給Binder設(shè)置死亡代理,當(dāng)Binder死亡時,就會收到通知,這個時候就可以選擇重連或者別的操作。那么問題來了,如何給Binder設(shè)置死亡代理?

1)聲明一個DeathRecipient對象

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if (bookManager == null) {
            return;
        }
        Log.d(TAG, "binderDied: ");
        bookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
        bookManager = null;
        Intent intent = new Intent(MainActivity.this, BookManagerService.class);
        bindService(intent, connection,BIND_AUTO_CREATE);
    }
};

2)在客戶端綁定服務(wù)成功后,給返回的Binder設(shè)置死亡代理

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
    Log.d(TAG, "onServiceConnected: ");
    bookManager = IBookManage.Stub.asInterface(service);
    try {
        bookManager.getBookList();
    } catch (RemoteException e) {
        e.printStackTrace();
    }
    try {
        service.linkToDeath(mDeathRecipient, 0);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

另外,可以通過binder的isBinderAlive方法查看binder時候還“活著”。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容