androidandroid-networkingandroid-handlerandroid-threading

Infrequent Handler.post's cause huge frame drops and crashes


I have a simple thread listening to UDP broadcasts to discover devices on my network. Whenever it receives a broadcast, it posts a Runnable to a Handler on the UI thread to add the device's IP to an ArrayList. At most, it receives packets once every 2 seconds. I've verified that the posts only happen every 2 seconds.

My app also has a few fragments. Whenever I switch using NavController.navigate() between fragments I get anywhere from 100-400 frame drops and ANRs. When I disable the thread, or even just disable searching for duplicates and adding the device to the ArrayList, the frame drops go away, but the searching and adding take less than 1ms (according to debug prints).

I've read a bunch of other thread about problems with Handlers, but none of them helped. Any idea why my posts are causing so many frame drops?

Here's the run method of the Thread:

public void run(){
            try {
                DatagramSocket ds = new DatagramSocket(DISCOVERY_PORT);
                ds.setSoTimeout(500);
                ds.setBroadcast(true);
                byte[] rxBuffer = new byte[1024];
                DatagramPacket dp = new DatagramPacket(rxBuffer,rxBuffer.length);

                while (!this.isInterrupted()) {
                    try {
                        ds.receive(dp);

                        String msg = new String(rxBuffer, 0, dp.getLength());
                        dp.setLength(rxBuffer.length);
                        callbackH.post(new Runnable() {
                            @Override
                            public void run() {
                                addGeophone(dp.getAddress().toString(),Integer.parseInt(msg));
                            }
                        });
                    }
                    catch (SocketTimeoutException e){
                        continue;
                    }
                }

                ds.close();
            }
            catch(Exception e){
                e.printStackTrace();
            }
        }

And the addGeophone function that gets posted to the UI thread:

    private static void addGeophone(String ipAddress, int id){
        boolean geophoneFound = false;
        Geophone g;
        //REMOVE BELOW HERE AND EVERYTHING SPEEDS UP.
        for (int i = 0; i < geophones.size(); i++){
            g = geophones.get(i);
            if (g.id == id){
                geophoneFound = true;
            }
        }
        if (!geophoneFound) {
            geophones.add(new Geophone(ipAddress, id));
       }
    }

Solution

  • Looks like DatagramPacket.getAddress() takes a really long time if it's executed from another thread. When I moved the getAddress call to the poster thread, everything sped up.

    public void run(){
                try {
                    DatagramSocket ds = new DatagramSocket(DISCOVERY_PORT);
                    ds.setSoTimeout(500);
                    ds.setBroadcast(true);
                    byte[] rxBuffer = new byte[1024];
                    DatagramPacket dp = new DatagramPacket(rxBuffer,rxBuffer.length);
                    while (!this.isInterrupted()) {
                        try {
                            ds.receive(dp);
    
                            String msg = new String(rxBuffer, 0, dp.getLength());
                            dp.setLength(rxBuffer.length);
                            String ipAddress = dp.getAddress().toString();
                            callbackH.post(new Runnable() {
                                @Override
                                public void run() {
                                    addGeophone(ipAddress,Integer.parseInt(msg));
                                }
                            });
                        }
                        catch (SocketTimeoutException e){
                            continue;
                        }
                    }
    
                    ds.close();
                }
                catch(Exception e){
                    e.printStackTrace();
                }
            }