打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
使用AIDL实现跨进程双向通信和传输一个2MB大小的文件
userphoto

2022.07.21 广东

关注
前言
实现这个功能要解决两个问题:
如何使用AIDL进行跨进程双向通信?
如何传输一个2MB大小的文件?
问题1很简单,可以参考AIDL官方文档,这里不做过多介绍。本文主要集中火力解决问题2,讲解如何通过匿名共享内存实现跨进程双向大文件传输。
AIDL简介
AIDL是Android中实现跨进程通信(Inter-Process Communication)的一种方式。AIDL的传输数据机制基于Binder,Binder对传输数据大小有限制,
传输超过1M的文件就会报android.os.TransactionTooLargeException异常,一种解决办法就是使用匿名共享内存进行大文件传输。
AIDL传输大文件.png
共享内存简介
共享内存是进程间通信的一种方式,通过映射一块公共内存到各自的进程空间来达到共享内存的目的。
共享内存.png
对于进程间需要传递大量数据的场景下,这种通信方式是十分高效的,但是共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。
Android中的匿名共享内存(Ashmem)是基于Linux共享内存的,借助Binder+文件描述符(FileDescriptor)实现了共享内存的传递。它可以让多个进程操作同一块内存区域,并且除了物理内存限制,没有其他大小限制。相对于Linux的共享内存,Ashmem对内存的管理更加精细化,并且添加了互斥锁。Java层在使用时需要用到MemoryFile,它封装了native代码。Android平台上共享内存通常的做法如下:
进程A通过MemoryFile创建共享内存,得到fd(FileDescriptor)
进程A通过fd将数据写入共享内存
进程A将fd封装成实现Parcelable接口的ParcelFileDescriptor对象,通过Binder将ParcelFileDescriptor对象发送给进程B
进程B获从ParcelFileDescriptor对象中获取fd,从fd中读取数据
客户端和服务端双向通信+传输大文件实战
先放上实现效果图:
demo.gif
我们先实现客户端向服务端传输大文件,然后再实现服务端向客户端传输大文件。
定义AIDL接口
//IMyAidlInterface.aidlinterface IMyAidlInterface { void client2server(in ParcelFileDescriptor pfd);}
服务端
实现IMyAidlInterface接口
//AidlService.ktclass AidlService : Service() { private val mStub: IMyAidlInterface.Stub = object : IMyAidlInterface.Stub() { @Throws(RemoteException::class) override fun sendData(pfd: ParcelFileDescriptor) { } } override fun onBind(intent: Intent): IBinder { return mStub }}
接收数据
//AidlService.kt@Throws(RemoteException::class)override fun sendData(pfd: ParcelFileDescriptor) { /** * 从ParcelFileDescriptor中获取FileDescriptor */ val fileDescriptor = pfd.fileDescriptor /** * 根据FileDescriptor构建InputStream对象 */ val fis = FileInputStream(fileDescriptor) /** * 从InputStream中读取字节数组 */ val data = fis.readBytes() ......}
客户端
绑定服务在项目的src目录中加入.aidl文件
声明一个IMyAidlInterface接口实例(基于AIDL生成)
创建ServiceConnection实例,实现android.content.ServiceConnection接口
调用Context.bindService()绑定服务,传入ServiceConnection实例
在onServiceConnected()实现中,调用IMyAidlInterface.Stub.asInterface(binder),将返回参数转换为IMyAidlInterface类型
//MainActivity.ktclass 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() }}
发送数据将发送文件转换成字节数组ByteArray
创建MemoryFile对象
向MemoryFile对象中写入字节数组
获取MemoryFile对应的FileDescriptor
根据FileDescriptor创建ParcelFileDescriptor
调用IPC方法,发送ParcelFileDescriptor对象
//MainActivity.ktprivate fun sendLargeData() { if (mStub == null) { return } try { /** * 读取assets目录下文件 */ val inputStream = assets.open("large.jpg") /** * 将inputStream转换成字节数组 */ val byteArray=inputStream.readBytes() /** * 创建MemoryFile */ val memoryFile=MemoryFile("image", byteArray.size) /** * 向MemoryFile中写入字节数组 */ memoryFile.writeBytes(byteArray, 0, 0, byteArray.size) /** * 获取MemoryFile对应的FileDescriptor */ val fd=MemoryFileUtils.getFileDescriptor(memoryFile) /** * 根据FileDescriptor创建ParcelFileDescriptor */ val pfd= ParcelFileDescriptor.dup(fd) /** * 发送数据 */ mStub?.client2server(pfd) } catch (e: IOException) { e.printStackTrace() } catch (e: RemoteException) { e.printStackTrace() }}
至此,我们已经实现了客户端向服务端传输大文件,下面就继续实现服务端向客户端传输大文件功能。服务端主动给客户端发送数据,客户端只需要进行监听即可。
定义监听回调接口
//ICallbackInterface.aidlpackage io.github.kongpf8848.aidlserver;interface ICallbackInterface { void server2client(in ParcelFileDescriptor pfd);}
在IMyAidlInterface.aidl中添加注册回调和反注册回调方法,如下:
//IMyAidlInterface.aidlimport io.github.kongpf8848.aidlserver.ICallbackInterface;interface IMyAidlInterface { ...... void registerCallback(ICallbackInterface callback); void unregisterCallback(ICallbackInterface callback);}
服务端实现接口方法
//AidlService.ktprivate 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.ktprivate 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.ktprivate 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
本文完整的代码已经上传GitHub,地址:https://github.com/kongpf8848/aidldemo
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
【Android】Service
Android系统匿名共享内存Ashmem(Anonymous Shared Memory...
camera api2封装层
Android 开发之漫漫长途 IX——彻底掌握 Binder
android6.0源码分析之Camera API2.0简介
回调函数Callback
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服