Android 匿名共享内存的使用

2023-09-15 07:15:00

注:本文内容转载自如下文章:Android 匿名共享内存的使用

Android View 的绘制是如何把数据传递给 SurfaceFlinger 的呢? 跨进程通信时,数据量大于1MB要怎么传递呢?用 匿名共享内存(Ashmem) 是个不错的选择,它不仅可以减少内存复制的次数,还没有内存大小的限制。这篇文章介绍在 Java 层如何使用匿名共享内存在进程间传递数据。

1. 简述

Android匿名共享内存(Ashmem) 基于 Linux 的共享内存,都是在临时文件系统(tmpfs)上创建虚拟文件,再映射到不同的进程。它可以让多个进程操作同一块内存区域,并且除了物理内存限制,没有其他大小限制。相对于 Linux 的共享内存,Ashmem 对内存的管理更加精细化,并且添加了互斥锁Java 层在使用时需要用到 MemoryFile,它封装了 native 代码。

Java 层使用匿名共享内存的4个点:

  1. 通过 MemoryFile 开辟内存空间,获得 FileDescriptor
  2. FileDescriptor 传递给其他进程;
  3. 往共享内存写入数据;
  4. 从共享内存读取数据。

下面用一个例子介绍匿名共享内存的使用,假设需要开辟一段共享内存,写入一些数据,再在另外一个进程读取这段数据。

2. 创建 MemoryFile 和 数据写入

/**
* 需要写入到共享内存中的数据
*/
private val bytes = "落霞与孤鹜齐飞,秋水共长天一色。".toByteArray()

/**
* 创建 MemoryFile 并返回 ParcelFileDescriptor
*/
private fun createMemoryFile(): ParcelFileDescriptor? {
    // 创建 MemoryFile 对象,1024 是最大占用内存的大小。
    val file = MemoryFile("TestAshmemFile", 1024)

    // 获取文件描述符,因为方法被标注为 @hide,只能反射获取
    val descriptor = invokeMethod("getFileDescriptor", file) as? FileDescriptor

    // 如果获取失败,返回
    if (descriptor == null) {
        Log.i("ZHP", "获取匿名共享内存的 FileDescriptor 失败")
        return null
    }

    // 往共享内存中写入数据
    file.writeBytes(bytes, 0, 0, bytes.size)

    // 因为要跨进程传递,需要序列化 FileDescriptor
    return ParcelFileDescriptor.dup(descriptor)
}

/**
* 通过反射执行 obj.name() 方法
*/
private fun invokeMethod(name: String, obj: Any): Any? {
    val method = obj.javaClass.getDeclaredMethod(name)
    return method.invoke(obj)
}

MemoryFile 有两个构造方法,上面是一种,另一种是根据已有的 FileDescriptor 创建。 MemoryFile 创建时指定的大小并不是实际占用的物理内存大小,实际占用内存大小由写入的数据决定,但不能超过指定的大小。

3. 将文件描述符传递到其他进程

这里选择用 Binder 传递 ParcelFileDescriptor。 我们定义一个 Code,用于 C/S 两端通信确定事件:

/**
* 两个进程在传递 FileDescriptor 时用到的 Code。
*/
const val MY_TRANSACT_CODE = 920511

再在需要的地方 bindService

// 创建服务进程
val intent = Intent(this, MyService::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)

bind 成功之后将 文件描述符数据大小 序列化,然后通过 Binder 传递到 Service 进程:

private val serviceConnection = object: ServiceConnection {

    override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
        if (binder == null) {
            return
        }

        // 创建 MemoryFile,并拿到 ParcelFileDescriptor
        val descriptor = createMemoryFile() ?: return

        // 传递 FileDescriptor 和 共享内存中数据的大小
        val sendData = Parcel.obtain()
        sendData.writeParcelable(descriptor, 0)
        sendData.writeInt(bytes.size)

        // 保存对方进程的返回值
        val reply = Parcel.obtain()

        // 开始跨进程传递
        binder.transact(MY_TRANSACT_CODE, sendData, reply, 0)

        // 读取 Binder 执行的结果
        val msg = reply.readString()
        Log.i("ZHP", "Binder 执行结果是:「$msg」")
    }

    override fun onServiceDisconnected(name: ComponentName?) {}
}

两个进程的文件描述符指向同一个文件结构体,文件结构体指向了一片内存共享区域(ASMA),使得两个文件描述符对应到同一片ASMA中。

4. 在其他进程接收 FileDescriptor 并读取数据

先定义一个 MyService 用于开启子进程:

class MyService : Service() {
    private val binder by lazy { MyBinder() }
    override fun onBind(intent: Intent) = binder
}

再实现具体的 MyBinder 类,主要包含3个步骤:

  1. 从序列化数据中读取 FileDescriptor 和 共享内存中保存的数据大小;
  2. 根据 FileDescriptor 创建 FileInputStream
  3. 读取共享内存中的数据。
/**
* 这里不必使用 AIDL,继承 Binder 类 重写 onTransact 即可。
*/
class MyBinder: Binder() {

	/**
	* 文件描述符 和 数据大小 通过 data 传入。
	*/
	override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
	    val parent = super.onTransact(code, data, reply, flags)
	    if (code != MY_TRANSACT_CODE && code != 931114) {
	        return parent
	    }
	
	    // 读取 ParcelFileDescriptor 并转为 FileDescriptor
	    val pfd = data.readParcelable<ParcelFileDescriptor>(javaClass.classLoader)
	    if (pfd == null) {
	        return parent
	    }
	    val descriptor = pfd.fileDescriptor
	
	    // 读取共享内存中数据的大小
	    val size = data.readInt()
	
	    // 根据 FileDescriptor 创建 InputStream
	    val input = FileInputStream(descriptor)
	
	    // 从 共享内存 中读取字节,并转为文字
	    val bytes = input.readBytes()
	    val message = String(bytes, 0, size, Charsets.UTF_8)
	
	    Log.i("ZHP", "读取到另外一个进程写入的字符串:「$message」")
	
	    // 回复调用进程
	    reply?.writeString("Server 端收到 FileDescriptor, 并且从共享内存中读到了:「$message」")
	
	    return true
	}
}

这里拿到 FileDescriptor 后不仅可以读也能写入数据,还可以再创建一个 MemoryFile 对象。

这就是Android匿名共享内存的使用啦~

更多推荐

Vue入门简介(带你打开Vue的大门)

目录前言一、Vue简介1.什么是Vue2.Vue的应用场景3.Vue的作用(重要性)4.什么是MVVM模式5.开源库网址二、Vue入门使用1.基础使用步骤1.1引入Vue.js1.2创建Vue实例1.3编写Vue模板1.4数据绑定与指令1.5调用Vue方法和生命周期钩子2.Vue的优势2.1原生代码效果2.2Vue编写

2024届数字IC设计秋招面经-鼎信

背景985硕士,计算机科班,实验室做cpu设计和fpga算法加速,我做处理器安全方向,有项目。投递8.25没有笔试,两轮面试,直接通知下周一面试,草草的准备了下。一面技术面9.4不到半小时问项目和基础知识,当天出结果,第二天安排面试(不确实是技术还是HR)1.自我介绍2.挑一个熟悉的项目介绍下(AES加密算法)3.项目

岭回归与LASSO回归:解析两大经典线性回归方法

文章目录🍋引言🍋岭回归(RidgeRegression)🍋实战---岭回归🍋LASSO回归(LASSORegression)🍋实战---LASSO回归🍋岭回归和LASSO哪个更容易是直线🍋岭回归与LASSO回归的应用🍋L1正则化和L2正则化🍋偏差和方差🍋引言在机器学习和统计建模中,回归分析是一项重要

DPDK环境搭建

(1)虚拟环境:VMware®Workstation16Pro网上随便下载一个也行(2)操作系统:ubuntu-22.04-beta-desktop-amd64.iso下载地址:oldubuntu-releases-releases-22.04安装包下载_开源镜像站-阿里云(3)DPDK版本:22.07下载地址:htt

003-第一代硬件系统环境搭建

第一代硬件系统环境搭建文章目录第一代硬件系统环境搭建项目介绍摘要结构部分电路部分软件部分关键字:Qt、Qml、硬件、系统、搭建项目介绍欢迎来到我们的QML&C++项目!这个项目结合了QML(QtMeta-ObjectLanguage)和C++的强大功能,旨在开发出色的用户界面和高性能的后端逻辑。在项目中,我们利用QML

CocosCreator3.8研究笔记(十七)CocosCreator UI组件(一)

CocosCreator中,用户界面User-interface(UI)组件和2d渲染对象的区别在于2D渲染对象一般只负责将2D对象渲染出来,而UI则更多的承担着用户交互的能力。常用的UI控件可通过添加节点的方式来创建。在层级管理器中点击左上角的+创建节点按钮,然后选择UI来创建所需的UI节点,相应的UI组件便会自动挂

亚马逊商品详情数据接口

亚马逊商品详情数据接口可以用于获取商品详情信息,包括但不限于商品ID、商品标题、商品描述等。这些信息可以用于多种用途,例如:了解商品信息,帮助消费者做出购买决策。整合其他平台或网站的数据,提供全面的商品信息比较。为其他网站或应用程序提供数据支持,例如个性化推荐、搜索引擎优化等。总之,亚马逊商品详情数据接口可以促进商品信

域名解析--nslookup和dig

dig(DomainInformationGroper)dig是一个功能强大且更灵活的DNS查询工具,通常在Linux和macOS等Unix-like操作系统上使用。以下是dig的一些常见用法和区别:查询域名信息digexample.com这将返回与指定域名相关的DNS记录,包括IP地址、MX记录、CNAME记录等。指

【Go 基础篇】Go语言标识符解析:命名的艺术与最佳实践

介绍在计算机编程中,标识符(Identifier)是用来标识程序实体(变量、函数、类型等)的名称。良好的标识符命名可以使代码更加可读、易于维护,也能够提高代码的可理解性和可扩展性。在Go语言(Golang)中,标识符的命名规范和最佳实践对于编写高质量的代码至关重要。本篇博客将深入探讨Go语言中标识符的基本概念、命名规范

【Vue】上万个字把事件处理讲解的淋漓尽致

hello,我是小索奇,精心制作的Vue系列教程持续更新哈,想要学习&巩固&避坑就一起学习吧~事件处理事件的基本用法重点内容使用v-on:xxx缩写@xxx绑定事件,其中xxx是事件名(回顾:v-bind缩写为冒号:)事件的回调需要配置在methods对象中,最终会在VM上methods中配置的函数,不要用箭头函数,否

Linux网络编程(高并发服务器)

文章目录前言一、什么是高并发服务器二、使用多线程和多进程实现高并发服务器的思路三、多进程服务器代码编写四、多线程服务器代码编写总结前言本篇文章带大家学习Linux网络编程中的高并发服务器。首先我们需要了解什么是高并发服务器,然后是学习如何来编写高并发服务器。一、什么是高并发服务器高并发服务器是指能够同时处理大量并发请求

热文推荐