In my application, I am using Intentservice to fetch the data from server and store fetched data in to local sqlite db. I am using 5 IntentService to fetch and fill five tables. Data may up to 300 rows for each Intent Service. What should I do?
IntentService Code:
public class SendBpRecordServer extends IntentService { DbHelper dbHelper; public static final String TAG="BP Service"; public JSONObject finalData; public SendBpRecordServer() { super("BP"); dbHelper=new DbHelper(this); Log.i("In Constructor",TAG); } @Override protected void onHandleIntent(Intent intent) { String recName=intent.getStringExtra("nameTag"); if (recName.equals("fetch")) { Log.i("Send Data","Yes"); fetchDatFromServer(); ResultReceiver resultReceiver=intent.getParcelableExtra("receiverTagBP"); Log.d("BP Reseult ",resultReceiver.toString()); Bundle bun= new Bundle(); bun.putBoolean("Process_Complete_bp",true); resultReceiver.send(2, bun); } else { Log.i("Inside Service PUT BP ","Yes"); dataCollected(); } } private void fetchDatFromServer() { Response.Listener listener=new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { Log.i("Json Array Response", response.toString()); try { Boolean responseStatusCondition=response.getBoolean("success"); if (responseStatusCondition) { JSONArray jsonArray=response.getJSONArray("response"); Log.i("Array Size is",Integer.toString(jsonArray.length())); for (int i=0;i<jsonArray.length();i++) { JSONObject childJSONObject = jsonArray.getJSONObject(i); //Set the flag bit to 1 since data is already at server long result=dbHelper.insertIntoBloodPressureDetails(childJSONObject.getInt("systolic"), childJSONObject.getInt("diastolic"), childJSONObject.getLong("timestamp"),1 ); // Log.i("Insertion result ",Long.toString(result)); } Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); c.set(Calendar.HOUR_OF_DAY, 0); c.set(Calendar.MINUTE, 0); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); //This is midnight time in GMT long unixTimeStamp = c.getTimeInMillis(); //Convert it in to Indian time System long indianMidNightTime=unixTimeStamp-19800000; //Log.i("Now Calculates Days","doing"); dbHelper.calculateDays(dbHelper.bpTableName, dbHelper.bpCOLUMN_days, dbHelper.bpCOLUMN_timestamp, indianMidNightTime); } }catch (JSONException js) { js.printStackTrace(); } } }; Response.ErrorListener errorListener=new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { NetworkResponse response = error.networkResponse; // Handle Error if (error instanceof TimeoutError || error instanceof NoConnectionError) { error.printStackTrace(); Toast.makeText(getApplicationContext(), " this Network Error", Toast.LENGTH_SHORT).show(); } else if (error instanceof AuthFailureError) { //TODO error.printStackTrace(); Toast.makeText(getApplicationContext(), "User not authorized", Toast.LENGTH_SHORT).show(); } else if (error instanceof ServerError && response != null) { try { String res = new String(response.data, HttpHeaderParser.parseCharset(response.headers, "utf-8")); // Now you can use any deserializer to make sense of data JSONObject obj = new JSONObject(); obj.put("New Error",res); Log.i("New Server Error",obj.toString()); } catch (UnsupportedEncodingException e1) { // Couldn't properly decode data to string e1.printStackTrace(); } catch (JSONException e2) { // returned data is not JSONObject? e2.printStackTrace(); } } else if (error instanceof NetworkError) { //TODO error.printStackTrace(); Toast.makeText(getApplicationContext(), "Network Error", Toast.LENGTH_SHORT).show(); } else if (error instanceof ParseError) { //TODO error.printStackTrace(); Toast.makeText(getApplicationContext(), "Error consuming request", Toast.LENGTH_SHORT).show(); } else error.printStackTrace(); } }; String bp_url=Constants.url+"bp"; JsonObjectHeader customRequest=new JsonObjectHeader(bp_url, null, listener, errorListener); RequestQueue requestQueue = Volley.newRequestQueue(getApplicationContext()); requestQueue.add(customRequest); } private void dataCollected() { JSONArray jsonArray=new JSONArray(); ArrayList<Graph> arrayList=dbHelper.fetchDataFromBp(); for (int i=0;i<arrayList.size();i++) { Graph graph=new Graph(); graph=arrayList.get(i); try { JSONObject jsonObject=new JSONObject(); jsonObject.put("systolic",graph.getHeight()); jsonObject.put("diastolic",graph.getWeight()); jsonObject.put("timestamp",graph.getTime()); jsonArray.put(jsonObject); }catch (JSONException js) { js.printStackTrace(); } } finalData=new JSONObject(); try { finalData.put("bp",jsonArray); }catch (JSONException js) { js.printStackTrace(); } sendDatatoServer(); } private void sendDatatoServer() { Response.Listener listener=new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { try { // Parsing json object response // response will be a json object //Toast.makeText(getApplicationContext(),response.toString(),Toast.LENGTH_LONG).show(); Boolean responseStatusCondition = response.getBoolean("success"); Log.i("Response BP",responseStatusCondition.toString()); if (responseStatusCondition) dbHelper.setTheFlagBpTable(); else Log.i("Something Went Wrong"," On Server"); } catch (JSONException k) { Log.i("On Response",k.getMessage()); k.printStackTrace(); } } }; Response.ErrorListener errorListener=new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // Handle Error if (error instanceof TimeoutError || error instanceof NoConnectionError) { error.printStackTrace(); Toast.makeText(getApplicationContext(), " this Network Error", Toast.LENGTH_SHORT).show(); } else if (error instanceof AuthFailureError) { //TODO error.printStackTrace(); Toast.makeText(getApplicationContext(), "User not authorized", Toast.LENGTH_SHORT).show(); } else if (error instanceof ServerError) { //TODO error.printStackTrace(); Toast.makeText(getApplicationContext(), "Server error", Toast.LENGTH_SHORT).show(); } else if (error instanceof NetworkError) { //TODO error.printStackTrace(); Toast.makeText(getApplicationContext(), "Network Error", Toast.LENGTH_SHORT).show(); } else if (error instanceof ParseError) { //TODO error.printStackTrace(); Toast.makeText(getApplicationContext(), "Error consuming request", Toast.LENGTH_SHORT).show(); } else error.printStackTrace(); } }; Log.i("Bp Final Data",finalData.toString()); JsonObjectHeader customRequest=new JsonObjectHeader(Request.Method.PUT, Constants.url+"bp", finalData, listener, errorListener); RequestQueue requestQueue = Volley.newRequestQueue(getApplicationContext()); requestQueue.add(customRequest); } }
Code to call intentservice:
Log.i("Fetch BP Data ", SendBpRecordServer.TAG); resultReceiverBP=new MyResultReceiver(new Handler()); resultReceiverBP.setReceiver(this); Log.i("Bp resultreceiver ",resultReceiverBP.toString()); Intent intent2=new Intent(this,SendBpRecordServer.class); intent2.putExtra("nameTag","fetch"); intent2.putExtra("receiverTagBP",resultReceiverBP); startService(intent2);
and intent send me the code when it complete the task
In the onReceiveResult.. resultcode is send through the calling intentservice and when the count goes to 5 then lauching the mainactivity(although this trick is not working i.e. launching mainactivity without complete the task assigned to intentservice).
@Override public void onReceiveResult(int resultCode, Bundle resultData) { // TODO Auto-generated method stub Log.d("Diabetes","received result from Service= "+resultData.getString("ServiceTag")); // Log.d("BMI ","received result from Service= "+resultData.getString("ServiceTag")); /* if (progressDialog.isShowing()) { progressDialog.dismiss(); }*/ Log.i("Get Values ",Integer.toString(Constants.getValues)); if (resultCode==0 && resultData.getBoolean("Process_Complete")) Constants.getValues++; if (resultCode==1 && resultData.getBoolean("Process_Complete_bmi")) Constants.getValues++; if (resultCode==2 && resultData.getBoolean("Process_Complete_bp")) Constants.getValues++; if (resultCode==3 && resultData.getBoolean("Process_Complete_med_stats")) Constants.getValues++; if (resultCode==4 && resultData.getBoolean("Process_Complete_one")) Constants.getValues++; if (Constants.getValues==5) { Constants.getValues=0; if (progressDialog.isShowing()) { progressDialog.dismiss(); } startActivity(new Intent(this,MainActivity.class)); finish(); } }
Advertisement
Answer
Are you absolutely certain that it’s the IntentService
that is the root cause of the UI freezes? Intent services are specially designed to run in worker threads in order to offload processing from the main (UI) thread, with one of the main reasons for this being to help prevent UI freezes.
Perhaps try starting your debugging efforts at the UI level. In particular, what provides the ResultReceiver
to the IntentService
when you start it, and what do you do in the onReceiveResult
callback method in that receiver instance?
Other than that, for the activity which you’re experiencing the freezes in, check what kind of operations you’re taking. Loading large amounts of data from a database on the main thread (i.e. without using a Loader
or something similar to offload the processing to a worker thread) is a common cause of UI freezes, at least in my experience so far.
Update
I think I’ve worked out what the problem is. There are two main issues, both stemming from how you’re using Volley. When you add a request to the Volley queue, it gets executed asynchronously. That means that the queue
method returns immediately. In your intent service code, this means that the service immediately goes on to tell the ResultReceiver
that it has finished processing, when actually all it has done is queue the request. All five intent services will do this, meaning that MainActivity
will be entered into very quickly. This is the first issue.
The second issue explains the freeze you’re experiencing. Although Volley executes requests on worker threads, it returns the parsed responses to requests on the main thread – see the documentation here. This means that all the response processing you’re doing in the intent service (putting the data into the database, etc.) is actually happening on the main (UI) thread. This explains the freezing.
What you probably want to do here is switch over to using Volley’s RequestFuture
instead. This basically turns an asynchronous request into a synchronous one by allowing you to block until the request finishes. To do this, create a future of the appropriate type (JSONObject
in your case) and set it as both the listener and error listener for the request. Then, queue the request as you do now, and immediately afterward call call the get
method on the future. This method will block until the response has finished processing. It’s okay to do this in an intent service because it runs on a worker thread, not the UI thread.
If the request succeeds you’ll get the data returned and you can execute all of the logic which is currently in your Response.Listener
implementation. If an error occurs (i.e. the request fails for some reason), the request future will throw an exception which you can handle to take any appropriate actions.
Using request futures is quite a different approach to using listeners and you may need to change your code quite a bit to get it to work, but it should resolve the issues that you’re seeing.
Hope that helps, and my sincere apologies for not picking up on the bug earlier.