Search code examples
androidandroid-serviceandroid-intentserviceandroid-thread

UI Freezes Running multiple Threads in IntentService


So in my android application, I have an intent service which pings devices and finds whether they are online/offline.

When I start my IntentService my UI freezes(Debug points to when ping commands are being executed) in the Service.

Service is started from the parent activity after I get the response of a network call

loadFragment(printersFrag, Constants.CONTAINER_ACT_DASHBOARD, PrintersListingFragment.class.getSimpleName(), false, false, false);

serviceIntent = new Intent(this, PrinterPingIntentService.class);
serviceIntent.putExtra("PrinterList", printersResponse);
this.startService(serviceIntent);

The code for my IntentService is as follows:

public class PrinterPingIntentService extends IntentService {
    /**
     * The IP Address to ping
     */
    private String msIPAddressToPing = null;

    /**
     * Countdown latch instance to decrement after the thread is done
     */
    private CountDownLatch mCountDownLatch;
    /**
     * Handler to handle ping threads
     */
    private PingHandler mPingThreadHandler = null;

    /**
     * Volatile count variable to manage the ping thread count
     */
    private volatile int mnPingThreadCount = 0;
    /**
     * The currently list of valid IP Addresses
     */
    private ConcurrentHashMap<String, Device> mPrinterMap = new ConcurrentHashMap<String, Device>();

    public PrinterPingIntentService() {
        super(PrinterPingIntentService.class.getName());
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        Bundle bundle = intent.getExtras();
        PrintersResponseBean printerResponse = bundle.getParcelable("PrinterList");
        for (int i = 0; i < printerResponse.getDevices().size(); i++) {
            mPrinterMap.put(printerResponse.getDevices().get(i).getDeviceIP(), printerResponse.getDevices().get(i));
        }

        validatePrinterIP();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        /*
        * Fire up the Ping handler
        */
        mPingThreadHandler = new PingHandler();
    }

    /**
     * Validate the PrinterIPs by pinging them
     *
     * @author 
     */
    private void validatePrinterIP() {
        try {
            mnPingThreadCount = 0;
            mCountDownLatch = new CountDownLatch(mPrinterMap.size());
            for (String sIP : mPrinterMap.keySet()) {
                PingRunnable runnable = new PingRunnable(sIP, mCountDownLatch);
                Thread thread = new Thread(runnable);
                ++mnPingThreadCount;
                Log.d("BAT", "validatePrinterIP - Thread count - " + mnPingThreadCount);
                thread.start();
            }
        } catch (Exception e) {
            Log.d("BAT", "Exception validatePrinterIP - " + e.getMessage());
        }
    }

    /**
     * Runnable to make a ping to the given Ip Address
     *
     * @author 
     */
    public class PingRunnable implements Runnable {
        ////////////////////////////////// CLASS MEMBERS ///////////////////////////////////////////
        /**
         * The IP Address to ping
         */
        private String msIPAddressToPing = null;

        /**
         * Countdown latch instance to decrement after the thread is done
         */
        private CountDownLatch mCountDownLatch;

        ////////////////////////////////// CLASS METHODS ///////////////////////////////////////////
        public PingRunnable(String sIPAddress, CountDownLatch latch) {
            msIPAddressToPing = sIPAddress;
            mCountDownLatch = latch;
        }

        @Override
        public void run() {
            try {
                /*
                 * If the destination is not reachable, remove the IP address
                 * from the printer map and set the bundle value accordingly
                 */
                if (!pingURL(msIPAddressToPing)) {
                    Log.d("BAT", "Could not ping " + msIPAddressToPing + ". Removing from Map");
                    mPrinterMap.remove(msIPAddressToPing);
                } else {
                    Log.d("BAT", "Could ping " + msIPAddressToPing + ". Present in Map");
                }
            } catch (Exception e) {
                Log.d("BAT", "Exception in Ping Runnable - " + e.getMessage());
            } finally {
                mPingThreadHandler.sendEmptyMessage(0);
                mCountDownLatch.countDown();
            }
        }
    }

    /**
     * Static Handler class to handle messsages.
     * Reduce the count by one each time we receive a message to keep
     * track that all threads have returned
     *
     * @author 
     */
    public class PingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.d("BAT", "Returning thread..");
            if (msg.what == 0) {
                mnPingThreadCount--;
                Log.d("BAT", "Thread Return count - " + mnPingThreadCount);
            }

            /*
            Await Latch
             */
            try {
                mCountDownLatch.await();
            } catch (InterruptedException e) {
                Log.d("BAT", "InterruptedException PingHandler - " + e.getMessage());
            }

            if (mnPingThreadCount == 0) {
                //////TEMP
                Log.d("BAT", "All threads accounted for. Final Printer List...");
                ArrayList<Device> onlinePrinters = new ArrayList<>();
                for (String sIP : mPrinterMap.keySet()) {
                    onlinePrinters.add(mPrinterMap.get(sIP));
                    Log.d("BAT", "Printers Active " + sIP);
                }

                //send data back to fragment via localBroadcastReceiver
                Intent localBroadcast = new Intent();
                localBroadcast.putParcelableArrayListExtra("onlinePrinters", onlinePrinters);
                localBroadcast.setAction("printer");
                sendBroadcast(localBroadcast);
            }
        }
    }

    /**
     * Ping a device. First we try the usual isReachable method. If that does not work,
     * we go with the Ping command execution
     *
     * @param sURL THe uRL / IP Address to ping
     * @author 
     */
    public boolean pingURL(String sURL) {
        try {
            Log.d("BAT", "Pinging IP sURL");
            //First try with isReachable
            if (Inet4Address.getByName(sURL).isReachable(1000)) {
                Log.d("BAT", "Host Reachable by InetAddress " + sURL);
                return true;
            }
            //else try and ping. If neither works, we return false
            else {
                Log.d("BAT", "Host Not Reachable by InetAddress. Pinging IP with RunTime... " + sURL);
                StringBuffer echo = new StringBuffer();
                Runtime runtime = Runtime.getRuntime();
                Process proc = runtime.exec("ping -c 1 " + sURL);
                // "/system/bin/ping -c 8 "  + sURL
                int nReturnVal = proc.waitFor();
                Log.d("BAT", "Done Pinging - " + sURL + ((nReturnVal == 0) ? " Successful" : " Unsuccessful"));
                return (nReturnVal == 0);
            }
        } catch (IOException e) {
            Log.d("BAT", "IOEXception in pingURL - " + e.getMessage().toString());
        } catch (InterruptedException e) {
            Log.d("BAT", "InterruptedException in pingURL - " + e.getMessage());
        } catch (Exception e) {
            Log.d("BAT", "EXception in pingURL - " + e.getMessage());
        }
        return false;
    }
}

From my intent service, I send back the data of active devices to my Fragment using:

//send data back to fragment via localBroadcastReceiver
Intent localBroadcast = new Intent();
localBroadcast.putParcelableArrayListExtra("onlinePrinters", onlinePrinters);
localBroadcast.setAction("printer");
sendBroadcast(localBroadcast);

and extract this info in my Fragment using:

IntentFilter filter = new IntentFilter();
filter.addAction("printer");
updateUIReceiver = new BroadcastReceiver() {
   @Override
   public void onReceive(Context context, Intent intent) {
     //UI update here
     Bundle bundle = intent.getExtras();
     if (bundle.get("onlinePrinters") != null) {
        onlinePrinters = (ArrayList) bundle.get("onlinePrinters");
        setPrinterStatus();
     }
   }
};

As I'm using an IntentService a UI freeze should be unlikely as the task is performed on a worker thread and not in the Main Thread.

Not able to figure out the root cause of the UI freeze


Solution

    • onCreate() method of your service is called on the main thread.
    • PingHandler instance that you're creating there is associated with the main thread.
    • So handleMessage for this handler is also executed on the main thread. You seem to have blocking operations there which may be the cause of your problem.