《android开发艺术探索》读书笔记(二)--IPC机制

时间:2022-10-01 21:48:33

接上篇《android开发艺术探索》读书笔记(一)

No1:

在android中使用多进程只有一种方法,那就是给四大组件在AndroidMenifest中指定android:process属性。

No2:

默认进程的进程名是包名。

No3:

《android开发艺术探索》读书笔记(二)--IPC机制

《android开发艺术探索》读书笔记(二)--IPC机制

":"要加上当前包名,进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中;另外一种属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。

No4:

Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。

No5:

Android为每一个应用(或者说每个进程)分配了一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。修改一个进程中的值只会影响当前进程

No6:

所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败,这也是多进程所带来的主要影响。

No7:

使用多进程会造成几个问题:

(1)静态成员和单例模式完全失效

(2)线程同步机制完全失效(不是一块内存,不同进程锁不是同一个对象)

(3)SharedPerference的可靠性下降(SharedPreferences不支持两个进程同时去执行写操作)

(4)Application会多次创建(相当于应用重新启动,运行在同一个进程中的组件时属于同一个虚拟机和同一个Application的)

No8:

进程间通信的方式有:Bundle、Intent、文件共享、SharedPerference、AIDL、Messenger、ContentProvider、Socket

No9:

当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializable。

No10:

实际上,不声明serialVersionUID同样也可以实现序列化,但是这将会对反序列化过程产生影响。

No11:

Serializable序列化

//序列化过程
User user = new User(0,"jake",true);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close(); //反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User)in.readObject();
in.close();

No12:

serialVersionUID的详细工作机制是这样的:

序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了变化,这个时候是无法正常反序列化的。

No13:

1.静态成员变量属于类不属于对象,所以不会参与序列化过程

2.用transient关键字标记的成员变量不参与序列化过程

No14:

实现了Parcelable接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。

No15:

序列化由writeToParcel方法来完成

反序列化功能由CREATOR来完成

内容描述功能由describeContents方法来完成

《android开发艺术探索》读书笔记(二)--IPC机制

No16:

系统已经为我们提供了许多实现了Parcelable接口的类,它们都是可以直接序列化的,比如Intent、Bundle、Bitmap等,同时List和Map也可以序列化,前提是它们里面的每个元素都是可序列化的。

No17:

Serializable是java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量I/O操作。Parcelable缺点就是使用起来稍微麻烦点,但是它的效率很高,主要用在内存序列化上。首选Parcelable

将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但是这个过程会稍显复杂,因此这两种情况下建议使用Serializable。

No18:

从IPC角度来说,Binder是android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;

从Android Framework角度来说,Binder是ServiceManager链接各种Manager(ActivityManager、WindowManager等等)和ManagerService的桥梁;

从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

No19:

手动实现一个Binder步骤:

1.声明一个继承IInterface接口的AIDL性质的接口,接口中只有一个asBinder方法。

2.实现Stub类和Stub类中的Proxy代理类。

No20:

AIDL文件的本质是系统为我们提供了一种快速实现Binder的工具。

No21:

给Binder设置死亡代理,监听Binder是否死亡

声明接口DeathRecipient,其内部只有一个方法binderDied,当Binder死亡的时候,系统会回调binderDied方法,然后我们就可以移出之前绑定的binder代理并重新绑定远程服务

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied(){
if(mBookManager == null) {
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
}
}

在客户端绑定远程服务成功后,给binder设置死亡代理

mService = IMessageBoxManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);

另外,通过Binder的方法isBinderAlive也可以判断Binder是否死亡。

No22:

跨进程通信方式

1.通过在Intent中附加extras来传递信息

2.通过共享文件的方式

3.采用Binder方式

4.ContentProvider天生就是支持跨进程通信的

5.网络通信可以实现数据传递,所以Socket也可以实现IPC

No23:

Messenger的使用方法很简单,它对AIDL做了封装。由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,因为服务端中不存在并发执行的情形。Messenger是以串行的方式处理客户端发来的消息。

No24:

AIDL文件支持的数据类型:

1.基本数据类型

2.String和CharSequence

3.ArrayList,且里面每个元素都必须能够被AIDL支持

4.HashMap,且里面每个元素都必须能够被AIDL支持,包括key和value

5.Parcelable,所有实现了Parcelable接口的对象

6.AIDL,所有的AIDL接口本身

No25:

AIDL中每个实现了Parcelable接口的类都需要按照上面那种方式去创建相应的AIDL文件并声明那个类为parcelable。

No26:

CopyOnWriteArrayList支持并发读/写。AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步,而我们直接使用CopyOnWriteArrayList来进行自动的线程同步。

No27:

AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务器返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个新的ArrayList传递给客户端(AIDL中能够使用的List只有ArrayList)。

No28:

对象是不能跨进程传输的,对象的跨进程传输本质上都是反序列化的过程,这就是为什么AIDL中的自定义对象都必须要实现Parcelable接口的原因。

RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,这点从它的声明就可以看出,因为所有的AIDL接口都继承自IInterface接口。

RemoteCallbackList:当客户端解注册的时候,我们只要遍历服务端所有的listener,找出那个和解注册listener具有相同Binder对象的服务端listener并把它删掉即可。并且它能够自动移除客户端所注册的listener。

RemoteCallbackList内部自动实现了线程同步的功能。

No29:

RemoteCallbackList不是List,它的用法是固定的

final int N = remoteCallbackList.beginBroadcast();
for(int i = 0;i<N;i++){
Object object = remoteCallbackList.getBroadcastItem(i);
if(object !=null){
//
}
}
remoteCallbackList.finishBroadcast();

No30:

客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起。

由于客户端的onServiceConnected和onServiceDisconnected方法都运行在UI线程中,所以也不可以在它们里面直接调用服务端的耗时方法。

由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时操作,切记不要开线程去执行异步任务。

No31:

在AIDL中进行权限验证有两种常用方法:

1.在onBind中验证:使用permission

2.在服务端的onTransact方法中进行权限验证:采用permission

No32:

ContentProvider的底层实现同样也是Binder。

创建一个自定义的ContentProvider很简单,只需要继承ContentProvider类并实现六个抽象方法即可:onCreate、query、update、insert、delete和getType。

这六个方法均运行在ContentProvider的进程中,除了onCreate由系统回调并运行在主线程里,其他五个方法均由外界回调并运行在Binder线程池中。

No33:

Android系统所提供的MediaStore功能就是文件类型的ContentProvider。

ContentProvider对底层的数据存储方式没有任何要求,我们即可以使用SQLite数据库,也可以使用普通的文件,甚至可以采用内存中的一个对象来进行数据的存储。

No34:

例:BookProvider注册:

<provider
android:name=".provider.BookProvider"
android:authorities="com.ryg.chapter_2.book.provider"
android:permission="com.ryg.PROVIDER"
android:process=":provider/>

No35:

AIDL使用流程:首先创建一个Service和一个AIDL接口,接着创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法,在Service的onBind方法中返回这个类的对象,然后客户端就可以绑定服务端Service,建立连接后就可以访问远程服务端的方法了。

No36:

Binder连接池工作机制:每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象;对于服务端来说,只需要一个Service就可以了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。

No37:

Binder连接池的主要作用就是讲每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程。

《android开发艺术探索》读书笔记(二)--IPC机制

No38:

《android开发艺术探索》读书笔记(二)--IPC机制

RPC--远程过程调用