那么经过了一礼拜,我们开始学习这本书的第二章啦。第二章讲的是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内存区域于内存溢出异常
这里再列举一下多进程会导致的其他一些问题:
我们针对第四条做个试验:
新建一个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接口是一个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。
我们在看一些同样实现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相比于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等)