Crash when calling Looper.quit()

1

I have a Android IntentService that connects to a web service to download some jobs in json format. These are then parsed and put into a SQLite3 DB.

The following code (with the sensitive bits added back in) worked on the emulator but never worked on the actual device.

public class FetchJobsService extends IntentService {
    private final static String LOG_TAG = "FetchJobsService";

    public FetchJobsService() {
        super("FetchJobsService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.i(LOG_TAG, "Fetch Jobs Service Started");

        AsyncHttpClient client = new AsyncHttpClient();

        Map<String, Object> params = new HashMap<String, Object>();
        // Add params to send in the request!
        JSONObject jsonData = new JSONObject(params);

        RequestParams requestParams = new RequestParams();
        requestParams.put(Constants.DATA, jsonData.toString());


        client.post(Constants.FETCH_JOBS, requestParams,
            new AsyncHttpResponseHandler() {
                @Override
                public void onSuccess(int i, Header[] headers, byte[] bytes) {
                    try {
                        JSONObject returnObj = new JSONObject(new String(bytes));
                        Log.i(LOG_TAG, "Success! Jobs downloaded");
                        JSONObject dataObj = returnObj.getJSONObject(Constants.DATA);
                        JSONArray jobs = dataObj.getJSONArray(Constants.JOBS);
                        // Do something with the data downloaded!
                    } catch (JSONException e) {
                        Log.e(LOG_TAG, "Failure! Jobs not successfully downloaded", e);
                    } 
                }

                @Override
                public void onFailure(int statusCode, Header[] headers, byte[] bytes,
                                      Throwable throwable) {
                    Log.i(LOG_TAG, "Failure! Status Code: " + statusCode);
                }
            }
        );

        Log.i(LOG_TAG, "Fetch Jobs Service Finished");
    }
}

It complained about posting to a Dead Thread. After some reading around, I added a Looper to keep the IntentService around until the data had been downloaded and parsed, as such:

public class FetchJobsService extends IntentService {
    private final static String LOG_TAG = "FetchJobsService";

    public FetchJobsService() {
        super("FetchJobsService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Log.i(LOG_TAG, "Fetch Jobs Service Started");

        if (Looper.myLooper() == null) {
            Looper.prepare();
        }

        AsyncHttpClient client = new AsyncHttpClient();

        Map<String, Object> params = new HashMap<String, Object>();
        // Add params to send in the request!
        JSONObject jsonData = new JSONObject(params);

        RequestParams requestParams = new RequestParams();
        requestParams.put(Constants.DATA, jsonData.toString());


        client.post(Constants.FETCH_JOBS, requestParams,
            new AsyncHttpResponseHandler() {
                @Override
                public void onSuccess(int i, Header[] headers, byte[] bytes) {
                    try {
                        JSONObject returnObj = new JSONObject(new String(bytes));
                        Log.i(LOG_TAG, "Success! Jobs downloaded");
                        JSONObject dataObj = returnObj.getJSONObject(Constants.DATA);
                        JSONArray jobs = dataObj.getJSONArray(Constants.JOBS);
                        // Do something with the data downloaded!
                    } catch (JSONException e) {
                        Log.e(LOG_TAG, "Failure! Jobs not successfully downloaded", e);
                    } finally {
                        if (Looper.myLooper() != null) {
                            Looper.myLooper().quit();
                        }
                    }
                }

                @Override
                public void onFailure(int statusCode, Header[] headers, byte[] bytes,
                                      Throwable throwable) {
                    Log.i(LOG_TAG, "Failure! Status Code: " + statusCode);
                    if (Looper.myLooper() != null) {
                        Looper.myLooper().quit();
                    }
                }
            }
        );


        Looper.loop();
        Log.i(LOG_TAG, "Fetch Jobs Service Finished");
    }
}

The jobs are now downloaded successfully however the call to quit() or quitSafely() causes a crash Fatal signal 11 (SIGSEGV) at 0x0000000c (code=1).

If I remove the calls to quit() then there is no crash but obviously the service is then not going to stop and release resources until the application quits.

What am I doing wrong?

android
asked on Stack Overflow Nov 11, 2015 by Nick J Adams

1 Answer

1

An IntentService is meant to handle its own lifecycle. You just override the onHandleIntent callback and do what you need to do - once this method exits the IntentService will shut itself down.

The IntentService is already running in a seperate thread, so it doesn't make sense to make the request asynchronously. You should execute your network call synchronously within the onHandleIntent callback. There are some examples in this article.

You could try something along these lines:

HttpEntity requestEntity = new UrlEncodedFormEntity(params);

//set up post request
final HttpPost post = new HttpPost(url);
post.addHeader(requestEntity.getContentType());
post.setEntity(requestEntity);

//get http client
DefaultHttpClient mHttpClient = new DefaultHttpClient();
final HttpParams httpParams = mHttpClient.getParams();
HttpConnectionParams.setConnectionTimeout(httpParams, TIMEOUT);
HttpConnectionParams.setSoTimeout(httpParams, TIMEOUT);
ConnManagerParams.setTimeout(httpParams, TIMEOUT);

// get response -- this line will block until it is complete -- which is completely fine in an IntentService!
HttpResponse response = getHttpClient().execute(post);

int statusCode = response.getStatusLine().getStatusCode();
String responseEntity = EntityUtils.toString(response.getEntity());

if (statusCode != HttpStatus.SC_OK) {
    // something bad happened, request failed!
} else{
    // response was good, and "responseEntity" can be used for whatever you need
}
answered on Stack Overflow Nov 11, 2015 by JoeyJubb

User contributions licensed under CC BY-SA 3.0