Android报错too many open files

本文详细记录了一位开发者在Android应用开发中遇到的因线程管理和内存泄漏导致的程序崩溃问题。通过分析日志、代码审查和使用Profiler工具,最终定位到OkHttp客户端实例过多导致的线程无法及时回收。解决方案是通过设置连接池和调整线程回收时间来优化,同时修复了Handler可能导致的内存泄漏问题,确保线程数量稳定。
摘要由CSDN通过智能技术生成

今天在开发时,出现了这个错误,然后软件闪退了,其实以前也出现过,只不过没有上心,这次正好让老板看到这个问题,所以必须要想办法解决了。。。
看报错,说是打开了太多的文件(其实不准确,只是我一开始是这么以为的),然后就从这方面开始入手去找问题,巧的是正好那个地方确实有打开文件,所以我一开始以为就是这个原因导致的。
这里我是请求接口,然后展示相关数据,再去对各条数据进行其他处理,这里会展示多张图片,因为这些图片是基本固定的,所以我就把这些图片缓存在了本地,节省用户的流量,然后就是每次展示图片都会去读取这些图片,找到读取图片相关的代码:

	FileInputStream fs = null;
	try {
    	fs = new FileInputStream(goodsImages  + "/" + dataListBean.getGoodsId() + ".jpg");
    	Bitmap bitmap  = BitmapFactory.decodeStream(fs);
    	holder.iv_icon.setImageBitmap(bitmap);
    } catch (IOException e) {
    	e.printStackTrace();
    	Log.e("MedicineListAdapter", "图片读取错误" + e.getLocalizedMessage());
    }

发现这里确实是只打开了流,没有把它关闭,所以在这里读取完之后,加一个关闭流:

	FileInputStream fs = null;
	try {
		fs = new FileInputStream(goodsImages  + "/" + dataListBean.getGoodsId() + ".jpg");
		Bitmap bitmap  = BitmapFactory.decodeStream(fs);
		//在这里加一个关闭
		fs.close();
		holder.iv_icon.setImageBitmap(bitmap);
	} catch (IOException e) {
		e.printStackTrace();
		Log.e("MedicineListAdapter", "图片读取错误" + e.getLocalizedMessage());
	}

但是加完之后,发现还是不行,还是那个错误,还是会闪退,又因为我这里会往日志文件里边写日志,所以我又怀疑是不是因为我写日志的时候打开了日志文件,然后导致的呢?
于是去看了写日志的代码,没有发现有什么问题,我就把写日志那里给注释掉了,来确实是不是他的问题,发现注释掉之后,还是一样的错误,那就肯定不是这里的问题了啊,但是我别的也没有什么打开文件的操作了啊,于是我一点一点的去注释代码,一步一步的排除,最后确定到了一段代码,就是添加物品那里。
这里呢就是调用接口来将所选择的这个物品告诉到服务端,也就是会一遍一遍的去掉同一个接口,而问题又是因为大量的点击才会出现,所以基本确定问题就在这里了,难道是因为开的线程太多了?但是我当时在写的时候就是害怕开的线程太多,所以在这里使用的是线程池,而且是固定大小的线程池啊,空闲线程应该是被回收的啊,所以我很纳闷。
但是又测试了几次,发现确实是这里的问题,于是开始修改这里:

public static final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(8);

这是创建线程池。
使用:

fixedThreadPool.submit(new ThreadInfo());
    private class ThreadInfo implements Runnable{
        //在run方法里写具体的操作
        @Override
        public void run() {
            try {
                OkHttpClient client = new OkHttpClient();
                StringBuilder sb = new StringBuilder(baseUrl+"/xxxx/xxx");
                sb.append("/").append("xxxx"); 
                Request request = new Request.Builder()
                        .url(sb.toString())
                        .get()
                        .build();
                client.newCall(request).enqueue(new Callback() {
                    @Override
                    public void onFailure(Call call, IOException e) {
                        Looper.prepare();
                        ToastUtil.showTextToast(context,"请求数据数据失败:" + e.getMessage(),2500);
                        Looper.loop();
                    }
                    @Override
                    public void onResponse(Call call, Response response) throws IOException {
                        String responseData = response.body().string();
                        Log.e("ThreadInfo",responseData);
                        Gson gson = new Gson();
                        CommonBean bean = gson.fromJson(responseData,CommonBean.class);
                        if(bean.getStatus != 200){
                        	Looper.prepare();
                        	ToastUtil.showTextToast(context,bean.getMessage,2500);
                        	Looper.loop();
                        }
                    }
                });
            } catch (Exception e) {
                e.printStackTrace();
                Looper.prepare();
                ToastUtil.showTextToast(context,"请求数据数据失败:" + e.getMessage(),2500);
                Looper.loop();
            }
        }
    }

我把请求的改了一下:

    private class ThreadInfo implements Runnable{
        //在run方法里写具体的操作
        @Override
        public void run() {
            Log.e("ThreadInfo","请求过了");
        }
    }

再运行,发现无论请求多少次,都不会报错,所以其实我的线程池是可以回收线程的,那么问题就出在了这个请求接口上边,我合理的猜测是因为这个接口的问题,于是我从网上找了一个第三方的接口,也就是那种一天调无数次的大公司给出来的官方的接口,发现还是有这个问题,所以,猜测是请求的问题,而不是接口的问题,于是再次搜索,发现有人说OKhttpclient实例在空闲一分钟之后才会回收销毁,所以可能就是因为这个原因。
于是打开studio的Profiler观察,发现调用这个接口之后,确实线程数是在不断的增加的,并没有减少,在一分钟之后,才会开始一点点的减少,所以猜测,确实是这个问题:
在这里插入图片描述
可以看到这里有六百多个线程,后边的基本都是okhttp产生的,所以可以确定,问题就是这个了。
但是这个请求,在String responseData = response.body().string();之后应该就会自动关闭了啊。。。
查询可知,是因为我这里每次调用这个接口,都会产生一个新的client的实例,而他在一分钟之后才会被回收,如果请求速度太快的,就会导致程序崩溃了,问题发现了,那么解决的思路就有了,一是要减少创建实例,二是想办法修改回收时间,于是这里就改成了:

OkHttpClient client = new OkHttpClient().newBuilder().connectionPool(new ConnectionPool(50,10, TimeUnit.SECONDS)).build();

全局创建一个实例,在他的连接池里允许最大是存在50个线程,线程空闲5秒之后就会被回收,这样应该就没问题了吧。
但是,修改完之后还是有很多线程,也并没有按照我想的那样空闲5秒之后就回收,而是一直在sleep,于是,再找原因,发现是looper的问题,looper.loop并不是就把开的这个关闭了,所以他就一直在这里死循环着,导致线程回收不掉,但是在这里,我又没有办法去弹出来一个吐司,于是用handler来进行处理了:

Message message = Message.obtain();
message.what = 1;
message.obj = medicineBuyBean.getMessage();
handler.sendMessage(message);
    @SuppressLint("HandlerLeak")
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    ToastUtil.showTextToast(context,(String)msg.obj,2500);
                    break;
            }
        }
    };

再试,发现不论点击多快,点击多少次,线程都会稳定维持在一个大概的数字,除非真的是请求的速度远远超过了回收的速度:
在这里插入图片描述
这个就是改完之后的程序运行的线程,基本都维持在这么一个水准。

写下来就是因为自己费了很大的劲才算是勉强解决了这个问题(当然,可能大家解决这个问题要比我快得多),上边应该是有很多地方有错误或者是表达不准确,欢迎大家帮忙指出来。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值