AIDL+MemoryFile匿名共享内存实现跨进程大文件传输

注:本文内容转载自如下文章:使用AIDL实现跨进程高效传输大文件

AIDL

AIDL 是 Android 中实现跨进程通信(Inter-Process Communication)的一种方式。AIDL 的传输数据机制基于 BinderBinder 对传输数据大小有限制,传输超过1M的文件就会报android.os.TransactionTooLargeException异常,一种解决办法就是使用匿名共享内存进行大文件传输。

在这里插入图片描述

共享内存简介

Linux 中的共享内存:

  • 通过映射同一块公共物理内存到不同进程的用户虚拟地址空间来达到共享内存的目的。
    在这里插入图片描述

对于进程间需要传递大量数据的场景下,这种通信方式是十分高效的,但是共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量

匿名共享内存Ashmem(Anonymous Shared Memory),提供了一种使 APP 跨进程传递大数据能突破1M-8KB的限制的方案。

Android 中的匿名共享内存(Ashmem) 是基于 Linux 共享内存的,都是在 tmpfs 文件系统上新建文件,只是 AndroidLinux 的基础上进行了改造,借助 Binder+文件描述符(FileDescriptor) 实现了共享内存的传递。相对于 Linux 的共享内存,Ashmem 对内存的管理更加精细化,并且添加了互斥锁。它可以让多个进程操作同一块内存区域,并且除了物理内存限制,没有其他大小限制。Android 中的 SurfaceFlinger 进程和 App 进程之间View数据的传递就是通过匿名共享内存

MemoryFile是 Android 为匿名共享内存而封装的一个 Java 层对象,它封装了 native 代码,同时MemoryFile 也是进程间大数据传递的一个手段,开发的时候可使用。

Android 平台上共享内存通常的做法如下:

  1. 进程 A 通过MemoryFile创建共享内存,得到fdFileDescriptor
  2. 进程 A 通过fd将数据写入共享内存
  3. 进程 Afd封装成实现Parcelable接口的ParcelFileDescriptor对象,通过BinderParcelFileDescriptor对象发送给进程 B
  4. 进程 BParcelFileDescriptor对象中获取fd,从fd中读取数据

本质就是通过 Binder 机制传递 MemoryFilefd 到不同进程, 不同进程通过这个 fd 进行操作共享内存。

客户端和服务端双向通信+传输大文件实战

效果图:

在这里插入图片描述

运行的时候先启动服务端,然后再启动客户端,手机上可以使用分屏功能将客户端和服务端显示在同一个屏幕上,客户端绑定服务后,双方就可以相互发送图片了。

我们先实现客户端向服务端传输大文件,然后再实现服务端向客户端传输大文件。

定义AIDL接口

//IMyAidlInterface.aidl
interface IMyAidlInterface {void client2server(in ParcelFileDescriptor pfd);
}

服务端

实现IMyAidlInterface接口

// AidlService.kt
class AidlService : Service() {private val mStub: IMyAidlInterface.Stub = object : IMyAidlInterface.Stub() {@Throws(RemoteException::class)override fun client2server(pfd: ParcelFileDescriptor) {val fileDescriptor = pfd.fileDescriptor // 从ParcelFileDescriptor中获取FileDescriptorval fis = FileInputStream(fileDescriptor) // 根据FileDescriptor构建InputStream对象val data = fis.readBytes() // 从InputStream中读取字节数组......}}override fun onBind(intent: Intent): IBinder {return mStub}
}

客户端

1. 绑定服务

  • 在项目的src目录中加入.aidl文件
  • 声明一个IMyAidlInterface接口实例(基于AIDL生成)
  • 创建ServiceConnection实例,实现android.content.ServiceConnection接口
  • 调用Context.bindService()绑定服务,传入ServiceConnection实例
  • onServiceConnected()实现中,调用IMyAidlInterface.Stub.asInterface(binder),将返回参数转换为IMyAidlInterface类型
// MainActivity.kt
class MainActivity : AppCompatActivity() {private var mStub: IMyAidlInterface? = nullprivate val serviceConnection = object : ServiceConnection {override fun onServiceConnected(name: ComponentName, binder: IBinder) {mStub = IMyAidlInterface.Stub.asInterface(binder)}override fun onServiceDisconnected(name: ComponentName) {mStub = null}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)button1.setOnClickListener {bindService()}}private fun bindService() {if (mStub != null) return val intent = Intent("io.github.kongpf8848.aidlserver.AidlService")        intent.setClassName("io.github.kongpf8848.aidlserver","io.github.kongpf8848.aidlserver.AidlService")try {val bindSucc = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)if (bindSucc) {Toast.makeText(this, "bind ok", Toast.LENGTH_SHORT).show()} else {Toast.makeText(this, "bind fail", Toast.LENGTH_SHORT).show()}} catch (e: Exception) {e.printStackTrace()}}override fun onDestroy() {if(mStub!=null) {unbindService(serviceConnection)}super.onDestroy()}
}

2. 发送数据

  • 将发送文件转换成字节数组ByteArray
  • 创建MemoryFile对象
  • MemoryFile对象中写入字节数组
  • 获取MemoryFile对应的FileDescriptor
  • 根据FileDescriptor创建ParcelFileDescriptor
  • 调用 IPC 方法,发送ParcelFileDescriptor对象
// MainActivity.kt
private fun sendLargeData() {if (mStub == null) returntry {val inputStream = assets.open("large.jpg") // 读取assets目录下文件val byteArray = inputStream.readBytes() // 将inputStream转换成字节数组val memoryFile = MemoryFile("image", byteArray.size) // 创建MemoryFilememoryFile.writeBytes(byteArray, 0, 0, byteArray.size) // 向MemoryFile中写入字节数组val fd = MemoryFileUtils.getFileDescriptor(memoryFile) // 获取MemoryFile对应的FileDescriptorval pfd = ParcelFileDescriptor.dup(fd) // 根据FileDescriptor创建ParcelFileDescriptormStub?.client2server(pfd) // 发送数据} catch (e: IOException) {e.printStackTrace()} catch (e: RemoteException) {e.printStackTrace()}
}

至此,我们已经实现了客户端向服务端传输大文件,下面就继续实现服务端向客户端传输大文件功能。 服务端主动给客户端发送数据,客户端只需要进行监听即可。

  • 定义监听回调接口
// ICallbackInterface.aidl
package io.github.kongpf8848.aidlserver;interface ICallbackInterface {void server2client(in ParcelFileDescriptor pfd);
} 
  • IMyAidlInterface.aidl中添加注册回调和反注册回调方法,如下:
// IMyAidlInterface.aidl
import io.github.kongpf8848.aidlserver.ICallbackInterface;interface IMyAidlInterface {......void registerCallback(ICallbackInterface callback);void unregisterCallback(ICallbackInterface callback);
}
  • 服务端实现接口方法
// AidlService.kt
private val callbacks = RemoteCallbackList<ICallbackInterface>()private val mStub: IMyAidlInterface.Stub = object : IMyAidlInterface.Stub() {......override fun registerCallback(callback: ICallbackInterface) {callbacks.register(callback)}override fun unregisterCallback(callback: ICallbackInterface) {callbacks.unregister(callback)}
}
  • 客户端绑定服务后注册回调
// MainActivity.kt
private val callback = object: ICallbackInterface.Stub() {override fun server2client(pfd: ParcelFileDescriptor) {val fileDescriptor = pfd.fileDescriptorval fis = FileInputStream(fileDescriptor)val bytes = fis.readBytes()if (bytes != null && bytes.isNotEmpty()) {......}}
}
private val serviceConnection = object : ServiceConnection {override fun onServiceConnected(name: ComponentName, binder: IBinder) {mStub = IMyAidlInterface.Stub.asInterface(binder)mStub?.registerCallback(callback)}override fun onServiceDisconnected(name: ComponentName) {mStub = null}
}
  • 服务端发送文件,回调给客户端。此处仅贴出核心代码,如下:
// AidlService.kt
private fun server2client(pfd:ParcelFileDescriptor){val n = callbacks.beginBroadcast()for(i in 0 until n){val callback = callbacks.getBroadcastItem(i);if (callback!=null){try {callback.server2client(pfd)} catch (e:RemoteException) {e.printStackTrace()}}}callbacks.finishBroadcast()
}

至此,我们实现了客户端和服务端双向通信和传输大文件。

GitHub地址: https://github.com/kongpf8848/aidldemo

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微服务简介

微服务简介 微服务架构是一种软件架构模式&#xff0c;它将一个大型应用程序拆分为一组小型、独立的服务&#xff0c;每个服务都有自己的业务逻辑和数据存储。这些服务可以独立开发、部署和扩展&#xff0c;通常使用HTTP或其他轻量级通信协议进行通信。 以下是微服务架构的一…

KDM CCA Secure FHE

参考文献&#xff1a; [BFM88] Blum M, Feldman P, Micali S. Non-interactive zero-knowledge and its applications[M]//Providing Sound Foundations for Cryptography: On the Work of Shafi Goldwasser and Silvio Micali. 2019: 329-349.[FS90] Feige U, Shamir A. Witn…

阿里云CDN缓存配置及优化-oss绑定CDN缓存自动刷新功能

参考阿里云官网文档&#xff1a;https://help.aliyun.com/practice_detail/603170 1.缓存时间配置 在缓存管理中&#xff0c;可以方便地指定目录和文件后缀名在CDN节点上的缓存时间&#xff0c;缓存时长配置的长短&#xff0c;取决于源站对该文件的变更频率。我们需要分析下业务…

华为云云耀云服务器L实例评测|基于云耀云服务器在Docker上部署nginx服务

文章目录 1、服务介绍云耀云服务器Docker介绍Docker-Compse介绍 2、在云耀云服务器安装Docker3、通过Docker run命令运行nginx服务4、在云耀云服务器安装docker-compose5、通过docker-compose方式启动nginx服务 1、服务介绍 云耀云服务器 云耀云服务器&#xff08;Hyper Elas…

医疗知识图谱 neo4j

开源项目&#xff1a; https://github.com/liuhuanyong/QASystemOnMedicalKG 一.效果 二.需要安装&#xff1a; pip install pyahocorasick pip install py2neo 三.需要修改&#xff1a; 需要改的点&#xff1a; 1.改连接的方式 2.改读文件的方式 MedicalGraph 运行&am…

Spring Boot的配置文件

Spring Boot中的配置文件有两种&#xff1a;properties格式和yml格式 一、propertise格式 propertise格式是Springboot默认的配置文件的格式&#xff0c;它才有键值对keyvalue的方式存储配置信息&#xff0c;有系统key值和自定义key值两类。 系统key值就是Spring boot约定的…

Qt Quick Layouts Overview

Qt快速布局概述 #【中秋征文】程序人生&#xff0c;中秋共享# Qt快速布局是用于在用户界面中排列项目的项目。由于Qt快速布局还可以调整其项目的大小&#xff0c;因此它们非常适合可调整大小的用户界面。 开始 可以使用文件中的以下导入语句将 QML 类型导入到应用程序中。.qml…

python: excel假期时间提取统计

# encoding: utf-8 # 版权所有 2023 涂聚文有限公司 # 许可信息查看&#xff1a; # 描述&#xff1a; # Author : geovindu,Geovin Du 涂聚文. # IDE : PyCharm 2023.1 python 311 # Datetime : 2023/9/3 7:04 # User : geovindu # Product : PyCharm # Proje…

Vulkan入门——编译Shaderc

编译 Vulkan-Samples时&#xff0c;遇到了如下shaderc编译报错。 ninja: error: /Users/xiaxl/Library/Android/sdk/ndk/21.1.6352462/sources/third_party/shaderc/libs/c_static/armeabi-v7a/libshaderc.a, needed by ../../../../build/intermediates/cmake/debug/obj/arme…

【CVPR2021】MVDNet论文阅读分析与总结

Challenge&#xff1a; 现有的目标检测器主要融合激光雷达和相机&#xff0c;通常提供丰富和冗余的视觉信息 利用最先进的成像雷达&#xff0c;其分辨率比RadarNet和LiRaNet中使用的分辨率要细得多&#xff0c;提出了一种有效的深度后期融合方法来结合雷达和激光雷达信号。 MV…

还没用熟 TypeScript 社区已经开始抛弃了

根据 rich-harris-talks-sveltekit-and-whats-next-for-svelte 这篇文章的报道&#xff0c; Svelte 计划要把代码从 TS 换到 JS 了。 The team is switching the underlying code from TypeScript to JavaScript. That and the update will then allow the team to incorporate…

SpingMyc项目如何搭建

目录 一、创建项目 二、环境搭建 &#xff08;1&#xff09;引入相关依赖 &#xff08;2&#xff09;在web.xml中配置前端控制器DispatcherServlet &#xff08;3&#xff09;编写SpringMVC核心配置文件springmvc.xml 三、测试是否成功 &#xff08;1&#xff09;编写控…