Android开发艺术探索学习笔记(2)--IPC机制(1)

那么经过了一礼拜,我们开始学习这本书的第二章啦。第二章讲的是Android内部的IPC机制(Inter-Process Communication),含义为进程间通信或者跨进程通信。注意是进程,而不是线程!这两个东西的区别相信大家应该都有所了解。线程是CPU的最小调度单位,一个进程可以有很多线程。所以说我们一个程序往往有许许多多的线程,而很少会有很多的进程(一般小程序一个进程就足够了)。

多进程技术导致的问题

就Java与android程序而言,一个进程分配一个虚拟机。那么也就是说我们如果使用多进程技术,相应的也会产生多个虚拟机。这个现象就会导致许多问题的产生。我们可以做个试验:
注:使用多进程只有一种方法就是在Manifiest里面注明android:process属性。

<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=".FirstActivity"
    android:process=":test" />

<activity
    android:name=".SecondActivity"
    android:process="com.example.testapp"/>

上面的Manifiest的写法。大家可以看到我写了三个activity,方便起见我们按顺序就叫ABC吧。跳转顺序是A->B->C。此处的android:process=”:test” ,:表示前面跟上包名,所以其应该为com.example.testapp:test。而在A中没有指定process属性则默认为包名:com.example.testapp。所以C和A是同一个进程。
我们再新建一个静态的变量

public class Test {
    public static int i;
}

然后在A B C的onCreate方法里面都把i的值打印出来。在A里面额外加一条Test.i++;
下面是运行结果:
04-08 10:44:17.490 15516-15516/com.example.testapp D/111: 1
04-08 10:44:24.655 15676-15676/com.example.testapp:test D/111: 0
04-08 10:44:25.725 15516-15516/com.example.testapp D/111: 1

我们从调试信息能看到,A和C的i都是1,而B的i却为0。这是为什么呢?其实原因我在前面已经讲过,不同的进程有不同的虚拟机。就上述实验代码而言,两个虚拟机意味着整个程序中会有两块独立的静态变量池,AC占一块,B占另一块。所以A中i++,B中还是为0。关于JAVA内存分布有什么疑问的可以看看我的另一篇博客Java内存区域于内存溢出异常

这里再列举一下多进程会导致的其他一些问题:

  1. 静态成员与单例模式完全失效(原因已经提过)
  2. 线程同步机制完全失效(原因如上)
  3. Sharepreference可靠性下降(Sharepreference不允许两个进程同时写或者一个写一个读)
  4. Application多次创建(创建新进程并且分配独立的虚拟机这就是一个重新启动Application的过程)

我们针对第四条做个试验:
新建一个TestApplication继承Application,简单输出一个值

public class TestApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("111","1212");
    }
}

然后在Manifiest里面把我们的系统默认的application的name命名为TestApplicationandroid:name=".TestApplication"。此时重复上面的流程A->B->C,我们会发现1212出现了两次:
04-08 13:33:49.755 25494-25494/com.example.testapp D/111: 1212
04-08 13:33:33.020 25355-25355/com.example.testapp:test D/111: 1212

注:若不指定process,则“com.example.testapp”有可能会为?。

接下来我们就来看看我们可以通过哪几种方式

Serializable接口

Serializable接口是一个Java提供的序列化接口,其本身为空(大家可以追踪看一下)

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package java.io;

public interface Serializable {
}

我们如果要使用Serializable方式来序列化,其实很简单。只需要在我们自己的类里面实现这个接口就行。如:

public class Test implements Serializable {
    int i;
    String string;
    double dd;
    public Test(int i,String string,double dd)
    {
        this.i = i;
        this.string = string;
        this.dd = dd;
    }
    double d() {
        return dd;
    }

    public String toString()
    {
        return i+" "+string +" "+dd;
    }
}

然后我们在Activity里面实现序列化和反序列化看看效果:
序列化

Test test = new Test(1,"test",2.0);
try {
    File file = new File(Environment.getExternalStorageDirectory(),"test.txt");
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
    out.writeObject(test);
    out.close();
} catch (IOException e) {
    e.printStackTrace();
}

反序列化

File file = new File(Environment.getExternalStorageDirectory(),"test.txt");
 try {
     ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
     Test test = (Test) in.readObject();
     in.close();
     Log.d("111", test.toString()+test.d());
 } catch (Exception e) {
     e.printStackTrace();
 }

结果输出为:04-08 15:13:25.725 30518-30518/com.example.testapp D/111: 1 test 2.02.0
说明我们这个还是保存成功了。过程就是把test对象通过输出流的方式保存在本地,然后再通过输入流的方式再读取进来。
注:两个test对象并不是同一个。我们可以试验一下,把两个test对象变成全局变量,然后比一下test1==test2。

serialVersionUID

我们在看一些同样实现serializable接口的代码的时候,往往会看到一句private static final long serialVersionUID = ;
那么这个serialVersionUID是干嘛用的呢?我先把书上说的原话抄一遍:serialVersionUID是用于协助序列化和反序列化的:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中,当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致才可以反序列化成功。

如果我们不指定serialVersionUID,那么系统会自动根据类的结构来计算hash值并将其赋给serialVersionUID,保证每个类都不同。这样子的话,如果先存好了文件,之后修改了类里面的一些东西(如:增加变量和方法),那么由于hash值改变导致反序列化失败。但如果我们自己指定serialVersionUID就不太会有这个问题。
我自己试了一下如下情况:指定serialVersionUID之后,我们可以修改类的变量名,删除变量,修改方法。而如果不指定,则除了修改方法,其他操作都会抛出异常。

总结来说serialVersionUID就是用来提高兼容性的。指定了serialVersionUID之后除非你写的类被你改的面目全非了,否则基本上都可以反序列化。另外序列化和反序列化的过程也是可以修改的。看我上面写的代码其实也就是out.writeObject(test1);和test2 = (Test) in.readObject();这两个方法。

Parcelable接口

Parcelable相比于Serializable要复杂一些,而且Parcelable是Android提供的序列方式(还记得Serialable是什么提供的吗?没错是Java),所以比较适合用在Android平台上(毕竟自己用自己的东西最爽啦~)。下面呈上Parcelable的代码:

public class Test implements Parcelable {
    int i;
    String string;

    public Test(int i, String string, double dd) {
        this.i = i;
        this.string = string;
    }

    public String toString() {
        return i + " " + string;
    }

    protected Test(Parcel in) {//读的顺序要跟下面写的顺序一样
        i = in.readInt();
        string = in.readString();
    }

    public static final Creator CREATOR = new Creator() {
        @Override
        public Test createFromParcel(Parcel in) {//从序列化后的对象中创建原始对象
            return new Test(in);
        }

        @Override
        public Test[] newArray(int size) {//创建指定长度的原始对象数组
            return new Test[size];
        }
    };


    @Override
    public int describeContents() {//返回当前对象的内容描述。如果含有文件描述符则返回1,否则返回0。大多数情况都返回0
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {//将当前对象写入序列化结构中其中i有两个值0/1
        parcel.writeInt(i);                          //为1时标识当前对象需要作为返回值返回,不能立即释放资源,几                                       
        parcel.writeString(string);                  //所有情况都为0
    }
}

上面的代码是实现parcelable接口的类,相应的注释我都写上去了。说实在的,我其实也没怎么看懂书上的这些注释,而且我发现了书上没有把这个类实例保存到本地的代码。网上找了好多也都是差不多的介绍怎么实现parcelable接口。。。诶算了,继续往下看吧。

**总结:**Serializable是Java中的序列化接口,用起来简单但是开销大,序列化和反序列化都需要大量的I/O操作。而Parcelable的缺点就是用起来麻烦,但是效率高。所以Parcelable主要用在内存序列化,而序列化到存储设备或者序列化后通过网络传输就推荐用Serializable。

下一章我们讲接下去的内容——Binder还有Android的一些机制如(Bunder,Messenger,AIDL等)

你可能感兴趣的:(android艺术探索,学习笔记)