IPC基础

1.IPC

Inter-Process Communication,即进程间通信或者跨进程通信。

2.进程与线程

进程与线程是不同的概念,按照操作系统中的描述,线程是CUP调度的最小单元,而进程一般是指一个执行单元,对于PC和移动设备来说,通常指一个程序或者应用的一次动态的过程。一个进程可以包含多个线程,即进程与线程是包含与被包含的关系。

3.什么情况下会出现多进程

①一个应用存在多进程;②多个应用之间构成多进程。
在Android中使用多进程常规的方法只有一种,那就是在注册四大组件时在AndroidManifest.xml中为四大组件声明属性 android:process属性来指定该组件在哪个进程中工作。例如本例中创建了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指定在新的进程 “包名:remote“中工作,ThirdActivity在 “com.jdqm.ipcdemo.remote”进程中工作,启动应用,并依此启动这个三个Activity,通过菜单Tools-->Android-->Android Devices monitor来查看当前设备的工作进程。

Android Devices monitor查看系统进程信息

8600、8603、8614这三个进程就是该应用新建的进程。除了以上方式外,我们还可以通过命令行的方式去查看进程信息: adb shell ps | grep com.jdqm.ipcdemo,其中com.jdqm.ipcdemo 是包名。

命令行方式查看系统进程信息

注:另外还有一种比较特殊的方式去创建多进程,那就是在jni层去fork新的进程。

在上面的例子中,android:process=":remote"与android:process="com.jdqm.ipcdemo.remote"有什么不同?
①前者会在冒号前添加当前应用的包名,即完整进程名为:com.jdqm.ipcdeo:remote;
②前者是当前应用的私有进程,其他的应用不能访问,而后者可以通过SharedUID方式与它跑在同一个进程中。

4.使用多进程会带来什么问题?

①静态成员和单例模式失效(每个进程有独立的虚拟机);
②线程同步机制失效(既然不是同一块内存,不管是锁对象还是锁全局类都无法保证线程的同步);
④SharedPreferences可靠新下降(并发读写都有可能出现问题);
④Application会多次创建(系统创建一个新的进程,就会分配一个新的虚拟机,这个过程就是应用启动的过程);

5.进程间通信有哪些方式?

Intent传递数据,共享文件和SharedPreferences,基于Binder的Messenger和AIDL以及Socket等。

6.Android实现对象的序列化和反序列化的两种方式

①实现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接口实现序列化和反序列化比较简单,只要该类实现Serializable接口即可,那其中的 “private static final long serialVersionUID = 1L”指定的这个值有什么用?serialVersionUID 的工作机制是这样的:在序列化时会把这个serialVersionUID写入到序列化文件中(也可能是其他中介),当反序列化时系统会去检查这个值与当前类的serialVersionUID是否相等,若相等则说明序列化时该类的版本与当前的版本是一致的,这个时候是能成功被反序列化的;否则说明当前类的结构发生了某些变换,比如类的成员变量的数量、类型发生了改变,这个时候是无法正常被反序列化的。当然这个值如果不指定的话,系统会根据类的结构计算一个Hash值,一旦这个类发送任何改变都会引起这个值得改变。有时候虽然我们的类发生了改变,但是我们仍可以利用反序列化来恢复原来的数据,这个时候必须要手动声明serialVersionUID。例如:假设序列化的时候类的结构是这样子的:

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();

这个时候我们去改变类的结构,增加一个字段 "private float score;",这个时候类的结构发生了改变,但是我们指定serialVersionUID 不变,“欺骗”系统。

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();

可以看到原来的两个字段被正确的反序列化,而增加的字段则是一个默认值。假设序列化时的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
只要实现这个接口的类的对象就可以通过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;
    }

    /**
     *返回当前对象的内容描述
     * @return 0或者1,当含有文件描述符时返回1,其他情况0,几乎所有的情况都是返回0
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     *
     * @param dest
     * @param flags 这个标记为可以是0或者1,为1时标示当前对象需要作为返回值返回,不能立即释放。几乎所有情况都是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有什么区别?

Serializable是java的序列化接口,使用起来简单但是开销大,会伴随着大量的I/O操作,而Parcelable是Android中的序列化方式,稍显复杂但是效率更高。Parcelable主要用于内存序列化上,通过Parcelable将对象序列化到存储设备上或者序列化后通过网络传输也是可以的,但是过程会稍显复杂,这两种情况下建议使用Serializable。

8.Binder

Binder是一个类,实现了IBinder接口。从IPC的角度来说,Binder是Android的一种跨进程通信方式。Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder。

9.Binder很重要的两个方法linkToDeath和unlinkToDeath

当我们发起RPC的过程中,由于某种原因服务端由于异常终止了,这个时候客户端到服务端的Binder连接就断了,这样就导致远程调用失败,如果我们不知道连接已经断了,那就会影响客户端。为了解决这个问题,Binder提供一配对的方法linkToDeath和unlinkToDeath,通过linkToDeath我们可以给Binder设置死亡代理,当Binder死亡时,就会收到通知,这个时候就可以选择重连或者别的操作。那么问题来了,如何给Binder设置死亡代理?

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)在客户端绑定服务成功后,给返回的Binder设置死亡代理

@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时候还“活着”。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容