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


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

    AIDL

    AIDLAndroid 中实现跨进程通信(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);
    }
    
    • 1
    • 2
    • 3
    • 4

    服务端

    实现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中获取FileDescriptor
              val fis = FileInputStream(fileDescriptor) // 根据FileDescriptor构建InputStream对象
              val data = fis.readBytes() // 从InputStream中读取字节数组
              ......
            }
        }
    
        override fun onBind(intent: Intent): IBinder {
            return mStub
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    客户端

    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? = null
    
        private 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()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    2. 发送数据
    • 将发送文件转换成字节数组ByteArray
    • 创建MemoryFile对象
    • MemoryFile对象中写入字节数组
    • 获取MemoryFile对应的FileDescriptor
    • 根据FileDescriptor创建ParcelFileDescriptor
    • 调用 IPC 方法,发送ParcelFileDescriptor对象
    // MainActivity.kt
    private fun sendLargeData() {
       if (mStub == null) return
       try {
           val inputStream = assets.open("large.jpg") // 读取assets目录下文件
    	   val byteArray = inputStream.readBytes() // 将inputStream转换成字节数组
    	   
    	   val memoryFile = MemoryFile("image", byteArray.size) // 创建MemoryFile
    	   memoryFile.writeBytes(byteArray, 0, 0, byteArray.size) // 向MemoryFile中写入字节数组
    	   
    	   val fd = MemoryFileUtils.getFileDescriptor(memoryFile) // 获取MemoryFile对应的FileDescriptor
    	   val pfd = ParcelFileDescriptor.dup(fd) // 根据FileDescriptor创建ParcelFileDescriptor
    	   
    	   mStub?.client2server(pfd) // 发送数据
        } catch (e: IOException) {
    	    e.printStackTrace()
        } catch (e: RemoteException) {
    	    e.printStackTrace()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

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

    • 定义监听回调接口
    // ICallbackInterface.aidl
    package io.github.kongpf8848.aidlserver;
    
    interface ICallbackInterface {
        void server2client(in ParcelFileDescriptor pfd);
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • IMyAidlInterface.aidl中添加注册回调和反注册回调方法,如下:
    // IMyAidlInterface.aidl
    import io.github.kongpf8848.aidlserver.ICallbackInterface;
    
    interface IMyAidlInterface {
        ......
        void registerCallback(ICallbackInterface callback);
        void unregisterCallback(ICallbackInterface callback);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 服务端实现接口方法
    // 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)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 客户端绑定服务后注册回调
    // MainActivity.kt
    private val callback = object: ICallbackInterface.Stub() {
        override fun server2client(pfd: ParcelFileDescriptor) {
            val fileDescriptor = pfd.fileDescriptor
            val 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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 服务端发送文件,回调给客户端。此处仅贴出核心代码,如下:
    // 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()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

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

  • 相关阅读:
    必须要会回答的Java面试题(字符串篇)
    docker引擎学习
    java计算机毕业设计-民宿管理系统-演示录像2020源程序+mysql+系统+lw文档+远程调试
    【Springboot】基于注解式开发Springboot-Vue3整合Mybatis-plus实现分页查询
    精心准备200题Java相关面试,友情分享
    星闪:咫尺之间,联接智能世界
    数据库和缓存如何保持一致性
    SPI : Service Provider Interface
    LabVIEW对EAST长脉冲等离子体运行的陀螺稳态运行控制
    css样式引入方式及优缺点
  • 原文地址:https://blog.csdn.net/lyabc123456/article/details/132892246