Android 中线程处理的问题

2019-01-23  本文已影响8人  sunjiandev

Android 中线程处理的问题

先说一下问题发生的背景,公司有一款产品是终端自组网,通信是使用的sip协议,用户列表维护是用的udp 组播的方式实现,由于年代久远,代码不太友好,有好多坑,今天要说的这个就是最近返回的问题

问题描述

用户反馈,在链路通断的情况下有大概率情况,会出现,udp 消息接受不到,wireshark 抓包分析是由于,收到 udp消息之后,ip层 收到icmp 协议,提示 端口不可达,adb shell 进去之后,发现端口以及被释放,消息当然收不到
看一下代码实现逻辑是这样的:

        @Override
        public void run() {
            super.run();

            try {
                socketReceive = new DatagramSocket(CommandType.RECV_PORT);
                
                while (!socketReceive.isClosed() && null != socketReceive) {
                    recvBuffer = new byte[CommandType.bufferSize];
                    DatagramPacket rdp = new DatagramPacket(recvBuffer,
                            recvBuffer.length);
                    socketReceive.receive(rdp);
                    
                    //业务处理
                    parsePackage(recvBuffer);
                }
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    if (null != socketReceive && !socketReceive.isClosed()) {
                        socketReceive.close();
                    }
                } catch (Exception e1) {
                    e1.printStackTrace();
                }

            }

        }

代码貌似没什么问题,仔细分析了一下,应该是while循环关闭了,看了一下catch里面的内容,会不会是处理业务的时候抛了异常,导致端口socket 关闭?
于是模拟了一下场景:拿udp 发包工具往监听的端口发送大量数据,然后立即关闭网络连接,此时数据io读写还没有完成,必然会出现io exception ,然后socket 会释放

经过模拟测试,果然出现这问题,于是把异常处理的代码注释了不就行了,于是这样:

        @Override
        public void run() {
            super.run();

            try {
                socketReceive = new DatagramSocket(CommandType.RECV_PORT);
                while (!socketReceive.isClosed() && null != socketReceive) {
                    recvBuffer = new byte[CommandType.bufferSize];
                    DatagramPacket rdp = new DatagramPacket(recvBuffer,
                            recvBuffer.length);
                    socketReceive.receive(rdp);                 
                    //业务处理
                    parsePackage(recvBuffer);
                }
            } catch (Exception e) {
                e.printStackTrace();
               // try {
               //      if (null != socketReceive && !socketReceive.isClosed()) {
               //         socketReceive.close();
               //     }
               // } catch (Exception e1) {
               //     e1.printStackTrace();
               // }

            }

        }

再次模拟一下之前的那个场景,果然解决,so easy,打包,发布版本 完美!!!
第二天又反馈还是出现这个问题,只不过概率比原来小很多, wtf 看现场抓取的日志:
发现监听的消息的线程终止了,我 艹,光想这解决问题,没有从根本上想发生问题的原因,分析原因,之前解决应该是复现问题的一个场景,还有别的情况会导致线程中断,看代码:

        @Override
        public void run() {
            super.run();

            try {
                socketReceive = new DatagramSocket(CommandType.RECV_PORT);
                while (!socketReceive.isClosed() && null != socketReceive) {
                    recvBuffer = new byte[CommandType.bufferSize];
                    DatagramPacket rdp = new DatagramPacket(recvBuffer,
                            recvBuffer.length);
                    socketReceive.receive(rdp);                 
                    //业务处理
                    parsePackage(recvBuffer);//此处是业务逻辑处理,只要里面有异常发生,都会导致监听线程终止
                }
            } catch (Exception e) {
                e.printStackTrace();
               // try {
               //      if (null != socketReceive && !socketReceive.isClosed()) {
               //         socketReceive.close();
               //     }
               // } catch (Exception e1) {
               //     e1.printStackTrace();
               // }

            }

        }

看这个代码就知道,这个函数是在 线程内部实现的,其实这就是前辈挖的坑,正常应该是吧业务逻辑放在单独的线程处理,监听线程只负责服务数据获取,不应该操作业务逻辑,业务服务和业务分开,单独处理,跟服务器的大哥沟通了一下,发现他们就是这么处理的,虽然发现问题,但是我不打算改了,因为涉及改动太大,怕又引发其他潜在的bug,想一个代价最小的方式解决这个问题,都是前辈留下的坑!!!

于是采取如下方式解决该问题(强烈不建议使用):

在服务开启的时候,开一个监听线程,定期检测udp服务监听线程是否存活,如果不存活,重新开启,代码如下:

private class ObserverThread extends Thread{        
        @Override
        public void run() {
            
            while(isStartOK){
                SystemClock.sleep(2000);
                MyLog.e(TAG, "adhoc thread is alive :"+adhocCom.isAlive());
                
                if (!adhocCom.isAlive()) {
                    adhocCom = new AdhocCommunication("adhoc");//此处一点要重新复制,虽然这个线程对象没有被销毁,
//但是已经不能被再次开启了,还有要注意的就是在之前的udp监听线程里面异常处理的代码放开,不然再次开启的时候,开启失败,端口被占用了
                    adhocCom.start();
                }
            }
        }
    }

接这样吧!

上一篇下一篇

猜你喜欢

热点阅读