吐槽
中午不想起来啊啊啊,但是不起来又不行,唉,坚持坚持,这个世界不会因为我一个人的懒惰而不转的。
参考任玉刚老师的《Android开发艺术探索》
Android IPC简介
IPC是进程间的通信或者跨进程通信,是指两个进程之间的进行数据交换的过程
Bundle、文件共享、AIDL(Android Interface definition language一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口,涉及Binder连接池的概念)、Messenger、ContentProvider、Socket都是进程间通信的方式。
进程:和线程是截然不同的概念。线程是CPU调度的最小单元,同时线程是一种有限的系统资源。而进程一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程(是包含的关系)。最简单的情况下,一个进程中可以只有一个线程,即主线程。Android中主线程也叫UI线程,在UI线程里面才能操作页面元素(耗时的任务在主线程中执行会造成界面无法响应:ANR,所以要把耗时的线程放在其他线程中)。
注意点:
- 一个进程包含很多线程
- 任何的一个系统都会有IPC
- 安卓中最具有特色的通信方式就是Binder
Android中的多进程模式
好像多进程没有那么简单
开启多进程模式
让一个应用开启多进程模式有一下的两种方法
- 在AndroidMenifest中指定android:process属性
- 通过JNI在native层去fork一个新的进程(特殊情况)
在第一种情况下的时候,AndroidMenifest中没有给某个组件指定process属性时默认运行默认进程,默认进程的名字为程序包名。如果要指定,有两种方式。
android:process=":ffy"
android:process="com.sakura.test.ffy"
这两种好像还不一样
- 第一种分号的意思“:”的含义是指要在当前进程名前面附件上当前的包名,完整的进程名字是com.sakura.test.ffy
- 第二种进程名直接就是属性值,进程名字com.sakura.test.ffy
- 第一种属于私有进程,和其他应用的组件不可以跑到同一个进程里面
- 第二种属于全局进程,其他应用可以通过ShareUID的方式和他跑在同一个进程里面
UID 指用户ID.
UID在linux中就是用户的ID,表明时哪个用户运行了这个程序,主要用于权限的管理。而在android 中又有所不同,因为android为单用户系统,这时UID 便被赋予了新的使命,数据共享,为了实现数据共享,android为每个应用几乎都分配了不同的UID,不像传统的linux,每个用户相同就为之分配相同的UID。(当然这也就表明了一个问题,android只能时单用户系统,在设计之初就被他们的工程师给阉割了多用户),使之成了数据共享的工具。
PID 指进程ID.
PID是进程的身份标识,程序一旦运行,就会给应用分配一个独一无二的PID(ps:一个应用可能包含多个进程,每个进程有唯一的一个PID)
进程终止后PID会被系统收回,再次打开应用,会重新分配一个PID(新进程的PID一般比之前的号要大)
因此在android中PID,和UID都是用来识别应用程序的身份的,但UID是为了不同的程序来使用共享的数据。
多进程运行机制
在安卓里面,我们知道安卓为每个应用都分配了一个独立的虚拟机,或者说为了每个进程都分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这个就导致不同虚拟机访问同一个对象的时候,产生多份副本。
所有的运行在不同进程中的四大组件都不能通过共享内存来共享数据,会造成以下的影响
- 静态成员和单例模式完全失效
原因:多进程会分配不同的虚拟机,产生多个对象副本
- 线程同步结构完全失效
原因:不同进程的对象不是同一个对象,所以加锁达不到效果
- SharedPreference的可靠性完全失效
原因:SharePreferences不支持两个进程同时进行写操作,会导致一定几率数据丢失,SharePreferences底层是通过读写XML来实现的,并且内存中会有缓存。
- Application会多次创建
原因:当一个组件跑在新进程中时,系统会在创建新进程时同时分配独立的虚拟机,这个过程是一个应用启动的过程,相当于把应用重新启动了一次,自然Application也会重新创建
Application和Activity,Service一样,是android框架的一个系统组件,当android程序启动时系统会创建一个 application对象,用来存储系统的一些信息。通常我们是不需要指定一个Application的,这时系统会自动帮我们创建,如果需要创建自己 的Application,也很简单创建一个类继承 Application并在manifest的application标签中进行注册(只需要给Application标签增加个name属性把自己的 Application的名字定入即可)。
android系统会为每个程序运行时创建一个Application类的对象且仅创建一个,所以Application可以说是单例 (singleton)模式的一个类.且application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。因为它是全局 的单例的,所以在不同的Activity,Service中获得的对象都是同一个对象。所以通过Application来进行一些,数据传递,数据共享 等,数据缓存等操作。
运行在两个进程中的两个组件会拥有独立的虚拟机、独立的Application、独立的内存空间。
它们也就相当于是两个应用拥有相同的ShareUID和签名,但不跑在同一进程中,它们可以访问对方数据,但是不能通过共享内存的方式。虽然不能共享内存了,但是还有很多方式可以实现跨进程的数据交互。
多进程通信的方式
以下若干名词都是IPC的基础概念,先大概了解一下:Serializable和Parcelable接口可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serilizable。还有的时候我们需要把对象持久化到存储设备上或者通过网络传输给其他客户端,也需要Serializable来完成对象的持久化
序列化 (Serialization):将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
序列化的目的
- 永久的保存对象数据(将对象数据保存在文件当中,或者是磁盘中
- 通过序列化操作将对象数据在网络上进行传输(由于网络传输是以字节流的方式对数据进行传输的.因此序列化的目的是将对象数据转换成字节流的形式)
- 将对象数据在进程之间进行传递(Activity之间传递对象数据时,需要在当前的Activity中对对象数据进行序列化操作.在另一个Activity中需要进行反序列化操作讲数据取出)
- Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长(即每个对象都在JVM中)但在现实应用中,就可能要停止JVM运行,但有要保存某些指定的对象,并在将来重新读取被保存的对象。这是Java对象序列化就能够实现该功能。(可选择入数据库、或文件的形式保存)
- 在Intent之间,基本的数据类型直接进行相关传递即可,但是一旦数据类型比较复杂的时候,就需要进行序列化操作了.
安卓中两种序列化的实现方法
(1)Implements Serializable 接口 (声明一下即可)
通过这个方法的实现的对象的序列化,实现起来非常简单,几乎所有的工作已经自动完成了,在对象进行序列化和反序列化的时候,只需要调用ObjectoutputStream和ObjectInputStream即实现
public class Person implements Serializable{
private static final long serialVersionUID = -7060210544600464481L;
private String name;
private int age;
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
}
注意点:
serialVersionUID:这是用来辅助序列化和反序列化过程的(有用),原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能正常地被反序列化。serialVersionUID的工作机制是:
序列化的时候系统把当前类的serialVersionUID写入序列化的文件中(也有可能是其他中介),当反序列化的时候就回去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这时候就可以成功反序列化;否则说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的(报java.io.InvalidClassException)。
一般来说,应该手动指定serialVersionUID的值,比如1L,也可以让AS根据当前类的结构自动生成它的hash值(本质一样),这样序列化和反序列化时两者的serialVersionUID是相同的,因此可以正常进行反序列化。
如果不手动指定serialVersionUID的值,反序列化时当前类有所改变,比如增加或删除了某些成员变量,系统会重新计算当前类的hash值并把它赋值给serialVersionUID,这时候类的serialVersionUID就和序列化数据中的serialVersionUID不一致,于是反序列化失败,程序就会出现crash,所以手动指定serialVersionUID可以很大程度避免反序列化过程的失败。
2)Implements Parcelable 接口(不仅仅需要声明,还需要实现内部的相应方法)
注:写入数据的顺序和读出数据的顺序必须是相同的.
public class Book implements Parcelable{
private String bookName;
private String author;
private int publishDate;
public Book(){
}
public String getBookName(){
return bookName;
}
public void setBookName(String bookName){
this.bookName = bookName;
}
public String getAuthor(){
return author;
}
public void setAuthor(String author){
this.author = author;
}
public int getPublishDate(){
return publishDate;
}
public void setPublishDate(int publishDate){
this.publishDate = publishDate;
}
@Override
public int describeContents(){
return 0;
}
@Override
public void writeToParcel(Parcel out, int flags){
out.writeString(bookName);
out.writeString(author);
out.writeInt(publishDate);
}
public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>(){
@Override
public Book[] newArray(int size){
return new Book[size];
}
@Override
public Book createFromParcel(Parcel in){
return new Book(in);
}
};
public Book(Parcel in){
//如果元素数据是list类型的时候需要: lits = new ArrayList<?> in.readList(list);
//否则会出现空指针异常.并且读出和写入的数据类型必须相同.如果不想对部分关键字进行序列化,可以使用transient关键字来修饰以及static修饰.
bookName = in.readString();
author = in.readString();
publishDate = in.readInt();
}
}
Serializable 接口和Parcelable 接口的选择
相同点:
都可以实现实例化,而且都可以用于Intent的数据传递
不同点
- Serializable是java的序列化的接口,用起来很方便但是开销大,序列化和反序列话的时候需要大量的I/O操作
- Parcelable是安卓自带的序列化接口,使用起来很麻烦,但是效率很高
总结
今天就看了这么多,感觉自己目前还是用不到这么多。但是先记下来就好了,死肥宅还是好好学习,天天向上吧。