Android 使用AIDL传输超大型文件

这篇具有很好参考价值的文章主要介绍了Android 使用AIDL传输超大型文件。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

最近在写车载Android的第5篇视频教程「AIDL的实践与封装」时,遇到一个有意思的问题,能不能通过AIDL传输超过 1M 以上的文件?

我们先不细究,为什么要用AIDL传递大文件,单纯从技术的角度考虑能不能实现。众所周知,AIDL是一种基于Binder实现的跨进程调用方案,Binder 对传输数据大小有限制,传输超过 1M 的文件就会报 android.os.TransactionTooLargeException 异常。

如果文件相对比较小,还可以将文件分片,大不了多调用几次AIDL接口,但是当遇到大型文件或超大型文件时,这种方法就显得耗时又费力。好在,Android 系统提供了现成的解决方案,其中一种解决办法是,使用AIDL传递文件描述符ParcelFileDescriptor,来实现超大型文件的跨进程传输。

ParcelFileDescriptor

ParcelFileDescriptor 是一个实现了 Parcelable 接口的类,它封装了一个文件描述符 (FileDescriptor),可以通过 Binder 将它传递给其他进程,从而实现跨进程访问文件或网络套接字。ParcelFileDescriptor 也可以用来创建管道 (pipe),用于进程间的数据流传输。

ParcelFileDescriptor 的具体用法有以下几种:

  • 通过 ParcelFileDescriptor.createPipe() 方法创建一对 ParcelFileDescriptor 对象,分别用于读写管道中的数据,实现进程间的数据流传输。
  • 通过 ParcelFileDescriptor.fromSocket() 方法将一个网络套接字 (Socket)转换为一个 ParcelFileDescriptor 对象,然后通过 Binder 将它传递给其他进程,实现跨进程访问网络套接字。
  • 通过 ParcelFileDescriptor.open() 方法打开一个文件,并返回一个 ParcelFileDescriptor 对象,然后通过 Binder 将它传递给其他进程,实现跨进程访问文件。
  • 通过 ParcelFileDescriptor.close() 方法关闭一个 ParcelFileDescriptor 对象,释放其占用的资源。

ParcelFileDescriptor.createPipe()和ParcelFileDescriptor.open() 都可以实现,跨进程文件传输,接下来我们会分别演示。

实践

  • 第一步,定义AIDL接口
interface IOptions {
    void transactFileDescriptor(in ParcelFileDescriptor pfd);
}
  • 第二步,在「传输方」使用ParcelFileDescriptor.open实现文件发送
private void transferData() {
    try {
        // file.iso 是要传输的文件,位于app的缓存目录下,约3.5GB
        ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(new File(getCacheDir(), "file.iso"), ParcelFileDescriptor.MODE_READ_ONLY);
        // 调用AIDL接口,将文件描述符的读端 传递给 接收方
        options.transactFileDescriptor(fileDescriptor);
        fileDescriptor.close();

    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • 或,在「传输方」使用ParcelFileDescriptor.createPipe实现文件发送

ParcelFileDescriptor.createPipe 方法会返回一个数组,数组中的第一个元素是管道的读端,第二个元素是管道的写端。

使用时,我们先将「读端-文件描述符」使用AIDL发给「接收端」,然后将文件流写入「写端」的管道即可。

    private void transferData() {
        try {
/******** 下面的方法也可以实现文件传输,「接收端」不需要任何修改,原理是一样的 ********/
//        createReliablePipe 创建一个管道,返回一个 ParcelFileDescriptor 数组,
//        数组中的第一个元素是管道的读端,
//        第二个元素是管道的写端
            ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createReliablePipe();
            ParcelFileDescriptor pfdRead = pfds[0];
            // 调用AIDL接口,将管道的读端传递给 接收端
            options.transactFileDescriptor(pfdRead);
            ParcelFileDescriptor pfdWrite = pfds[1];
            // 将文件写入到管道中
            byte[] buffer = new byte[1024];
            int len;
            try (
                    // file.iso 是要传输的文件,位于app的缓存目录下
                    FileInputStream inputStream = new FileInputStream(new File(getCacheDir(), "file.iso"));
                    ParcelFileDescriptor.AutoCloseOutputStream autoCloseOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfdWrite);
            ) {
                while ((len = inputStream.read(buffer)) != -1) {
                    autoCloseOutputStream.write(buffer, 0, len);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

注意,管道写入的文件流 总量限制在64KB,所以「接收方」要及时将文件从管道中读出,否则「传输方」的写入操作会一直阻塞。

  • 第三步,在「接收方」读取文件流并保存到本地
private final IOptions.Stub options = new IOptions.Stub() {
    @Override
    public void transactFileDescriptor(ParcelFileDescriptor pfd) {
        Log.i(TAG, "transactFileDescriptor: " + Thread.currentThread().getName());
        Log.i(TAG, "transactFileDescriptor: calling pid:" + Binder.getCallingPid() + " calling uid:" + Binder.getCallingUid());
        File file = new File(getCacheDir(), "file.iso");
        try (
                ParcelFileDescriptor.AutoCloseInputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
        ) {
            file.delete();
            file.createNewFile();
            FileOutputStream stream = new FileOutputStream(file);
            byte[] buffer = new byte[1024];
            int len;
            // 将inputStream中的数据写入到file中
            while ((len = inputStream.read(buffer)) != -1) {
                stream.write(buffer, 0, len);
            }
            stream.close();
            pfd.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
};
  • 运行程序

在程序运行之前,需要将一个大型文件放置到client app的缓存目录下,用于测试。目录地址:data/data/com.example.server/cache。

注意:如果使用模拟器测试,模拟器的硬盘要预留 3.5GB * 2 的闲置空间。

将程序运行起来,可以发现,3.5GB 的 file.iso 顺利传输到了Server端。

aidl传输数据大小,android开发,android,AIDL

大文件是可以传输了,那么使用这种方式会很耗费内存吗?我们继续在文件传输时,查看一下内存占用的情况,如下所示:

  • 传输方-Client,内存使用情况

aidl传输数据大小,android开发,android,AIDL

  • 接收方-Server,内存使用情况

aidl传输数据大小,android开发,android,AIDL

从Android Studio Profiler给出的内存取样数据可以看出,无论是传输方还是接收方的内存占用都非常的克制、平缓。

总结

在编写本文之前,我在掘金上还看到了另一篇文章:一道面试题:使用AIDL实现跨进程传输一个2M大小的文件 - 掘金

该文章与本文类似,都是使用AIDL向接收端传输ParcelFileDescriptor,不过该文中使用共享内存MemoryFile构造出ParcelFileDescriptor,MemoryFile的创建需要使用反射,对于使用MemoryFile映射超大型文件是否会导致内存占用过大的问题,我个人没有尝试,欢迎有兴趣的朋友进行实践。

总得来说 ParcelFileDescriptor 和 MemoryFile 的区别有以下几点:

  • ParcelFileDescriptor 是一个封装了文件描述符的类,可以通过 Binder 传递给其他进程,实现跨进程访问文件或网络套接字。MemoryFile 是一个封装了匿名共享内存的类,可以通过反射获取其文件描述符,然后通过 Binder 传递给其他进程,实现跨进程访问共享内存。
  • ParcelFileDescriptor 可以用来打开任意的文件或网络套接字,而 MemoryFile 只能用来创建固定大小的共享内存。
  • ParcelFileDescriptor 可以通过 ParcelFileDescriptor.createPipe() 方法创建一对 ParcelFileDescriptor 对象,分别用于读写管道中的数据,实现进程间的数据流传输。MemoryFile 没有这样的方法,但可以通过 MemoryFile.getInputStream() 和 MemoryFile.getOutputStream() 方法获取输入输出流,实现进程内的数据流传输。

在其他领域的应用方面,ParcelFileDescriptor 和 MemoryFile也有着性能上的差异,主要取决于两个方面:

  • 数据的大小和类型。

如果数据是大型的文件或网络套接字,那么使用 ParcelFileDescriptor 可能更合适,因为它可以直接传递文件描述符,而不需要复制数据。如果数据是小型的内存块,那么使用 MemoryFile 可能更合适,因为它可以直接映射到物理内存,而不需要打开文件或网络套接字。

  • 数据的访问方式。

如果数据是需要频繁读写的,那么使用 MemoryFile 可能更合适,因为它可以提供输入输出流,实现进程内的数据流传输。如果数据是只需要一次性读取的,那么使用 ParcelFileDescriptor 可能更合适,因为它可以通过 ParcelFileDescriptor.createPipe() 方法创建一对 ParcelFileDescriptor 对象,分别用于读写管道中的数据,实现进程间的数据流传输。

本文示例demo的地址:https://github.com/linxu-link/Aidl_transfer_file

好了,以上就是本文的所有内容了,感谢你的阅读,希望对你有所帮助。文章来源地址https://www.toymoban.com/news/detail-688523.html

到了这里,关于Android 使用AIDL传输超大型文件的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android stdio 无法新建或打开AIDL文件(解决方法)

    1.在gradle文件中添加如下代码 2.AIDL要求minsdk=16,并且要使 aidl true(在Gradle中添加) android{     buildFeatures {         aidl true     } } 我们看见,可以创建AIDL文件了 3.接着,我们看到文件出现如下提示  4.在gradle中添加如下代码 android{compileOptions.encoding=\\\"GBK\\\"} 5.若在文件中应用的

    2024年02月08日
    浏览(35)
  • Android中AIDL的简单使用(Hello world)

    AIDL:Android Interface Definition Language(Android接口定义语言) 作用:跨进程通讯。如A应用调用B应用提供的接口 A应用创建aidl接口,并且创建一个Service来实现这个接口(在onBind方法里面return我们这个接口的实例)。 把A应用创建的aidl文件原封不动的搬至B应用中(注意包名类名都

    2024年02月11日
    浏览(39)
  • Android Binder常用案例使用分析,跨进程通信aidl

    service组件的binderService获取binder通信。 servicemanager.getService方法获取相关服务。 本质上都是IBinder通信。 客户端:使用intent,启动服务端的service,使用binderservice,在onServiceConnected回调方法中获取服务端的实际binder对象。使用aidl中的接口调用服务端的方法即可。 服务端:定义

    2024年02月07日
    浏览(35)
  • [Android AIDL] --- AIDL原理简析

    上一篇文章已经讲述了如何在Android studio中搭建基于aidl的cs模型框架,只是用起来了,这次对aidl及cs端如何调用的原理进行简单分析 AIDL 文件可以分为两类。 一类是用来定义接口方法,声明要暴露哪些接口给客户端调用; 一类用来声明实现了 Parcelable 接口的数据类型,以供其

    2024年02月10日
    浏览(30)
  • [Android AIDL] --- AIDL工程搭建

    AIDL(Android Interface Definition Language)是一种 IDL 语言,用于生成可以在 Android 设备上两个进程之间进行进程间通信(IPC)的代码。 通过 AIDL,可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,从而满足进程间通信的需求。通常,暴露方法给其他应用进行调用的

    2024年02月10日
    浏览(40)
  • 【Android】Binder(一)Binder的介绍和AIDL使用Binder的实例

    Android 中的 Binder 是一个进程间通信机制,它允许不同进程之间相互调用方法和传递数据。Binder 主要用于实现系统服务和应用程序之间的通信,以及实现 IPC(Inter-Process Communication,进程间通信)。 Binder 的核心是 Binder 驱动程序,它负责管理不同进程之间的通信。每个进程都可

    2024年02月07日
    浏览(30)
  • Android:AIDL实战详解

    AIDL:Android Interface Definition Language AIDL是为了实现进程间通信而设计的Android接口语言 Android进程间通信有多种方式,Binder机制是其中最常见的一种 AIDL的本质 就是基于对Binder的运用从而实现进程间通信 这篇博文从实战出发,用一个尽可能精简的Demo,实现两个App (也是两个进程:

    2024年02月08日
    浏览(31)
  • android aidl进程间通信封装通用实现

    接上一篇的分析,今天继续 aidl复杂流程封装-CSDN博客 今天的任务就是将代码梳理下放进来 1 项目gradle配置:    需要将对应的代码放到各自的目录下,这里仅贴下关键内容,细节可以下载代码慢慢看     sourceSets {         main {             manifest.srcFile \\\'src/main/AndroidManife

    2024年02月22日
    浏览(29)
  • Android Binder——APP中AIDL实现(十九)

            AIDL(Android Interface Definition Language)其实就是对 Binder 通信的一个封装,方便在开发中对 Binder 通信的使用,这里我们就从头开始来看一下 AIDL 通信的创建过程。         通过 Android Studio 的 File -New - AIDL 的方式就会生成对应包名的 aidl 文件。即 aidl 文件要与应用

    2024年04月28日
    浏览(20)
  • android aidl进程间通信封装通用实现-用法说明

    接上一篇:android aidl进程间通信封装通用实现-CSDN博客 该aar包的使用还是比较方便的 一先看客户端 1 初始化 2 发送事件:  就是通过json的形式将参数包裹进来,另一端统一解析  唯一标志位:KEY_CLIENT_REQUEST_AUTHOR 是必须要加的,不然区分不出来是那个客户端 二再看服务端

    2024年02月20日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包