Android需要打开很多文件或文件描述符时底层抛出“Too many open files”

本文分析了一个关于Android应用在持续播放多个音效文件时遇到的“Too many open files”错误,并提出了解决方案,即优化文件描述符的管理方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android需要打开很多文件或文件描述符时底层抛出“Too many open files”

需求背景描述

A设备有个脚踏开关,当脚踏开关被踩下时下位机会给上位机上报B指令,上位机需要在收到B指令后播放对应的输出音效,而当用户抬脚时脚踏开关弹起,这时上位机就不需要再播放输出音效,也就是说如果用户踩下且长时间未抬脚时,从脚踏开关被踩下到弹起的这个过程需要输出音效一直播放且不能被中途打断更要有规律的连续播放

错误案例示范

    /**
     * start play
     * first take {@link AssetFileDescriptor} object into audio play queue, if audio play thread not started, then start audio play thread, otherwise don't doing anything
     *
     * @param assetFileDescriptor {@link AssetFileDescriptor} instance object
     */
    private void startPlay(AssetFileDescriptor assetFileDescriptor) {
        boolean isEmpty = audioPlayQueue.isEmpty();
        audioPlayQueue.add(assetFileDescriptor);
        if (isEmpty) {
            synchronized (queueDetectLock) {
                queueDetectLock.notify();
            }
        }
        if (audioPlayThread != null) return;
        release = false;
        audioPlayThread = new Thread(this);
        audioPlayThread.setName(TAG);
        audioPlayThread.start();
    }

    /**
     * stop play
     */
    private void stopPlay() {
        if (audioPlayThread == null) return;
        release = true;
        synchronized (playLock) {
            playLock.notify();
        }
    }

    @Override
    public void onCompletion(MediaPlayer mediaPlayer) {
        synchronized (playLock) {
            playLock.notify();
        }
    }

    @Override
    public void run() {
        AssetFileDescriptor assetFileDescriptor;
        while (!release) {
            try {
                while (!audioPlayQueue.isEmpty()) {
                    assetFileDescriptor = audioPlayQueue.peek();//poll
                    if (assetFileDescriptor == null) {
                        audioPlayQueue.poll();
                        continue;
                    }
                    play(assetFileDescriptor);
                    synchronized (playLock) {
                        // blocking until current audio assetFileDescriptor play completed
                        // Waiting for onCompletion method called, unlock this blocking lock
                        playLock.wait(3000);//2500
                    }
                    audioPlayQueue.poll();
                    assetFileDescriptor.close();
                    if (release) break;
                }
                // Waiting for next audio assetFileDescriptor
                synchronized (queueDetectLock) {
                    // isEmpty() may take some time, so we set timeout to detect next audio assetFileDescriptor
                    queueDetectLock.wait(3000);//500ms
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        audioPlayThread = null;
    }

首先分析出错代码的流程,当有需要播放的输出音效文件描述符时,会先判断队列是否未空然后把该音频文件给加入到播放队列当中去,再执行判断如果为空表示此时线程已经处于空闲等待状态,对其进行唤醒然后转而处理队列中的元素,当然这一切的前提是线程已被启动,所以当线程对象不为空时,说明此时播放线程已经处于工作状态

当线程进入工作状态时,最先执行的代码块是判断音频文件队列是否是空的,如果非空那就循环处理队列中的元素直至全部处理完毕为止。通过peek获取队首的第一个元素但不移除,如果音频文件为空则将其从队首的位置弹出且移除继续取自动收缩后的队列列首,如果非空则进行播放并且阻塞3000毫秒,阻塞时间根据音频文件的播放时长决定,如果音效播放时长小于1秒,那就可以阻塞1500毫秒,当音频播放完毕且onCompletion方法被回调时此时播放所playLock会被唤醒继续执行后面的逻辑,将该音频从列首弹出移除最后执行close,如果队列的元素被处理完后会进入队列检测状态,每3000毫秒对队列是否为空进行一次判断,当如果有新的音频文件需要播放时,此时3000毫秒的阻塞会被唤醒打断再次进入队列的元素处理逻辑

错误分析处理

上述错误示范代码看似没有问题实则已经存在了埋雷点,因为startPlay每次被调用都会执行一个文件FD的open操作,代码笔者没有贴上来,如果长时间没抬起脚踏就会产生问题导致程序崩溃。肯定很多小伙伴会说代码最后再每次音效播放完毕后都进行了闭环啊执行了close呀,这就是一个致命的地雷!Linux再底层对文件open是有数量限制的,也就是文件打开句柄。通常Linux默认一般都是1024个,也就是说不管你最后有没有close,只要open文件个数超出了1024程序会立马崩溃

根据项目实际情况进行分析,因为音效种类只有8种,所以只需要把音效文件的raw id或者本地路径跟文件的FD进行绑定就行了,也就是说只需要执行一次open然后将他们保存到map中去,需要的时候get一下就行了,再闭环的时候迭代map进行close就行,当然具体问题具体分析,每个项目的场景或许都不一样,但处理思路肯定大同小异

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Engineer-Jsp

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

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

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

打赏作者

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

抵扣说明:

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

余额充值