1.Android 坑档案:迷失的源码 FileDescriptor
2.linux文件描述符0,1,2linux文件描述符
3.system.out.println
4.FFmpeg读取Assets资源文件
5.每日一例 | 更优雅地关闭流(Stream)
Android 坑档案:迷失的 FileDescriptor
在Android开发中,遇到的源码挑战和难题层出不穷,每一步前进都可能面临新的源码“坑”。我将这些开发过程中的源码经验和挑战汇集起来,编撰成了这部『Android坑档案』,源码以帮助开发者避免或解决常见问题。源码人人源码
项目的源码背景是需要在应用中添加提示音播放功能,为了实现这一需求,源码我们对MediaPlayer进行了一层封装,源码以期望提高易用性。源码封装完成后,源码编写了一个demo进行测试,源码确保一切正常。源码然而,源码在将这一功能应用到生产环境时,源码却遭遇了同事的质疑。在生产环境中,提示音播放功能出现了问题,应用无法正常播放音效。
我们首先怀疑是MediaPlayer类在多线程环境下的生命周期管理问题。对比demo代码和生产环境代码后,发现两者的差异仅在于调用MediaPlayer生命周期函数时的线程环境。然而,深入研究后发现,MediaPlayer本身具有完善的并发处理机制,无论在哪个线程调用,都不会引发问题。因此,多线程问题并非根本原因。天地源码
深入挖掘后,发现问题的根源在于FileDescriptor的处理。在封装MediaPlayer时,我们设计了支持多种资源形式的播放功能,包括文件路径、资源文件和assets文件。然而,对于assets文件的处理过程中,遇到了FileDescriptor的异常变化。在播放队列中,文件描述符被意外地设置为-1,导致播放失败。
经过一系列的排查和分析,最终发现是AssetsFileDescriptor在执行`ParcelFileDescriptor.close()`方法时,对FileDescriptor进行了关闭操作。这一操作导致了在垃圾回收(GC)过程中,FileDescriptor被意外释放,从而导致了播放队列中文件描述符的异常变化。
总结来说,对于开发过程中的问题,深入研究源代码是解决问题的关键。通过分析MediaPlayer的并发处理机制和了解FileDescriptor的生命周期管理,最终找到了问题的根源。解决方法是在获取FileDescriptor时,确保持有AssetsFileDescriptor实例,以避免在GC过程中FileDescriptor被释放,从而保持播放队列的稳定性和功能完整性。
linux文件描述符0,游玩源码1,2linux文件描述符
epoll是什么手表?不是手表,epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
epoll和select区别总结?
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
elect是一个计算机函数,位于头文件#include。该函数用于监视文件描述符的变化情况——读写或是异常。
fd文件夹是什么意思?
fd,即filedescriptor,文件描述符。linux下,所有的操作都是对文件进行操作,而对文件的操作是利用文件描述符(filedescriptor)来实现的。
每个文件进程控制块中都有一份文件描述符表(可以把它看成是一个数组,里面的元素是指向file结构体指针类型),这个数组的下标就是文件描述符。在源代码中,一般用fd作为文件描述符的标识。
linux什么数据结构存放进程打开的文件信息?
linux系统下查看进程打开文件在/proc下,对应每个进程有一个以进程号命名的目录,该目录下有一个fd目录,该目录下面的每个文件是一个符号连接,其文件名对应该进程占用的一个文件描述符,而连接指向的内容表示文件描述符对应的实际文件,有多少个文件描述符表示该进程打开了多少文件。
另外Linux默认的pod源码进程打开文件上限是个,可以通过ulimit-n查看。很多系统上限可以通过修改/etc/security/limits.conf文件改变,这个文件有详细的注释,对如何修改做了说明。
如果希望把所有用户的进程打开文件上限改为,可以加入下面两行*softnofile*hardnofile还可以只真对某个用户或某个组做修改,具体方法参见文件注释。修改后需要重新启动系统才能生效。
linux如何设置进程所能打开的最大文件描述符个数?
每个进程的文件描述符都是唯一的;文件描述符是file_struct结构中的file(打开文件创建的对象)指针数组的索引,file对象只有打开文件时才会创建并与文件描述符相关联fd_install(fd,f)
;进程间传递文件描述符除了父子进程外,没啥意义.父子进程之间会将file_struct的file指针数组全部拷贝,所以子进程才可以用父进程fd.
system.out.println
é¦å ä½ ç¬¬ä¸å¥è¯é£ä¸ªçæè¯å¥ä¸çæå§ãout å°±æ¯ outï¼out çç±»åæ¯ PrintStreamï¼å«ä¹±åï¼æ²¡ä½ é£ç§åæ³ã
å ¶æ¬¡åé¢ â并ä¸æ¯è¯´ out çè¿åå¼æ¯ PrintStream èæ¯è¯´ååçç±»å为 PrintStreamâ æè§å¾è¿æ¯å¥åºè¯ãout æ¯ä¸ªåéï¼æ¬èº«å°±æ²¡æè¿åå¼çæ¦å¿µã
æåä½ çé®é¢ï¼ä½ ç¡®å®ä½ çåæ件æ¥æºå¯é åï¼å¦æå¯é çè¯ï¼out å¯è½å¨ static åä¸è¢«éæ°å®åäºãfinal static çåéè¦ä¹å®ä¹çæ¶åç´æ¥èµå¼ï¼è¦ä¹å¨ static åä¸èµå¼ã
FFmpeg读取Assets资源文件
在Android开发中,我们经常将原生资源文件放置于assets目录下,以便在需要时进行读取。通过API提供的resources.assets.open(filename)/openFd(filenam)方法,可以方便地获得InputStream或FileDescriptor。然而,在使用FFmpeg读取assets资源文件时,遇到了难以克服的困难。主要问题是,FFmpeg在读取媒体文件时通过传入的URL来判断IO协议,因此,为了使FFmpeg正确读取assets文件,我们需要选择合适的IO协议构造URL并传入avformat_open_input(...)方法,但实际上操作起来似乎问题重重。
AssetFileDescriptor提供了资源文件的有效文件标识符。是否可以通过此fd来打开媒体文件?答案是肯定的。然而,assets返回的AssetFileDescriptor带有mStartOffset,这意味着实际的有效数据需要从mStartOffset处开始读取。
在Stackoverflow上,源码这么有人提出了类似的问题:如何通过AssetFileDescriptor正确地将assets文件传递给FFmpeg使用JNI在Android中。实现方式是在调用avformat_open_input(...)方法之前手动创建AVFormatContext并设置skip_initial_bytes参数。
在使用上述方法时,发现无法正常解码mp4(mov格式)视频文件,Stackoverflow上也有类似的反馈。尽管解码失败,但可以获取视频文件的基本信息。查看FFmpeg的日志后,发现FFmpeg在解析mp4文件信息时并未出错,正确识别了ftyp、mdat、moov等关键atom,但在后续解析中正常解析了sample与chunk的映射关系(stsc),但在解码阶段报出明显的Invalid NAL unit size异常。
分析表明,assets目录下的媒体文件可能被Android进行了特殊处理。然而,从AssetFileDescriptor的官方描述中可以得知,Android并没有对assets下的文件进行特殊处理。在Android音视频开发中,我们经常使用MediaCodec的setDataSource(fd)方法传递媒体数据,MediaCodec仍然可以正常读取assets资源文件,这引发了一个疑问:在使用Android的AssetFileDescriptor时是否需要特殊的处理?MediaCodec是如何处理AssetFileDescriptor的?
搜索源码后发现,MediaExtractor#setDataSource所调用的native方法为NuMediaExtractor::setDataSource,该方法最终将fd封装为FileSource对象。FileSource中读取数据的操作使用了普通的linux内核函数read,这一过程并没有对fd做任何特殊的处理。
在FFmpeg中,文件协议处理在libavformat/file.c中。比较FFmpeg与MediaCodec处理AssetFileDescriptor的逻辑,两者都使用了read函数,不同的是file_read函数中并没有seek相关的逻辑。这说明libavformat/file.c中封装的是基础的IO操作,并未包含其他无关逻辑。FFmpeg将所有的IO协议封装为URLProtocl结构体。在mov格式中的定义为:
FFmpeg与MediaCodec在读取AssetFileDescriptor时都使用了read函数,但暂时无法确定FFmpeg内部seek的逻辑是否存在问题。怀疑FFmpeg可能没有正确处理AssetFileDescriptor的startOffset。测试AVFormatContext中的skip_initial_bytes是否存在问题。
在Android应用层和Native层进行了相关测试,正常解码。应用层调用与上述相同,Native层需要设置skip_initial_bytes变量。测试结果:不能正常解码,可以获取媒体文件基本信息,日志与上述“问题”中的日志相同。这表明FFmpeg在处理mp4(mov格式)文件时,如果设置了AVFormatContext的skip_initial_bytes变量,FFmpeg将不能正确读取和解码文件。
原因在于,在调用重要函数init_input之后,avformat_open_input将文件seek到了指定的offset位置,但并没有进行其余处理逻辑。随后,avformat_open_input将调用AVInputformat中的read_header函数指针,该指针指向对应文件格式的函数,在mov格式中的read_header函数为mov_read_header。mov_read_header函数中,FFmpeg将根据read_header解析到的位置重新seek,导致从av_read_frame获得的AVPacket中的数据是错误的数据,因此给到编码器也无法正常解码。
解决方案相对简单,因为mov.c解析atom时只传递了AVIOContext,因此在AVIOContext中添加了同样的skip_initial_bytes字段。在调用mov_build_index并在AVIndexEntry(采样映射关系的结构体)赋值pos时加上相应的skip_initial_bytes。这种方案已被提交到Github,并详细说明了修改的文件。基于WhatTheCodec工程编写的新demo经过测试,并没有发现其他问题。如有其他问题,欢迎交流并互相学习。
在上述Stackoverflow的提问中,有提到使用pipe协议,实际上这种方式也是可行的。但在实现过程中,发现了一些需要注意的问题。为了更具有通用性,在Native层手动创建了pipe,并将pipe的输出端fd给到FFmpeg,输入端fd由应用层持有并在IO线程中写入数据。这样,我们便可以利用pipe协议灵活地写入数据,甚至可以把内存中的视频数据直接传入FFmpeg中。
总结本文,分析了使用AssetFileDescriptor向FFmpeg传递数据时遇到的问题,这一问题实际上是由于FFmpeg在解析mov格式文件时未能正确处理skip_initial_bytes所致。分享了在使用pipe协议时遇到的问题与解决方案。这两个问题的解决可能会大大方便FFmpeg在Android上的使用。虽然MediaCodec在音视频开发的部分场景中已经渐渐取代了FFmpeg,但FFmpeg的通用性、稳定性和兼容性使之仍然可能在未来的Android音视频开发中长期存在。
每日一例 | 更优雅地关闭流(Stream)
在日常编程中,流(Stream)的应用极为广泛,如InputStream/OutPutStream、FileInputStream/FileOutPutStream等。你是否习惯于像下面这样关闭流,或者干脆不进行关闭操作?本文将深入探讨流关闭问题,提供更优雅的解决方案。
通常的关闭方式
让我们先看一段代码示例:
不符合代码质量规范
从逻辑角度看,这似乎是可以接受的,然而,从代码质量和规范性上,它并不达标。使用如sonar或sonarLint插件扫描代码时,会提示存在“Code smell”,推荐采用改进方式。
try-with-resources方式
优化后的代码如下所示:
区别
优化后的代码相较于原始版本更为简洁,主要变化在于将需要在try代码块中使用的资源放于try()中。这种做法实质上是一种语法优化,简化了资源关闭过程,编译器会在编译时自动处理关闭操作,避免了手动关闭资源的繁琐。下面通过反编译代码进一步解析这一变化。
发现新大陆
尽管优化已到位,但为了探究本质差异,我们在close()方法上设置了断点,以观察在不手动关闭资源的情况下,资源是否仍能被自动关闭。
实验结果
首先采用try-with-resources方式,发现close方法确实被调用,符合预期。接着,尝试传统写法,同样观察到close方法被调用。然而,更令人好奇的是,在不手动关闭资源的情况下,close方法也成功被调用两次。进一步观察发现,传统写法中,资源在调用close方法后,接着执行了finally块中的关闭操作。
深入分析
在查看FileInputStream的源码时,我们发现创建流时,会将其加入到FileDescriptor中。FileDescriptor中的closeAll方法会循环关闭加入其中的流。然而,这一方法在FileInputStream的close方法中被调用,解释了资源的自动关闭机制。
疑惑与解答
有人疑惑资源是否在非正常情况下不会自动关闭,但实验显示,即使在异常情况下,资源仍会被正确关闭。至于是否是软件自动完成了关闭操作,答案是否定的,进一步调查证实,这与所使用的JDK版本无关。最终,我们认识到,资源的自动关闭与JDK的try-with-resources语句优化密切相关。
总结
本文详细解析了流关闭问题,介绍了优化资源管理的try-with-resources方法,并揭示了资源自动关闭背后的机制。通过深入分析与实验,我们对资源关闭有了更深入的理解,为日常编程提供了更优雅、高效的方法。