Search code examples
androidmultithreadingandroid-anr-dialogfuturetask

Android ANR , Service is locking main GUI Activity


Im starting several background services that take a while to configure due to web services calls, etc...

However Im starting these services via AsyncTask in order to avoid locking the main thread & GUI, however the GUI stills becomes locked.

Im using AsyncTask to call start a BluetoothService in my Activity onCreate():

I only included relevant lines of code:

 //asynchronously - start the bluetooth service
            new BluetoothServiceStart().execute();

Then in the BluetoothServiceStart service class, Im using Callable & Future task to get bytes from a web service:

 @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // stop the service when the notification bar is pressed
        if (intent != null && ACTION_STOP_SERVICE.equals(intent.getAction())) {
            Log.d(TAG, "Stopping bluetooth service...");
            broadcastServiceState(false);
            stopSelf();
            return START_NOT_STICKY;
        }

        // in case of attempting to restart while already running
        clearSubscriptions();

        Util.logToast(this, TAG, "Bluetooth service starting", Toast.LENGTH_SHORT, Util.DEBUG);
        setupNotification();


        // find and load JSON config file
        loadDevices();
}

   /**
     * Gets the UUIDs of devices to connect to from the bluetooth JSON file.
     */
    private void loadDevices() {
        devicesLoaded = false;


        Byte [] bytesFromWebService = null;
        InputStream is = null;
        URL url = null;
        try {

            if (ConnectivityMonitoring.hasNetwork()) {
                //lets get the path of rest service that has the config file
                String address = NgfrApp.getContext().getResources().getString(R.string.address);
                String configuration_restful_port = NgfrApp.getContext().getResources().getString(R.string.rest_port);
                String client_name = NgfrApp.getContext().getResources().getString(R.string.client_name);
                String protocol = NgfrApp.getContext().getResources().getString(R.string.protocol);
                //construct bluetooth config path
                String bluetooth_config_path = NgfrApp.getContext().getResources().getString(R.string.bluetooth_path);
                url = new URL(protocol + "://" + address + ":" + configuration_restful_port + bluetooth_config_path + client_name);
                //lets execute an FutureTask (async task with a result, that blocks until result is returned).
                ExecutorService exService = Executors.newSingleThreadExecutor();
                Log.i(TAG, "making call to URL:" + url.toString());
                Future<byte []> future = exService.submit(new CallWebServiceAndGetBytes(url));
                bytesFromWebService = Util.toObjects(future.get());
            }
            if (bytesFromWebService != null) {
                devices = readDeviceConfigFromWebService(bytesFromWebService);
                Log.i(TAG, "Loaded configuration from URL:" + url.toString());

            } else {
                // read in the device UUIDs from the file
                is = Util.scanForJson(getString(R.string.file_path), getString(R.string.bt_config_file));
                devices = Util.readJsonStream(is, localConfigReadFunc);
                Log.i(TAG, "Read config file from PATH:" + getString(R.string.file_path)+getString(R.string.bt_config_file));
            }
            if (devices != null) {
                if (devices.size() < 1)
                    Log.w(TAG, "No devices to load!");
                devicesLoaded = true;
            }

            // devices successfully loaded
            if (devices != null && devicesLoaded) {
                Log.d(TAG, "" + devices.size() + " BLE device IDs retrieved");
                Log.d(TAG, "Devices: " + devices.toString());
            }

            // failed to load devices or find the JSON file
            else {
                Log.e(TAG, "Unable to load devices! Creating empty list...");
                devices = new ArrayList<>();
            }
        }
        catch (MalformedURLException e1) {
            e1.printStackTrace();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        catch (FileNotFoundException e) {
            Log.d(TAG, "Unable to locate bluetooth config file: " + getString(R.string.bt_config_file));
        } catch (IOException e) {
            Log.d(TAG, "Error reading json file: " + e.getMessage());
        }

    }//end loadDevices

Im getting an ANR & later crash.

Android Thread dump:

"main@4817" prio=5 tid=0x2 nid=NA waiting java.lang.Thread.State: WAITING blocks main@4817 at java.lang.Object.wait(Object.java:-1) at java.lang.Thread.parkFor$(Thread.java:2135) - locked <0x1a72> (a java.lang.Object) at sun.misc.Unsafe.park(Unsafe.java:358) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:190) at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:450) at java.util.concurrent.FutureTask.get(FutureTask.java:192) at ngfr.wams.controller.core.services.RuleEngineService.loadRules(RuleEngineService.java:358) at ngfr.wams.controller.core.services.RuleEngineService.updateRules(RuleEngineService.java:462) at ngfr.wams.controller.core.services.RuleEngineService.onCreate(RuleEngineService.java:131) at android.app.ActivityThread.handleCreateService(ActivityThread.java:3542) at android.app.ActivityThread.-wrap4(ActivityThread.java:-1) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1786) at android.os.Handler.dispatchMessage(Handler.java:105) at android.os.Looper.loop(Looper.java:164) at android.app.ActivityThread.main(ActivityThread.java:6938) at java.lang.reflect.Method.invoke(Method.java:-1) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)

The error line points to future.get().

I understand that the future.get() blocks, which is intended behavior in order to wait for the web service to return the bytes otherwise in low network connectivity/bandwidth situations the code will continue to execute and miss the network response & data.

The future.get() blocks the Service, however since the BluetoothService is started using BluetoothServiceStart AsyncTask, then why is the UI blocked???

Thanks


Solution

  • It's a common mistake to assume, that a service is running on another thread than the starting activity. It will run on the main thread as well as stated here: Services

    Caution: A service runs in the main thread of its hosting process; the service does not create its own thread and does not run in a separate process unless you specify otherwise. If your service is going to perform any CPU-intensive work or blocking operations, such as MP3 playback or networking, you should create a new thread within the service to complete that work. By using a separate thread, you can reduce the risk of Application Not Responding (ANR) errors, and the application's main thread can remain dedicated to user interaction with your activities.

    The startService call will not change this behaviour, even if you call it in an AsyncTask. So if you want to reanimate your app, you should create a thread inside of your service, which is not blocking the service, thus not blocking the main thread.

    Note that IntentServices offload the task to a worker thread but automatically stop when there is no further work to do.

    Clients send requests through Context.startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.

    Maybe this is not what you want with a bluetooth service.