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中获取FileDescriptor
          val 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? = 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()
    }
}
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()
    }
}

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

  • 定义监听回调接口
// 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.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
    }
}
  • 服务端发送文件,回调给客户端。此处仅贴出核心代码,如下:
// 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

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
AIDL(Android Interface Definition Language)是一种Android特有的IPC(Inter-Process Communication,进程间通信)机制,可以实现进程通信。以下是使用AIDL实现进程通信的步骤: 1. 定义AIDL接口:在AIDL文件中定义接口和方法,这些接口和方法将在不同进程之间进行调用。例如,定义一个接口IStudentManager和一个方法addStudent()。 2. 实现AIDL接口:在Service中实现AIDL接口,可以在Service中创建一个Binder对象,然后将该对象返回给客户端,客户端可以使用这个Binder对象调用Service中的方法。 3. 绑定Service:在客户端中绑定Service,通过这种方式,客户端可以获取到Service中的Binder对象,然后调用Service中的方法。 4. 调用方法:在客户端中通过Binder对象调用Service中的方法,实现进程通信。 注意事项: 1. 在使用AIDL时,需要在AndroidManifest.xml文件中注册Service。 2. AIDL接口中只能使用基本数据类型和Parcelable类型,不支持其他类型。 3. AIDL接口中定义的方法必须是线程安全的,因为它们可能在不同进程中被调用。 4. AIDL接口中定义的方法必须是只读的,不应该修改传入的参数。 5. AIDL接口中的方法返回值必须是void或者基本数据类型、String、CharSequence、Parcelable类型。 6. AIDL接口中的方法可以抛出RemoteException异常,因为AIDL接口是基于Binder实现的,所以需要处理Binder连接中断的情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值