解密 Android IPC 机制

news2024/11/29 16:36:49

在我们使用 Android 手机的时候,有时我们使用的软件会需要消耗比较大的内存,也经常会需要同时打开多个软件。这些时候,我们都会需要使用到多进程技术。作为 Android 开发者,相信我们都知道如何去开启应用的单个多进程,但是开启多进程之后,如何进行「进程间通信(IPC)」呢?进程通信的方法有很多,他们适用于不同的使用场景,下面我们来逐个了解。

前置知识

  • Android 四大组件
  • Java 序列化

IPC简介

相信学过大学「操作系统」这门课的同学都还记得 进程间通信 信号量机制 这些名词,今天我们学习的也是操作系统的通信,不过是针对以 Linux 为内核的 Android 操作系统。我们通常会以一个软件或者程序为进程。而 Android 是可以使用多进程的,对于稍微大型一些的软件也都会使用到多进程。使用多进程的目的有如下几个:

  1. 进程隔离,以达到某些特殊的业务需求
  2. 扩大软件的内存大小。

多进程的开启很简单,其唯一方法是给注册文件 AndroidManifest.xml 中的四大组件部分添加 android:process 属性即可

<activity
 android:name="com.qxy.potatos.module.mine.activity.WebViewActivity"
 android:configChanges="orientation|screenSize|keyboardHidden"
 android:exported="false"
 android:process=":h5"
 android:screenOrientation="portrait"
 android:theme="@style/BlackTheme" />

上述的 android:process=":h5" 指的是该程序下的私有进程,是 com.qxy.potatos:h5 的简写。如果属性值换为全局形式的 com.qxy.potatos.h5,则表示该进程是全局的,可共享给其他应用的。

而多进程出现后会导致如下的一些问题:

  1. 静态成员和单例模式完全失效
  2. 线程同步机制完全失效
  3. SharePreferences 的可靠性下降
  4. Application 多次创建

上述问题为何会出现呢?

由于 Android 为每个独立进程都分配了一个虚拟机,那么虚拟机的内存空间必然是不同的,所以不同的量在不同内存中都有一份副本,在不同进程中只能修改其内存下的副本。所以1和2中,无论是加何种锁,不作用在同一片内存空间中都是失效的。

而3则是由于 SharePreferences 是存储在 xml 文件中的,不同进程对该文件的并发读写时会导致数据出错的

4中则是由于 Android 的启动机制是每次都要由启动新的 Application,则每个进程都会有一个自己的 Application。我们也需要着重注意这个问题,在Application 中做好启动分类,在多进程启动阶段,防止不需要的资源多次加载

基于上述的原因和问题,我们需要深入了解 IPC 机制,让跨进程通信更好的服务于我们,解决多进程所带来的问题。

IPC基础知识

序列化与反序列化

概述

在谈论序列化与反序列化问题之前,我们需要先了解他们是什么,且作用有哪些。

序列化的意思就是将对象转化为字节序列的过程

反序列化则是将字节序列恢复为对象的过程

那么将对象序列化为字节序列有什么用呢?

将对象序列化为字节序列,可以在传递和保存对象的时候,保证对象的完整性和可传递性。使其易于保存在本地或者在网络空间中传输。

而反序列化,可以将字节流中保存的对象重建为对象

所以,其最核心的作用就是,对象状态的保存和重建

序列化优点
  1. 序列化后的为字节流的对象,存储在硬盘中方便JVM重启调用
  2. 序列化后的二进制序列能够减少存储空间,方便永久性保存对象
  3. 序列化成二进制字节流的对象方便进行网络传输
  4. 序列化后的对象可以进行进程间通信
Android中的序列化手段

基于上述的讨论,我们知道了何为序列化以及序列化的作用和优点。这其中提到序列化的一大特性就是用于进程间通信,而在后续提到的进程间通信手段中,他们共同的点都是传递信息时将对象序列化,接收信息时则是将对象反序列化。

在Android中需要学习使用到的序列化手段有两个,分别是 SerializableParcelable

  • Serializable

    Serializable 是 Java 自带的序列化接口,我们使用者只需要继承 Serializable 接口即可实现对该对象的序列化了。而具体去调用对其序列化和反序列化过程的也是 Java 提供的API。ObjectOutputStream 可以实现对象的序列化,ObjectInputStream 实现对象的反序列化。

    //序列化
    User user = new User(1, "hello world", false); 
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(
     new FileOutputStream("cache.txt"));
    objectOutputStream.writeObject(user);
    objectOutputStream.close();
    ​
    //反序列化
    ObjectInputStream objectInputStream = new ObjectInputStream(
     new FileInputStream("cache.txt"));
    User user = (User) objectInputStream.readObject();//会在此处进行检查,是否同一 serialVersionUID
    objectInputStream.close();
    

    需要注意的是,被序列化的 User 类一般情况下需要指定一个 serialVersionUID,其作用是对该类做唯一标识,在反序列化时候会进行 serialVersionUID 的比对,如果不一致则会认为版本不同出现报错。

    但是,如果不指定该 ID 也是可以正常实现序列化和反序列化的,因为系统会自动生成该类的 hash 值赋给 serialVersionUID 。那么为什么我们还要建议手动复制呢?因为 hash 值是根据类的变化在变化的,如果 ID 是 hash 值的话,我们在序列化对象后更改了对象的结构就会导致前后 ID 不一致,使得该对象无法被反序列化。但是手动指定的 ID 可以让被更改过的对象依旧可以被反序列化,可以最大限度地恢复其内容

     public class User implements Serializable {
     private static final long serialVersionUID = 519067123721295773L;//静态成员不参与序列化过程,代表类的状态public int userId;
     public String userName;
     public boolean isMale;
     
     public transient int hhhhh;//被 transient 关键字修饰不参与序列化过程,代表对象的临时数据
     
     public Book book;//该类必须可以被序列化,即继承了 Serializable 接口,否则会报错。每个成员变量都必须可被序列化。
    }
    
  • Parcelable

    Parcelable 是 Android 特有的序列化方法。他也是一个接口,实现该接口比 Serializable 要复杂一些。由于他是 Android 自带的序列化方法,所以对 Android 更加友好,实现该接口后的对象可以通过 Binder 来实现跨进程传递。

     public class User implements Parcelable {public int userId;
     public String userName;
     public boolean isMale;public Book book;public User(int userId, String userName, boolean isMale) {
     this.userId = userId;
     this.userName = userName;
     this.isMale = isMale;
     }
    
     /**
     * 自定义构造函数,辅助反序列化过程,其中由于 book 是另一个可序列化对象
     * 所以,反序列化过程需要传递当前线程的上下文类加载器
     */
     private User(Parcel in) {
     userId = in.readInt();
     userName = in.readString();
     isMale = in.readInt() == 1;
     book = in.readParcelable(Thread.currentThread().getContextClassLoader());
     }/**
     * 内容描述,基本都返回 0 
     */
     @Override
     public int describeContents() {
     return 0;
     }/**
     * 序列化过程
     */
     @Override
     public void writeToParcel(Parcel out, int flags) {
     out.writeInt(userId);
     out.writeString(userName);
     out.writeInt(isMale ? 1 : 0);
     out.writeParcelable(book, 0);
     }
     
     /**
     * 反序列化过程
     */
     public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
     @Override
     public User createFromParcel(Parcel in) {
     return new User(in);//调用自写的构造函数
     }
    ​
     @Override
     public User[] newArray(int size) {
     return new User[size];
     }
     };}
    

    系统自带的实现了 Parcelable 接口的类有:Intent、Bundle、Bitmap…,其中 List 和 Map 也可以直接序列化,但是要保证其中的每个元素都是可序列化的。注意,基本数据类型是天然支持序列化的了。

优劣对比

Serializable:Java自带、操作简单,但是I/O操作多、开销大(多用于序列化到存储设备,以及网络传输中)

Parcelable:Android自带、效率高,使用稍微复杂些(Android 中首选,多用于内存序列化)

Binder_AIDL

上面提到过 Binder 可用于进程间通信。那么 Binder 是什么东西呢?我们该如何理解 Binder ?

Binder 实际上是 Android 内部的一个类,其实现了 IBinder 接口,主要作用是用于支持 Android 的跨进程通信。

在这里插入图片描述

如上图所示,我们可以视 Binder 为一个驱动,其连接客户端(进程1)与服务端(进程2),客户端和服务端绑定后就借助 Binder 驱动来通信和获取对应服务。而在 Android 的底层设计中,Binder 也是 ServiceManager 连接各种 Manager 的桥梁。

那标题的 AIDL 又是什么呢?AIDL 使用 Binder 进行进程间通信的较常用较典型的方法。查看一个简单的 AIDL 示例可以简单的了解它是如何使用 Binder 进行进程间通信的。

AIDL 全称为:Android Interface Definition Language,即Android接口定义语言。它是一种模板,我们根据对应的规则写好我们需要的通信接口之后, AIDL 会为我们自动生成对应的 IPC 代码。

这里做一个示例,假设我们需要进行进程间通信的相关类为 Book,需要的通信服务为获得 Book 书单,以及向 Book 书单中添加书籍。那么我么可以以如下方式编写一个 AIDL 文件。

首先需要将 Book 类序列化

public class Book implements Parcelable {public int bookId;
 public String bookName;public Book(int bookId, String bookName) {
 this.bookId = bookId;
 this.bookName = bookName;
 }private Book(Parcel source) {
 bookId = source.readInt();
 bookName = source.readString();
 }/**
 * Describe the kinds of special objects contained in this Parcelable
 * instance's marshaled representation. For example, if the object will
 * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
 * the return value of this method must include the
 * {@link #CONTENTS_FILE_DESCRIPTOR} bit.
 *
 * @return a bitmask indicating the set of special object types marshaled
 * by this Parcelable object instance.
 */
 @Override public int describeContents() {
 return 0;
 }/**
 * Flatten this object in to a Parcel.
 *
 * @param dest  The Parcel in which the object should be written.
 * @param flags Additional flags about how the object should be written.
 *              May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
 */
 @Override public void writeToParcel(Parcel dest, int flags) {
 dest.writeInt(bookId);
 dest.writeString(bookName);
 }public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() {
 /**
 * Create a new instance of the Parcelable class, instantiating it
 * from the given Parcel whose data had previously been written by
 * {@link Parcelable#writeToParcel Parcelable.writeToParcel()}.
 *
 * @param source The Parcel to read the object's data from.
 * @return Returns a new instance of the Parcelable class.
 */
 @Override public Book createFromParcel(Parcel source) {
 return new Book(source);
 }/**
 * Create a new array of the Parcelable class.
 *
 * @param size Size of the array.
 * @return Returns an array of the Parcelable class, with every entry
 * initialized to null.
 */
 @Override public Book[] newArray(int size) {
 return new Book[size];
 }};
}

然后要编写两个 AIDL 文件

// Book.aidl
package com.qxy.potatos.module.test.aidl;
​
parcelable Book;// IBookManager.aidl
package com.qxy.potatos.module.test.aidl;import com.qxy.potatos.module.test.aidl.Book;interface IBookManager {List<Book> getBookList();void addBook(in Book book);
}

Book.aidl 文件中,需要对自定义序列化的 Book 类进行序列化声明。然后在 IBookManager.aidl 文件中,声明两个方法,分别对应提出的两个需求,同时记得一定要手动导入 Book 类的路径。

编写好 AIDL 文件后,将项目 rebuild 一下,就可以在 build 的 generate 文件中看到生成的 IBookManager 类了。生成的这个类就可以用于辅助我们进行进程间通信了。

由于生成的文件较长,这里就不放出生成的文件源码了。我们看其生成的模板代码,可以得知,在不同进程时候,调用的是 Stub 内部的代理类 Proxy 来执行跨进程功能。同一进程的时候,则不会走这种跨进程的 transact 功能。

其中,几类方法或者成员变量值得关注

  • DESCRIPTOR:Binder 唯一标识,一般是类名
  • asInterface:负责将服务端的 Binder 对象转化为该接口类型。根据是否同一进程有不同转换返回值。
  • asBinder:返回当前 Binder 值
  • onTransact:运行在服务端线程池中,从 data 中提出参数,执行 code 的目标,然后再 reply 中返回。该方法是 Boolean 类型,返回值为 true 表示执行成功, false 表示请求执行失败,可以控制这个返回值做权限隔离。
  • Proxy#getBookList Proxy#addBook:这两个接口的实现方法运行在客户端线程池中。把参数信息写入 data 中,然后通过序列化、Binder 等手段发送给服务端去执行,然后线程挂起等待执行结果。如果有结果返回后,在 reply 中取出结果。

由上述可知,这样的 Binder 过程是耗时的,不应执行在 UI 线程中;同时,由于其运行规则,我们需要采取同步的方式来进行,即使它很耗时,都需要客户端挂起等待服务端响应。

如需完整版 Android学习笔记 请点击此处免费获取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/528200.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux常见指令以及权限理解】权限理解(4)

写在前面 这篇文章&#xff0c;我们来聊一聊Linux下权限相关的知识&#xff0c;我打算从这几个方面展开&#xff1a; 1. 认识Linux下用户的分类 2. 什么叫做权限 3. 没有权限会是什么样子 4. 如何修改权限 5. 其它重要的问题 那么废话不多说&#xff0c;我们现在开始。 …

PDF怎么转换成Word?将PDF转换为Word的三种方法!

在我们需要将PDF文件转换为Word文件时&#xff0c;有几种方法可以选择。通常&#xff0c;我们在文件传输过程中使用的文件格式是PDF&#xff0c;但如果我们需要对文件进行编辑&#xff0c;就需要将其转换为可编辑的Word格式。下面是几种转换方法的介绍&#xff0c;让我们一起来…

【Python从入门到进阶】Python异常处理

接上篇《18、文件内容序列化和反序列化操作》 上一篇我们学习了文件读取及写入数据序列化和反序列化的操作。本篇我们来学习Python中有关异常&#xff08;捕获异常、处理异常等&#xff09;的知识。 一、异常的定义 在编写代码时&#xff0c;我们无法完全掌控程序运行过程中会…

原神服务端建模修改模型贴图(SpecialK)教程

原神服务端建模修改模型贴图(SpecialK)教程 我是艾西&#xff0c;今天跟大家闲聊一下原神建模修改模型等。在一个游戏里开发者会按照自己这个游戏的大方向去运营&#xff0c;而总是有一些小伙伴有不一样的需求&#xff0c;如果是建模拥有独一无二的角色或者是外观装扮等那么艾…

多系统启动U盘Ventory下载、安装、使用

官网链接 Ventoy Ventoy 简介 简单来说&#xff0c;Ventoy是一个制作可启动U盘的开源工具。 有了Ventoy你就无需反复地格式化U盘&#xff0c;你只需要把 ISO/WIM/IMG/VHD(x)/EFI 等类型的文件直接拷贝到U盘里面就可以启动了&#xff0c;无需其他操作。 你可以一次性拷贝很多个…

OpenCL编程指南-4.4矢量操作符

矢量操作符 如下描述了可用于矢量数据类型或矢量和标量数据类型组合的各类操作符。 算术操作符 算术操作符&#xff08;加&#xff08;)、减&#xff08;–)、乘&#xff08;*&#xff09;和除&#xff08;/)&#xff09;&#xff0c;可以作用于内置整数、浮点标量和矢量数…

次郎家书——第一天关于数值计算方法考试后——的一些思考和反思

考试的复盘&#xff1a;传送门&#xff1a;数值计算方法考试复盘 对此次考试的看法&#xff1a; 这次考试考试内容虽然有没复习到的如复合辛普森和复合梯形公式还有最小二乘的推广(这里上课的时候听懂了但是复习的时候嫌麻烦没看原来&#xff0c;结果大题是真的写错了&#…

nuxt 一直报错 http://localhost:24678/_nuxt/

解决&#xff1a; 这个错误可能是由于Nuxt.js应用程序无法正确加载/_nuxt/路径下的资源而导致的。解决这个问题的方法有以下几种&#xff1a; 1.检查nuxt.config.js文件 在nuxt.config.js文件中&#xff0c;检查build.publicPath属性是否设置为正确的公共路径。例如&#xff1a…

UART驱动情景分析-read

一、源码框架回顾 shell读数据&#xff0c;一开始的时候没有就休眠。数据从串口发送到驱动&#xff0c;驱动接收到中断&#xff0c;驱动读取串口数据&#xff0c;这个数据会传给行规程。 行规程获取到数据后&#xff0c;会回显。按下删除就删除一个字符&#xff0c;按下回车&am…

pytorch 测量模型运行时间,GPU时间和CPU时间,model.eval()介绍

文章目录 1. 测量时间的方式2. model.eval(), model.train(), torch.no_grad()方法介绍2.1 model.train()和model.eval()2.2 model.eval()和torch.no_grad() 3. 模型推理时间方式4. 一个完整的测试模型推理时间的代码5. 参考&#xff1a; 1. 测量时间的方式 time.time() time.…

使用qt creator编译zlib

zlib被设计为一个免费的&#xff0c;通用的&#xff0c;法律上不受限制的-即不受任何专利保护的无损数据压缩库&#xff0c;几乎可以在任何计算机硬件和操作系统上使用。 官网&#xff1a;http://www.zlib.net/ 下载zlib源码:http://www.zlib.net/zlib1213.zip 备用地址&#x…

关于使用API接口获取商品数据的那些事

随着电商行业的不断发展&#xff0c;越来越多的企业和个人需要获取各大电商平台上的商品数据。而最常用的方法是使用API接口获取商品数据。本文将为您介绍使用API接口获取商品数据的步骤和注意事项。 一、选择API接口 首先需要了解各大电商平台提供的API接口&#xff0c;目前…

由浅入深理解java集合(一)——集合框架 Collection、Map

Java 提供了一套完整的集合类&#xff08;也可以叫做容器类&#xff09;来管理一组长度可变的对象&#xff08;也就是集合的元素&#xff09;&#xff0c;其中常见的类型包括 List、Set、Queue 和 Map。从我个人的编程经验来看&#xff0c;List 的实现类 ArrayList 和 Map 的实…

华为OD机试 - 查找树中元素(Python)

题目描述 已知树形结构的所有节点信息,现要求根据输入坐标(x,y)找到该节点保存的内容值,其中x表示节点所在的层数,根节点位于第0层,根节点的子节点位于第1层,依次类推;y表示节点在该层内的相对偏移,从左至右,第一个节点偏移0,第二个节点偏移1,依次类推; 举例:…

手把手教你用代码画架构图 | 京东云技术团队

作者&#xff1a;京东物流 覃玉杰 1. 前言 本文将给大家介绍一种简洁明了软件架构可视化模型——C4模型&#xff0c;并手把手教大家如何使用代码绘制出精美的C4架构图。 阅读本文之后&#xff0c;读者画的架构图将会是这样的&#xff1a; 注&#xff1a;该图例仅作绘图示例使…

【入土级】详解C++类对象(中篇)

目录 前言&#xff1a;类的6个默认成员函数 一&#xff0c; 构造函数 1. 概念 2. 特性 二&#xff0c; 析构函数 2.1 概念 2.2 特性 2.3 牛刀小试 三&#xff0c; 拷贝构造函数 3.1概念 3. 2 特点 四&#xff0c; 赋值运算符重载 4. 1 运算符重载 五&#xff0…

网站测试的主要方法

网站测试的主要方法 网站测试是保证网站质量的重要手段&#xff0c;通过对网站进行测试可以及时发现问题并修复&#xff0c;提高用户体验和网站的可靠性。本文将介绍网站测试的主要方法。 1.功能测试&#xff1a;测试网站的所有功能是否正常。通过模拟用户的操作&#xff0c;确…

最新水果FLStudio21中文版下载及快捷键操作教程

任何一款软件&#xff0c;其快捷键永远扮演着至关重要的作用&#xff0c;熟练运用快捷键不仅能够节省时间&#xff0c;提高工作效率&#xff0c;还有助于熟练掌握所使用的软件。作为一款功能强大的音乐编曲软件&#xff0c;FL Studio有着大量的快捷键&#xff0c;这些快捷键在一…

【ArcGIS Pro二次开发】(28):用地图斑导出用地用海汇总表

本工具的作用是将现状用地或规划用地导出Excel格式的用地用海汇总表。 实现这个功能的Arcpy脚本工具我之前已经做过&#xff0c;详见&#xff1a;ArcGisPro脚本工具【8】——用地图斑导出用地用海汇总表 这次试着在ArcGIS Pro SDK中来实现同样的功能。 一、要实现的功能 如上…

【存储数据恢复】H3C存储卷中的数据恢复案例

存储数据恢复环境&故障&#xff1a; H3C FlexStorage某型号存储&#xff0c;25块磁盘组建的RAID5&#xff0c;其中包含一块热备盘。 工作人员误操作将存储设备中原先的2个卷删除&#xff0c;删除之后又使用和删除2个卷同样大小的空间重建了一个卷。用户希望恢复删除的2个卷…