Android 匿名共享内存的使用

news/2024/7/7 22:20:36

注:本文内容转载自如下文章: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匿名共享内存的使用啦~


http://lihuaxi.xjx100.cn/news/1561254.html

相关文章

uniapp开发h5,解决项目启动时,Network: unavailable问题

网上搜了很多&#xff0c;发现都说是要禁用掉电脑多余的网卡&#xff0c;这方法我试了没有好&#xff0c;不晓得为啥子&#xff0c;之后在网上看&#xff0c;uniapp的devServer vue2的话对标的就是webpack4的devserver&#xff08;除了复杂的函数配置项&#xff09;&#xff0c…

day37 代码回想录 单调递增的数字监控二叉树

大纲 ● 738.单调递增的数字 ● 968.监控二叉树 738.单调递增的数字 题目&#xff1a;738.单调递增的数字 // 最大递增数 // 从后向前&#xff0c;找到第一个满足是每位递增的数字 // 怎么判断是否是递增值// 但是超时了 bool isIncreaseVal(int val) {// to stringauto st…

【python】anaconda使用指南

安装anaconda 访问官方 官网链接注册并登陆安装 无脑下一步即可配置path D:\ProgramData\anaconda3D:\ProgramData\anaconda3\ScriptsD:\ProgramData\anaconda3\Library\binD:\ProgramData\anaconda3\Library\mingw-w64\bin 进入anaconda环境 # 查询版本 conda --version# …

软考之软件设计师考试总结(内附资料)

今年5月27日参加的软考&#xff0c;虽然研究生专业已经和计算机无缘了&#xff0c;但是只要想学&#xff0c;就没有什么能够阻挡。 参加软考的初衷只是因为&#xff0c;&#xff0c;&#xff0c;辽宁省软考它不要钱&#xff0c;不要钱的证书咱不白嫖一个说不过去&#xff0c;先…

基于51单片机多路DTH11温湿度检测控制系统

一、系统方案 1、本设计采用51单片机作为主控器。 2、DHT11采集温度度&#xff0c;支持3路温度度&#xff0c;液晶1602显示。 3、按键设置报警阀值。 4、系统声光报警。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 //初始化LCD*********…

“网易云音乐”永久黑胶会员,速取!

今天&#xff0c;雨&#xff0c;坐在办公桌前&#xff0c;望着窗外秋雨绵绵。老板出差了&#xff0c;我戴上耳机&#xff0c;打开网易云音乐&#xff0c;让它用心动模式随意播放着音乐。 突然&#xff0c;有一首非常好听&#xff0c;是轻音乐《遥远的小渔村》&#xff0c;但只听…

区块链的使用场景和优势

区块链技术是一种基于分布式数据库、密码学和去中心化结构的新型计算模式&#xff0c;其具有以下应用场景和优势&#xff1a; 应用场景&#xff1a; 1.数字货币&#xff1a;比特币、以太坊等数字货币系统能够实现去中心化、快速高效的支付功能。 2.数字身份认证&#xff1a;基…

QT之QLineEdit简介

QT之QLineEdit简介 QLineEdit 是 Qt 的一个类&#xff0c;用于创建和管理单行的文本输入框。以下是一些常用的 QLineEdit 函数和它们的用法&#xff1a; 1&#xff09;QLineEdit(QWidget *parent nullptr) 构造函数&#xff0c;创建一个新的 QLineEdit 对象。parent 参数是指…